diff options
author | Ayke van Laethem <[email protected]> | 2023-01-14 17:16:42 +0100 |
---|---|---|
committer | Ron Evans <[email protected]> | 2023-01-15 08:49:18 +0100 |
commit | 80077ef2765ab0616ac014fe3989c345e2770abc (patch) | |
tree | 7a2a3f127f77f42fb6307afd7e9eff79cdce1df6 | |
parent | 911ce3a4bcaaed7140bd1922c3c5f5645d87f08d (diff) | |
download | tinygo-80077ef2765ab0616ac014fe3989c345e2770abc.tar.gz tinygo-80077ef2765ab0616ac014fe3989c345e2770abc.zip |
test: print package name when compilation failed
Before this patch, a compile error would prevent the 'ok' or 'FAIL' line
to be printed. That's unexpected. This patch changes the code in such a
way that it's obvious a test result line is printed in all cases.
To be able to also print the package name, I had to make sure the build
result is passed through everywhere even on all the failure paths. This
results in a bit of churn, but it's all relatively straightforward.
Found while working on Go 1.20.
-rw-r--r-- | builder/build.go | 100 | ||||
-rw-r--r-- | main.go | 37 | ||||
-rw-r--r-- | main_test.go | 2 |
3 files changed, 67 insertions, 72 deletions
diff --git a/builder/build.go b/builder/build.go index 7c41354f9..26395d53f 100644 --- a/builder/build.go +++ b/builder/build.go @@ -190,12 +190,21 @@ func Build(pkgName, outpath, tmpdir string, config *compileopts.Config) (BuildRe lprogram, err := loader.Load(config, pkgName, config.ClangHeaders, types.Config{ Sizes: compiler.Sizes(machine), }) - if err != nil { - return BuildResult{}, err + result := BuildResult{ + ModuleRoot: lprogram.MainPkg().Module.Dir, + MainDir: lprogram.MainPkg().Dir, + ImportPath: lprogram.MainPkg().ImportPath, + } + if result.ModuleRoot == "" { + // If there is no module root, just the regular root. + result.ModuleRoot = lprogram.MainPkg().Root + } + if err != nil { // failed to load AST + return result, err } err = lprogram.Parse() if err != nil { - return BuildResult{}, err + return result, err } // Create the *ssa.Program. This does not yet build the entire SSA of the @@ -278,7 +287,7 @@ func Build(pkgName, outpath, tmpdir string, config *compileopts.Config) (BuildRe for _, imported := range pkg.Pkg.Imports() { job, ok := packageActionIDJobs[imported.Path()] if !ok { - return BuildResult{}, fmt.Errorf("package %s imports %s but couldn't find dependency", pkg.ImportPath, imported.Path()) + return result, fmt.Errorf("package %s imports %s but couldn't find dependency", pkg.ImportPath, imported.Path()) } importedPackages = append(importedPackages, job) actionIDDependencies = append(actionIDDependencies, job) @@ -573,17 +582,17 @@ func Build(pkgName, outpath, tmpdir string, config *compileopts.Config) (BuildRe // Run jobs to produce the LLVM module. err := runJobs(programJob, config.Options.Semaphore) if err != nil { - return BuildResult{}, err + return result, err } // Generate output. switch outext { case ".o": llvmBuf, err := machine.EmitToMemoryBuffer(mod, llvm.ObjectFile) if err != nil { - return BuildResult{}, err + return result, err } defer llvmBuf.Dispose() - return BuildResult{}, os.WriteFile(outpath, llvmBuf.Bytes(), 0666) + return result, os.WriteFile(outpath, llvmBuf.Bytes(), 0666) case ".bc": var buf llvm.MemoryBuffer if config.UseThinLTO() { @@ -592,10 +601,10 @@ func Build(pkgName, outpath, tmpdir string, config *compileopts.Config) (BuildRe buf = llvm.WriteBitcodeToMemoryBuffer(mod) } defer buf.Dispose() - return BuildResult{}, os.WriteFile(outpath, buf.Bytes(), 0666) + return result, os.WriteFile(outpath, buf.Bytes(), 0666) case ".ll": data := []byte(mod.String()) - return BuildResult{}, os.WriteFile(outpath, data, 0666) + return result, os.WriteFile(outpath, data, 0666) default: panic("unreachable") } @@ -629,19 +638,19 @@ func Build(pkgName, outpath, tmpdir string, config *compileopts.Config) (BuildRe // Prepare link command. linkerDependencies := []*compileJob{outputObjectFileJob} - executable := filepath.Join(tmpdir, "main") + result.Executable = filepath.Join(tmpdir, "main") if config.GOOS() == "windows" { - executable += ".exe" + result.Executable += ".exe" } - tmppath := executable // final file - ldflags := append(config.LDFlags(), "-o", executable) + result.Binary = result.Executable // final file + ldflags := append(config.LDFlags(), "-o", result.Executable) // Add compiler-rt dependency if needed. Usually this is a simple load from // a cache. if config.Target.RTLib == "compiler-rt" { job, unlock, err := CompilerRT.load(config, tmpdir) if err != nil { - return BuildResult{}, err + return result, err } defer unlock() linkerDependencies = append(linkerDependencies, job) @@ -716,7 +725,7 @@ func Build(pkgName, outpath, tmpdir string, config *compileopts.Config) (BuildRe ldflags = append(ldflags, "--strip-debug") } else { // Other linkers may have different flags. - return BuildResult{}, errors.New("cannot remove debug information: unknown linker: " + config.Target.Linker) + return result, errors.New("cannot remove debug information: unknown linker: " + config.Target.Linker) } } @@ -768,7 +777,7 @@ func Build(pkgName, outpath, tmpdir string, config *compileopts.Config) (BuildRe } err = link(config.Target.Linker, ldflags...) if err != nil { - return &commandError{"failed to link", executable, err} + return &commandError{"failed to link", result.Executable, err} } var calculatedStacks []string @@ -777,7 +786,7 @@ func Build(pkgName, outpath, tmpdir string, config *compileopts.Config) (BuildRe // Try to determine stack sizes at compile time. // Don't do this by default as it usually doesn't work on // unsupported architectures. - calculatedStacks, stackSizes, err = determineStackSizes(mod, executable) + calculatedStacks, stackSizes, err = determineStackSizes(mod, result.Executable) if err != nil { return err } @@ -787,14 +796,14 @@ func Build(pkgName, outpath, tmpdir string, config *compileopts.Config) (BuildRe if config.AutomaticStackSize() { // Modify the .tinygo_stacksizes section that contains a stack size // for each goroutine. - err = modifyStackSizes(executable, stackSizeLoads, stackSizes) + err = modifyStackSizes(result.Executable, stackSizeLoads, stackSizes) if err != nil { return fmt.Errorf("could not modify stack sizes: %w", err) } } if config.RP2040BootPatch() { // Patch the second stage bootloader CRC into the .boot2 section - err = patchRP2040BootCRC(executable) + err = patchRP2040BootCRC(result.Executable) if err != nil { return fmt.Errorf("could not patch RP2040 second stage boot loader: %w", err) } @@ -827,8 +836,8 @@ func Build(pkgName, outpath, tmpdir string, config *compileopts.Config) (BuildRe args = append(args, opt, "-g", - executable, - "--output", executable, + result.Executable, + "--output", result.Executable, ) cmd := exec.Command(goenv.Get("WASMOPT"), args...) @@ -847,7 +856,7 @@ func Build(pkgName, outpath, tmpdir string, config *compileopts.Config) (BuildRe for _, pkg := range lprogram.Sorted() { packagePathMap[pkg.OriginalDir()] = pkg.Pkg.Path() } - sizes, err := loadProgramSize(executable, packagePathMap) + sizes, err := loadProgramSize(result.Executable, packagePathMap) if err != nil { return err } @@ -883,7 +892,7 @@ func Build(pkgName, outpath, tmpdir string, config *compileopts.Config) (BuildRe // is simpler and cannot be parallelized. err = runJobs(linkJob, config.Options.Semaphore) if err != nil { - return BuildResult{}, err + return result, err } // Get an Intel .hex file or .bin file from the .elf file. @@ -894,56 +903,43 @@ func Build(pkgName, outpath, tmpdir string, config *compileopts.Config) (BuildRe case "hex", "bin": // Extract raw binary, either encoding it as a hex file or as a raw // firmware file. - tmppath = filepath.Join(tmpdir, "main"+outext) - err := objcopy(executable, tmppath, outputBinaryFormat) + result.Binary = filepath.Join(tmpdir, "main"+outext) + err := objcopy(result.Executable, result.Binary, outputBinaryFormat) if err != nil { - return BuildResult{}, err + return result, err } case "uf2": // Get UF2 from the .elf file. - tmppath = filepath.Join(tmpdir, "main"+outext) - err := convertELFFileToUF2File(executable, tmppath, config.Target.UF2FamilyID) + result.Binary = filepath.Join(tmpdir, "main"+outext) + err := convertELFFileToUF2File(result.Executable, result.Binary, config.Target.UF2FamilyID) if err != nil { - return BuildResult{}, err + return result, err } case "esp32", "esp32-img", "esp32c3", "esp8266": // Special format for the ESP family of chips (parsed by the ROM // bootloader). - tmppath = filepath.Join(tmpdir, "main"+outext) - err := makeESPFirmareImage(executable, tmppath, outputBinaryFormat) + result.Binary = filepath.Join(tmpdir, "main"+outext) + err := makeESPFirmareImage(result.Executable, result.Binary, outputBinaryFormat) if err != nil { - return BuildResult{}, err + return result, err } case "nrf-dfu": // special format for nrfutil for Nordic chips tmphexpath := filepath.Join(tmpdir, "main.hex") - err := objcopy(executable, tmphexpath, "hex") + err := objcopy(result.Executable, tmphexpath, "hex") if err != nil { - return BuildResult{}, err + return result, err } - tmppath = filepath.Join(tmpdir, "main"+outext) - err = makeDFUFirmwareImage(config.Options, tmphexpath, tmppath) + result.Binary = filepath.Join(tmpdir, "main"+outext) + err = makeDFUFirmwareImage(config.Options, tmphexpath, result.Binary) if err != nil { - return BuildResult{}, err + return result, err } default: - return BuildResult{}, fmt.Errorf("unknown output binary format: %s", outputBinaryFormat) + return result, fmt.Errorf("unknown output binary format: %s", outputBinaryFormat) } - // If there's a module root, use that. - moduleroot := lprogram.MainPkg().Module.Dir - if moduleroot == "" { - // if not, just the regular root - moduleroot = lprogram.MainPkg().Root - } - - return BuildResult{ - Executable: executable, - Binary: tmppath, - MainDir: lprogram.MainPkg().Dir, - ModuleRoot: moduleroot, - ImportPath: lprogram.MainPkg().ImportPath, - }, nil + return result, nil } // createEmbedObjectFile creates a new object file with the given contents, for @@ -246,7 +246,8 @@ func Test(pkgName string, stdout, stderr io.Writer, options *compileopts.Options buf := bytes.Buffer{} passed := false - err = buildAndRun(pkgName, config, &buf, flags, nil, 0, func(cmd *exec.Cmd, result builder.BuildResult) error { + var duration time.Duration + result, err := buildAndRun(pkgName, config, &buf, flags, nil, 0, func(cmd *exec.Cmd, result builder.BuildResult) error { if testCompileOnly || outpath != "" { // Write test binary to the specified file name. if outpath == "" { @@ -312,10 +313,7 @@ func Test(pkgName string, stdout, stderr io.Writer, options *compileopts.Options // Run the test. start := time.Now() err = cmd.Run() - duration := time.Since(start) - - // Print the result. - importPath := strings.TrimSuffix(result.ImportPath, ".test") + duration = time.Since(start) passed = err == nil // print the test output if @@ -326,23 +324,23 @@ func Test(pkgName string, stdout, stderr io.Writer, options *compileopts.Options buf.WriteTo(stdout) } - // final status line - if passed { - fmt.Fprintf(stdout, "ok \t%s\t%.3fs\n", importPath, duration.Seconds()) - } else { - fmt.Fprintf(stdout, "FAIL\t%s\t%.3fs\n", importPath, duration.Seconds()) - } if _, ok := err.(*exec.ExitError); ok { // Binary exited with a non-zero exit code, which means the test - // failed. + // failed. Return nil to avoid printing a useless "exited with + // error" error message. return nil } return err }) + importPath := strings.TrimSuffix(result.ImportPath, ".test") if err, ok := err.(loader.NoTestFilesError); ok { fmt.Fprintf(stdout, "? \t%s\t[no test files]\n", err.ImportPath) // Pretend the test passed - it at least didn't fail. return true, nil + } else if passed { + fmt.Fprintf(stdout, "ok \t%s\t%.3fs\n", importPath, duration.Seconds()) + } else { + fmt.Fprintf(stdout, "FAIL\t%s\t%.3fs\n", importPath, duration.Seconds()) } return passed, err } @@ -765,16 +763,17 @@ func Run(pkgName string, options *compileopts.Options, cmdArgs []string) error { return err } - return buildAndRun(pkgName, config, os.Stdout, cmdArgs, nil, 0, func(cmd *exec.Cmd, result builder.BuildResult) error { + _, err = buildAndRun(pkgName, config, os.Stdout, cmdArgs, nil, 0, func(cmd *exec.Cmd, result builder.BuildResult) error { return cmd.Run() }) + return err } // buildAndRun builds and runs the given program, writing output to stdout and // errors to os.Stderr. It takes care of emulators (qemu, wasmtime, etc) and // passes command line arguments and evironment variables in a way appropriate // for the given emulator. -func buildAndRun(pkgName string, config *compileopts.Config, stdout io.Writer, cmdArgs, environmentVars []string, timeout time.Duration, run func(cmd *exec.Cmd, result builder.BuildResult) error) error { +func buildAndRun(pkgName string, config *compileopts.Config, stdout io.Writer, cmdArgs, environmentVars []string, timeout time.Duration, run func(cmd *exec.Cmd, result builder.BuildResult) error) (builder.BuildResult, error) { // Determine whether we're on a system that supports environment variables // and command line parameters (operating systems, WASI) or not (baremetal, // WebAssembly in the browser). If we're on a system without an environment, @@ -828,7 +827,7 @@ func buildAndRun(pkgName string, config *compileopts.Config, stdout io.Writer, c // Create a temporary directory for intermediary files. tmpdir, err := os.MkdirTemp("", "tinygo") if err != nil { - return err + return builder.BuildResult{}, err } if !config.Options.Work { defer os.RemoveAll(tmpdir) @@ -838,7 +837,7 @@ func buildAndRun(pkgName string, config *compileopts.Config, stdout io.Writer, c format, fileExt := config.EmulatorFormat() result, err := builder.Build(pkgName, fileExt, tmpdir, config) if err != nil { - return err + return result, err } // If needed, set a timeout on the command. This is done in tests so @@ -857,7 +856,7 @@ func buildAndRun(pkgName string, config *compileopts.Config, stdout io.Writer, c } else { emulator, err := config.Emulator(format, result.Binary) if err != nil { - return err + return result, err } name = emulator[0] emuArgs := append([]string(nil), emulator[1:]...) @@ -898,9 +897,9 @@ func buildAndRun(pkgName string, config *compileopts.Config, stdout io.Writer, c stdout.Write([]byte(fmt.Sprintf("--- timeout of %s exceeded, terminating...\n", timeout))) err = ctx.Err() } - return &commandError{"failed to run compiled binary", result.Binary, err} + return result, &commandError{"failed to run compiled binary", result.Binary, err} } - return nil + return result, nil } func touchSerialPortAt1200bps(port string) (err error) { diff --git a/main_test.go b/main_test.go index af2a7c7f7..72f3c4bbc 100644 --- a/main_test.go +++ b/main_test.go @@ -329,7 +329,7 @@ func runTestWithConfig(name string, t *testing.T, options compileopts.Options, c // Build the test binary. stdout := &bytes.Buffer{} - err = buildAndRun("./"+path, config, stdout, cmdArgs, environmentVars, time.Minute, func(cmd *exec.Cmd, result builder.BuildResult) error { + _, err = buildAndRun("./"+path, config, stdout, cmdArgs, environmentVars, time.Minute, func(cmd *exec.Cmd, result builder.BuildResult) error { return cmd.Run() }) if err != nil { |