blob: 8e1effa7c5b1eae3fa0e12d07db6b345823996fd [file] [log] [blame]
// Copyright 2021 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 vulncheck
import (
"path"
"reflect"
"testing"
"golang.org/x/tools/go/packages/packagestest"
)
// TestImportsOnly checks for module and imports graph correctness
// for the Config.ImportsOnly=true mode. The inlined test code has
// the following package (left) and module (right) imports graphs:
//
// entry/x entry/y entry
// \ / \ / \
// amod/avuln zmod/z amod zmod
// | |
// wmod/w wmod
// | |
// bmod/bvuln bmod
//
// Packages ending in "vuln" have some known vulnerabilities.
func TestImportsOnly(t *testing.T) {
e := packagestest.Export(t, packagestest.Modules, []packagestest.Module{
{
Name: "golang.org/entry",
Files: map[string]interface{}{
"x/x.go": `
package x
import "golang.org/amod/avuln"
func X() {
avuln.VulnData{}.Vuln1()
}
`,
"y/y.go": `
package y
import (
"golang.org/amod/avuln"
"golang.org/zmod/z"
)
func Y() {
avuln.VulnData{}.Vuln2()
z.Z()
}
`}},
{
Name: "golang.org/zmod@v0.0.0",
Files: map[string]interface{}{"z/z.go": `
package z
func Z() {}
`},
},
{
Name: "golang.org/amod@v1.1.3",
Files: map[string]interface{}{"avuln/avuln.go": `
package avuln
import "golang.org/wmod/w"
type VulnData struct {}
func (v VulnData) Vuln1() { w.W() }
func (v VulnData) Vuln2() {}
`},
},
{
Name: "golang.org/bmod@v0.5.0",
Files: map[string]interface{}{"bvuln/bvuln.go": `
package bvuln
func Vuln() {}
`},
},
{
Name: "golang.org/wmod@v0.0.0",
Files: map[string]interface{}{"w/w.go": `
package w
import "golang.org/bmod/bvuln"
func W() { bvuln.Vuln() }
`},
},
})
defer e.Cleanup()
// Make sure local vulns can be loaded.
fetchingInTesting = true
// Load x and y as entry packages.
pkgs, err := loadPackages(e, path.Join(e.Temp(), "entry/x"), path.Join(e.Temp(), "entry/y"))
if err != nil {
t.Fatal(err)
}
if len(pkgs) != 2 {
t.Fatal("failed to load x and y test packages")
}
cfg := &Config{
Client: testClient,
ImportsOnly: true,
}
result, err := Source(pkgs, cfg)
if err != nil {
t.Fatal(err)
}
// Check that we find the right number of vulnerabilities.
// There should be three entries as there are three vulnerable
// symbols in the two import-reachable OSVs.
if len(result.Vulns) != 3 {
t.Errorf("want 3 Vulns, got %d", len(result.Vulns))
}
// Check that vulnerabilities are connected to the imports
// and requires graph.
for _, v := range result.Vulns {
if v.ImportSink == 0 || v.RequireSink == 0 {
t.Errorf("want ImportSink !=0 and RequireSink !=0 for %v:%v; got %v and %v", v.Symbol, v.PkgPath, v.ImportSink, v.RequireSink)
}
}
// The imports slice should include import chains:
// x -> avuln -> w -> bvuln
// |
// y ---->
// That is, z package shoud not appear in the slice.
wantImports := map[string][]string{
"golang.org/entry/x": {"golang.org/amod/avuln"},
"golang.org/entry/y": {"golang.org/amod/avuln"},
"golang.org/amod/avuln": {"golang.org/wmod/w"},
"golang.org/wmod/w": {"golang.org/bmod/bvuln"},
}
if igStrMap := impGraphToStrMap(result.Imports); !reflect.DeepEqual(wantImports, igStrMap) {
t.Errorf("want %v imports graph; got %v", wantImports, igStrMap)
}
// The requires slice should include requires chains:
// entry -> amod -> wmod -> bmod
// That is, zmod module shoud not appear in the slice.
wantRequires := map[string][]string{
"golang.org/entry": {"golang.org/amod"},
"golang.org/amod": {"golang.org/wmod"},
"golang.org/wmod": {"golang.org/bmod"},
}
if rgStrMap := reqGraphToStrMap(result.Requires); !reflect.DeepEqual(wantRequires, rgStrMap) {
t.Errorf("want %v requires graph; got %v", wantRequires, rgStrMap)
}
}