aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--main.go140
-rw-r--r--main_test.go105
2 files changed, 119 insertions, 126 deletions
diff --git a/main.go b/main.go
index c1a39ee60..63baa9ee0 100644
--- a/main.go
+++ b/main.go
@@ -2,6 +2,7 @@ package main
import (
"bytes"
+ "context"
"encoding/json"
"errors"
"flag"
@@ -714,38 +715,121 @@ func Run(pkgName string, options *compileopts.Options) error {
return err
}
- return builder.Build(pkgName, ".elf", config, func(result builder.BuildResult) error {
- emulator := config.Emulator()
- if len(emulator) == 0 {
- // Run directly.
- cmd := executeCommand(config.Options, result.Binary)
- cmd.Stdout = os.Stdout
- cmd.Stderr = os.Stderr
- err := cmd.Run()
- if err != nil {
- if err, ok := err.(*exec.ExitError); ok && err.Exited() {
- // Workaround for QEMU which always exits with an error.
- return nil
- }
- return &commandError{"failed to run compiled binary", result.Binary, err}
+ return buildAndRun(pkgName, config, os.Stdout, nil, nil, 0)
+}
+
+// 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) error {
+ // make sure any special vars in the emulator definition are rewritten
+ emulator := config.Emulator()
+
+ // 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,
+ // we need to pass command line arguments and environment variables through
+ // global variables (built into the binary directly) instead of the
+ // conventional way.
+ needsEnvInVars := config.GOOS() == "js"
+ for _, tag := range config.BuildTags() {
+ if tag == "baremetal" {
+ needsEnvInVars = true
+ }
+ }
+ var args, env []string
+ if needsEnvInVars {
+ runtimeGlobals := make(map[string]string)
+ if len(cmdArgs) != 0 {
+ runtimeGlobals["osArgs"] = strings.Join(cmdArgs, "\x00")
+ }
+ if len(environmentVars) != 0 {
+ runtimeGlobals["osEnv"] = strings.Join(environmentVars, "\x00")
+ }
+ if len(runtimeGlobals) != 0 {
+ // This sets the global variables like they would be set with
+ // `-ldflags="-X=runtime.osArgs=first\x00second`.
+ // The runtime package has two variables (osArgs and osEnv) that are
+ // both strings, from which the parameters and environment variables
+ // are read.
+ config.Options.GlobalValues = map[string]map[string]string{
+ "runtime": runtimeGlobals,
}
- return nil
+ }
+ } else if len(emulator) != 0 && emulator[0] == "wasmtime" {
+ // Wasmtime needs some special flags to pass environment variables
+ // and allow reading from the current directory.
+ args = append(args, "--dir=.")
+ for _, v := range environmentVars {
+ args = append(args, "--env", v)
+ }
+ args = append(args, cmdArgs...)
+ } else {
+ // Pass environment variables and command line parameters as usual.
+ // This also works on qemu-aarch64 etc.
+ args = cmdArgs
+ env = environmentVars
+ }
+
+ return builder.Build(pkgName, "", config, func(result builder.BuildResult) error {
+ // If needed, set a timeout on the command. This is done in tests so
+ // they don't waste resources on a stalled test.
+ var ctx context.Context
+ if timeout != 0 {
+ var cancel context.CancelFunc
+ ctx, cancel = context.WithTimeout(context.Background(), timeout)
+ defer cancel()
+ }
+
+ // Set up the command.
+ var name string
+ if len(emulator) == 0 {
+ name = result.Binary
} else {
- // Run in an emulator.
- args := append(emulator[1:], result.Binary)
- cmd := executeCommand(config.Options, emulator[0], args...)
- cmd.Stdout = os.Stdout
- cmd.Stderr = os.Stderr
- err := cmd.Run()
- if err != nil {
- if err, ok := err.(*exec.ExitError); ok && err.Exited() {
- // Workaround for QEMU which always exits with an error.
- return nil
- }
- return &commandError{"failed to run emulator with", result.Binary, err}
+ name = emulator[0]
+ emuArgs := append([]string(nil), emulator[1:]...)
+ emuArgs = append(emuArgs, result.Binary)
+ args = append(emuArgs, args...)
+ }
+ var cmd *exec.Cmd
+ if ctx != nil {
+ cmd = exec.CommandContext(ctx, name, args...)
+ } else {
+ cmd = exec.Command(name, args...)
+ }
+ cmd.Env = env
+
+ // Configure stdout/stderr. The stdout may go to a buffer, not a real
+ // stdout.
+ cmd.Stdout = stdout
+ cmd.Stderr = os.Stderr
+ if len(emulator) != 0 && emulator[0] == "simavr" {
+ cmd.Stdout = nil // don't print initial load commands
+ cmd.Stderr = stdout
+ }
+
+ // If this is a test, reserve CPU time for it so that increased
+ // parallelism doesn't blow up memory usage. If this isn't a test but
+ // simply `tinygo run`, then it is practically a no-op.
+ config.Options.Semaphore <- struct{}{}
+ defer func() {
+ <-config.Options.Semaphore
+ }()
+
+ // Run binary.
+ if config.Options.PrintCommands != nil {
+ config.Options.PrintCommands(cmd.Path, cmd.Args...)
+ }
+ err := cmd.Run()
+ if err != nil {
+ if cerr := ctx.Err(); cerr == context.DeadlineExceeded {
+ stdout.Write([]byte(fmt.Sprintf("--- timeout of %s exceeded, terminating...\n", timeout)))
+ err = cerr
}
- return nil
+ return &commandError{"failed to run compiled binary", result.Binary, err}
}
+ return nil
})
}
diff --git a/main_test.go b/main_test.go
index b4210fd9b..4b043932c 100644
--- a/main_test.go
+++ b/main_test.go
@@ -6,7 +6,6 @@ package main
import (
"bufio"
"bytes"
- "context"
"errors"
"flag"
"fmt"
@@ -14,7 +13,6 @@ import (
"io/ioutil"
"os"
"os/exec"
- "path/filepath"
"regexp"
"runtime"
"strings"
@@ -324,110 +322,21 @@ func runTestWithConfig(name string, t *testing.T, options compileopts.Options, c
t.Fatal("could not read expected output file:", err)
}
- // Create a temporary directory for test output files.
- tmpdir := t.TempDir()
-
- // 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,
- // we need to pass command line arguments and environment variables through
- // global variables (built into the binary directly) instead of the
- // conventional way.
- spec, err := compileopts.LoadTarget(&options)
+ config, err := builder.NewConfig(&options)
if err != nil {
- t.Fatal("failed to load target spec:", err)
- }
- needsEnvInVars := spec.GOOS == "js"
- for _, tag := range spec.BuildTags {
- if tag == "baremetal" {
- needsEnvInVars = true
- }
- }
- if needsEnvInVars {
- runtimeGlobals := make(map[string]string)
- if len(cmdArgs) != 0 {
- runtimeGlobals["osArgs"] = strings.Join(cmdArgs, "\x00")
- }
- if len(environmentVars) != 0 {
- runtimeGlobals["osEnv"] = strings.Join(environmentVars, "\x00")
- }
- if len(runtimeGlobals) != 0 {
- // This sets the global variables like they would be set with
- // `-ldflags="-X=runtime.osArgs=first\x00second`.
- // The runtime package has two variables (osArgs and osEnv) that are
- // both strings, from which the parameters and environment variables
- // are read.
- options.GlobalValues = map[string]map[string]string{
- "runtime": runtimeGlobals,
- }
- }
+ t.Fatal(err)
}
- // Build the test binary.
- binary := filepath.Join(tmpdir, "test")
- if spec.GOOS == "windows" {
- binary += ".exe"
- }
- err = Build("./"+path, binary, &options)
- if err != nil {
- printCompilerError(t.Log, err)
- t.Fail()
- return
- }
-
- // Reserve CPU time for the test to run.
- // This attempts to ensure that the test is not CPU-starved.
- options.Semaphore <- struct{}{}
- defer func() { <-options.Semaphore }()
-
- // Create the test command, taking care of emulators etc.
- ctx, cancel := context.WithTimeout(context.Background(), time.Minute)
- defer cancel()
- var cmd *exec.Cmd
-
// make sure any special vars in the emulator definition are rewritten
- config := compileopts.Config{Target: spec}
emulator := config.Emulator()
- if len(emulator) == 0 {
- cmd = exec.CommandContext(ctx, binary)
- } else {
- args := append(emulator[1:], binary)
- cmd = exec.CommandContext(ctx, emulator[0], args...)
- }
-
- if len(emulator) != 0 && emulator[0] == "wasmtime" {
- // Allow reading from the current directory.
- cmd.Args = append(cmd.Args, "--dir=.")
- for _, v := range environmentVars {
- cmd.Args = append(cmd.Args, "--env", v)
- }
- cmd.Args = append(cmd.Args, cmdArgs...)
- } else {
- if !needsEnvInVars {
- cmd.Args = append(cmd.Args, cmdArgs...) // works on qemu-aarch64 etc
- cmd.Env = append(cmd.Env, environmentVars...)
- }
- }
-
- // Run the test.
+ // Build the test binary.
stdout := &bytes.Buffer{}
- if len(emulator) != 0 && emulator[0] == "simavr" {
- cmd.Stdout = os.Stderr
- cmd.Stderr = stdout
- } else {
- cmd.Stdout = stdout
- cmd.Stderr = os.Stderr
- }
- err = cmd.Start()
+ err = buildAndRun("./"+path, config, stdout, cmdArgs, environmentVars, time.Minute)
if err != nil {
- t.Fatal("failed to start:", err)
- }
- err = cmd.Wait()
-
- if cerr := ctx.Err(); cerr == context.DeadlineExceeded {
- stdout.WriteString("--- test ran too long, terminating...\n")
- err = cerr
+ printCompilerError(t.Log, err)
+ t.Fail()
+ return
}
// putchar() prints CRLF, convert it to LF.