Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

chore: ensure syft binary is up-to-date when running CLI tests locally #2016

Draft
wants to merge 10 commits into
base: main
Choose a base branch
from

Conversation

kzantow
Copy link
Contributor

@kzantow kzantow commented Aug 10, 2023

While attempting to debug a CLI test locally, I got dramatically different results, which took debugging to realize that these tests use syft binaries built with make snapshot. However, it is confusing when this happens and not especially convenient to run make snapshot each time a code change is made before running each tests. Additionally, make snapshot is not especially fast: ~17s on my local machine, so the dev-test cycle is a bit painful.

I implemented something that can build the syft binary fairly quickly (in the 2-4s range, depending on the amount changed from the last build) and also much more quickly if there are no changes -- in the < 1sec range. However, this is using the go build command directly, which is a bit less than ideal -- since we're using goreleaser, it would be nice to have this consistent.

It should be noted: in all cases, this runs once for each test run, whether it's a single test or the entire CLI test suite.

So I implemented a few different options for this and ran them under different scenarios (I've included build times with an up-to-date go build cache):

Build using go directly

  • Empty snapshot: 3.82s
  • Up to date: 731ms

This populates the goreleaser build flags with [hopefully] obvious placeholder values.

Version output:

Application:     syft
Version:         VERSION
BuildDate:       DATE
GitCommit:       COMMIT
GitDescription:  SUMMARY
Platform:        darwin/amd64
GoVersion:       go1.19.5
Compiler:        gc

Build using goreleaser directly

  • Empty snapshot: 4.5s
  • Up to date: 4.308s

Version output:

Application:     syft
Version:         0.86.1-SNAPSHOT-3ccbea96
BuildDate:       2023-08-10T19:55:06Z
GitCommit:       3ccbea960e41369e3c8d2c4fc578288c422925e0
GitDescription:  v0.86.1-31-g3ccbea96-dirty
Platform:        darwin/amd64
GoVersion:       go1.19.5
Compiler:        gc

Build using make snapshot

  • Empty snapshot: 16.509s
  • Up to date: fails to update snapshots -- does not run because snapshot dir exists
    • If update to .PHONY: 16.82s

Version output is the same as building with goreleaser directly.

Summary:

make snapshot builds all binaries every time it's run. The 17-ish second turnaround would be painful to sit through when running an individual test with a dev-test cycle.

Building with goreleaser needs --clean, to remove the prior build artifact, so it rebuilds each time. ~4 seconds overhead for each dev-test cycle is a little painful, still, but not the worst.

Building with go directly is fast with no changes, but each change could result in each run adding approximately 4s if changing files between runs. However, testing this with a small change can still be in the <1s range:

    utils_test.go:310: built binary: ./snapshot/darwin-build_darwin_amd64_v1/syft in 740ms
        affected paths:
        github.com/anchore/syft/syft/cpe

Finally: since both go and goreleaser variants only build a single binary that matches the current platform and architecture, they do not build the linux variant with any of these changes. This means the one test that runs syft on docker fails: TestDirectoryScanCompletesWithinTimeout. The make snapshot does not have this problem, since it's building all platform/architectures.

Signed-off-by: Keith Zantow <kzantow@gmail.com>
bin := getSyftBinaryLocationByOS(t, runtime.GOOS)

// only run on valid bin target, when not running in CI
if bin != "" && os.Getenv("CI") == "" {
Copy link
Contributor Author

@kzantow kzantow Aug 10, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm unsure the best way to determine if this is being run locally. It would also be fine (for me, at least) if this only runs when running a single test.

This also could be moved to getSyftBinaryLocationByOS so the linux build also works (and we'd have to do something slightly different to ensure each architecture/platform gets built only once); this draft PR is a prototype.


var stdout, stderr string
var err error
switch "go" {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Change this to try the different variants.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like the idea of by default having the goreleaser approach done since it would be most consistent with how we're going to release. That being said, I also like that this is swappable strategy... we could simply leave this here and wire it up to an environment variable so the dev could choose between go and goreleaser (should probably removed the make approach though).

Comment on lines 323 to 348
d := yaml.NewDecoder(strings.NewReader(goreleaserYamlContents(dir)))
type releaser struct {
Builds []struct {
ID string `yaml:"id"`
LDFlags string `yaml:"ldflags"`
} `yaml:"builds"`
}
r := releaser{}
_ = d.Decode(&r)
ldflags := ""
for _, b := range r.Builds {
if b.ID == "linux-build" {
ldflags = executeTemplate(b.LDFlags, struct {
Version string
Commit string
Date string
Summary string
}{
Version: "VERSION",
Commit: "COMMIT",
Date: "DATE",
Summary: "SUMMARY",
})
break
}
}
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This just just for using the same ldflags the goreleaser build uses.

@github-actions
Copy link

github-actions bot commented Aug 10, 2023

Benchmark Test Results

Benchmark results from the latest changes vs base branch
goos: linux%0Agoarch: amd64%0Apkg: github.com/anchore/syft/test/integration%0Acpu: Intel(R) Xeon(R) Platinum 8370C CPU @ 2.80GHz%0A                                                              │ ./.tmp/benchmark-5ee10ca.txt │%0A                                                              │            sec/op            │%0AImagePackageCatalogers/alpmdb-cataloger-2                                       12.08m ±  1%25%0AImagePackageCatalogers/apkdb-cataloger-2                                        669.9µ ±  1%25%0AImagePackageCatalogers/binary-cataloger-2                                       203.6µ ±  1%25%0AImagePackageCatalogers/dpkgdb-cataloger-2                                       559.6µ ±  1%25%0AImagePackageCatalogers/dotnet-portable-executable-cataloger-2                   20.84µ ±  0%25%0AImagePackageCatalogers/go-module-binary-cataloger-2                             91.15µ ± 22%25%0AImagePackageCatalogers/java-cataloger-2                                         13.30m ±  1%25%0AImagePackageCatalogers/graalvm-native-image-cataloger-2                         90.21µ ±  1%25%0AImagePackageCatalogers/javascript-package-cataloger-2                           343.8µ ±  1%25%0AImagePackageCatalogers/nix-store-cataloger-2                                    249.1µ ±  2%25%0AImagePackageCatalogers/php-composer-installed-cataloger-2                       733.2µ ±  1%25%0AImagePackageCatalogers/portage-cataloger-2                                      416.5µ ±  1%25%0AImagePackageCatalogers/python-package-cataloger-2                               3.171m ±  2%25%0AImagePackageCatalogers/r-package-cataloger-2                                    178.7µ ±  2%25%0AImagePackageCatalogers/rpm-db-cataloger-2                                       474.5µ ±  1%25%0AImagePackageCatalogers/ruby-gemspec-cataloger-2                                 836.1µ ±  2%25%0AImagePackageCatalogers/sbom-cataloger-2                                         116.3µ ±  1%25%0Ageomean                                                                         453.2µ%0A%0A                                                              │ ./.tmp/benchmark-5ee10ca.txt │%0A                                                              │             B/op             │%0AImagePackageCatalogers/alpmdb-cataloger-2                                       5.145Mi ± 0%25%0AImagePackageCatalogers/apkdb-cataloger-2                                        205.2Ki ± 0%25%0AImagePackageCatalogers/binary-cataloger-2                                       30.55Ki ± 0%25%0AImagePackageCatalogers/dpkgdb-cataloger-2                                       172.8Ki ± 0%25%0AImagePackageCatalogers/dotnet-portable-executable-cataloger-2                   3.697Ki ± 0%25%0AImagePackageCatalogers/go-module-binary-cataloger-2                             9.906Ki ± 0%25%0AImagePackageCatalogers/java-cataloger-2                                         2.823Mi ± 0%25%0AImagePackageCatalogers/graalvm-native-image-cataloger-2                         8.594Ki ± 0%25%0AImagePackageCatalogers/javascript-package-cataloger-2                           94.39Ki ± 0%25%0AImagePackageCatalogers/nix-store-cataloger-2                                    49.32Ki ± 0%25%0AImagePackageCatalogers/php-composer-installed-cataloger-2                       186.3Ki ± 0%25%0AImagePackageCatalogers/portage-cataloger-2                                      120.2Ki ± 0%25%0AImagePackageCatalogers/python-package-cataloger-2                               1.004Mi ± 0%25%0AImagePackageCatalogers/r-package-cataloger-2                                    53.29Ki ± 0%25%0AImagePackageCatalogers/rpm-db-cataloger-2                                       181.5Ki ± 0%25%0AImagePackageCatalogers/ruby-gemspec-cataloger-2                                 144.1Ki ± 0%25%0AImagePackageCatalogers/sbom-cataloger-2                                         14.21Ki ± 0%25%0Ageomean                                                                         100.6Ki%0A%0A                                                              │ ./.tmp/benchmark-5ee10ca.txt │%0A                                                              │          allocs/op           │%0AImagePackageCatalogers/alpmdb-cataloger-2                                        88.14k ± 0%25%0AImagePackageCatalogers/apkdb-cataloger-2                                         4.190k ± 0%25%0AImagePackageCatalogers/binary-cataloger-2                                         848.0 ± 0%25%0AImagePackageCatalogers/dpkgdb-cataloger-2                                        3.145k ± 0%25%0AImagePackageCatalogers/dotnet-portable-executable-cataloger-2                     132.0 ± 0%25%0AImagePackageCatalogers/go-module-binary-cataloger-2                               281.0 ± 0%25%0AImagePackageCatalogers/java-cataloger-2                                          40.63k ± 0%25%0AImagePackageCatalogers/graalvm-native-image-cataloger-2                           228.0 ± 0%25%0AImagePackageCatalogers/javascript-package-cataloger-2                            1.342k ± 0%25%0AImagePackageCatalogers/nix-store-cataloger-2                                      898.0 ± 0%25%0AImagePackageCatalogers/php-composer-installed-cataloger-2                        4.079k ± 0%25%0AImagePackageCatalogers/portage-cataloger-2                                       2.272k ± 0%25%0AImagePackageCatalogers/python-package-cataloger-2                                16.45k ± 0%25%0AImagePackageCatalogers/r-package-cataloger-2                                      929.0 ± 0%25%0AImagePackageCatalogers/rpm-db-cataloger-2                                        3.992k ± 0%25%0AImagePackageCatalogers/ruby-gemspec-cataloger-2                                  2.447k ± 0%25%0AImagePackageCatalogers/sbom-cataloger-2                                           394.0 ± 0%25%0Ageomean                                                                          2.063k

took := time.Now().Sub(start).Round(time.Millisecond)
if err == nil {
if len(stderr) == 0 {
t.Logf("binary is up to date: %s in %v", outfile, took)
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note this is not entirely accurate. This is reported if there are no code changes and the build cache hasn't changed, but the binary is rebuilt anyway.

Comment on lines 379 to 383
goreleaserYaml := goreleaserYamlContents(dir)

// # create a config with the dist dir overridden
tmpGoreleaserYamlFile := path.Join(tmpDir, "goreleaser.yaml")
_ = os.WriteFile(tmpGoreleaserYamlFile, []byte("dist: snapshot\n"+goreleaserYaml), os.ModePerm)
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Emulating what happens in the Makefile

Signed-off-by: Keith Zantow <kzantow@gmail.com>
Copy link
Contributor

@wagoodman wagoodman left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

my vote is to get this change in, though should probably drop the make approach entirely and default to either go or goreleaser and have the strategy be accessible via environment variable and document it in the DEVELOPING.md.

Signed-off-by: Keith Zantow <kzantow@gmail.com>
Signed-off-by: Keith Zantow <kzantow@gmail.com>
Signed-off-by: Keith Zantow <kzantow@gmail.com>
Signed-off-by: Keith Zantow <kzantow@gmail.com>
Signed-off-by: Keith Zantow <kzantow@gmail.com>
Signed-off-by: Keith Zantow <kzantow@gmail.com>
Signed-off-by: Keith Zantow <kzantow@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

2 participants