ci: run the full unit suite on windows (#5370)

* ci: run the full unit suite on windows via make unit

Drops the curated `unit:windows` subset; the windows job now bootstraps like linux and runs `make unit`. Exceptions get added as failures show up.

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>

* ci: skip fixture refresh for windows unit tests

binny has no windows release, so the bootstrap action can't install the tools the fixture refresh needs.

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>

* test: skip linux container fixtures when docker can't run them

Windows CI runners only run windows containers, so image fixtures go through `testutils.SkipWithoutLinuxContainers`. No behavior change on linux/mac.

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>

* fix: close leaked file handles and posix squashfs paths on windows

- output writers are now closed on early returns in `scan`/`convert` (open handles block cleanup on windows)
- `newSBOMMultiWriter` used `path.Dir` on host paths, so output subdirectories were never created on windows
- squashfs walk paths were built with `filepath.Join`
- skip a few posix-only test cases (dir perms, xdg fallback, go-rpmdb ndb handle leak upstream)

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>

* fix(golang): resolve go source module dir natively on windows

- `parseGoModFile` passed the resolver's posix (`/c/...`) path to the go toolchain, so go source resolution silently found nothing on windows
- skip container-built fixtures (go archs, java builds) and golden image tarballs on windows; stereoscope's layer cache filenames contain `:`, which windows rejects

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>

* fix: keep posix paths posix on windows

- `ChrootContext.ToChrootPath` nested volumes (`D:\cwd\C:\root`) when the scan root was on another drive, so the prefix was never trimmed
- bitnami, python wheel/egg, and alpm parsers used `filepath` on posix location paths
- alpm backup-file lookups silently missed on windows

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>

* ci: pull the test fixture cache for windows unit tests

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>

* fix: don't map virtual file tree paths through the host on windows

`FiletreeResolver` without a chroot (snaps) ran request paths through `filepath.Abs`, rooting them on the current volume (`/payload.txt` -> `/d/payload.txt`), so snap scans on windows found nothing.

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>

* fix: match tar entry globs as posix on windows

- `matchesAnyGlob` used `doublestar.PathMatch` (host separator) on tar entry names, so tar-wrapped java archives silently produced nothing on windows
- test helpers now treat binary snippet keys and `FromFile` locations as posix

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>

* incorporate windows path fix

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>

* incorporate go-rpmdb close fix

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>

* close squashfs fixture writers before finalizing

- windows dir listings can report a zero size for files still open, so the manifest was packed empty

(also adds a temporary windows diagnostic for the binary cataloger)

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>

* temp: surface windows binary cataloger diagnostics

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>

* fix: keep directory symlink targets posix on windows

- the directory indexer joined symlink targets with native separators, so links in the posix file tree pointed nowhere and globs on link names found nothing
- binary fixture logical keys used native separators, so nested snippets were picked as the fixture dir on windows

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>

* keep addSymlinkToIndex under the funlen limit

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>

* drop windows skips covered by the stereoscope and go-rpmdb bumps

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>

---------

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>
This commit is contained in:
Alex Goodman
2026-10-05 10:41:03 -04:00
committed by GitHub
parent 6d2493bae4
commit 6c9bb732dd
60 changed files with 391 additions and 135 deletions
@@ -8,6 +8,7 @@ import (
"io"
"math"
"os"
"path"
"path/filepath"
"slices"
"strings"
@@ -522,14 +523,15 @@ func TestSafeTensorsMergeProcessor(t *testing.T) {
t.Run("dir scan: config.json _name_or_path beats the parent directory fallback", func(t *testing.T) {
// When a sibling config.json carries _name_or_path
dir := t.TempDir()
require.NoError(t, os.WriteFile(filepath.Join(dir, "config.json"),
// (location paths are posix, so keep the temp dir slash-separated for windows)
dir := filepath.ToSlash(t.TempDir())
require.NoError(t, os.WriteFile(path.Join(dir, "config.json"),
[]byte(`{"_name_or_path":"org/preferred-name"}`), 0o644))
stPath := filepath.Join(dir, "weights.safetensors")
stPath := path.Join(dir, "weights.safetensors")
p := dirPkg(stPath, pkg.SafeTensorsModelInfo{
Format: "safetensors", TensorCount: 1, MetadataHash: "abc",
})
resolver := file.NewMockResolverForPaths(filepath.Join(dir, "config.json"))
resolver := file.NewMockResolverForPaths(path.Join(dir, "config.json"))
out, _, err := safeTensorsMergeProcessor(context.Background(), resolver, []pkg.Package{p}, nil, nil)
require.NoError(t, err)
require.Len(t, out, 1)
+4 -2
View File
@@ -114,7 +114,7 @@ func fetchPkgFiles(base string, resolver file.Resolver) ([]pkg.AlpmFileRecord, f
func fetchBackupFiles(base string, resolver file.Resolver) ([]pkg.AlpmFileRecord, file.Location, error) {
// We only really do this to get any backup database entries from the files database
target := filepath.Join(base, "files")
target := path.Join(base, "files")
loc, err := getLocation(target, resolver)
if err != nil {
@@ -395,7 +395,9 @@ func parseMtreeWithLimits(r io.Reader, maxSize int64, maxLines int) ([]pkg.AlpmF
if ok := ignoredFiles[f.Name]; ok {
continue
}
path := fmt.Sprintf("/%s", f.Name)
// go-mtree runs filepath.Clean on entry names, which turns them into host paths on windows.
// ponytail: a vis-encoded backslash (\134) in a name gets mangled on windows only, unvis first if that matters
path := fmt.Sprintf("/%s", filepath.ToSlash(f.Name))
fileFields["path"] = path
for _, kv := range f.Keywords {
kw := string(kv.Keyword())
@@ -17,6 +17,7 @@ import (
"github.com/anchore/packageurl-go"
"github.com/anchore/stereoscope/pkg/imagetest"
"github.com/anchore/syft/internal/testutils"
"github.com/anchore/syft/syft/cpe"
"github.com/anchore/syft/syft/file"
"github.com/anchore/syft/syft/pkg"
@@ -3169,6 +3170,7 @@ func Test_Cataloger_DefaultClassifiers_PositiveCases_Image(t *testing.T) {
t.Run(test.name, func(t *testing.T) {
c := NewClassifierCataloger(DefaultClassifierCatalogerConfig())
testutils.SkipWithoutLinuxContainers(t)
img := imagetest.GetFixtureImage(t, "docker-archive", test.fixtureImage)
src := stereoscopesource.New(img, stereoscopesource.ImageConfig{
Reference: test.fixtureImage,
@@ -1,6 +1,7 @@
package config
import (
"path/filepath"
"testing"
"github.com/stretchr/testify/assert"
@@ -123,10 +124,10 @@ func TestAllStorePaths(t *testing.T) {
},
dest: "dest",
expected: []string{
"dest/test/1.0/platform1/test1",
"dest/test/1.0/platform1/test2",
"dest/test/1.0/platform2/test1",
"dest/test/1.0/platform2/test2",
filepath.FromSlash("dest/test/1.0/platform1/test1"),
filepath.FromSlash("dest/test/1.0/platform1/test2"),
filepath.FromSlash("dest/test/1.0/platform2/test1"),
filepath.FromSlash("dest/test/1.0/platform2/test2"),
},
},
}
@@ -4,6 +4,7 @@ import (
"fmt"
"iter"
"os"
"path"
"path/filepath"
"slices"
"sort"
@@ -167,7 +168,7 @@ func getLogicalKey(managedBinaryPath string) (*LogicalEntryKey, error) {
OrgName: items[0],
Version: items[1],
Platform: items[2],
Filename: filepath.Join(items[3:]...),
Filename: path.Join(items[3:]...), // posix, since this is part of the logical key
}, nil
}
@@ -64,8 +64,8 @@ func TestListAllBinaries(t *testing.T) {
assert.Equal(t,
Entries{
LogicalEntryKey{OrgName: "busybox", Version: "1.3.6", Platform: "linux-amd64", Filename: "busybox"}: EntryInfo{IsConfigured: true, BinaryPath: "testdata/bin/busybox/1.3.6/linux-amd64/busybox", SnippetPath: ""},
LogicalEntryKey{OrgName: "busybox", Version: "1.3.6", Platform: "linux-arm64", Filename: "busybox"}: EntryInfo{IsConfigured: false, BinaryPath: "testdata/bin/busybox/1.3.6/linux-arm64/busybox", SnippetPath: ""},
LogicalEntryKey{OrgName: "busybox", Version: "1.3.6", Platform: "linux-amd64", Filename: "busybox"}: EntryInfo{IsConfigured: true, BinaryPath: filepath.FromSlash("testdata/bin/busybox/1.3.6/linux-amd64/busybox"), SnippetPath: ""},
LogicalEntryKey{OrgName: "busybox", Version: "1.3.6", Platform: "linux-arm64", Filename: "busybox"}: EntryInfo{IsConfigured: false, BinaryPath: filepath.FromSlash("testdata/bin/busybox/1.3.6/linux-arm64/busybox"), SnippetPath: ""},
},
entries,
)
@@ -105,10 +105,10 @@ func TestListAllEntries(t *testing.T) {
assert.Equal(t,
Entries{
LogicalEntryKey{OrgName: "busybox", Version: "1.3.6", Platform: "linux-amd64", Filename: "busybox"}: EntryInfo{IsConfigured: true, BinaryPath: "testdata/bin/busybox/1.3.6/linux-amd64/busybox", SnippetPath: "testdata/snippets/busybox/1.3.6/linux-amd64/busybox"},
LogicalEntryKey{OrgName: "busybox", Version: "1.3.6", Platform: "linux-arm64", Filename: "busybox"}: EntryInfo{IsConfigured: false, BinaryPath: "testdata/bin/busybox/1.3.6/linux-arm64/busybox", SnippetPath: "testdata/snippets/busybox/1.3.6/linux-arm64/busybox"},
LogicalEntryKey{OrgName: "busybox", Version: "1.3.6", Platform: "linux-amd64", Filename: "busybox"}: EntryInfo{IsConfigured: true, BinaryPath: filepath.FromSlash("testdata/bin/busybox/1.3.6/linux-amd64/busybox"), SnippetPath: filepath.FromSlash("testdata/snippets/busybox/1.3.6/linux-amd64/busybox")},
LogicalEntryKey{OrgName: "busybox", Version: "1.3.6", Platform: "linux-arm64", Filename: "busybox"}: EntryInfo{IsConfigured: false, BinaryPath: filepath.FromSlash("testdata/bin/busybox/1.3.6/linux-arm64/busybox"), SnippetPath: filepath.FromSlash("testdata/snippets/busybox/1.3.6/linux-arm64/busybox")},
// note the standalone snippet!
LogicalEntryKey{OrgName: "postgres", Version: "9.6.10", Platform: "linux-amd64", Filename: "postgres"}: EntryInfo{IsConfigured: false, BinaryPath: "", SnippetPath: "testdata/snippets/postgres/9.6.10/linux-amd64/postgres"},
LogicalEntryKey{OrgName: "postgres", Version: "9.6.10", Platform: "linux-amd64", Filename: "postgres"}: EntryInfo{IsConfigured: false, BinaryPath: "", SnippetPath: filepath.FromSlash("testdata/snippets/postgres/9.6.10/linux-amd64/postgres")},
},
entries,
)
@@ -1,6 +1,7 @@
package internal
import (
"path/filepath"
"strings"
"testing"
@@ -13,7 +14,7 @@ func TestSplitFilepath(t *testing.T) {
path string
expected []string
}{
{path: "a/b/c", expected: []string{"a", "b", "c"}},
{path: filepath.Join("a", "b", "c"), expected: []string{"a", "b", "c"}},
}
for _, test := range tests {
@@ -3,7 +3,9 @@ package testutil
import (
"fmt"
"os"
"path"
"path/filepath"
"strings"
"testing"
"github.com/stretchr/testify/require"
@@ -18,10 +20,11 @@ import (
// not specify the "bin" or "snippets" parent path... this is determined logically [snippets > binary unless told
// otherwise]). Path should also be to the directory containing the binary or snippets of interest (not the binaries
// or snippets itself).
func SnippetOrBinary(t *testing.T, path string, requireBinary bool) string {
func SnippetOrBinary(t *testing.T, logicalPath string, requireBinary bool) string {
t.Helper()
require.Len(t, internal.SplitFilepath(path), 3, "path must be a in the form <name>/<version>/<arch>")
// logicalPath is a posix key, not a host path
require.Len(t, strings.Split(logicalPath, "/"), 3, "path must be a in the form <name>/<version>/<arch>")
// cd to testdata directory and load the config
@@ -43,11 +46,11 @@ func SnippetOrBinary(t *testing.T, path string, requireBinary bool) string {
var fixturePath string
for k, v := range entries.Sorted() {
if filepath.Dir(k.Path()) == path {
if path.Dir(k.Path()) == logicalPath {
// prefer the snippet over the binary
if !requireBinary {
if v.SnippetPath != "" {
t.Logf("using snippet for %q", path)
t.Logf("using snippet for %q", logicalPath)
require.NoError(t, validateSnippet(v.BinaryPath, v.SnippetPath))
fixturePath = v.SnippetPath
break
@@ -56,10 +59,10 @@ func SnippetOrBinary(t *testing.T, path string, requireBinary bool) string {
fixturePath = v.BinaryPath
break
}
t.Fatalf("no binary or snippet found for %q", path)
t.Fatalf("no binary or snippet found for %q", logicalPath)
}
if v.BinaryPath != "" {
t.Logf("forcing the use of the original binary for %q", path)
t.Logf("forcing the use of the original binary for %q", logicalPath)
fixturePath = v.BinaryPath
break
}
@@ -68,12 +71,12 @@ func SnippetOrBinary(t *testing.T, path string, requireBinary bool) string {
t.Skip("no binary found, but is covered by a snippet. Please add this case to the 'binary/testdata/config.yaml' and recreate the snippet")
}
t.Fatalf("no binary found for %q", path)
t.Fatalf("no binary found for %q", logicalPath)
}
}
if fixturePath == "" {
t.Fatalf("no fixture found for %q", path)
t.Fatalf("no fixture found for %q", logicalPath)
}
// this should be relative to the tests-fixtures directory and should be the directory containing the binary or
@@ -101,6 +104,7 @@ func validateSnippet(binaryPath, snippetPath string) error {
if err != nil {
return err
}
defer f.Close()
expected, err := internal.Sha256SumFile(f)
if err != nil {
+2 -2
View File
@@ -5,7 +5,7 @@ package bitnami
import (
"context"
"path/filepath"
"path"
"strings"
"github.com/anchore/syft/internal/log"
@@ -73,7 +73,7 @@ func parseSBOM(_ context.Context, resolver file.Resolver, _ *generic.Environment
// Bitnami packages reported in a SPDX file are shipped under the same directory
// as the SPDX file itself.
metadata.Path = filepath.Dir(reader.RealPath)
metadata.Path = path.Dir(reader.RealPath)
if p.ID() != mainPkgID {
metadata.Files = packageFiles(s.Relationships, p, metadata.Path)
secondaryPkgsFiles = append(secondaryPkgsFiles, metadata.Files...)
+1 -2
View File
@@ -3,7 +3,6 @@ package bitnami
import (
"fmt"
"path"
"path/filepath"
"slices"
"strings"
@@ -73,7 +72,7 @@ func packageFiles(relationships []artifact.Relationship, p pkg.Package, baseDire
// mainPkgFiles returns the files owned by the main package in the SPDX file.
func mainPkgFiles(resolver file.Resolver, spdxFilePath string, secondaryPkgsFiles []string) ([]string, error) {
ownedPathGlob := fmt.Sprintf("%s/**", filepath.Dir(spdxFilePath))
ownedPathGlob := fmt.Sprintf("%s/**", path.Dir(spdxFilePath))
ownedLocations, err := resolver.FilesByGlob(ownedPathGlob)
if err != nil {
return nil, err
@@ -4,7 +4,7 @@ import (
"context"
"encoding/json"
"fmt"
"path/filepath"
"path"
"sort"
"strings"
@@ -67,7 +67,7 @@ func parseComponentsJSON(_ context.Context, _ file.Resolver, _ *generic.Environm
Revision: revision,
Architecture: entry.Arch,
Distro: entry.Distro,
Path: filepath.Join(filepath.Dir(reader.RealPath), name),
Path: path.Join(path.Dir(reader.RealPath), name),
}
p := pkg.Package{
@@ -1,6 +1,9 @@
package golang
import (
"path/filepath"
"runtime"
"strings"
"testing"
"github.com/spf13/afero"
@@ -202,10 +205,23 @@ func TestFindAllLicenseCandidatesUpwards(t *testing.T) {
t.Run(tt.name, func(t *testing.T) {
// Create in-memory filesystem
fs := afero.NewMemMapFs()
tt.setupFS(fs)
setupFS, startDir, stopAt, expectedFiles := afero.Fs(fs), tt.startDir, tt.stopAt, tt.expectedFiles
if runtime.GOOS == "windows" {
// the finder wants native absolute paths, so root the posix-style cases on a drive
setupFS = afero.NewBasePathFs(fs, `C:\`)
startDir, stopAt = winAbs(startDir), winAbs(stopAt)
expectedFiles = nil
if tt.expectedFiles != nil {
expectedFiles = []string{}
}
for _, f := range tt.expectedFiles {
expectedFiles = append(expectedFiles, winAbs(f))
}
}
tt.setupFS(setupFS)
// Run the function
result, err := findAllLicenseCandidatesUpwards(tt.startDir, tt.stopAt, fs)
result, err := findAllLicenseCandidatesUpwards(startDir, stopAt, fs)
// Check error expectation
if tt.expectedError {
@@ -214,7 +230,15 @@ func TestFindAllLicenseCandidatesUpwards(t *testing.T) {
}
require.NoError(t, err, tt.description)
assert.Equal(t, tt.expectedFiles, result, tt.description)
assert.Equal(t, expectedFiles, result, tt.description)
})
}
}
// winAbs roots posix absolute paths on C:\, leaving relative ones relative (some cases assert they're rejected)
func winAbs(p string) string {
if !strings.HasPrefix(p, "/") {
return p
}
return filepath.Join(`C:\`, filepath.FromSlash(p))
}
@@ -20,6 +20,7 @@ import (
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/anchore/syft/internal/testutils"
"github.com/anchore/syft/syft/cataloging"
"github.com/anchore/syft/syft/file"
"github.com/anchore/syft/syft/internal/fileresolver"
@@ -30,6 +31,8 @@ import (
// make will run the default make target for the given test fixture path
func runMakeTarget(t *testing.T, fixtureName string) {
// the fixture binaries are built inside linux containers
testutils.SkipWithoutLinuxContainers(t)
cwd, err := os.Getwd()
require.NoError(t, err)
fixtureDir := filepath.Join(cwd, "testdata/", fixtureName)
+7 -1
View File
@@ -20,6 +20,7 @@ import (
"github.com/anchore/syft/syft/artifact"
"github.com/anchore/syft/syft/file"
"github.com/anchore/syft/syft/internal/fileresolver"
"github.com/anchore/syft/syft/internal/windows"
"github.com/anchore/syft/syft/pkg"
"github.com/anchore/syft/syft/pkg/cataloger/generic"
)
@@ -38,7 +39,12 @@ func newGoModCataloger(opts CatalogerConfig) *goModCataloger {
// parseGoModFile takes a go.mod and tries to resolve and lists all packages discovered.
func (c *goModCataloger) parseGoModFile(ctx context.Context, resolver file.Resolver, _ *generic.Environment, reader file.LocationReadCloser) (pkgs []pkg.Package, relationships []artifact.Relationship, err error) {
modDir := filepath.Dir(string(reader.Location.Reference().RealPath))
realPath := string(reader.Location.Reference().RealPath)
if windows.HostRunningOnWindows() {
// directory resolvers hand back volume-encoded posix paths (/c/...), the go toolchain needs the native path
realPath = windows.FromPosix(realPath)
}
modDir := filepath.Dir(realPath)
digests, err := parseGoSumFile(resolver, reader)
if err != nil {
log.Debugf("unable to get go.sum: %v", err)
+2
View File
@@ -10,6 +10,7 @@ import (
stereoscopeFile "github.com/anchore/stereoscope/pkg/file"
"github.com/anchore/stereoscope/pkg/imagetest"
"github.com/anchore/syft/internal/testutils"
)
// every case here is one the magic scan must reject, asserted on parseUPXInfo's errNotUPX result.
@@ -114,6 +115,7 @@ func TestParseUPXInfo_ValidHeader(t *testing.T) {
// ignores that padding if there is some. A build that needs none passes either way, which is how the bug
// went unnoticed. The case without padding is covered by TestDecompressUPX_TailExtentsArePlacedPastTheLoader.
func TestImageSmallUPXNeedsLoaderPadding(t *testing.T) {
testutils.SkipWithoutLinuxContainers(t)
img := imagetest.GetFixtureImage(t, "docker-archive", "image-small-upx")
rc, err := img.OpenPathFromSquash(stereoscopeFile.Path("/run-me"))
require.NoError(t, err)
@@ -10,6 +10,7 @@ import (
"github.com/stretchr/testify/require"
"github.com/anchore/stereoscope/pkg/imagetest"
"github.com/anchore/syft/internal/testutils"
"github.com/anchore/syft/syft/file"
"github.com/anchore/syft/syft/source"
"github.com/anchore/syft/syft/source/stereoscopesource"
@@ -199,6 +200,7 @@ func fixtureResolvers(t *testing.T, fixtures ...string) map[string]file.Resolver
continue
}
testutils.SkipWithoutLinuxContainers(t)
img := imagetest.GetFixtureImage(t, "docker-archive", fixture)
s := stereoscopesource.New(img, stereoscopesource.ImageConfig{
@@ -24,6 +24,7 @@ import (
"github.com/anchore/syft/internal/cmptest"
"github.com/anchore/syft/internal/licenses"
"github.com/anchore/syft/internal/relationship"
"github.com/anchore/syft/internal/testutils"
"github.com/anchore/syft/internal/tmpdir"
"github.com/anchore/syft/syft/artifact"
"github.com/anchore/syft/syft/file"
@@ -150,9 +151,11 @@ func (p *CatalogTester) FromFile(t *testing.T, path string) *CatalogTester {
fixture, err := os.Open(path)
require.NoError(t, err)
// parsers don't own the reader (the generic cataloger closes it), so close it here
t.Cleanup(func() { _ = fixture.Close() })
p.reader = file.LocationReadCloser{
Location: file.NewVirtualLocationFromDirectory(fixture.Name(), fixture.Name(), *stereofile.NewFileReference(stereofile.Path(absPath))),
Location: file.NewVirtualLocationFromDirectory(filepath.ToSlash(fixture.Name()), filepath.ToSlash(fixture.Name()), *stereofile.NewFileReference(stereofile.Path(absPath))),
ReadCloser: fixture,
}
return p
@@ -201,6 +204,7 @@ func (p *CatalogTester) WithImageResolver(t *testing.T, fixtureName string) *Cat
return p
}
testutils.SkipWithoutLinuxContainers(t)
img := imagetest.GetFixtureImage(t, "docker-archive", fixtureName)
s := stereoscopesource.New(img, stereoscopesource.ImageConfig{
@@ -6,6 +6,7 @@ import (
"io"
"os"
"os/exec"
"path"
"path/filepath"
"strings"
"syscall"
@@ -18,6 +19,7 @@ import (
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/anchore/syft/internal/testutils"
"github.com/anchore/syft/syft/artifact"
"github.com/anchore/syft/syft/file"
"github.com/anchore/syft/syft/license"
@@ -1605,6 +1607,9 @@ func generateJavaBuildFixture(t *testing.T, fixturePath string) {
return
}
// the fixtures are built with maven/gradle inside linux containers
testutils.SkipWithoutLinuxContainers(t)
makeTask := strings.TrimPrefix(fixturePath, "testdata/java-builds/")
t.Log(color.Bold.Sprintf("Generating Fixture from 'make %s'", makeTask))
@@ -1630,7 +1635,8 @@ func generateJavaMetadataJarFixture(t *testing.T, fixtureName string, fileExtens
return fixturePath
}
makeTask := filepath.Join("cache", fixtureName+"."+fileExtension)
// make targets are posix paths regardless of host
makeTask := path.Join("cache", fixtureName+"."+fileExtension)
t.Log(color.Bold.Sprintf("Generating Fixture from 'make %s'", makeTask))
cwd, err := os.Getwd()
@@ -47,6 +47,8 @@ func Test_defaultMavenLocalRepoDir(t *testing.T) {
home, err := filepath.Abs(test.home)
require.NoError(t, err)
t.Setenv("HOME", home)
// os.UserHomeDir reads USERPROFILE on windows
t.Setenv("USERPROFILE", home)
}
got := defaultMavenLocalRepoDir()
require.Equal(t, test.expected, got)
@@ -31,7 +31,7 @@ func MockRepo(t *testing.T, dir string) (url string) {
t.Cleanup(server.Close)
matches, err := doublestar.Glob(os.DirFS(dir), filepath.Join("**", "*.pom"))
matches, err := doublestar.Glob(os.DirFS(dir), "**/*.pom")
require.NoError(t, err)
for _, match := range matches {
@@ -8,6 +8,7 @@ import (
"github.com/anchore/stereoscope/pkg/imagetest"
"github.com/anchore/syft/internal/packagemetadata"
"github.com/anchore/syft/internal/testutils"
"github.com/anchore/syft/syft/artifact"
"github.com/anchore/syft/syft/file"
"github.com/anchore/syft/syft/pkg"
@@ -523,6 +524,7 @@ func TestCpanCatalogers_metadataTypes(t *testing.T) {
func imageResolver(t *testing.T, fixture string) file.Resolver {
t.Helper()
testutils.SkipWithoutLinuxContainers(t)
img := imagetest.GetFixtureImage(t, "docker-archive", fixture)
resolver, err := stereoscopesource.New(img, stereoscopesource.ImageConfig{Reference: fixture}).
FileResolver(source.SquashedScope)
+7 -8
View File
@@ -5,7 +5,6 @@ import (
"context"
"encoding/json"
"path"
"path/filepath"
"strings"
"github.com/scylladb/go-set/strset"
@@ -53,7 +52,7 @@ func fetchInstalledFiles(resolver file.Resolver, metadataLocation file.Location,
// to reconcile the installed-files.txt path to the same layer (or the next adjacent lower layer).
// find the installed-files.txt file relative to the directory where the METADATA file resides (in path AND layer structure)
installedFilesPath := filepath.Join(filepath.Dir(metadataLocation.RealPath), "installed-files.txt")
installedFilesPath := path.Join(path.Dir(metadataLocation.RealPath), "installed-files.txt")
installedFilesRef := resolver.RelativeFileByPath(metadataLocation, installedFilesPath)
if installedFilesRef != nil {
@@ -83,7 +82,7 @@ func fetchRecordFiles(resolver file.Resolver, metadataLocation file.Location) (f
// to reconcile the RECORD path to the same layer (or the next adjacent lower layer).
// find the RECORD file relative to the directory where the METADATA file resides (in path AND layer structure)
recordPath := filepath.Join(filepath.Dir(metadataLocation.RealPath), "RECORD")
recordPath := path.Join(path.Dir(metadataLocation.RealPath), "RECORD")
recordRef := resolver.RelativeFileByPath(metadataLocation, recordPath)
if recordRef != nil {
@@ -107,8 +106,8 @@ func fetchRecordFiles(resolver file.Resolver, metadataLocation file.Location) (f
// fetchTopLevelPackages finds a corresponding top_level.txt file for the given python package metadata file and returns the set of package names contained.
func fetchTopLevelPackages(resolver file.Resolver, metadataLocation file.Location) (pkgs []string, sources []file.Location, err error) {
// a top_level.txt file specifies the python top-level packages (provided by this python package) installed into site-packages
parentDir := filepath.Dir(metadataLocation.RealPath)
topLevelPath := filepath.Join(parentDir, "top_level.txt")
parentDir := path.Dir(metadataLocation.RealPath)
topLevelPath := path.Join(parentDir, "top_level.txt")
topLevelLocation := resolver.RelativeFileByPath(metadataLocation, topLevelPath)
if topLevelLocation == nil {
@@ -157,8 +156,8 @@ type vcsInfo struct {
}
func fetchDirectURLData(resolver file.Resolver, metadataLocation file.Location) (d *pkg.PythonDirectURLOriginInfo, sources []file.Location, err error) {
parentDir := filepath.Dir(metadataLocation.RealPath)
directURLPath := filepath.Join(parentDir, "direct_url.json")
parentDir := path.Dir(metadataLocation.RealPath)
directURLPath := path.Join(parentDir, "direct_url.json")
directURLLocation := resolver.RelativeFileByPath(metadataLocation, directURLPath)
if directURLLocation == nil {
@@ -269,7 +268,7 @@ func findLicenses(ctx context.Context, resolver file.Resolver, m parsedData) pkg
continue
}
if licenses.IsLicenseFile(filepath.Base(f.Path)) {
if licenses.IsLicenseFile(path.Base(f.Path)) {
candidatePaths.Add(path.Join(m.SitePackagesRootPath, f.Path))
}
}
@@ -2,7 +2,7 @@ package python
import (
"fmt"
"path/filepath"
"path"
"strings"
"github.com/bmatcuk/doublestar/v4"
@@ -48,13 +48,13 @@ func parseWheelOrEggMetadata(locationReader file.LocationReadCloser) (parsedData
}
// add additional metadata not stored in the egg/wheel metadata file
path := locationReader.Path()
metadataPath := locationReader.Path()
pd.SitePackagesRootPath = determineSitePackagesRootPath(path)
pd.SitePackagesRootPath = determineSitePackagesRootPath(metadataPath)
if pd.Licenses != "" || pd.LicenseExpression != "" {
pd.LicenseFilePath = path
pd.LicenseFilePath = metadataPath
} else if pd.LicenseFile != "" {
pd.LicenseFilePath = filepath.Join(filepath.Dir(path), pd.LicenseFile)
pd.LicenseFilePath = path.Join(path.Dir(metadataPath), pd.LicenseFile)
}
pd.DistInfoLocation = locationReader.Location
@@ -144,18 +144,18 @@ func getFieldType(key, in string) any {
// isEggRegularFile determines if the specified path is the regular file variant
// of egg metadata (as opposed to a directory that contains more metadata
// files).
func isEggRegularFile(path string) bool {
return doublestar.MatchUnvalidated(eggInfoGlob, path)
func isEggRegularFile(p string) bool {
return doublestar.MatchUnvalidated(eggInfoGlob, p)
}
// determineSitePackagesRootPath returns the path of the site packages root,
// given the egg metadata file or directory specified in the path.
func determineSitePackagesRootPath(path string) string {
if isEggRegularFile(path) {
return filepath.Clean(filepath.Dir(path))
func determineSitePackagesRootPath(p string) string {
if isEggRegularFile(p) {
return path.Clean(path.Dir(p))
}
return filepath.Clean(filepath.Dir(filepath.Dir(path)))
return path.Clean(path.Dir(path.Dir(p)))
}
// handleFieldBodyContinuation returns the updated value for the specified field after processing the specified line.
@@ -6,6 +6,7 @@ import (
"errors"
"fmt"
"io"
"path"
"path/filepath"
"strings"
@@ -87,11 +88,13 @@ func parseInstalledFiles(reader io.Reader, location, sitePackagesRootPath string
}
if location != "" && sitePackagesRootPath != "" {
joinedPath := filepath.Join(filepath.Dir(location), line)
joinedPath := path.Join(path.Dir(location), line)
// the path package has no Rel, and filepath.Rel hands back host separators on windows
line, err = filepath.Rel(sitePackagesRootPath, joinedPath)
if err != nil {
return nil, err
}
line = filepath.ToSlash(line)
}
installedFile := pkg.PythonFileRecord{
@@ -122,8 +122,7 @@ func TestParsePackageResolved_empty(t *testing.T) {
dir := t.TempDir()
fixture = filepath.Join(dir, "Package.resolved")
_, err := os.Create(fixture)
require.NoError(t, err)
require.NoError(t, os.WriteFile(fixture, nil, 0600))
pkgtest.TestFileParser(t, fixture, parsePackageResolved, nil, nil)
}