blob: a91a5f8af1fc7087deedb726baf9e7051078e41e [file] [log] [blame]
// 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)
}