mcuboot/samples/zephyr/run-tests.go

291 lines
5.9 KiB
Go

// +build ignore
//
// Build multiple configurations of MCUboot for Zephyr, making sure
// that they run properly.
//
// Run as:
//
// go run run-tests.go [flags]
//
// Add -help as a flag to get help. See comment below for logIn on
// how to configure terminal output to a file so this program can see
// the output of the Zephyr device.
package main
import (
"archive/zip"
"bufio"
"flag"
"fmt"
"io"
"log"
"os"
"os/exec"
"strings"
"time"
"github.com/mcu-tools/mcuboot/samples/zephyr/mcutests"
)
// logIn gives the pathname of the log output from the Zephyr device.
// In order to see the serial output, but still be useful for human
// debugging, the output of the terminal emulator should be teed to a
// file that this program will read from. This can be done with
// something like:
//
// picocom -b 115200 /dev/ttyACM0 | tee /tmp/zephyr.out
//
// Other terminal programs should also have logging options.
var logIn = flag.String("login", "/tmp/zephyr.out", "File name of terminal log from Zephyr device")
// Output from this test run is written to the given log file.
var logOut = flag.String("logout", "tests.log", "Log file to write to")
var preBuilt = flag.String("prebuilt", "", "Name of file with prebuilt tests")
func main() {
err := run()
if err != nil {
log.Fatal(err)
}
}
func run() error {
flag.Parse()
lines := make(chan string, 30)
go readLog(lines)
// Write output to a log file
logFile, err := os.Create(*logOut)
if err != nil {
return err
}
defer logFile.Close()
lg := bufio.NewWriter(logFile)
defer lg.Flush()
var extractor *Extractor
if *preBuilt != "" {
// If there are pre-built images, open them.
extractor, err = NewExtractor(*preBuilt)
if err != nil {
return err
}
defer extractor.Close()
}
for _, group := range mcutests.Tests {
fmt.Printf("Running %q\n", group.Name)
fmt.Fprintf(lg, "-------------------------------------\n")
fmt.Fprintf(lg, "---- Running %q\n", group.Name)
for _, test := range group.Tests {
if *preBuilt == "" {
// No prebuilt, build the tests
// ourselves.
err = runCommands(test.Build, lg)
if err != nil {
return err
}
} else {
// Extract the build artifacts from
// the zip file.
err = extractor.Extract(group.ShortName)
if err != nil {
return err
}
}
err = runCommands(test.Commands, lg)
if err != nil {
return err
}
err = expect(lg, lines, test.Expect)
if err != nil {
return err
}
fmt.Fprintf(lg, "---- Passed\n")
}
fmt.Printf(" Passed!\n")
}
return nil
}
// Run a set of commands
func runCommands(cmds [][]string, lg io.Writer) error {
for _, cmd := range cmds {
fmt.Printf(" %s\n", cmd)
fmt.Fprintf(lg, "---- Run: %s\n", cmd)
err := runCommand(cmd, lg)
if err != nil {
return err
}
}
return nil
}
// Run a single command.
func runCommand(cmd []string, lg io.Writer) error {
c := exec.Command(cmd[0], cmd[1:]...)
c.Stdout = lg
c.Stderr = lg
return c.Run()
}
// Expect the given string.
func expect(lg io.Writer, lines <-chan string, exp string) error {
// Read lines, and if we hit a timeout before seeing our
// expected line, then consider that an error.
fmt.Fprintf(lg, "---- expect: %q\n", exp)
stopper := time.NewTimer(10 * time.Second)
defer stopper.Stop()
outer:
for {
select {
case line := <-lines:
fmt.Fprintf(lg, "---- target: %q\n", line)
if strings.Contains(line, exp) {
break outer
}
case <-stopper.C:
fmt.Fprintf(lg, "timeout, didn't receive output\n")
return fmt.Errorf("timeout, didn't receive expected string: %q", exp)
}
}
return nil
}
// Read things from the log file, discarding everything already there.
func readLog(sink chan<- string) {
file, err := os.Open(*logIn)
if err != nil {
log.Fatal(err)
}
_, err = file.Seek(0, 2)
if err != nil {
log.Fatal(err)
}
prefix := ""
for {
// Read lines until EOF, then delay a bit, and do it
// all again.
rd := bufio.NewReader(file)
for {
line, err := rd.ReadString('\n')
if err == io.EOF {
// A partial line can happen because
// we are racing with the writer.
if line != "" {
prefix = line
}
break
}
if err != nil {
log.Fatal(err)
}
line = prefix + line
prefix = ""
sink <- line
// fmt.Printf("line: %q\n", line)
}
// Pause a little
time.Sleep(250 * time.Millisecond)
}
}
// An Extractor holds an opened Zip file, and is able to extract files
// based on the directory name.
type Extractor struct {
file *os.File
zip *zip.Reader
}
// NewExtractor returns an Extractor based on the contents of a zip
// file.
func NewExtractor(name string) (*Extractor, error) {
f, err := os.Open(name)
if err != nil {
return nil, err
}
size, err := f.Seek(0, 2)
if err != nil {
f.Close()
return nil, err
}
rd, err := zip.NewReader(f, size)
if err != nil {
f.Close()
return nil, err
}
return &Extractor{
file: f,
zip: rd,
}, nil
}
func (e *Extractor) Close() error {
return e.file.Close()
}
// Extract extracts the files of the given directory name into the
// current directory. These files will overwrite any files of these
// names that already exist (presumably from previous extractions).
func (e *Extractor) Extract(dir string) error {
prefix := dir + "/"
count := 0
for _, file := range e.zip.File {
if len(file.Name) > len(prefix) && strings.HasPrefix(file.Name, prefix) {
outName := file.Name[len(prefix):len(file.Name)]
fmt.Printf("->%q\n", outName)
err := e.single(file, outName)
if err != nil {
return err
}
count += 1
}
}
if count == 0 {
return fmt.Errorf("File for %s missing from archive", dir)
}
return nil
}
// single extracts a single file from the zip archive, writing the
// results to a file 'outName'.
func (e *Extractor) single(file *zip.File, outName string) error {
inf, err := file.Open()
if err != nil {
return err
}
outf, err := os.Create(outName)
if err != nil {
return err
}
defer outf.Close()
_, err = io.Copy(outf, inf)
return err
}