github.com/traefik/yaegi@v0.15.1/interp/interp_consistent_test.go (about)

     1  package interp_test
     2  
     3  import (
     4  	"go/build"
     5  	"io"
     6  	"os"
     7  	"os/exec"
     8  	"path/filepath"
     9  	"strings"
    10  	"testing"
    11  
    12  	"github.com/traefik/yaegi/interp"
    13  	"github.com/traefik/yaegi/stdlib"
    14  	"github.com/traefik/yaegi/stdlib/unsafe"
    15  )
    16  
    17  func TestInterpConsistencyBuild(t *testing.T) {
    18  	if testing.Short() {
    19  		t.Skip("short mode")
    20  	}
    21  	dir := filepath.Join("..", "_test", "tmp")
    22  	if _, err := os.Stat(dir); os.IsNotExist(err) {
    23  		if err := os.Mkdir(dir, 0o700); err != nil {
    24  			t.Fatal(err)
    25  		}
    26  	}
    27  
    28  	baseDir := filepath.Join("..", "_test")
    29  	files, err := os.ReadDir(baseDir)
    30  	if err != nil {
    31  		t.Fatal(err)
    32  	}
    33  
    34  	for _, file := range files {
    35  		if filepath.Ext(file.Name()) != ".go" ||
    36  			file.Name() == "assign11.go" || // expect error
    37  			file.Name() == "assign12.go" || // expect error
    38  			file.Name() == "assign15.go" || // expect error
    39  			file.Name() == "bad0.go" || // expect error
    40  			file.Name() == "break0.go" || // expect error
    41  			file.Name() == "cont3.go" || // expect error
    42  			file.Name() == "const9.go" || // expect error
    43  			file.Name() == "export1.go" || // non-main package
    44  			file.Name() == "export0.go" || // non-main package
    45  			file.Name() == "for7.go" || // expect error
    46  			file.Name() == "fun21.go" || // expect error
    47  			file.Name() == "fun22.go" || // expect error
    48  			file.Name() == "fun23.go" || // expect error
    49  			file.Name() == "fun24.go" || // expect error
    50  			file.Name() == "fun25.go" || // expect error
    51  			file.Name() == "gen7.go" || // expect error
    52  			file.Name() == "gen8.go" || // expect error
    53  			file.Name() == "gen9.go" || // expect error
    54  			file.Name() == "if2.go" || // expect error
    55  			file.Name() == "import6.go" || // expect error
    56  			file.Name() == "init1.go" || // expect error
    57  			file.Name() == "io0.go" || // use random number
    58  			file.Name() == "issue-1093.go" || // expect error
    59  			file.Name() == "issue-1276.go" || // expect error
    60  			file.Name() == "issue-1330.go" || // expect error
    61  			file.Name() == "op1.go" || // expect error
    62  			file.Name() == "op7.go" || // expect error
    63  			file.Name() == "op9.go" || // expect error
    64  			file.Name() == "bltn0.go" || // expect error
    65  			file.Name() == "method16.go" || // private struct field
    66  			file.Name() == "method39.go" || // expect error
    67  			file.Name() == "switch8.go" || // expect error
    68  			file.Name() == "switch9.go" || // expect error
    69  			file.Name() == "switch13.go" || // expect error
    70  			file.Name() == "switch19.go" || // expect error
    71  			file.Name() == "time0.go" || // display time (similar to random number)
    72  			file.Name() == "factor.go" || // bench
    73  			file.Name() == "fib.go" || // bench
    74  
    75  			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
    76  			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
    77  
    78  			file.Name() == "redeclaration0.go" || // expect error
    79  			file.Name() == "redeclaration1.go" || // expect error
    80  			file.Name() == "redeclaration2.go" || // expect error
    81  			file.Name() == "redeclaration3.go" || // expect error
    82  			file.Name() == "redeclaration4.go" || // expect error
    83  			file.Name() == "redeclaration5.go" || // expect error
    84  			file.Name() == "redeclaration-global0.go" || // expect error
    85  			file.Name() == "redeclaration-global1.go" || // expect error
    86  			file.Name() == "redeclaration-global2.go" || // expect error
    87  			file.Name() == "redeclaration-global3.go" || // expect error
    88  			file.Name() == "redeclaration-global4.go" || // expect error
    89  			file.Name() == "redeclaration-global5.go" || // expect error
    90  			file.Name() == "redeclaration-global6.go" || // expect error
    91  			file.Name() == "redeclaration-global7.go" || // expect error
    92  			file.Name() == "pkgname0.go" || // has deps
    93  			file.Name() == "pkgname1.go" || // expect error
    94  			file.Name() == "pkgname2.go" || // has deps
    95  			file.Name() == "ipp_as_key.go" || // has deps
    96  			file.Name() == "restricted0.go" || // expect error
    97  			file.Name() == "restricted1.go" || // expect error
    98  			file.Name() == "restricted2.go" || // expect error
    99  			file.Name() == "restricted3.go" || // expect error
   100  			file.Name() == "server6.go" || // syntax parsing
   101  			file.Name() == "server5.go" || // syntax parsing
   102  			file.Name() == "server4.go" || // syntax parsing
   103  			file.Name() == "server3.go" || // syntax parsing
   104  			file.Name() == "server2.go" || // syntax parsing
   105  			file.Name() == "server1a.go" || // syntax parsing
   106  			file.Name() == "server1.go" || // syntax parsing
   107  			file.Name() == "server0.go" || // syntax parsing
   108  			file.Name() == "server.go" || // syntax parsing
   109  			file.Name() == "range9.go" || // expect error
   110  			file.Name() == "unsafe6.go" || // needs go.mod to be 1.17
   111  			file.Name() == "unsafe7.go" || // needs go.mod to be 1.17
   112  			file.Name() == "type24.go" || // expect error
   113  			file.Name() == "type27.go" || // expect error
   114  			file.Name() == "type28.go" || // expect error
   115  			file.Name() == "type29.go" || // expect error
   116  			file.Name() == "type30.go" || // expect error
   117  			file.Name() == "type31.go" || // expect error
   118  			file.Name() == "type32.go" || // expect error
   119  			file.Name() == "type33.go" { // expect error
   120  			continue
   121  		}
   122  
   123  		file := file
   124  		t.Run(file.Name(), func(t *testing.T) {
   125  			filePath := filepath.Join(baseDir, file.Name())
   126  
   127  			// catch stdout
   128  			backupStdout := os.Stdout
   129  			defer func() {
   130  				os.Stdout = backupStdout
   131  			}()
   132  			r, w, _ := os.Pipe()
   133  			os.Stdout = w
   134  
   135  			i := interp.New(interp.Options{GoPath: build.Default.GOPATH})
   136  			if err := i.Use(stdlib.Symbols); err != nil {
   137  				t.Fatal(err)
   138  			}
   139  			if err := i.Use(interp.Symbols); err != nil {
   140  				t.Fatal(err)
   141  			}
   142  			if err := i.Use(unsafe.Symbols); err != nil {
   143  				t.Fatal(err)
   144  			}
   145  
   146  			_, err = i.EvalPath(filePath)
   147  			if err != nil {
   148  				t.Fatal(err)
   149  			}
   150  
   151  			// read stdout
   152  			if err = w.Close(); err != nil {
   153  				t.Fatal(err)
   154  			}
   155  			outInterp, err := io.ReadAll(r)
   156  			if err != nil {
   157  				t.Fatal(err)
   158  			}
   159  
   160  			// restore Stdout
   161  			os.Stdout = backupStdout
   162  
   163  			bin := filepath.Join(dir, strings.TrimSuffix(file.Name(), ".go"))
   164  
   165  			cmdBuild := exec.Command("go", "build", "-tags=dummy", "-o", bin, filePath)
   166  			outBuild, err := cmdBuild.CombinedOutput()
   167  			if err != nil {
   168  				t.Log(string(outBuild))
   169  				t.Fatal(err)
   170  			}
   171  
   172  			cmd := exec.Command(bin)
   173  			outRun, err := cmd.CombinedOutput()
   174  			if err != nil {
   175  				t.Log(string(outRun))
   176  				t.Fatal(err)
   177  			}
   178  
   179  			if string(outInterp) != string(outRun) {
   180  				t.Errorf("\nGot: %q,\n want: %q", string(outInterp), string(outRun))
   181  			}
   182  		})
   183  	}
   184  }
   185  
   186  func TestInterpErrorConsistency(t *testing.T) {
   187  	testCases := []struct {
   188  		fileName       string
   189  		expectedInterp string
   190  		expectedExec   string
   191  	}{
   192  		{
   193  			fileName:       "assign11.go",
   194  			expectedInterp: "6:2: assignment mismatch: 3 variables but fmt.Println returns 2 values",
   195  			expectedExec:   "6:12: assignment mismatch: 3 variables but fmt.Println returns 2 values",
   196  		},
   197  		{
   198  			fileName:       "assign12.go",
   199  			expectedInterp: "6:2: assignment mismatch: 3 variables but fmt.Println returns 2 values",
   200  			expectedExec:   "6:13: assignment mismatch: 3 variables but fmt.Println returns 2 values",
   201  		},
   202  		{
   203  			fileName:       "bad0.go",
   204  			expectedInterp: "1:1: expected 'package', found println",
   205  			expectedExec:   "1:1: expected 'package', found println",
   206  		},
   207  		{
   208  			fileName:       "break0.go",
   209  			expectedInterp: "15:5: invalid break label OuterLoop",
   210  			expectedExec:   "15:11: invalid break label OuterLoop",
   211  		},
   212  		{
   213  			fileName:       "cont3.go",
   214  			expectedInterp: "15:5: invalid continue label OuterLoop",
   215  			expectedExec:   "15:14: invalid continue label OuterLoop",
   216  		},
   217  		{
   218  			fileName:       "const9.go",
   219  			expectedInterp: "5:2: constant definition loop",
   220  			expectedExec:   "5:2: initialization",
   221  		},
   222  		{
   223  			fileName:       "if2.go",
   224  			expectedInterp: "7:5: non-bool used as if condition",
   225  			expectedExec:   "7:5: non-boolean condition in if statement",
   226  		},
   227  		{
   228  			fileName:       "for7.go",
   229  			expectedInterp: "4:14: non-bool used as for condition",
   230  			expectedExec:   "4:14: non-boolean condition in for statement",
   231  		},
   232  		{
   233  			fileName:       "fun21.go",
   234  			expectedInterp: "4:2: not enough arguments to return",
   235  			expectedExec:   "4:2: not enough return values",
   236  		},
   237  		{
   238  			fileName:       "fun22.go",
   239  			expectedInterp: "6:2: not enough arguments in call to time.Date",
   240  			expectedExec:   "6:2: not enough arguments in call to time.Date",
   241  		},
   242  		{
   243  			fileName:       "fun23.go",
   244  			expectedInterp: "3:17: too many arguments to return",
   245  			expectedExec:   "3:24: too many return values",
   246  		},
   247  		{
   248  			fileName:       "issue-1093.go",
   249  			expectedInterp: "9:6: cannot use type untyped string as type int in assignment",
   250  			expectedExec:   `9:6: cannot use "a" + b() (value of type string)`,
   251  		},
   252  		{
   253  			fileName:       "op1.go",
   254  			expectedInterp: "5:2: invalid operation: mismatched types int and untyped float",
   255  			expectedExec:   "5:7: 1.3 (untyped float constant) truncated to int",
   256  		},
   257  		{
   258  			fileName:       "bltn0.go",
   259  			expectedInterp: "4:7: use of builtin println not in function call",
   260  			expectedExec:   "4:7: println (built-in) must be called",
   261  		},
   262  		{
   263  			fileName:       "import6.go",
   264  			expectedInterp: "import cycle not allowed",
   265  			expectedExec:   "import cycle not allowed",
   266  		},
   267  		{
   268  			fileName:       "switch8.go",
   269  			expectedInterp: "5:2: fallthrough statement out of place",
   270  			expectedExec:   "5:2: fallthrough statement out of place",
   271  		},
   272  		{
   273  			fileName:       "switch9.go",
   274  			expectedInterp: "9:3: cannot fallthrough in type switch",
   275  			expectedExec:   "fallthrough",
   276  		},
   277  		{
   278  			fileName:       "switch13.go",
   279  			expectedInterp: "9:2: i is not a type",
   280  			expectedExec:   "9:7: i (variable of type interface{}) is not a type",
   281  		},
   282  		{
   283  			fileName:       "switch19.go",
   284  			expectedInterp: "37:2: duplicate case Bir in type switch",
   285  			expectedExec:   "37:7: duplicate case Bir in type switch",
   286  		},
   287  	}
   288  
   289  	for _, test := range testCases {
   290  		t.Run(test.fileName, func(t *testing.T) {
   291  			if len(test.expectedInterp) == 0 && len(test.expectedExec) == 0 {
   292  				t.Fatal("at least expectedInterp must be define")
   293  			}
   294  
   295  			filePath := filepath.Join("..", "_test", test.fileName)
   296  
   297  			i := interp.New(interp.Options{GoPath: build.Default.GOPATH})
   298  			if err := i.Use(stdlib.Symbols); err != nil {
   299  				t.Fatal(err)
   300  			}
   301  
   302  			_, errEval := i.EvalPath(filePath)
   303  			if errEval == nil {
   304  				t.Fatal("An error is expected but got none.")
   305  			}
   306  
   307  			if !strings.Contains(errEval.Error(), test.expectedInterp) {
   308  				t.Errorf("got %q, want: %q", errEval.Error(), test.expectedInterp)
   309  			}
   310  
   311  			cmd := exec.Command("go", "run", filePath)
   312  			outRun, errExec := cmd.CombinedOutput()
   313  			if errExec == nil {
   314  				t.Log(string(outRun))
   315  				t.Fatal("An error is expected but got none.")
   316  			}
   317  
   318  			if len(test.expectedExec) == 0 && !strings.Contains(string(outRun), test.expectedInterp) {
   319  				t.Errorf("got %q, want: %q", string(outRun), test.expectedInterp)
   320  			} else if !strings.Contains(string(outRun), test.expectedExec) {
   321  				t.Errorf("got %q, want: %q", string(outRun), test.expectedExec)
   322  			}
   323  		})
   324  	}
   325  }