Files
syft/.make/main.go
T
Alex Goodman 6ac7afb439 Fix missing file hashes when running on windows system (#5341)
* test: add windows unit test job and capture dir resolver path bug

- add a `Unit tests (Windows)` job running a new `make unit:windows` task, scoped to the packages that own host path handling (tests needing linux docker fixtures are skipped)
- `Unit tests` is now a gate job over the Linux and Windows unit jobs
- add a resolver test asserting that locations from `AllLocations` resolve again via `FilesByPath` and use posix paths. This is expected to fail on windows today, which is why file digests come back empty for `dir:` scans (#5325)
- fix windows-only test files that no longer compiled

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

* chore: drop verbose output from integration tests

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

* fix: windows path handling outside of the file lookup bug

- chroot paths are always posix (`ToChrootPath` no longer returns backslash paths on windows), same for the directory source name derived from `base`
- `windows.FromPosix` no longer panics on input that isn't volume-encoded (e.g. relative or already native paths)
- skip tests on windows that rely on symlink fixtures, shell script fixtures, or emulate windows paths on a posix host
- disable `core.autocrlf` on the windows CI job so fixture digests match linux

This leaves only the `FilesByPath` lookup failures from #5325.

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

* test: skip posix-only fileresolver tests on windows

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

* fix: split contained paths on posix separators on windows

- `allContainedPaths` cleaned with the native separator but split on `/`, so a root reached through a symlink never had its ancestors indexed on windows
- index assertions in tests now key on the posix path the index actually stores
- skip symlink fixture tests and a posix-only test helper on windows

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

* chore: revert unrelated go.sum churn and allContainedPaths change

- restore `go.sum` and `.make/go.sum` (accidentally populated by a local `-mod=mod` invocation)
- revert the `allContainedPaths` separator change: production passes native windows paths there, and splitting them on `/` produces drive-relative ancestors, so skip the posix-only test on windows instead
- annotate the remaining `filepath` usages that operate on native paths

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

* fix: search the file tree with posix paths on windows (#5325)

The directory and file resolvers key their file tree on posix paths (`/c/some/path`), but `FilesByPath`, `HasPath` and root-anchored `FilesByGlob` searched it with the native path from the chroot context (`C:\some\path`). On windows every lookup silently missed, so anything that resolves files by path found nothing. This is why file digests were empty for `dir:` scans.

Requests are now converted to posix before searching the tree.

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

---------

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>
2026-09-30 09:08:13 -04:00

214 lines
7.9 KiB
Go

package main
import (
"os"
"path/filepath"
"runtime"
"strconv"
. "github.com/anchore/go-make"
"github.com/anchore/go-make/config"
"github.com/anchore/go-make/file"
"github.com/anchore/go-make/lang"
"github.com/anchore/go-make/run"
"github.com/anchore/go-make/tasks/golint"
"github.com/anchore/go-make/tasks/goreleaser"
"github.com/anchore/go-make/tasks/gotask"
"github.com/anchore/go-make/tasks/gotest"
)
func main() {
Makefile(
// shared anchore tasks
golint.Tasks(),
goreleaser.Tasks(),
// unit tests: exclude packages under any test/ directory (matches the syft
// Taskfile's prior `grep -v` against test paths). Coverage threshold of 62%
// preserves the prior coverage gate that used to live in scripts/coverage.py.
gotest.Tasks(
gotest.Name("unit"),
gotest.ExcludeGlob("**/test/**"),
gotest.CoverageThreshold(62),
race(),
),
// integration tests: run `go test` directly instead of via gotest.Tasks(), which
// has no way to set a timeout. The suite is a single package of ~36 sequential
// tests over 18 docker fixture images, so with a cold fixture cache (every image
// built + saved inline, under -race) it runs well past `go test`'s default 10m
// timeout -- which is exactly the case when regenerating the fixture cache from
// scratch. -count=1 keeps the go test cache from short-circuiting a run whose
// side effect (the built fixtures) is the thing we're after.
//
// The race-detector smoke against a real image stays bundled here (RunsOn
// integration) so `make integration` behaves like the Taskfile version did.
Task{
Name: "integration",
Description: "run integration tests",
RunsOn: lang.List("test"),
Run: func() {
raceFlag := ""
if raceEnabled() {
raceFlag = " -race"
}
Run(
"go test -count=1 -timeout=30m"+raceFlag+" ./cmd/syft/internal/test/integration/...",
run.Env("GODEBUG", "dontfreezetheworld=1"),
)
},
},
Task{
Name: "integration:race-smoke",
Description: "exercise the CLI with the race detector",
RunsOn: lang.List("integration"),
Run: func() {
if !raceEnabled() {
Log("race detector disabled (RACE=false); skipping race smoke")
return
}
Run("go run -race cmd/syft/main.go anchore/test_images:grype-quality-dotnet-69f15d2")
},
},
// cli tests: native go-make Task. Runs SYFT_BINARY_LOCATION at an *absolute*
// path to the snapshot binary. Builds the snapshot only when the binary is
// missing rather than depending on the snapshot task unconditionally: in
// validations.yaml CI we download a pre-built snapshot artifact, so the binary
// already exists and rebuilding would both burn ~10m and clobber it. When
// `make test` runs cold (e.g. the release pipeline) or locally with no
// snapshot, we build a single-target snapshot (current OS/arch only) since
// that's all the CLI tests need.
Task{
Name: "cli",
Description: "Run CLI tests",
RunsOn: lang.List("test"),
Run: func() {
bin := snapshotBinPath()
if !file.Exists(bin) {
Log("snapshot binary not found at %s; building single-target snapshot", bin)
Run("make snapshot:single-target")
}
Log("testing binary: %s", bin)
Run(
"go test -count=1 -timeout=15m -v ./test/cli",
run.Env("SYFT_BINARY_LOCATION", bin),
)
},
},
// windows unit tests: deliberately not hooked into "test" (no RunsOn). The full unit suite leans on
// linux docker fixture images and posix-only assumptions, so this covers the packages that own
// host path handling (where windows bugs live) and skips the tests that need docker image fixtures.
// ponytail: curated package list, widen it as more of the suite is made windows-safe.
Task{
Name: "unit:windows",
Description: "run the windows-relevant subset of unit tests",
Run: func() {
Run("go test -count=1"+
` -skip "Image|Squash|AllLayers|MixFileTypes|GivenCoordinates"`+
" ./syft/internal/windows/... ./syft/internal/fileresolver/... ./syft/source/directorysource/... ./syft/source/filesource/..."+
" ./syft/file ./syft/file/cataloger/filedigest/... ./syft/file/cataloger/filecontent/...",
run.Stdout(os.Stderr),
)
},
},
// default validation pipeline (replaces Taskfile `default`/`pr-validations`/`validations`).
Task{
Name: "default",
Description: "Run all validation tasks",
Dependencies: Deps("static-analysis", "test", "install-test"),
},
// --- everything else is implemented in Taskfile.yaml. gotask.Tasks()
// discovers every (non-internal) Taskfile task — including the namespaced
// `generate:cpe-index:*` tasks from the included task.d file — and surfaces
// them as first-class go-make tasks (with their canonical names and `desc:`)
// that forward to `task <name>`. Descriptions live in Taskfile.yaml as the
// single source of truth; no per-task wrapping needed here.
gotask.Tasks(),
// gotask.Tasks() discovers canonical task names only, not Taskfile aliases,
// so re-expose `refresh-fixtures`'s `fixtures` alias for manual use.
Task{
Name: "fixtures",
Description: "Clear and fetch all test fixture cache (alias of refresh-fixtures)",
Dependencies: Deps("refresh-fixtures"),
},
// gotask.Tasks() can't attach RunsOn labels, so wire the syft-specific
// Taskfile tasks into go-make's native phases here. These thin hooks have
// no body and no description (hidden from `make help`); they only pull the
// discovered tasks in when the labeled phase runs.
Task{
Name: "static-analysis:syft",
RunsOn: lang.List("static-analysis"),
Dependencies: Deps("check-json-schema-drift", "check-binary-fixture-size"),
},
Task{
Name: "test:syft",
RunsOn: lang.List("test"),
Dependencies: Deps("validate-cyclonedx-schema", "test-utils", "check-docker-cache"),
},
// refresh-fixtures hooks into "unit" so `make unit` triggers the stale-cache
// detection + download just like `task unit` did on main (its
// `deps: [tmpdir, fixtures]` is what kept the fixture cache fresh).
Task{
Name: "unit:syft",
RunsOn: lang.List("unit"),
Dependencies: Deps("refresh-fixtures"),
},
Task{
Name: "clean:syft",
RunsOn: lang.List("clean"),
Dependencies: Deps(
"clean-snapshot",
"clean-docker-cache",
"clean-oras-cache",
"clean-cache",
"clean-test-observations",
),
},
)
}
// raceEnabled is the single switch for the race detector across every test suite.
// Unset it and we keep go-make's behavior (on in CI, off locally and on windows);
// set RACE=false to turn it off everywhere -- worth doing when rebuilding the test
// fixture cache from scratch, where every suite is dominated by building docker
// fixtures and the race detector only adds wall clock. RACE=true forces it on.
func raceEnabled() bool {
if enabled, err := strconv.ParseBool(config.Env("RACE", "")); err == nil {
return enabled
}
return config.CI && !config.Windows
}
// race applies raceEnabled() to a gotest suite. gotest exposes no functional option
// for the race detector, but gotest.Config.Race is exported.
func race() gotest.Option {
return func(c *gotest.Config) {
c.Race = raceEnabled()
}
}
// snapshotBinPath replicates the SNAPSHOT_BIN computation from the prior Taskfile:
// <repoRoot>/snapshot/<os>-build_<os>_<arch>/syft, where arch maps amd64->amd64_v1
// and arm64->arm64_v8.0 to match goreleaser's per-target output directory naming.
// Returns an absolute path: the cli tests' getSyftBinaryLocation contract requires
// SYFT_BINARY_LOCATION to be absolute because subtests run with cmd.Dir = t.TempDir().
func snapshotBinPath() string {
osName := runtime.GOOS
var arch string
switch runtime.GOARCH {
case "amd64":
arch = "amd64_v1"
case "arm64":
arch = "arm64_v8.0"
default:
arch = runtime.GOARCH
}
return filepath.Join(RootDir(), "snapshot", osName+"-build_"+osName+"_"+arch, "syft")
}