mirror of
https://github.com/hanzoai/yaegi.git
synced 2026-10-01 00:49:47 +00:00
Per the standing fork rule, a hanzoai fork declares its OWN module path and
is required directly -- never resolved via a `replace` against the upstream
path. Keeping the upstream path meant every import line in every consumer
carried the upstream brand.
module github.com/traefik/yaegi -> module github.com/hanzoai/yaegi
Mechanical and behaviour-neutral: every occurrence is an import path
(package self-imports, cmd/, _test/ fixtures interpreted at test time, and
the guthib.com/<org>/{fromage,cheese,vin} fake vendor trees under
example/pkg, whose directory components were renamed in lockstep with the
import strings that reference them).
GOPATH-mode source-import tests (interp TestFile) resolve the module by its
path under $GOPATH/src, so the required checkout location moves with the
module path -- as README already documents. Verified green at
$GOPATH/src/github.com/hanzoai/yaegi.
LICENSE is untouched: "Copyright 2020 Traefik Labs SAS" is the Apache-2.0
attribution and is a legal obligation, not a brand leak.
Also drops the inherited CONTRIBUTING.md CLA clause, which named an
upstream agreement this fork does not administer.
363 lines
12 KiB
Go
363 lines
12 KiB
Go
package interp_test
|
|
|
|
import (
|
|
"bytes"
|
|
"go/build"
|
|
"io"
|
|
"os"
|
|
"os/exec"
|
|
"path/filepath"
|
|
"runtime"
|
|
"strings"
|
|
"testing"
|
|
|
|
"github.com/hanzoai/yaegi/interp"
|
|
"github.com/hanzoai/yaegi/stdlib"
|
|
"github.com/hanzoai/yaegi/stdlib/unsafe"
|
|
)
|
|
|
|
var testsToSkipGo122 = map[string]bool{}
|
|
|
|
var go122 = strings.HasPrefix(runtime.Version(), "go1.22")
|
|
|
|
func TestInterpConsistencyBuild(t *testing.T) {
|
|
if testing.Short() {
|
|
t.Skip("short mode")
|
|
}
|
|
dir := filepath.Join("..", "_test", "tmp")
|
|
if _, err := os.Stat(dir); os.IsNotExist(err) {
|
|
if err := os.Mkdir(dir, 0o700); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
|
|
baseDir := filepath.Join("..", "_test")
|
|
files, err := os.ReadDir(baseDir)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
for _, file := range files {
|
|
if filepath.Ext(file.Name()) != ".go" ||
|
|
file.Name() == "assign11.go" || // expect error
|
|
file.Name() == "assign12.go" || // expect error
|
|
file.Name() == "assign15.go" || // expect error
|
|
file.Name() == "assign19.go" || // expect error
|
|
file.Name() == "bad0.go" || // expect error
|
|
file.Name() == "break0.go" || // expect error
|
|
file.Name() == "cont3.go" || // expect error
|
|
file.Name() == "const9.go" || // expect error
|
|
file.Name() == "export1.go" || // non-main package
|
|
file.Name() == "export0.go" || // non-main package
|
|
file.Name() == "for7.go" || // expect error
|
|
file.Name() == "fun21.go" || // expect error
|
|
file.Name() == "fun22.go" || // expect error
|
|
file.Name() == "fun23.go" || // expect error
|
|
file.Name() == "fun24.go" || // expect error
|
|
file.Name() == "fun25.go" || // expect error
|
|
file.Name() == "gen7.go" || // expect error
|
|
file.Name() == "gen8.go" || // expect error
|
|
file.Name() == "gen9.go" || // expect error
|
|
file.Name() == "if2.go" || // expect error
|
|
file.Name() == "import6.go" || // expect error
|
|
file.Name() == "init1.go" || // expect error
|
|
file.Name() == "io0.go" || // use random number
|
|
file.Name() == "issue-1093.go" || // expect error
|
|
file.Name() == "issue-1276.go" || // expect error
|
|
file.Name() == "issue-1330.go" || // expect error
|
|
file.Name() == "op1.go" || // expect error
|
|
file.Name() == "op7.go" || // expect error
|
|
file.Name() == "op9.go" || // expect error
|
|
file.Name() == "bltn0.go" || // expect error
|
|
file.Name() == "method16.go" || // private struct field
|
|
file.Name() == "method39.go" || // expect error
|
|
file.Name() == "switch8.go" || // expect error
|
|
file.Name() == "switch9.go" || // expect error
|
|
file.Name() == "switch13.go" || // expect error
|
|
file.Name() == "switch19.go" || // expect error
|
|
file.Name() == "time0.go" || // display time (similar to random number)
|
|
file.Name() == "factor.go" || // bench
|
|
file.Name() == "fib.go" || // bench
|
|
file.Name() == "issue-1618.go" || // bench (infinite running)
|
|
|
|
file.Name() == "type5.go" || // used to illustrate a limitation with no workaround, related to the fact that the reflect package does not allow the creation of named types
|
|
file.Name() == "type6.go" || // used to illustrate a limitation with no workaround, related to the fact that the reflect package does not allow the creation of named types
|
|
|
|
file.Name() == "redeclaration0.go" || // expect error
|
|
file.Name() == "redeclaration1.go" || // expect error
|
|
file.Name() == "redeclaration2.go" || // expect error
|
|
file.Name() == "redeclaration3.go" || // expect error
|
|
file.Name() == "redeclaration4.go" || // expect error
|
|
file.Name() == "redeclaration5.go" || // expect error
|
|
file.Name() == "redeclaration-global0.go" || // expect error
|
|
file.Name() == "redeclaration-global1.go" || // expect error
|
|
file.Name() == "redeclaration-global2.go" || // expect error
|
|
file.Name() == "redeclaration-global3.go" || // expect error
|
|
file.Name() == "redeclaration-global4.go" || // expect error
|
|
file.Name() == "redeclaration-global5.go" || // expect error
|
|
file.Name() == "redeclaration-global6.go" || // expect error
|
|
file.Name() == "redeclaration-global7.go" || // expect error
|
|
file.Name() == "panic0.go" || // expect error
|
|
file.Name() == "pkgname0.go" || // has deps
|
|
file.Name() == "pkgname1.go" || // expect error
|
|
file.Name() == "pkgname2.go" || // has deps
|
|
file.Name() == "ipp_as_key.go" || // has deps
|
|
file.Name() == "restricted0.go" || // expect error
|
|
file.Name() == "restricted1.go" || // expect error
|
|
file.Name() == "restricted2.go" || // expect error
|
|
file.Name() == "restricted3.go" || // expect error
|
|
file.Name() == "server6.go" || // syntax parsing
|
|
file.Name() == "server5.go" || // syntax parsing
|
|
file.Name() == "server4.go" || // syntax parsing
|
|
file.Name() == "server3.go" || // syntax parsing
|
|
file.Name() == "server2.go" || // syntax parsing
|
|
file.Name() == "server1a.go" || // syntax parsing
|
|
file.Name() == "server1.go" || // syntax parsing
|
|
file.Name() == "server0.go" || // syntax parsing
|
|
file.Name() == "server.go" || // syntax parsing
|
|
file.Name() == "range9.go" || // expect error
|
|
file.Name() == "unsafe6.go" || // needs go.mod to be 1.17
|
|
file.Name() == "unsafe7.go" || // needs go.mod to be 1.17
|
|
file.Name() == "type24.go" || // expect error
|
|
file.Name() == "type27.go" || // expect error
|
|
file.Name() == "type28.go" || // expect error
|
|
file.Name() == "type29.go" || // expect error
|
|
file.Name() == "type30.go" || // expect error
|
|
file.Name() == "type31.go" || // expect error
|
|
file.Name() == "type32.go" || // expect error
|
|
file.Name() == "type33.go" { // expect error
|
|
continue
|
|
}
|
|
// Skip some tests which are problematic in go1.21 only.
|
|
if go121 && testsToSkipGo121[file.Name()] {
|
|
continue
|
|
}
|
|
if go122 && testsToSkipGo122[file.Name()] {
|
|
continue
|
|
}
|
|
|
|
file := file
|
|
t.Run(file.Name(), func(t *testing.T) {
|
|
filePath := filepath.Join(baseDir, file.Name())
|
|
|
|
// catch stdout
|
|
backupStdout := os.Stdout
|
|
defer func() {
|
|
os.Stdout = backupStdout
|
|
}()
|
|
r, w, _ := os.Pipe()
|
|
os.Stdout = w
|
|
|
|
i := interp.New(interp.Options{GoPath: build.Default.GOPATH})
|
|
if err := i.Use(stdlib.Symbols); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := i.Use(interp.Symbols); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := i.Use(unsafe.Symbols); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
_, err = i.EvalPath(filePath)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
// read stdout
|
|
if err = w.Close(); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
outInterp, err := io.ReadAll(r)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
// restore Stdout
|
|
os.Stdout = backupStdout
|
|
|
|
bin := filepath.Join(dir, strings.TrimSuffix(file.Name(), ".go"))
|
|
if runtime.GOOS == "windows" {
|
|
bin += ".exe"
|
|
}
|
|
|
|
cmdBuild := exec.Command("go", "build", "-tags=dummy", "-o", bin, filePath)
|
|
outBuild, err := cmdBuild.CombinedOutput()
|
|
if err != nil {
|
|
t.Log(string(outBuild))
|
|
t.Fatal(err)
|
|
}
|
|
|
|
cmd := exec.Command(bin)
|
|
outRun, err := cmd.CombinedOutput()
|
|
if err != nil {
|
|
t.Log(string(outRun))
|
|
t.Fatal(err)
|
|
}
|
|
|
|
if !bytes.Equal(outInterp, outRun) {
|
|
t.Errorf("\nGot: %q,\n want: %q", string(outInterp), string(outRun))
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestInterpErrorConsistency(t *testing.T) {
|
|
testCases := []struct {
|
|
fileName string
|
|
expectedInterp string
|
|
expectedStderr string
|
|
expectedExec string
|
|
}{
|
|
{
|
|
fileName: "assign11.go",
|
|
expectedInterp: "6:2: assignment mismatch: 3 variables but fmt.Println returns 2 values",
|
|
expectedExec: "6:12: assignment mismatch: 3 variables but fmt.Println returns 2 values",
|
|
},
|
|
{
|
|
fileName: "assign12.go",
|
|
expectedInterp: "6:2: assignment mismatch: 3 variables but fmt.Println returns 2 values",
|
|
expectedExec: "6:13: assignment mismatch: 3 variables but fmt.Println returns 2 values",
|
|
},
|
|
{
|
|
fileName: "bad0.go",
|
|
expectedInterp: "1:1: expected 'package', found println",
|
|
expectedExec: "1:1: expected 'package', found println",
|
|
},
|
|
{
|
|
fileName: "break0.go",
|
|
expectedInterp: "15:5: invalid break label OuterLoop",
|
|
expectedExec: "15:11: invalid break label OuterLoop",
|
|
},
|
|
{
|
|
fileName: "cont3.go",
|
|
expectedInterp: "15:5: invalid continue label OuterLoop",
|
|
expectedExec: "15:14: invalid continue label OuterLoop",
|
|
},
|
|
{
|
|
fileName: "const9.go",
|
|
expectedInterp: "5:2: constant definition loop",
|
|
expectedExec: "5:2: initialization",
|
|
},
|
|
{
|
|
fileName: "if2.go",
|
|
expectedInterp: "7:5: non-bool used as if condition",
|
|
expectedExec: "7:5: non-boolean condition in if statement",
|
|
},
|
|
{
|
|
fileName: "for7.go",
|
|
expectedInterp: "4:14: non-bool used as for condition",
|
|
expectedExec: "4:14: non-boolean condition in for statement",
|
|
},
|
|
{
|
|
fileName: "fun21.go",
|
|
expectedInterp: "4:2: not enough arguments to return",
|
|
expectedExec: "4:2: not enough return values",
|
|
},
|
|
{
|
|
fileName: "fun22.go",
|
|
expectedInterp: "6:2: not enough arguments in call to time.Date",
|
|
expectedExec: "6:2: not enough arguments in call to time.Date",
|
|
},
|
|
{
|
|
fileName: "fun23.go",
|
|
expectedInterp: "3:17: too many arguments to return",
|
|
expectedExec: "3:24: too many return values",
|
|
},
|
|
{
|
|
fileName: "issue-1093.go",
|
|
expectedInterp: "9:6: cannot use type untyped string as type int in assignment",
|
|
expectedExec: `9:6: cannot use "a" + b() (value of type string)`,
|
|
},
|
|
{
|
|
fileName: "op1.go",
|
|
expectedInterp: "5:2: invalid operation: mismatched types int and untyped float",
|
|
expectedExec: "5:7: 1.3 (untyped float constant) truncated to int",
|
|
},
|
|
{
|
|
fileName: "bltn0.go",
|
|
expectedInterp: "4:7: use of builtin println not in function call",
|
|
expectedExec: "4:7: println (built-in) must be called",
|
|
},
|
|
{
|
|
fileName: "import6.go",
|
|
expectedInterp: "import cycle not allowed",
|
|
expectedExec: "import cycle not allowed",
|
|
},
|
|
{
|
|
fileName: "switch8.go",
|
|
expectedInterp: "5:2: fallthrough statement out of place",
|
|
expectedExec: "5:2: fallthrough statement out of place",
|
|
},
|
|
{
|
|
fileName: "switch9.go",
|
|
expectedInterp: "9:3: cannot fallthrough in type switch",
|
|
expectedExec: "fallthrough",
|
|
},
|
|
{
|
|
fileName: "switch13.go",
|
|
expectedInterp: "is not a type",
|
|
expectedExec: "is not a type",
|
|
},
|
|
{
|
|
fileName: "switch19.go",
|
|
expectedInterp: "37:2: duplicate case Bir in type switch",
|
|
expectedExec: "37:7: duplicate case Bir in type switch",
|
|
},
|
|
{
|
|
fileName: "panic0.go",
|
|
expectedInterp: "stop!",
|
|
expectedStderr: `
|
|
../_test/panic0.go:16:2: panic: main.baz(...)
|
|
../_test/panic0.go:12:2: panic: main.bar(...)
|
|
../_test/panic0.go:8:2: panic: main.foo(...)
|
|
../_test/panic0.go:4:2: panic: main.main(...)
|
|
`,
|
|
},
|
|
}
|
|
|
|
for _, test := range testCases {
|
|
t.Run(test.fileName, func(t *testing.T) {
|
|
if test.expectedInterp == "" && test.expectedExec == "" {
|
|
t.Fatal("at least expectedInterp must be defined")
|
|
}
|
|
|
|
filePath := filepath.Join("..", "_test", test.fileName)
|
|
|
|
var stderr bytes.Buffer
|
|
i := interp.New(interp.Options{GoPath: build.Default.GOPATH, Stderr: &stderr})
|
|
if err := i.Use(stdlib.Symbols); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
_, errEval := i.EvalPath(filePath)
|
|
if errEval == nil {
|
|
t.Fatal("An error is expected but got none.")
|
|
}
|
|
|
|
if !strings.Contains(errEval.Error(), test.expectedInterp) {
|
|
t.Errorf("got %q, want: %q", errEval.Error(), test.expectedInterp)
|
|
}
|
|
if test.expectedStderr != "" {
|
|
exp, got := strings.TrimSpace(test.expectedStderr), strings.TrimSpace(stderr.String())
|
|
if exp != got {
|
|
t.Errorf("got %q, want: %q", got, exp)
|
|
}
|
|
}
|
|
|
|
cmd := exec.Command("go", "run", filePath)
|
|
outRun, errExec := cmd.CombinedOutput()
|
|
if errExec == nil {
|
|
t.Log(string(outRun))
|
|
t.Fatal("An error is expected but got none.")
|
|
}
|
|
|
|
if test.expectedExec == "" && !strings.Contains(string(outRun), test.expectedInterp) {
|
|
t.Errorf("got %q, want: %q", string(outRun), test.expectedInterp)
|
|
} else if !strings.Contains(string(outRun), test.expectedExec) {
|
|
t.Errorf("got %q, want: %q", string(outRun), test.expectedExec)
|
|
}
|
|
})
|
|
}
|
|
}
|