Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions interp/scan.go
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,10 @@ func (e *evalPackage) hasSideEffects(fn llvm.Value) (*sideEffectResult, *Error)
return &sideEffectResult{severity: sideEffectNone}, nil
case name == "runtime.trackPointer":
return &sideEffectResult{severity: sideEffectNone}, nil
case name == "os.runtime_args":
// Special-casing this one because the (only) global it accesses changes
// before runtime.initAll is called.
return &sideEffectResult{severity: sideEffectLimited}, nil
case name == "llvm.dbg.value":
return &sideEffectResult{severity: sideEffectNone}, nil
case name == "(*sync/atomic.Value).Load" || name == "(*sync/atomic.Value).Store":
Expand Down
150 changes: 114 additions & 36 deletions main_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ import (
"os/exec"
"path/filepath"
"runtime"
"sort"
"strings"
"sync"
"testing"
Expand All @@ -24,40 +23,44 @@ import (
"github.com/tinygo-org/tinygo/goenv"
)

const TESTDATA = "testdata"

var testTarget = flag.String("target", "", "override test target")

func TestCompiler(t *testing.T) {
matches, err := filepath.Glob(filepath.Join(TESTDATA, "*.go"))
if err != nil {
t.Fatal("could not read test files:", err)
tests := []string{
"alias.go",
"atomic.go",
"binop.go",
"calls.go",
"cgo/",
"channel.go",
"coroutines.go",
"float.go",
"gc.go",
"init.go",
"init_multi.go",
"interface.go",
"map.go",
"math.go",
"print.go",
"reflect.go",
"slice.go",
"stdlib.go",
"string.go",
"structs.go",
"zeroalloc.go",
}

dirMatches, err := filepath.Glob(filepath.Join(TESTDATA, "*", "main.go"))
if err != nil {
t.Fatal("could not read test packages:", err)
}
if len(matches) == 0 || len(dirMatches) == 0 {
t.Fatal("no test files found")
}
for _, m := range dirMatches {
matches = append(matches, filepath.Dir(m)+string(filepath.Separator))
}

sort.Strings(matches)

if *testTarget != "" {
// This makes it possible to run one specific test (instead of all),
// which is especially useful to quickly check whether some changes
// affect a particular target architecture.
runPlatTests(*testTarget, matches, t)
runPlatTests(*testTarget, tests, t)
return
}

if runtime.GOOS != "windows" {
t.Run("Host", func(t *testing.T) {
runPlatTests("", matches, t)
runPlatTests("", tests, t)
})
}

Expand All @@ -66,26 +69,26 @@ func TestCompiler(t *testing.T) {
}

t.Run("EmulatedCortexM3", func(t *testing.T) {
runPlatTests("cortex-m-qemu", matches, t)
runPlatTests("cortex-m-qemu", tests, t)
})

if runtime.GOOS == "windows" || runtime.GOOS == "darwin" {
// Note: running only on Windows and macOS because Linux (as of 2020)
// usually has an outdated QEMU version that doesn't support RISC-V yet.
t.Run("EmulatedRISCV", func(t *testing.T) {
runPlatTests("riscv-qemu", matches, t)
runPlatTests("riscv-qemu", tests, t)
})
}

if runtime.GOOS == "linux" {
t.Run("X86Linux", func(t *testing.T) {
runPlatTests("i386--linux-gnu", matches, t)
runPlatTests("i386--linux-gnu", tests, t)
})
t.Run("ARMLinux", func(t *testing.T) {
runPlatTests("arm--linux-gnueabihf", matches, t)
runPlatTests("arm--linux-gnueabihf", tests, t)
})
t.Run("ARM64Linux", func(t *testing.T) {
runPlatTests("aarch64--linux-gnu", matches, t)
runPlatTests("aarch64--linux-gnu", tests, t)
})
goVersion, err := goenv.GorootVersionString(goenv.Get("GOROOT"))
if err != nil {
Expand All @@ -98,20 +101,20 @@ func TestCompiler(t *testing.T) {
// below that are also not supported but still seem to pass, so
// include them in the tests for now.
t.Run("WebAssembly", func(t *testing.T) {
runPlatTests("wasm", matches, t)
runPlatTests("wasm", tests, t)
})
}

t.Run("WASI", func(t *testing.T) {
runPlatTests("wasi", matches, t)
runPlatTests("wasi", tests, t)
})
}
}

func runPlatTests(target string, matches []string, t *testing.T) {
func runPlatTests(target string, tests []string, t *testing.T) {
t.Parallel()

for _, path := range matches {
for _, path := range tests {
path := path // redefine to avoid race condition

t.Run(filepath.Base(path), func(t *testing.T) {
Expand All @@ -133,13 +136,13 @@ func runBuild(src, out string, opts *compileopts.Options) error {
return Build(src, out, opts)
}

func runTest(path, target string, t *testing.T) {
func runTest(name, target string, t *testing.T) {
// Get the expected output for this test.
txtpath := path[:len(path)-3] + ".txt"
if path[len(path)-1] == os.PathSeparator {
txtpath = path + "out.txt"
txtfile := name[:len(name)-3] + ".txt"
if name[len(name)-1] == '/' {
txtfile = name + "out.txt"
}
expected, err := ioutil.ReadFile(txtpath)
expected, err := ioutil.ReadFile(filepath.Join("testdata", txtfile))
if err != nil {
t.Fatal("could not read expected output file:", err)
}
Expand Down Expand Up @@ -169,7 +172,7 @@ func runTest(path, target string, t *testing.T) {
}

binary := filepath.Join(tmpdir, "test")
err = runBuild("./"+path, binary, config)
err = runBuild("./testdata/"+name, binary, config)
if err != nil {
printCompilerError(t.Log, err)
t.Fail()
Expand Down Expand Up @@ -253,6 +256,81 @@ func runTest(path, target string, t *testing.T) {
}
}

// TestHostEnvironment tests command line arguments. In the future it may also
// test other things, such as environment variables.
func TestHostEnvironment(t *testing.T) {
// Create a temporary directory for test output files.
tmpdir, err := ioutil.TempDir("", "tinygo-test")
if err != nil {
t.Fatal("could not create temporary directory:", err)
}
defer func() {
rerr := os.RemoveAll(tmpdir)
if rerr != nil {
t.Errorf("failed to remove temporary directory %q: %s", tmpdir, rerr.Error())
}
}()

// Build the test binary.
config := &compileopts.Options{
Target: "",
Opt: "z",
PrintIR: false,
DumpSSA: false,
VerifyIR: true,
Debug: true,
PrintSizes: "",
WasmAbi: "",
}
binary := filepath.Join(tmpdir, "test")
err = runBuild("./testdata/environment.go", binary, config)
if err != nil {
printCompilerError(t.Log, err)
t.Fail()
return
}

// Run the test.
cmd := exec.Command(binary, "arg1", "\targ2 \n")
stdout := &bytes.Buffer{}
cmd.Stdout = stdout
cmd.Stderr = stdout
err = cmd.Start()
if err != nil {
t.Fatal("failed to start:", err)
}
err = cmd.Wait()

expected := fmt.Sprintf("args: 3\narg: %s\narg: arg1\narg: \targ2 \n", binary)

// Munge the output a bit to make it easier to work with: putchar() prints
// CRLF, convert it to LF.
actual := strings.Replace(string(stdout.Bytes()), "\r\n", "\n", -1)
actual = actual[:len(actual)-1] // remove trailing '\n' char

// Check whether the command ran successfully, and print the actual output
// if it differs.
fail := false
if err != nil {
t.Log("failed to run:", err)
fail = true
} else if expected != actual {
t.Log("output did not match")
fail = true
}
if fail {
r := bufio.NewReader(strings.NewReader(actual))
for {
line, err := r.ReadString('\n')
if err != nil {
break
}
t.Log("stdout:", line[:len(line)-1])
}
t.Fail()
}
}

// This TestMain is necessary because TinyGo may also be invoked to run certain
// LLVM tools in a separate process. Not capturing these invocations would lead
// to recursive tests.
Expand Down
3 changes: 3 additions & 0 deletions src/runtime/runtime.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,9 @@ func GOROOT() string {
// TODO: fill with real args.
var args = []string{"/proc/self/exe"}

// This function is called from the os package. It is special-cased by the
// interp package to not run at init time, otherwise it would find the
// uninitialized args slice.
//go:linkname os_runtime_args os.runtime_args
func os_runtime_args() []string {
return args
Expand Down
26 changes: 25 additions & 1 deletion src/runtime/runtime_unix.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@ import (
//export putchar
func _putchar(c int) int

//export strlen
func strlen(s *byte) uintptr

//export usleep
func usleep(usec uint) int

Expand Down Expand Up @@ -43,9 +46,30 @@ func postinit() {}

// Entry point for Go. Initialize all packages and call main.main().
//export main
func main() int {
func main(argc int32, argv **byte) int {
preinit()

// Make args global big enough so that it can store all command line
// arguments. Unfortunately this has to be done with some magic as the heap
// is not yet initialized.
argsSlice := (*struct {
ptr unsafe.Pointer
len uintptr
cap uintptr
})(unsafe.Pointer(&args))
argsSlice.ptr = malloc(uintptr(argc) * (unsafe.Sizeof(uintptr(0))) * 3)
argsSlice.len = uintptr(argc)
argsSlice.cap = uintptr(argc)

// Initialize command line parameters. Again, using some magic, this time to
// convert (argc, argv) to a Go slice without doing any memory allocations.
argvSlice := (*[1 << 16]*byte)(unsafe.Pointer(argv))[:argc]
for i, ptr := range argvSlice {
argString := (*_string)(unsafe.Pointer(&args[i]))
argString.length = strlen(ptr)
argString.ptr = ptr
}

// Obtain the initial stack pointer right before calling the run() function.
// The run function has been moved to a separate (non-inlined) function so
// that the correct stack pointer is read.
Expand Down
10 changes: 10 additions & 0 deletions testdata/environment.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package main

import "os"

func main() {
println("args:", len(os.Args))
for _, arg := range os.Args {
println("arg:", arg)
}
}