github.com/tinygo-org/tinygo@v0.31.3-0.20240404173401-90b0bf646c27/interp/interp_test.go (about)

     1  package interp
     2  
     3  import (
     4  	"os"
     5  	"strconv"
     6  	"strings"
     7  	"testing"
     8  	"time"
     9  
    10  	"tinygo.org/x/go-llvm"
    11  )
    12  
    13  func TestInterp(t *testing.T) {
    14  	llvmVersion, err := strconv.Atoi(strings.Split(llvm.Version, ".")[0])
    15  	if err != nil {
    16  		// Note: this should never happen and if it does, it will always happen
    17  		// for a particular build because llvm.Version is a constant.
    18  		panic(err)
    19  	}
    20  	for _, name := range []string{
    21  		"basic",
    22  		"phi",
    23  		"slice-copy",
    24  		"consteval",
    25  		"interface",
    26  		"revert",
    27  		"alloc",
    28  	} {
    29  		name := name // make local to this closure
    30  		if name == "slice-copy" && llvmVersion < 14 {
    31  			continue
    32  		}
    33  		t.Run(name, func(t *testing.T) {
    34  			t.Parallel()
    35  			runTest(t, "testdata/"+name)
    36  		})
    37  	}
    38  }
    39  
    40  func runTest(t *testing.T, pathPrefix string) {
    41  	// Read the input IR.
    42  	ctx := llvm.NewContext()
    43  	defer ctx.Dispose()
    44  	buf, err := llvm.NewMemoryBufferFromFile(pathPrefix + ".ll")
    45  	os.Stat(pathPrefix + ".ll") // make sure this file is tracked by `go test` caching
    46  	if err != nil {
    47  		t.Fatalf("could not read file %s: %v", pathPrefix+".ll", err)
    48  	}
    49  	mod, err := ctx.ParseIR(buf)
    50  	if err != nil {
    51  		t.Fatalf("could not load module:\n%v", err)
    52  	}
    53  	defer mod.Dispose()
    54  
    55  	// Perform the transform.
    56  	err = Run(mod, 10*time.Minute, false)
    57  	if err != nil {
    58  		if err, match := err.(*Error); match {
    59  			println(err.Error())
    60  			if len(err.Inst) != 0 {
    61  				println(err.Inst)
    62  			}
    63  			if len(err.Traceback) > 0 {
    64  				println("\ntraceback:")
    65  				for _, line := range err.Traceback {
    66  					println(line.Pos.String() + ":")
    67  					println(line.Inst)
    68  				}
    69  			}
    70  		}
    71  		t.Fatal(err)
    72  	}
    73  
    74  	// To be sure, verify that the module is still valid.
    75  	if llvm.VerifyModule(mod, llvm.PrintMessageAction) != nil {
    76  		t.FailNow()
    77  	}
    78  
    79  	// Run some cleanup passes to get easy-to-read outputs.
    80  	to := llvm.NewPassBuilderOptions()
    81  	defer to.Dispose()
    82  	mod.RunPasses("globalopt,dse,adce", llvm.TargetMachine{}, to)
    83  
    84  	// Read the expected output IR.
    85  	out, err := os.ReadFile(pathPrefix + ".out.ll")
    86  	if err != nil {
    87  		t.Fatalf("could not read output file %s: %v", pathPrefix+".out.ll", err)
    88  	}
    89  
    90  	// See whether the transform output matches with the expected output IR.
    91  	expected := string(out)
    92  	actual := mod.String()
    93  	if !fuzzyEqualIR(expected, actual) {
    94  		t.Logf("output does not match expected output:\n%s", actual)
    95  		t.Fail()
    96  	}
    97  }
    98  
    99  // fuzzyEqualIR returns true if the two LLVM IR strings passed in are roughly
   100  // equal. That means, only relevant lines are compared (excluding comments
   101  // etc.).
   102  func fuzzyEqualIR(s1, s2 string) bool {
   103  	lines1 := filterIrrelevantIRLines(strings.Split(s1, "\n"))
   104  	lines2 := filterIrrelevantIRLines(strings.Split(s2, "\n"))
   105  	if len(lines1) != len(lines2) {
   106  		return false
   107  	}
   108  	for i, line1 := range lines1 {
   109  		line2 := lines2[i]
   110  		if line1 != line2 {
   111  			return false
   112  		}
   113  	}
   114  
   115  	return true
   116  }
   117  
   118  // filterIrrelevantIRLines removes lines from the input slice of strings that
   119  // are not relevant in comparing IR. For example, empty lines and comments are
   120  // stripped out.
   121  func filterIrrelevantIRLines(lines []string) []string {
   122  	var out []string
   123  	for _, line := range lines {
   124  		line = strings.TrimSpace(line) // drop '\r' on Windows
   125  		if line == "" || line[0] == ';' {
   126  			continue
   127  		}
   128  		if strings.HasPrefix(line, "source_filename = ") {
   129  			continue
   130  		}
   131  		out = append(out, line)
   132  	}
   133  	return out
   134  }