Source file src/cmd/compile/internal/noder/unified_test.go

     1  // Copyright 2021 The Go Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  package noder_test
     6  
     7  import (
     8  	"encoding/json"
     9  	"flag"
    10  	exec "internal/execabs"
    11  	"os"
    12  	"reflect"
    13  	"runtime"
    14  	"strings"
    15  	"testing"
    16  )
    17  
    18  var (
    19  	flagCmp      = flag.Bool("cmp", false, "enable TestUnifiedCompare")
    20  	flagPkgs     = flag.String("pkgs", "std", "list of packages to compare (ignored in -short mode)")
    21  	flagAll      = flag.Bool("all", false, "enable testing of all GOOS/GOARCH targets")
    22  	flagParallel = flag.Bool("parallel", false, "test GOOS/GOARCH targets in parallel")
    23  )
    24  
    25  // TestUnifiedCompare implements a test similar to running:
    26  //
    27  //	$ go build -toolexec="toolstash -cmp" std
    28  //
    29  // The -pkgs flag controls the list of packages tested.
    30  //
    31  // By default, only the native GOOS/GOARCH target is enabled. The -all
    32  // flag enables testing of non-native targets. The -parallel flag
    33  // additionally enables testing of targets in parallel.
    34  //
    35  // Caution: Testing all targets is very resource intensive! On an IBM
    36  // P920 (dual Intel Xeon Gold 6154 CPUs; 36 cores, 192GB RAM), testing
    37  // all targets in parallel takes about 5 minutes. Using the 'go test'
    38  // command's -run flag for subtest matching is recommended for less
    39  // powerful machines.
    40  func TestUnifiedCompare(t *testing.T) {
    41  	// TODO(mdempsky): Either re-enable or delete. Disabled for now to
    42  	// avoid impeding others' forward progress.
    43  	if !*flagCmp {
    44  		t.Skip("skipping TestUnifiedCompare (use -cmp to enable)")
    45  	}
    46  
    47  	targets, err := exec.Command("go", "tool", "dist", "list").Output()
    48  	if err != nil {
    49  		t.Fatal(err)
    50  	}
    51  
    52  	for _, target := range strings.Fields(string(targets)) {
    53  		t.Run(target, func(t *testing.T) {
    54  			parts := strings.Split(target, "/")
    55  			goos, goarch := parts[0], parts[1]
    56  
    57  			if !(*flagAll || goos == runtime.GOOS && goarch == runtime.GOARCH) {
    58  				t.Skip("skipping non-native target (use -all to enable)")
    59  			}
    60  			if *flagParallel {
    61  				t.Parallel()
    62  			}
    63  
    64  			pkgs1 := loadPackages(t, goos, goarch, "-d=unified=0 -d=inlfuncswithclosures=0 -d=unifiedquirks=1 -G=0")
    65  			pkgs2 := loadPackages(t, goos, goarch, "-d=unified=1 -d=inlfuncswithclosures=0 -d=unifiedquirks=1 -G=0")
    66  
    67  			if len(pkgs1) != len(pkgs2) {
    68  				t.Fatalf("length mismatch: %v != %v", len(pkgs1), len(pkgs2))
    69  			}
    70  
    71  			for i := range pkgs1 {
    72  				pkg1 := pkgs1[i]
    73  				pkg2 := pkgs2[i]
    74  
    75  				path := pkg1.ImportPath
    76  				if path != pkg2.ImportPath {
    77  					t.Fatalf("mismatched paths: %q != %q", path, pkg2.ImportPath)
    78  				}
    79  
    80  				// Packages that don't have any source files (e.g., packages
    81  				// unsafe, embed/internal/embedtest, and cmd/internal/moddeps).
    82  				if pkg1.Export == "" && pkg2.Export == "" {
    83  					continue
    84  				}
    85  
    86  				if pkg1.BuildID == pkg2.BuildID {
    87  					t.Errorf("package %q: build IDs unexpectedly matched", path)
    88  				}
    89  
    90  				// Unlike toolstash -cmp, we're comparing the same compiler
    91  				// binary against itself, just with different flags. So we
    92  				// don't need to worry about skipping over mismatched version
    93  				// strings, but we do need to account for differing build IDs.
    94  				//
    95  				// Fortunately, build IDs are cryptographic 256-bit hashes,
    96  				// and cmd/go provides us with them up front. So we can just
    97  				// use them as delimeters to split the files, and then check
    98  				// that the substrings are all equal.
    99  				file1 := strings.Split(readFile(t, pkg1.Export), pkg1.BuildID)
   100  				file2 := strings.Split(readFile(t, pkg2.Export), pkg2.BuildID)
   101  				if !reflect.DeepEqual(file1, file2) {
   102  					t.Errorf("package %q: compile output differs", path)
   103  				}
   104  			}
   105  		})
   106  	}
   107  }
   108  
   109  type pkg struct {
   110  	ImportPath string
   111  	Export     string
   112  	BuildID    string
   113  	Incomplete bool
   114  }
   115  
   116  func loadPackages(t *testing.T, goos, goarch, gcflags string) []pkg {
   117  	args := []string{"list", "-e", "-export", "-json", "-gcflags=all=" + gcflags, "--"}
   118  	if testing.Short() {
   119  		t.Log("short testing mode; only testing package runtime")
   120  		args = append(args, "runtime")
   121  	} else {
   122  		args = append(args, strings.Fields(*flagPkgs)...)
   123  	}
   124  
   125  	cmd := exec.Command("go", args...)
   126  	cmd.Env = append(os.Environ(), "GOOS="+goos, "GOARCH="+goarch)
   127  	cmd.Stderr = os.Stderr
   128  	t.Logf("running %v", cmd)
   129  	stdout, err := cmd.StdoutPipe()
   130  	if err != nil {
   131  		t.Fatal(err)
   132  	}
   133  	if err := cmd.Start(); err != nil {
   134  		t.Fatal(err)
   135  	}
   136  
   137  	var res []pkg
   138  	for dec := json.NewDecoder(stdout); dec.More(); {
   139  		var pkg pkg
   140  		if err := dec.Decode(&pkg); err != nil {
   141  			t.Fatal(err)
   142  		}
   143  		if pkg.Incomplete {
   144  			t.Fatalf("incomplete package: %q", pkg.ImportPath)
   145  		}
   146  		res = append(res, pkg)
   147  	}
   148  	if err := cmd.Wait(); err != nil {
   149  		t.Fatal(err)
   150  	}
   151  	return res
   152  }
   153  
   154  func readFile(t *testing.T, name string) string {
   155  	buf, err := os.ReadFile(name)
   156  	if err != nil {
   157  		t.Fatal(err)
   158  	}
   159  	return string(buf)
   160  }
   161  

View as plain text