blob: ec60300494819f33f1e8e846bfe481d129256a6e [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"
"flag"
"fmt"
"io/fs"
"path/filepath"
"strings"
"testing"
"time"
"github.com/google/go-cmp/cmp"
"golang.org/x/tools/txtar"
"golang.org/x/vulndb/cmd/vulnreport/log"
"golang.org/x/vulndb/cmd/vulnreport/priority"
"golang.org/x/vulndb/internal/gitrepo"
"golang.org/x/vulndb/internal/proxy"
"golang.org/x/vulndb/internal/test"
)
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")
)
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)}
}
func (m *memWFS) WriteFile(fname string, b []byte) error {
m.written[fname] = b
return nil
}
func testFilename(t *testing.T) string {
return filepath.Join("testdata", t.Name()+".txtar")
}
// TODO(tatianabradley): embed these test files.
const (
testRepoFile = "testdata/repo.txtar"
testIssueTracker = "testdata/issue_tracker.txtar"
testLegacyGHSAs = "testdata/legacy_ghsas.txtar"
testModuleMap = "testdata/modules.csv"
)
// 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 newTestEnv(t, testRepoFile, testIssueTracker, testLegacyGHSAs, testModuleMap)
})
}
var testTime = time.Date(2022, 1, 1, 0, 0, 0, 0, time.UTC)
func newTestEnv(t *testing.T, reportRepoFile, issueTracker, legacyGHSAs, testModuleMap string) (*environment, error) {
t.Helper()
repo, err := gitrepo.ReadTxtarRepo(reportRepoFile, testTime)
if err != nil {
return nil, err
}
fsys, err := test.ReadTxtarFS(reportRepoFile)
if err != nil {
return nil, err
}
pc, err := proxy.NewTestClient(t, *realProxy)
if err != nil {
return nil, err
}
ic, err := newMemIC(issueTracker)
if err != nil {
return nil, err
}
gc, err := newMemGC(legacyGHSAs)
if err != nil {
return nil, err
}
mm, err := priority.CSVToMap(testModuleMap)
if err != nil {
return nil, err
}
return &environment{
reportRepo: repo,
reportFS: fsys,
pc: pc,
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},
}
for fname, b := range written {
files = append(files, txtar.File{Name: fname, Data: b})
}
return test.WriteTxtar(testFilename(t), files, comment)
}