1// +build ignore
2//
3// Build multiple configurations of MCUboot for Zephyr, making sure
4// that they run properly.
5//
6// Run as:
7//
8//    go run run-tests.go [flags]
9//
10// Add -help as a flag to get help.  See comment below for logIn on
11// how to configure terminal output to a file so this program can see
12// the output of the Zephyr device.
13
14package main
15
16import (
17	"archive/zip"
18	"bufio"
19	"flag"
20	"fmt"
21	"io"
22	"log"
23	"os"
24	"os/exec"
25	"strings"
26	"time"
27
28	"github.com/mcu-tools/mcuboot/samples/zephyr/mcutests"
29)
30
31// logIn gives the pathname of the log output from the Zephyr device.
32// In order to see the serial output, but still be useful for human
33// debugging, the output of the terminal emulator should be teed to a
34// file that this program will read from.  This can be done with
35// something like:
36//
37//     picocom -b 115200 /dev/ttyACM0 | tee /tmp/zephyr.out
38//
39// Other terminal programs should also have logging options.
40var logIn = flag.String("login", "/tmp/zephyr.out", "File name of terminal log from Zephyr device")
41
42// Output from this test run is written to the given log file.
43var logOut = flag.String("logout", "tests.log", "Log file to write to")
44
45var preBuilt = flag.String("prebuilt", "", "Name of file with prebuilt tests")
46
47func main() {
48	err := run()
49	if err != nil {
50		log.Fatal(err)
51	}
52}
53
54func run() error {
55	flag.Parse()
56
57	lines := make(chan string, 30)
58	go readLog(lines)
59
60	// Write output to a log file
61	logFile, err := os.Create(*logOut)
62	if err != nil {
63		return err
64	}
65	defer logFile.Close()
66	lg := bufio.NewWriter(logFile)
67	defer lg.Flush()
68
69	var extractor *Extractor
70
71	if *preBuilt != "" {
72		// If there are pre-built images, open them.
73		extractor, err = NewExtractor(*preBuilt)
74		if err != nil {
75			return err
76		}
77		defer extractor.Close()
78	}
79
80	for _, group := range mcutests.Tests {
81		fmt.Printf("Running %q\n", group.Name)
82		fmt.Fprintf(lg, "-------------------------------------\n")
83		fmt.Fprintf(lg, "---- Running %q\n", group.Name)
84
85		for _, test := range group.Tests {
86			if *preBuilt == "" {
87				// No prebuilt, build the tests
88				// ourselves.
89				err = runCommands(test.Build, lg)
90				if err != nil {
91					return err
92				}
93			} else {
94				// Extract the build artifacts from
95				// the zip file.
96				err = extractor.Extract(group.ShortName)
97				if err != nil {
98					return err
99				}
100			}
101
102			err = runCommands(test.Commands, lg)
103			if err != nil {
104				return err
105			}
106
107			err = expect(lg, lines, test.Expect)
108			if err != nil {
109				return err
110			}
111
112			fmt.Fprintf(lg, "---- Passed\n")
113		}
114		fmt.Printf("    Passed!\n")
115	}
116
117	return nil
118}
119
120// Run a set of commands
121func runCommands(cmds [][]string, lg io.Writer) error {
122	for _, cmd := range cmds {
123		fmt.Printf("    %s\n", cmd)
124		fmt.Fprintf(lg, "---- Run: %s\n", cmd)
125		err := runCommand(cmd, lg)
126		if err != nil {
127			return err
128		}
129	}
130
131	return nil
132}
133
134// Run a single command.
135func runCommand(cmd []string, lg io.Writer) error {
136	c := exec.Command(cmd[0], cmd[1:]...)
137	c.Stdout = lg
138	c.Stderr = lg
139	return c.Run()
140}
141
142// Expect the given string.
143func expect(lg io.Writer, lines <-chan string, exp string) error {
144	// Read lines, and if we hit a timeout before seeing our
145	// expected line, then consider that an error.
146	fmt.Fprintf(lg, "---- expect: %q\n", exp)
147
148	stopper := time.NewTimer(10 * time.Second)
149	defer stopper.Stop()
150outer:
151	for {
152		select {
153		case line := <-lines:
154			fmt.Fprintf(lg, "---- target: %q\n", line)
155			if strings.Contains(line, exp) {
156				break outer
157			}
158		case <-stopper.C:
159			fmt.Fprintf(lg, "timeout, didn't receive output\n")
160			return fmt.Errorf("timeout, didn't receive expected string: %q", exp)
161		}
162	}
163
164	return nil
165}
166
167// Read things from the log file, discarding everything already there.
168func readLog(sink chan<- string) {
169	file, err := os.Open(*logIn)
170	if err != nil {
171		log.Fatal(err)
172	}
173
174	_, err = file.Seek(0, 2)
175	if err != nil {
176		log.Fatal(err)
177	}
178
179	prefix := ""
180	for {
181		// Read lines until EOF, then delay a bit, and do it
182		// all again.
183		rd := bufio.NewReader(file)
184
185		for {
186			line, err := rd.ReadString('\n')
187			if err == io.EOF {
188				// A partial line can happen because
189				// we are racing with the writer.
190				if line != "" {
191					prefix = line
192				}
193				break
194			}
195			if err != nil {
196				log.Fatal(err)
197			}
198
199			line = prefix + line
200			prefix = ""
201			sink <- line
202			// fmt.Printf("line: %q\n", line)
203		}
204
205		// Pause a little
206		time.Sleep(250 * time.Millisecond)
207	}
208}
209
210// An Extractor holds an opened Zip file, and is able to extract files
211// based on the directory name.
212type Extractor struct {
213	file *os.File
214	zip  *zip.Reader
215}
216
217// NewExtractor returns an Extractor based on the contents of a zip
218// file.
219func NewExtractor(name string) (*Extractor, error) {
220	f, err := os.Open(name)
221	if err != nil {
222		return nil, err
223	}
224	size, err := f.Seek(0, 2)
225	if err != nil {
226		f.Close()
227		return nil, err
228	}
229
230	rd, err := zip.NewReader(f, size)
231	if err != nil {
232		f.Close()
233		return nil, err
234	}
235
236	return &Extractor{
237		file: f,
238		zip:  rd,
239	}, nil
240}
241
242func (e *Extractor) Close() error {
243	return e.file.Close()
244}
245
246// Extract extracts the files of the given directory name into the
247// current directory.  These files will overwrite any files of these
248// names that already exist (presumably from previous extractions).
249func (e *Extractor) Extract(dir string) error {
250	prefix := dir + "/"
251
252	count := 0
253	for _, file := range e.zip.File {
254		if len(file.Name) > len(prefix) && strings.HasPrefix(file.Name, prefix) {
255			outName := file.Name[len(prefix):len(file.Name)]
256			fmt.Printf("->%q\n", outName)
257
258			err := e.single(file, outName)
259			if err != nil {
260				return err
261			}
262
263			count += 1
264		}
265	}
266
267	if count == 0 {
268		return fmt.Errorf("File for %s missing from archive", dir)
269	}
270
271	return nil
272}
273
274// single extracts a single file from the zip archive, writing the
275// results to a file 'outName'.
276func (e *Extractor) single(file *zip.File, outName string) error {
277	inf, err := file.Open()
278	if err != nil {
279		return err
280	}
281
282	outf, err := os.Create(outName)
283	if err != nil {
284		return err
285	}
286	defer outf.Close()
287
288	_, err = io.Copy(outf, inf)
289	return err
290}
291