|  | // Copyright 2024 The Go Authors. All rights reserved. | 
|  | // Use of this source code is governed by a BSD-style | 
|  | // license that can be found in the LICENSE file. | 
|  |  | 
|  | package main | 
|  |  | 
|  | import ( | 
|  | "bytes" | 
|  | "context" | 
|  | _ "embed" | 
|  | "flag" | 
|  | "fmt" | 
|  | "io/fs" | 
|  | "path/filepath" | 
|  | "slices" | 
|  | "strings" | 
|  | "testing" | 
|  | "time" | 
|  |  | 
|  | "github.com/google/go-cmp/cmp" | 
|  | "golang.org/x/exp/maps" | 
|  | "golang.org/x/tools/txtar" | 
|  | "golang.org/x/vulndb/cmd/vulnreport/log" | 
|  | "golang.org/x/vulndb/internal/gitrepo" | 
|  | "golang.org/x/vulndb/internal/pkgsite" | 
|  | "golang.org/x/vulndb/internal/proxy" | 
|  | "golang.org/x/vulndb/internal/test" | 
|  | "golang.org/x/vulndb/internal/triage/priority" | 
|  | ) | 
|  |  | 
|  | // go test ./cmd/vulnreport -update-test -proxy -pkgsite | 
|  | var ( | 
|  | testUpdate = flag.Bool("update-test", false, "(for test) whether to update test files") | 
|  | realProxy  = flag.Bool("proxy", false, "(for test) whether to use real proxy") | 
|  | usePkgsite = flag.Bool("pkgsite", false, "(for test) whether to use real pkgsite") | 
|  | ) | 
|  |  | 
|  | type testCase struct { | 
|  | name    string | 
|  | args    []string | 
|  | wantErr bool | 
|  | } | 
|  |  | 
|  | type memWFS struct { | 
|  | written map[string][]byte | 
|  | } | 
|  |  | 
|  | func newInMemoryWFS() *memWFS { | 
|  | return &memWFS{written: make(map[string][]byte)} | 
|  | } | 
|  |  | 
|  | var _ wfs = &memWFS{} | 
|  |  | 
|  | func (m *memWFS) WriteFile(fname string, b []byte) (bool, error) { | 
|  | if bytes.Equal(m.written[fname], b) { | 
|  | return false, nil | 
|  | } | 
|  | m.written[fname] = b | 
|  | return true, nil | 
|  | } | 
|  | func testFilename(t *testing.T) string { | 
|  | return filepath.Join("testdata", t.Name()+".txtar") | 
|  | } | 
|  |  | 
|  | var ( | 
|  | //go:embed testdata/repo.txtar | 
|  | testRepo []byte | 
|  | //go:embed testdata/issue_tracker.txtar | 
|  | testIssueTracker []byte | 
|  | //go:embed testdata/legacy_ghsas.txtar | 
|  | testLegacyGHSAs []byte | 
|  | //go:embed testdata/modules.csv | 
|  | testModuleMap []byte | 
|  | ) | 
|  |  | 
|  | // runTest runs the command on the test case in the default test environment. | 
|  | func runTest(t *testing.T, cmd command, tc *testCase) { | 
|  | runTestWithEnv(t, cmd, tc, func(t *testing.T) (*environment, error) { | 
|  | return newDefaultTestEnv(t) | 
|  | }) | 
|  | } | 
|  |  | 
|  | var testTime = time.Date(2022, 1, 1, 0, 0, 0, 0, time.UTC) | 
|  |  | 
|  | func newDefaultTestEnv(t *testing.T) (*environment, error) { | 
|  | t.Helper() | 
|  |  | 
|  | ar := txtar.Parse(testRepo) | 
|  | repo, err := gitrepo.FromTxtarArchive(ar, testTime) | 
|  | if err != nil { | 
|  | return nil, err | 
|  | } | 
|  | fsys, err := test.TxtarArchiveToFS(ar) | 
|  | if err != nil { | 
|  | return nil, err | 
|  | } | 
|  |  | 
|  | pxc, err := proxy.NewTestClient(t, *realProxy) | 
|  | if err != nil { | 
|  | return nil, err | 
|  | } | 
|  |  | 
|  | pkc, err := pkgsite.TestClient(t, *usePkgsite) | 
|  | if err != nil { | 
|  | return nil, err | 
|  | } | 
|  |  | 
|  | ic, err := newMemIC(testIssueTracker) | 
|  | if err != nil { | 
|  | return nil, err | 
|  | } | 
|  |  | 
|  | gc, err := newMemGC(testLegacyGHSAs) | 
|  | if err != nil { | 
|  | return nil, err | 
|  | } | 
|  |  | 
|  | mm, err := priority.CSVToMap(bytes.NewReader(testModuleMap)) | 
|  | if err != nil { | 
|  | return nil, err | 
|  | } | 
|  | return &environment{ | 
|  | reportRepo: repo, | 
|  | reportFS:   fsys, | 
|  | pxc:        pxc, | 
|  | pkc:        pkc, | 
|  | wfs:        newInMemoryWFS(), | 
|  | ic:         ic, | 
|  | gc:         gc, | 
|  | moduleMap:  mm, | 
|  | }, nil | 
|  | } | 
|  |  | 
|  | func runTestWithEnv(t *testing.T, cmd command, tc *testCase, newEnv func(t *testing.T) (*environment, error)) { | 
|  | log.RemoveColor() | 
|  | t.Run(tc.name, func(t *testing.T) { | 
|  | // Re-generate a fresh env for each sub-test. | 
|  | env, err := newEnv(t) | 
|  | if err != nil { | 
|  | t.Error(err) | 
|  | return | 
|  | } | 
|  | out, logs := bytes.NewBuffer([]byte{}), bytes.NewBuffer([]byte{}) | 
|  | log.WriteTo(out, logs) | 
|  |  | 
|  | ctx := context.Background() | 
|  | err = run(ctx, cmd, tc.args, *env) | 
|  | if tc.wantErr { | 
|  | if err == nil { | 
|  | t.Errorf("run(%s, %s) = %v, want error", cmd.name(), tc.args, err) | 
|  | } | 
|  | } else if err != nil { | 
|  | t.Errorf("run(%s, %s) = %v, want no error", cmd.name(), tc.args, err) | 
|  | } | 
|  |  | 
|  | got := &golden{out: out.Bytes(), logs: logs.Bytes()} | 
|  | if *testUpdate { | 
|  | comment := fmt.Sprintf("Expected output of test %s\ncommand: \"vulnreport %s %s\"", t.Name(), cmd.name(), strings.Join(tc.args, " ")) | 
|  | var written map[string][]byte | 
|  | if env.wfs != nil { | 
|  | written = (env.wfs).(*memWFS).written | 
|  | } | 
|  | if err := writeGolden(t, got, comment, written); err != nil { | 
|  | t.Error(err) | 
|  | return | 
|  | } | 
|  |  | 
|  | } | 
|  |  | 
|  | want, err := readGolden(t) | 
|  | if err != nil { | 
|  | t.Errorf("could not read golden file: %v", err) | 
|  | return | 
|  | } | 
|  | if diff := cmp.Diff(want.String(), got.String()); diff != "" { | 
|  | t.Errorf("run(%s, %s) mismatch (-want, +got):\n%s", cmd.name(), tc.args, diff) | 
|  | } | 
|  | }) | 
|  | } | 
|  |  | 
|  | type golden struct { | 
|  | out  []byte | 
|  | logs []byte | 
|  | } | 
|  |  | 
|  | func (g *golden) String() string { | 
|  | return fmt.Sprintf("out:\n%s\nlogs:\n%s", g.out, g.logs) | 
|  | } | 
|  |  | 
|  | func readGolden(t *testing.T) (*golden, error) { | 
|  | fsys, err := test.ReadTxtarFS(testFilename(t)) | 
|  | if err != nil { | 
|  | return nil, err | 
|  | } | 
|  | out, err := fs.ReadFile(fsys, "out") | 
|  | if err != nil { | 
|  | return nil, err | 
|  | } | 
|  | logs, err := fs.ReadFile(fsys, "logs") | 
|  | if err != nil { | 
|  | return nil, err | 
|  | } | 
|  | return &golden{out: out, logs: logs}, nil | 
|  | } | 
|  |  | 
|  | func writeGolden(t *testing.T, g *golden, comment string, written map[string][]byte) error { | 
|  | files := []txtar.File{ | 
|  | {Name: "out", Data: g.out}, | 
|  | {Name: "logs", Data: g.logs}, | 
|  | } | 
|  | sortedFilenames := maps.Keys(written) | 
|  | slices.Sort(sortedFilenames) | 
|  | for _, fname := range sortedFilenames { | 
|  | files = append(files, txtar.File{Name: fname, Data: written[fname]}) | 
|  | } | 
|  |  | 
|  | return test.WriteTxtar(testFilename(t), files, comment) | 
|  | } |