Source file src/cmd/go/internal/modfetch/zip_sum_test/zip_sum_test.go

     1  // Copyright 2019 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 zip_sum_test tests that the module zip files produced by modfetch
     6  // have consistent content sums. Ideally the zip files themselves are also
     7  // stable over time, though this is not strictly necessary.
     8  //
     9  // This test loads a table from testdata/zip_sums.csv. The table has columns
    10  // for module path, version, content sum, and zip file hash. The table
    11  // includes a large number of real modules. The test downloads these modules
    12  // in direct mode and verifies the zip files.
    13  //
    14  // This test is very slow, and it depends on outside modules that change
    15  // frequently, so this is a manual test. To enable it, pass the -zipsum flag.
    16  package zip_sum_test
    17  
    18  import (
    19  	"context"
    20  	"crypto/sha256"
    21  	"encoding/csv"
    22  	"encoding/hex"
    23  	"flag"
    24  	"fmt"
    25  	"internal/testenv"
    26  	"io"
    27  	"os"
    28  	"path/filepath"
    29  	"strings"
    30  	"testing"
    31  
    32  	"cmd/go/internal/cfg"
    33  	"cmd/go/internal/modfetch"
    34  	"cmd/go/internal/modload"
    35  
    36  	"golang.org/x/mod/module"
    37  )
    38  
    39  var (
    40  	updateTestData = flag.Bool("u", false, "when set, tests may update files in testdata instead of failing")
    41  	enableZipSum   = flag.Bool("zipsum", false, "enable TestZipSums")
    42  	debugZipSum    = flag.Bool("testwork", false, "when set, TestZipSums will preserve its test directory")
    43  	modCacheDir    = flag.String("zipsumcache", "", "module cache to use instead of temp directory")
    44  	shardCount     = flag.Int("zipsumshardcount", 1, "number of shards to divide TestZipSums into")
    45  	shardIndex     = flag.Int("zipsumshard", 0, "index of TestZipSums shard to test (0 <= zipsumshard < zipsumshardcount)")
    46  )
    47  
    48  const zipSumsPath = "testdata/zip_sums.csv"
    49  
    50  type zipSumTest struct {
    51  	m                     module.Version
    52  	wantSum, wantFileHash string
    53  }
    54  
    55  func TestZipSums(t *testing.T) {
    56  	if !*enableZipSum {
    57  		// This test is very slow and heavily dependent on external repositories.
    58  		// Only run it explicitly.
    59  		t.Skip("TestZipSum not enabled with -zipsum")
    60  	}
    61  	if *shardCount < 1 {
    62  		t.Fatal("-zipsumshardcount must be a positive integer")
    63  	}
    64  	if *shardIndex < 0 || *shardCount <= *shardIndex {
    65  		t.Fatal("-zipsumshard must be between 0 and -zipsumshardcount")
    66  	}
    67  
    68  	testenv.MustHaveGoBuild(t)
    69  	testenv.MustHaveExternalNetwork(t)
    70  	testenv.MustHaveExecPath(t, "bzr")
    71  	testenv.MustHaveExecPath(t, "git")
    72  	// TODO(jayconrod): add hg, svn, and fossil modules to testdata.
    73  	// Could not find any for now.
    74  
    75  	tests, err := readZipSumTests()
    76  	if err != nil {
    77  		t.Fatal(err)
    78  	}
    79  
    80  	if *modCacheDir != "" {
    81  		cfg.BuildContext.GOPATH = *modCacheDir
    82  	} else {
    83  		tmpDir, err := os.MkdirTemp("", "TestZipSums")
    84  		if err != nil {
    85  			t.Fatal(err)
    86  		}
    87  		if *debugZipSum {
    88  			fmt.Fprintf(os.Stderr, "TestZipSums: modCacheDir: %s\n", tmpDir)
    89  		} else {
    90  			defer os.RemoveAll(tmpDir)
    91  		}
    92  		cfg.BuildContext.GOPATH = tmpDir
    93  	}
    94  
    95  	cfg.GOPROXY = "direct"
    96  	cfg.GOSUMDB = "off"
    97  	modload.Init()
    98  
    99  	// Shard tests by downloading only every nth module when shard flags are set.
   100  	// This makes it easier to test small groups of modules quickly. We avoid
   101  	// testing similarly named modules together (the list is sorted by module
   102  	// path and version).
   103  	if *shardCount > 1 {
   104  		r := *shardIndex
   105  		w := 0
   106  		for r < len(tests) {
   107  			tests[w] = tests[r]
   108  			w++
   109  			r += *shardCount
   110  		}
   111  		tests = tests[:w]
   112  	}
   113  
   114  	// Download modules with a rate limit. We may run out of file descriptors
   115  	// or cause timeouts without a limit.
   116  	needUpdate := false
   117  	for i := range tests {
   118  		test := &tests[i]
   119  		name := fmt.Sprintf("%s@%s", strings.ReplaceAll(test.m.Path, "/", "_"), test.m.Version)
   120  		t.Run(name, func(t *testing.T) {
   121  			t.Parallel()
   122  			zipPath, err := modfetch.DownloadZip(context.Background(), test.m)
   123  			if err != nil {
   124  				if *updateTestData {
   125  					t.Logf("%s: could not download module: %s (will remove from testdata)", test.m, err)
   126  					test.m.Path = "" // mark for deletion
   127  					needUpdate = true
   128  				} else {
   129  					t.Errorf("%s: could not download module: %s", test.m, err)
   130  				}
   131  				return
   132  			}
   133  
   134  			sum := modfetch.Sum(test.m)
   135  			if sum != test.wantSum {
   136  				if *updateTestData {
   137  					t.Logf("%s: updating content sum to %s", test.m, sum)
   138  					test.wantSum = sum
   139  					needUpdate = true
   140  				} else {
   141  					t.Errorf("%s: got content sum %s; want sum %s", test.m, sum, test.wantSum)
   142  					return
   143  				}
   144  			}
   145  
   146  			h := sha256.New()
   147  			f, err := os.Open(zipPath)
   148  			if err != nil {
   149  				t.Errorf("%s: %v", test.m, err)
   150  			}
   151  			defer f.Close()
   152  			if _, err := io.Copy(h, f); err != nil {
   153  				t.Errorf("%s: %v", test.m, err)
   154  			}
   155  			zipHash := hex.EncodeToString(h.Sum(nil))
   156  			if zipHash != test.wantFileHash {
   157  				if *updateTestData {
   158  					t.Logf("%s: updating zip file hash to %s", test.m, zipHash)
   159  					test.wantFileHash = zipHash
   160  					needUpdate = true
   161  				} else {
   162  					t.Errorf("%s: got zip file hash %s; want hash %s (but content sum matches)", test.m, zipHash, test.wantFileHash)
   163  				}
   164  			}
   165  		})
   166  	}
   167  
   168  	if needUpdate {
   169  		// Remove tests marked for deletion
   170  		r, w := 0, 0
   171  		for r < len(tests) {
   172  			if tests[r].m.Path != "" {
   173  				tests[w] = tests[r]
   174  				w++
   175  			}
   176  			r++
   177  		}
   178  		tests = tests[:w]
   179  
   180  		if err := writeZipSumTests(tests); err != nil {
   181  			t.Error(err)
   182  		}
   183  	}
   184  }
   185  
   186  func readZipSumTests() ([]zipSumTest, error) {
   187  	f, err := os.Open(filepath.FromSlash(zipSumsPath))
   188  	if err != nil {
   189  		return nil, err
   190  	}
   191  	defer f.Close()
   192  	r := csv.NewReader(f)
   193  
   194  	var tests []zipSumTest
   195  	for {
   196  		line, err := r.Read()
   197  		if err == io.EOF {
   198  			break
   199  		} else if err != nil {
   200  			return nil, err
   201  		} else if len(line) != 4 {
   202  			return nil, fmt.Errorf("%s:%d: malformed line", f.Name(), len(tests)+1)
   203  		}
   204  		test := zipSumTest{m: module.Version{Path: line[0], Version: line[1]}, wantSum: line[2], wantFileHash: line[3]}
   205  		tests = append(tests, test)
   206  	}
   207  	return tests, nil
   208  }
   209  
   210  func writeZipSumTests(tests []zipSumTest) (err error) {
   211  	f, err := os.Create(filepath.FromSlash(zipSumsPath))
   212  	if err != nil {
   213  		return err
   214  	}
   215  	defer func() {
   216  		if cerr := f.Close(); err == nil && cerr != nil {
   217  			err = cerr
   218  		}
   219  	}()
   220  	w := csv.NewWriter(f)
   221  	line := make([]string, 0, 4)
   222  	for _, test := range tests {
   223  		line = append(line[:0], test.m.Path, test.m.Version, test.wantSum, test.wantFileHash)
   224  		if err := w.Write(line); err != nil {
   225  			return err
   226  		}
   227  	}
   228  	w.Flush()
   229  	return nil
   230  }
   231  

View as plain text