blob: 56657f89660395069cc289508c4ffa9d790e1b38 [file] [log] [blame]
David Brown8e0016e2018-01-29 12:15:37 -07001// +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 (
David Browna84b6472020-09-10 09:41:32 -060017 "archive/zip"
David Brown8e0016e2018-01-29 12:15:37 -070018 "bufio"
19 "flag"
20 "fmt"
21 "io"
22 "log"
23 "os"
24 "os/exec"
25 "strings"
26 "time"
David Brown5e8dbb92020-09-09 13:17:38 -060027
28 "github.com/JuulLabs-OSS/mcuboot/samples/zephyr/mcutests"
David Brown8e0016e2018-01-29 12:15:37 -070029)
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
David Brownf15a0102020-09-10 08:25:53 -060045var preBuilt = flag.String("prebuilt", "", "Name of file with prebuilt tests")
46
David Brown8e0016e2018-01-29 12:15:37 -070047func 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
David Browna84b6472020-09-10 09:41:32 -060069 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
David Brown5e8dbb92020-09-09 13:17:38 -060080 for _, group := range mcutests.Tests {
81 fmt.Printf("Running %q\n", group.Name)
David Brown8e0016e2018-01-29 12:15:37 -070082 fmt.Fprintf(lg, "-------------------------------------\n")
David Brown5e8dbb92020-09-09 13:17:38 -060083 fmt.Fprintf(lg, "---- Running %q\n", group.Name)
David Brown8e0016e2018-01-29 12:15:37 -070084
David Brown5e8dbb92020-09-09 13:17:38 -060085 for _, test := range group.Tests {
David Brownf15a0102020-09-10 08:25:53 -060086 if *preBuilt == "" {
87 // No prebuilt, build the tests
88 // ourselves.
89 err = runCommands(test.Build, lg)
David Brown8e0016e2018-01-29 12:15:37 -070090 if err != nil {
91 return err
92 }
David Brownf15a0102020-09-10 08:25:53 -060093 } else {
David Browna84b6472020-09-10 09:41:32 -060094 // Extract the build artifacts from
95 // the zip file.
96 err = extractor.Extract(group.ShortName)
97 if err != nil {
98 return err
99 }
David Brownf15a0102020-09-10 08:25:53 -0600100 }
101
102 err = runCommands(test.Commands, lg)
103 if err != nil {
104 return err
David Brown8e0016e2018-01-29 12:15:37 -0700105 }
106
David Brown5e8dbb92020-09-09 13:17:38 -0600107 err = expect(lg, lines, test.Expect)
David Brown8e0016e2018-01-29 12:15:37 -0700108 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
David Brownf15a0102020-09-10 08:25:53 -0600120// 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
David Brown8e0016e2018-01-29 12:15:37 -0700134// 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}
David Browna84b6472020-09-10 09:41:32 -0600209
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}