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