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  }