tinygo.org/x/drivers@v0.27.1-0.20240509133757-7dbca2a54349/smoketest.go (about) 1 //go:build none 2 3 // Run all commands in smoketest.sh in parallel, for improved performance. 4 // This requires changing the -o flag to avoid a race condition between writing 5 // the output and reading it back to get the md5sum of the output. 6 7 package main 8 9 import ( 10 "bytes" 11 "crypto/md5" 12 "flag" 13 "fmt" 14 "os" 15 "os/exec" 16 "path" 17 "path/filepath" 18 "runtime" 19 20 "github.com/google/shlex" 21 ) 22 23 var flagXtensa = flag.Bool("xtensa", true, "Enable Xtensa tests") 24 25 func usage() { 26 fmt.Fprintln(os.Stderr, "usage: go run ./smoketest.go smoketest.txt") 27 flag.PrintDefaults() 28 } 29 30 func main() { 31 flag.Parse() 32 if flag.NArg() != 1 { 33 usage() 34 os.Exit(1) 35 } 36 err := runSmokeTest(flag.Arg(0)) 37 if err != nil { 38 fmt.Fprintln(os.Stderr, err) 39 usage() 40 os.Exit(1) 41 } 42 } 43 44 func runSmokeTest(filename string) error { 45 // Read all the lines in the file. 46 data, err := os.ReadFile(filename) 47 if err != nil { 48 return err 49 } 50 lines := bytes.Split(data, []byte("\n")) 51 52 // Start a number of goroutine workers. 53 jobChan := make(chan *Job, len(lines)) 54 for i := 0; i < runtime.NumCPU(); i++ { 55 go worker(jobChan) 56 } 57 58 // Create a temporary directory for the outputs of these tests. 59 tmpdir, err := os.MkdirTemp("", "drivers-smoketest-*") 60 if err != nil { 61 return err 62 } 63 64 // Send work to the workers. 65 var jobs []*Job 66 for _, lineBytes := range lines { 67 // Parse the line into command line parameters. 68 line := string(lineBytes) 69 fields, err := shlex.Split(line) 70 if err != nil { 71 return err 72 } 73 if len(fields) == 0 { 74 continue // empty line 75 } 76 77 // Replace the "output" flag, but store the original value. 78 var outpath, origOutpath string 79 for i := range fields { 80 if fields[i] == "-o" { 81 origOutpath = fields[i+1] 82 ext := path.Ext(origOutpath) 83 outpath = filepath.Join(tmpdir, fmt.Sprintf("output-%d%s", len(jobs), ext)) 84 fields[i+1] = outpath 85 break 86 } 87 } 88 if outpath == "" { 89 return fmt.Errorf("could not find -o flag in command: %v", fields) 90 } 91 92 // Parse the command line parameters to get the -target flag. 93 if fields[1] != "build" { 94 return fmt.Errorf("unexpected subcommand: %#v", fields[1]) 95 } 96 flagSet := flag.NewFlagSet(fields[0], flag.ContinueOnError) 97 _ = flagSet.String("size", "", "") 98 _ = flagSet.String("o", "", "") 99 _ = flagSet.String("stack-size", "", "") 100 targetFlag := flagSet.String("target", "", "") 101 err = flagSet.Parse(fields[2:]) 102 if err != nil { 103 return fmt.Errorf("failed to parse command from %s: %w", filename, err) 104 } 105 106 // Skip Xtensa tests if set in the flag. 107 if !*flagXtensa && *targetFlag == "m5stack-core2" { 108 continue 109 } 110 111 // Create TinyGo command (to build the driver example). 112 output := &bytes.Buffer{} 113 output.Write(lineBytes) 114 output.Write([]byte("\n")) 115 cmd := exec.Command(fields[0], fields[1:]...) 116 cmd.Stdout = output 117 cmd.Stderr = output 118 119 // Submit this command for execution. 120 job := &Job{ 121 output: output, 122 tinygoCmd: cmd, 123 outpath: outpath, 124 origOutpath: origOutpath, 125 resultChan: make(chan error), 126 } 127 jobChan <- job 128 jobs = append(jobs, job) 129 } 130 close(jobChan) // stops the workers (probably not necessary) 131 132 // Read the output from all these jobs, in order. 133 for _, job := range jobs { 134 result := <-job.resultChan 135 os.Stdout.Write(job.output.Bytes()) 136 if result != nil { 137 return result 138 } 139 } 140 141 return nil 142 } 143 144 func worker(jobChan chan *Job) { 145 for job := range jobChan { 146 // Run the tinygo command. 147 err := job.tinygoCmd.Run() 148 if err != nil { 149 job.resultChan <- err 150 } 151 152 // Create a md5sum, with output similar to the "md5sum" command line 153 // utility. 154 data, err := os.ReadFile(job.outpath) 155 if err != nil { 156 job.resultChan <- err 157 } 158 fmt.Fprintf(job.output, "%x %s\n", md5.Sum(data), job.origOutpath) 159 job.resultChan <- nil 160 } 161 } 162 163 type Job struct { 164 output *bytes.Buffer 165 tinygoCmd *exec.Cmd 166 outpath string 167 origOutpath string 168 resultChan chan error 169 }