main (tinygo test): build and run tests in concurrently
Этот коммит содержится в:
		
							родитель
							
								
									e594dbc133
								
							
						
					
					
						коммит
						12e314ccc9
					
				
					 1 изменённых файлов: 180 добавлений и 16 удалений
				
			
		
							
								
								
									
										196
									
								
								main.go
									
										
									
									
									
								
							
							
						
						
									
										196
									
								
								main.go
									
										
									
									
									
								
							|  | @ -18,6 +18,7 @@ import ( | ||||||
| 	"runtime/pprof" | 	"runtime/pprof" | ||||||
| 	"strconv" | 	"strconv" | ||||||
| 	"strings" | 	"strings" | ||||||
|  | 	"sync" | ||||||
| 	"sync/atomic" | 	"sync/atomic" | ||||||
| 	"time" | 	"time" | ||||||
| 
 | 
 | ||||||
|  | @ -177,7 +178,7 @@ func Build(pkgName, outpath string, options *compileopts.Options) error { | ||||||
| 
 | 
 | ||||||
| // Test runs the tests in the given package. Returns whether the test passed and | // Test runs the tests in the given package. Returns whether the test passed and | ||||||
| // possibly an error if the test failed to run. | // possibly an error if the test failed to run. | ||||||
| func Test(pkgName string, options *compileopts.Options, testCompileOnly, testVerbose, testShort bool, testRunRegexp string, outpath string) (bool, error) { | func Test(pkgName string, stdout, stderr io.Writer, options *compileopts.Options, testCompileOnly, testVerbose, testShort bool, testRunRegexp string, outpath string) (bool, error) { | ||||||
| 	options.TestConfig.CompileTestBinary = true | 	options.TestConfig.CompileTestBinary = true | ||||||
| 	config, err := builder.NewConfig(options) | 	config, err := builder.NewConfig(options) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
|  | @ -201,9 +202,13 @@ func Test(pkgName string, options *compileopts.Options, testCompileOnly, testVer | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
| 		// Run the test. | 		// Run the test. | ||||||
|  | 		config.Options.Semaphore <- struct{}{} | ||||||
|  | 		defer func() { | ||||||
|  | 			<-config.Options.Semaphore | ||||||
|  | 		}() | ||||||
| 		start := time.Now() | 		start := time.Now() | ||||||
| 		var err error | 		var err error | ||||||
| 		passed, err = runPackageTest(config, result, testVerbose, testShort, testRunRegexp) | 		passed, err = runPackageTest(config, stdout, stderr, result, testVerbose, testShort, testRunRegexp) | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
| 			return err | 			return err | ||||||
| 		} | 		} | ||||||
|  | @ -212,14 +217,14 @@ func Test(pkgName string, options *compileopts.Options, testCompileOnly, testVer | ||||||
| 		// Print the result. | 		// Print the result. | ||||||
| 		importPath := strings.TrimSuffix(result.ImportPath, ".test") | 		importPath := strings.TrimSuffix(result.ImportPath, ".test") | ||||||
| 		if passed { | 		if passed { | ||||||
| 			fmt.Printf("ok  \t%s\t%.3fs\n", importPath, duration.Seconds()) | 			fmt.Fprintf(stdout, "ok  \t%s\t%.3fs\n", importPath, duration.Seconds()) | ||||||
| 		} else { | 		} else { | ||||||
| 			fmt.Printf("FAIL\t%s\t%.3fs\n", importPath, duration.Seconds()) | 			fmt.Fprintf(stdout, "FAIL\t%s\t%.3fs\n", importPath, duration.Seconds()) | ||||||
| 		} | 		} | ||||||
| 		return nil | 		return nil | ||||||
| 	}) | 	}) | ||||||
| 	if err, ok := err.(loader.NoTestFilesError); ok { | 	if err, ok := err.(loader.NoTestFilesError); ok { | ||||||
| 		fmt.Printf("?   \t%s\t[no test files]\n", err.ImportPath) | 		fmt.Fprintf(stdout, "?   \t%s\t[no test files]\n", err.ImportPath) | ||||||
| 		// Pretend the test passed - it at least didn't fail. | 		// Pretend the test passed - it at least didn't fail. | ||||||
| 		return true, nil | 		return true, nil | ||||||
| 	} | 	} | ||||||
|  | @ -229,7 +234,7 @@ func Test(pkgName string, options *compileopts.Options, testCompileOnly, testVer | ||||||
| // runPackageTest runs a test binary that was previously built. The return | // runPackageTest runs a test binary that was previously built. The return | ||||||
| // values are whether the test passed and any errors encountered while trying to | // values are whether the test passed and any errors encountered while trying to | ||||||
| // run the binary. | // run the binary. | ||||||
| func runPackageTest(config *compileopts.Config, result builder.BuildResult, testVerbose, testShort bool, testRunRegexp string) (bool, error) { | func runPackageTest(config *compileopts.Config, stdout, stderr io.Writer, result builder.BuildResult, testVerbose, testShort bool, testRunRegexp string) (bool, error) { | ||||||
| 	var cmd *exec.Cmd | 	var cmd *exec.Cmd | ||||||
| 	if len(config.Target.Emulator) == 0 { | 	if len(config.Target.Emulator) == 0 { | ||||||
| 		// Run directly. | 		// Run directly. | ||||||
|  | @ -264,8 +269,8 @@ func runPackageTest(config *compileopts.Config, result builder.BuildResult, test | ||||||
| 		cmd = executeCommand(config.Options, config.Target.Emulator[0], args...) | 		cmd = executeCommand(config.Options, config.Target.Emulator[0], args...) | ||||||
| 	} | 	} | ||||||
| 	cmd.Dir = result.MainDir | 	cmd.Dir = result.MainDir | ||||||
| 	cmd.Stdout = os.Stdout | 	cmd.Stdout = stdout | ||||||
| 	cmd.Stderr = os.Stderr | 	cmd.Stderr = stderr | ||||||
| 	err := cmd.Run() | 	err := cmd.Run() | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		if _, ok := err.(*exec.ExitError); ok { | 		if _, ok := err.(*exec.ExitError); ok { | ||||||
|  | @ -1368,16 +1373,57 @@ func main() { | ||||||
| 		if len(pkgNames) == 0 { | 		if len(pkgNames) == 0 { | ||||||
| 			pkgNames = []string{"."} | 			pkgNames = []string{"."} | ||||||
| 		} | 		} | ||||||
| 		allTestsPassed := true | 		if *testCompileOnlyFlag && len(pkgNames) > 1 { | ||||||
| 		for _, pkgName := range pkgNames { | 			fmt.Println("cannot use -c flag with multiple packages") | ||||||
| 			// TODO: parallelize building the test binaries | 			os.Exit(1) | ||||||
| 			passed, err := Test(pkgName, options, *testCompileOnlyFlag, *testVerboseFlag, *testShortFlag, *testRunRegexp, outpath) | 		} | ||||||
| 			handleCompilerError(err) | 
 | ||||||
| 			if !passed { | 		// Build and run the tests concurrently, buffering the output. | ||||||
| 				allTestsPassed = false | 		fail := make(chan struct{}, 1) | ||||||
|  | 		var wg sync.WaitGroup | ||||||
|  | 		bufs := make([]testOutputBuf, len(pkgNames)) | ||||||
|  | 		for i, pkgName := range pkgNames { | ||||||
|  | 			pkgName := pkgName | ||||||
|  | 			buf := &bufs[i] | ||||||
|  | 			buf.done = make(chan struct{}) | ||||||
|  | 			wg.Add(1) | ||||||
|  | 			go func() { | ||||||
|  | 				defer wg.Done() | ||||||
|  | 				defer close(buf.done) | ||||||
|  | 				stdout := (*testStdout)(buf) | ||||||
|  | 				stderr := (*testStderr)(buf) | ||||||
|  | 				passed, err := Test(pkgName, stdout, stderr, options, *testCompileOnlyFlag, *testVerboseFlag, *testShortFlag, *testRunRegexp, outpath) | ||||||
|  | 				if err != nil { | ||||||
|  | 					printCompilerError(func(args ...interface{}) { | ||||||
|  | 						fmt.Fprintln(stderr, args...) | ||||||
|  | 					}, err) | ||||||
|  | 				} | ||||||
|  | 				if !passed { | ||||||
|  | 					select { | ||||||
|  | 					case fail <- struct{}{}: | ||||||
|  | 					default: | ||||||
|  | 					} | ||||||
|  | 				} | ||||||
|  | 			}() | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		// Flush the output one test at a time. | ||||||
|  | 		// This ensures that outputs from different tests are not mixed together. | ||||||
|  | 		for i := range bufs { | ||||||
|  | 			err := bufs[i].flush(os.Stdout, os.Stderr) | ||||||
|  | 			if err != nil { | ||||||
|  | 				// There was an error writing to stdout or stderr, so we probbably cannot print this. | ||||||
|  | 				select { | ||||||
|  | 				case fail <- struct{}{}: | ||||||
|  | 				default: | ||||||
|  | 				} | ||||||
| 			} | 			} | ||||||
| 		} | 		} | ||||||
| 		if !allTestsPassed { | 
 | ||||||
|  | 		// Wait for all tests to finish. | ||||||
|  | 		wg.Wait() | ||||||
|  | 		close(fail) | ||||||
|  | 		if _, fail := <-fail; fail { | ||||||
| 			fmt.Println("FAIL") | 			fmt.Println("FAIL") | ||||||
| 			os.Exit(1) | 			os.Exit(1) | ||||||
| 		} | 		} | ||||||
|  | @ -1515,3 +1561,121 @@ func main() { | ||||||
| 		os.Exit(1) | 		os.Exit(1) | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  | 
 | ||||||
|  | // testOutputBuf is used to buffer the output of concurrent tests. | ||||||
|  | type testOutputBuf struct { | ||||||
|  | 	mu             sync.Mutex | ||||||
|  | 	output         []outputEntry | ||||||
|  | 	stdout, stderr io.Writer | ||||||
|  | 	outerr, errerr error | ||||||
|  | 	done           chan struct{} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // flush the output to stdout and stderr. | ||||||
|  | // This waits until done is closed. | ||||||
|  | func (b *testOutputBuf) flush(stdout, stderr io.Writer) error { | ||||||
|  | 	b.mu.Lock() | ||||||
|  | 
 | ||||||
|  | 	var err error | ||||||
|  | 	b.stdout = stdout | ||||||
|  | 	b.stderr = stderr | ||||||
|  | 	for _, e := range b.output { | ||||||
|  | 		var w io.Writer | ||||||
|  | 		var errDst *error | ||||||
|  | 		if e.stderr { | ||||||
|  | 			w = stderr | ||||||
|  | 			errDst = &b.errerr | ||||||
|  | 		} else { | ||||||
|  | 			w = stdout | ||||||
|  | 			errDst = &b.outerr | ||||||
|  | 		} | ||||||
|  | 		if *errDst != nil { | ||||||
|  | 			continue | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		_, werr := w.Write(e.data) | ||||||
|  | 		if werr != nil { | ||||||
|  | 			if err == nil { | ||||||
|  | 				err = werr | ||||||
|  | 			} | ||||||
|  | 			*errDst = err | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	b.mu.Unlock() | ||||||
|  | 
 | ||||||
|  | 	<-b.done | ||||||
|  | 
 | ||||||
|  | 	return err | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // testStdout writes stdout from a test to the output buffer. | ||||||
|  | type testStdout testOutputBuf | ||||||
|  | 
 | ||||||
|  | func (out *testStdout) Write(data []byte) (int, error) { | ||||||
|  | 	buf := (*testOutputBuf)(out) | ||||||
|  | 	buf.mu.Lock() | ||||||
|  | 
 | ||||||
|  | 	if buf.stdout != nil { | ||||||
|  | 		// Write the output directly. | ||||||
|  | 		err := out.outerr | ||||||
|  | 		buf.mu.Unlock() | ||||||
|  | 		if err != nil { | ||||||
|  | 			return 0, err | ||||||
|  | 		} | ||||||
|  | 		return buf.stdout.Write(data) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	defer buf.mu.Unlock() | ||||||
|  | 
 | ||||||
|  | 	// Append the output. | ||||||
|  | 	var prev []byte | ||||||
|  | 	if len(buf.output) > 0 && !buf.output[len(buf.output)-1].stderr { | ||||||
|  | 		prev = buf.output[len(buf.output)-1].data | ||||||
|  | 		buf.output = buf.output[:len(buf.output)-1] | ||||||
|  | 	} | ||||||
|  | 	buf.output = append(buf.output, outputEntry{ | ||||||
|  | 		stderr: false, | ||||||
|  | 		data:   append(prev, data...), | ||||||
|  | 	}) | ||||||
|  | 
 | ||||||
|  | 	return len(data), nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // testStderr writes stderr from a test to the output buffer. | ||||||
|  | type testStderr testOutputBuf | ||||||
|  | 
 | ||||||
|  | func (out *testStderr) Write(data []byte) (int, error) { | ||||||
|  | 	buf := (*testOutputBuf)(out) | ||||||
|  | 	buf.mu.Lock() | ||||||
|  | 
 | ||||||
|  | 	if buf.stderr != nil { | ||||||
|  | 		// Write the output directly. | ||||||
|  | 		err := out.errerr | ||||||
|  | 		buf.mu.Unlock() | ||||||
|  | 		if err != nil { | ||||||
|  | 			return 0, err | ||||||
|  | 		} | ||||||
|  | 		return buf.stderr.Write(data) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	defer buf.mu.Unlock() | ||||||
|  | 
 | ||||||
|  | 	// Append the output. | ||||||
|  | 	var prev []byte | ||||||
|  | 	if len(buf.output) > 0 && buf.output[len(buf.output)-1].stderr { | ||||||
|  | 		prev = buf.output[len(buf.output)-1].data | ||||||
|  | 		buf.output = buf.output[:len(buf.output)-1] | ||||||
|  | 	} | ||||||
|  | 	buf.output = append(buf.output, outputEntry{ | ||||||
|  | 		stderr: true, | ||||||
|  | 		data:   append(prev, data...), | ||||||
|  | 	}) | ||||||
|  | 
 | ||||||
|  | 	return len(data), nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | type outputEntry struct { | ||||||
|  | 	stderr bool | ||||||
|  | 	data   []byte | ||||||
|  | } | ||||||
|  |  | ||||||
		Загрузка…
	
	Создание таблицы
		
		Сослаться в новой задаче
	
	 Nia Waldvogel
						Nia Waldvogel