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