From 8091db916d3c7daf6c901c2e857f9218abb10aaf Mon Sep 17 00:00:00 2001 From: Antonio Salinas Date: Wed, 10 Jun 2026 23:27:39 +0000 Subject: [PATCH 1/2] tests: Introduce lockfile to all Scenario tests --- .github/instructions/testing.instructions.md | 15 ++- scenario/component_build_test.go | 1 + scenario/component_changed_test.go | 60 +++++------- scenario/component_query_test.go | 1 + scenario/component_render_test.go | 24 +++-- .../projecttest/dynamictestproject.go | 37 ++++++- scenario/internal/projecttest/lockfile.go | 85 ++++++++++++++++ .../internal/projecttest/lockfile_test.go | 96 +++++++++++++++++++ scenario/testdata/simple/locks/a.lock | 2 + 9 files changed, 274 insertions(+), 47 deletions(-) create mode 100644 scenario/internal/projecttest/lockfile.go create mode 100644 scenario/internal/projecttest/lockfile_test.go create mode 100644 scenario/testdata/simple/locks/a.lock diff --git a/.github/instructions/testing.instructions.md b/.github/instructions/testing.instructions.md index fb257fab..a17c6630 100644 --- a/.github/instructions/testing.instructions.md +++ b/.github/instructions/testing.instructions.md @@ -113,7 +113,8 @@ to verify specific call expectations. ## Lock Files in Tests -Use `env.WriteLock(t, name, lock)` to create lock files on the test filesystem: +For unit tests using `testutils.NewTestEnv(t)`, use `env.WriteLock(t, name, lock)` +to create lock files on the in-memory test filesystem: ```go lock := lockfile.New() @@ -122,6 +123,18 @@ lock.ManualBump = 1 env.WriteLock(t, "curl", lock) ``` +For scenario project fixtures, use `projecttest.AddLock(...)` when the lock +should be serialized with the dynamic project, or `projecttest.WriteLock(...)` +when a test updates lock files between git commits: + +```go +projecttest.NewDynamicTestProject( + projecttest.AddLock("curl", projecttest.WithLockInputFingerprint("sha256:v1")), +) + +projecttest.WriteLock(t, projectDir, "curl", projecttest.WithLockInputFingerprint("sha256:v2")) +``` + ## Mocking External Commands `CmdFactory.RunHandler` and `RunAndGetOutputHandler` intercept ALL external diff --git a/scenario/component_build_test.go b/scenario/component_build_test.go index 59466ea6..51a2ada3 100644 --- a/scenario/component_build_test.go +++ b/scenario/component_build_test.go @@ -33,6 +33,7 @@ func TestBuildingLocalComponent(t *testing.T) { spec := projecttest.NewSpec(projecttest.WithBuildArch(projecttest.NoArch)) project := projecttest.NewDynamicTestProject( projecttest.AddSpec(spec), + projecttest.AddLock(spec.GetName(), projecttest.WithLockInputFingerprint("sha256:"+spec.GetName()+"-v1")), projecttest.UseTestDefaultConfigs(), projecttest.WithGitRepo(), ) diff --git a/scenario/component_changed_test.go b/scenario/component_changed_test.go index 9bd2c2ed..aacb7559 100644 --- a/scenario/component_changed_test.go +++ b/scenario/component_changed_test.go @@ -7,7 +7,6 @@ package scenario_tests import ( "encoding/json" - "fmt" "os" "os/exec" "path/filepath" @@ -88,6 +87,13 @@ func writeFileInDir(t *testing.T, dir, relPath, content string) { require.NoError(t, os.WriteFile(absPath, []byte(content), fileperms.PublicFile)) } +// writeLockInDir writes a simple fingerprint-only lock file for scenario tests. +func writeLockInDir(t *testing.T, dir, componentName, inputFingerprint string) { + t.Helper() + + projecttest.WriteLock(t, dir, componentName, projecttest.WithLockInputFingerprint(inputFingerprint)) +} + // TestComponentChanged_E2E exercises the full `azldev component changed` command // with a real git repository, verifying JSON output for multi-component change // detection. @@ -157,11 +163,8 @@ func TestComponentChanged_E2E(t *testing.T) { gitInDir(t, projectDir, "config", "user.email", "test@test.com") gitInDir(t, projectDir, "config", "user.name", "Test") - lockV1Curl := fmt.Sprintf("version = 1\ninput-fingerprint = %q\n", "sha256:curl-v1") - lockV1Bash := fmt.Sprintf("version = 1\ninput-fingerprint = %q\n", "sha256:bash-v1") - - writeFileInDir(t, projectDir, "locks/curl.lock", lockV1Curl) - writeFileInDir(t, projectDir, "locks/bash.lock", lockV1Bash) + writeLockInDir(t, projectDir, "curl", "sha256:curl-v1") + writeLockInDir(t, projectDir, "bash", "sha256:bash-v1") gitInDir(t, projectDir, "add", ".") gitInDir(t, projectDir, "-c", "commit.gpgsign=false", "commit", "-m", "initial") @@ -169,8 +172,7 @@ func TestComponentChanged_E2E(t *testing.T) { fromRef := gitInDir(t, projectDir, "rev-parse", "HEAD") // Second commit: change curl's lock, leave bash unchanged. - lockV2Curl := fmt.Sprintf("version = 1\ninput-fingerprint = %q\n", "sha256:curl-v2") - writeFileInDir(t, projectDir, "locks/curl.lock", lockV2Curl) + writeLockInDir(t, projectDir, "curl", "sha256:curl-v2") gitInDir(t, projectDir, "add", "locks/curl.lock") gitInDir(t, projectDir, "-c", "commit.gpgsign=false", "commit", "-m", "update curl") @@ -243,8 +245,7 @@ func TestComponentChanged_SameRef(t *testing.T) { gitInDir(t, projectDir, "config", "user.email", "test@test.com") gitInDir(t, projectDir, "config", "user.name", "Test") - lockContent := fmt.Sprintf("version = 1\ninput-fingerprint = %q\n", "sha256:v1") - writeFileInDir(t, projectDir, "locks/curl.lock", lockContent) + writeLockInDir(t, projectDir, "curl", "sha256:v1") gitInDir(t, projectDir, "add", ".") gitInDir(t, projectDir, "-c", "commit.gpgsign=false", "commit", "-m", "initial") @@ -387,8 +388,7 @@ func TestComponentChanged_SourcesChange(t *testing.T) { ) // Commit 1: initial lock + rendered sources. - writeFileInDir(t, projectDir, "locks/curl.lock", - fmt.Sprintf("version = 1\ninput-fingerprint = %q\n", "sha256:v1")) + writeLockInDir(t, projectDir, "curl", "sha256:v1") writeFileInDir(t, projectDir, "specs/c/curl/sources", "SHA512 (curl-8.0.tar.gz) = aaa111") gitInDir(t, projectDir, "add", ".") @@ -396,8 +396,7 @@ func TestComponentChanged_SourcesChange(t *testing.T) { ref1 := gitInDir(t, projectDir, "rev-parse", "HEAD") // Commit 2: change lock fingerprint AND sources (new tarball). - writeFileInDir(t, projectDir, "locks/curl.lock", - fmt.Sprintf("version = 1\ninput-fingerprint = %q\n", "sha256:v2")) + writeLockInDir(t, projectDir, "curl", "sha256:v2") writeFileInDir(t, projectDir, "specs/c/curl/sources", "SHA512 (curl-8.1.tar.gz) = bbb222") gitInDir(t, projectDir, "add", ".") @@ -405,8 +404,7 @@ func TestComponentChanged_SourcesChange(t *testing.T) { ref2 := gitInDir(t, projectDir, "rev-parse", "HEAD") // Commit 3: change lock fingerprint only (config tweak, same tarball). - writeFileInDir(t, projectDir, "locks/curl.lock", - fmt.Sprintf("version = 1\ninput-fingerprint = %q\n", "sha256:v3")) + writeLockInDir(t, projectDir, "curl", "sha256:v3") gitInDir(t, projectDir, "add", ".") gitInDir(t, projectDir, "-c", "commit.gpgsign=false", "commit", "-m", "config change") @@ -460,15 +458,13 @@ func TestComponentChanged_InvertedRefs(t *testing.T) { ) // Commit 1: v1 lock. - writeFileInDir(t, projectDir, "locks/curl.lock", - fmt.Sprintf("version = 1\ninput-fingerprint = %q\n", "sha256:old")) + writeLockInDir(t, projectDir, "curl", "sha256:old") gitInDir(t, projectDir, "add", ".") gitInDir(t, projectDir, "-c", "commit.gpgsign=false", "commit", "-m", "v1") oldRef := gitInDir(t, projectDir, "rev-parse", "HEAD") // Commit 2: v2 lock. - writeFileInDir(t, projectDir, "locks/curl.lock", - fmt.Sprintf("version = 1\ninput-fingerprint = %q\n", "sha256:new")) + writeLockInDir(t, projectDir, "curl", "sha256:new") gitInDir(t, projectDir, "add", ".") gitInDir(t, projectDir, "-c", "commit.gpgsign=false", "commit", "-m", "v2") newRef := gitInDir(t, projectDir, "rev-parse", "HEAD") @@ -526,8 +522,7 @@ func TestComponentChanged_NewComponent(t *testing.T) { fromRef := gitInDir(t, projectDir, "rev-parse", "HEAD") // Commit 2: add curl lock. - writeFileInDir(t, projectDir, "locks/curl.lock", - fmt.Sprintf("version = 1\ninput-fingerprint = %q\n", "sha256:first")) + writeLockInDir(t, projectDir, "curl", "sha256:first") gitInDir(t, projectDir, "add", ".") gitInDir(t, projectDir, "-c", "commit.gpgsign=false", "commit", "-m", "add curl") @@ -571,10 +566,8 @@ func TestComponentChanged_DeletedComponent(t *testing.T) { ) // Commit 1: lock files for curl (in config) and oldpkg (NOT in config). - writeFileInDir(t, projectDir, "locks/curl.lock", - fmt.Sprintf("version = 1\ninput-fingerprint = %q\n", "sha256:curl-v1")) - writeFileInDir(t, projectDir, "locks/oldpkg.lock", - fmt.Sprintf("version = 1\ninput-fingerprint = %q\n", "sha256:oldpkg-v1")) + writeLockInDir(t, projectDir, "curl", "sha256:curl-v1") + writeLockInDir(t, projectDir, "oldpkg", "sha256:oldpkg-v1") gitInDir(t, projectDir, "add", ".") gitInDir(t, projectDir, "-c", "commit.gpgsign=false", "commit", "-m", "initial") fromRef := gitInDir(t, projectDir, "rev-parse", "HEAD") @@ -643,12 +636,9 @@ func TestComponentChanged_JSONContract(t *testing.T) { ) // Commit 1: curl has lock + sources, bash has lock, oldpkg has lock (not in config). - writeFileInDir(t, projectDir, "locks/curl.lock", - fmt.Sprintf("version = 1\ninput-fingerprint = %q\n", "sha256:curl-v1")) - writeFileInDir(t, projectDir, "locks/bash.lock", - fmt.Sprintf("version = 1\ninput-fingerprint = %q\n", "sha256:bash-v1")) - writeFileInDir(t, projectDir, "locks/oldpkg.lock", - fmt.Sprintf("version = 1\ninput-fingerprint = %q\n", "sha256:old-v1")) + writeLockInDir(t, projectDir, "curl", "sha256:curl-v1") + writeLockInDir(t, projectDir, "bash", "sha256:bash-v1") + writeLockInDir(t, projectDir, "oldpkg", "sha256:old-v1") writeFileInDir(t, projectDir, "specs/c/curl/sources", "SHA512 (curl-1.0.tar.gz) = aaa") gitInDir(t, projectDir, "add", ".") @@ -656,8 +646,7 @@ func TestComponentChanged_JSONContract(t *testing.T) { fromRef := gitInDir(t, projectDir, "rev-parse", "HEAD") // Commit 2: curl fingerprint + sources changed, bash unchanged, oldpkg removed. - writeFileInDir(t, projectDir, "locks/curl.lock", - fmt.Sprintf("version = 1\ninput-fingerprint = %q\n", "sha256:curl-v2")) + writeLockInDir(t, projectDir, "curl", "sha256:curl-v2") writeFileInDir(t, projectDir, "specs/c/curl/sources", "SHA512 (curl-2.0.tar.gz) = bbb") gitInDir(t, projectDir, "rm", "locks/oldpkg.lock") @@ -762,8 +751,7 @@ func TestComponentChanged_IntegrityViolation(t *testing.T) { ) // Commit 1: lock + matching rendered sources. - writeFileInDir(t, projectDir, "locks/curl.lock", - fmt.Sprintf("version = 1\ninput-fingerprint = %q\n", "sha256:v1")) + writeLockInDir(t, projectDir, "curl", "sha256:v1") writeFileInDir(t, projectDir, "specs/c/curl/sources", "SHA512 (curl-8.0.tar.gz) = aaa111") gitInDir(t, projectDir, "add", ".") diff --git a/scenario/component_query_test.go b/scenario/component_query_test.go index b8340d32..1415cae4 100644 --- a/scenario/component_query_test.go +++ b/scenario/component_query_test.go @@ -31,6 +31,7 @@ func TestQueryingAComponent(t *testing.T) { // Create a simple project with the spec, using test default configs for distro and mock configurations. project := projecttest.NewDynamicTestProject( projecttest.AddSpec(spec), + projecttest.AddLock(spec.GetName(), projecttest.WithLockInputFingerprint("sha256:"+spec.GetName()+"-v1")), projecttest.UseTestDefaultConfigs(), ) diff --git a/scenario/component_render_test.go b/scenario/component_render_test.go index bd010feb..72b3bd76 100644 --- a/scenario/component_render_test.go +++ b/scenario/component_render_test.go @@ -32,6 +32,10 @@ func localComponentConfig(name string, overlays ...projectconfig.ComponentOverla } } +func localComponentLock(name string) projecttest.DynamicTestProjectOption { + return projecttest.AddLock(name, projecttest.WithLockInputFingerprint("sha256:"+name+"-v1")) +} + func TestRenderSimpleLocalSpec(t *testing.T) { t.Parallel() @@ -49,6 +53,7 @@ func TestRenderSimpleLocalSpec(t *testing.T) { project := projecttest.NewDynamicTestProject( projecttest.AddSpec(spec), projecttest.AddComponent(localComponentConfig("test-render")), + localComponentLock("test-render"), projecttest.UseTestDefaultConfigs(), projecttest.WithGitRepo(), ) @@ -99,6 +104,7 @@ func TestRenderWithConfiguredOutputDir(t *testing.T) { project := projecttest.NewDynamicTestProject( projecttest.AddSpec(spec), projecttest.AddComponent(localComponentConfig("config-test")), + localComponentLock("config-test"), projecttest.UseTestDefaultConfigs(), projecttest.WithGitRepo(), // Set rendered-specs-dir in project config instead of using -o. @@ -149,6 +155,7 @@ func TestRenderWithOverlayApplied(t *testing.T) { Value: "test-overlay-dep", }, )), + localComponentLock("test-overlay"), projecttest.UseTestDefaultConfigs(), projecttest.WithGitRepo(), ) @@ -205,6 +212,7 @@ func TestRenderWithPatchSidecar(t *testing.T) { Source: "patches/fix-stuff.patch", }, )), + localComponentLock("test-patch"), projecttest.AddFile("patches/fix-stuff.patch", patchContent), projecttest.UseTestDefaultConfigs(), projecttest.WithGitRepo(), @@ -251,6 +259,7 @@ func TestRenderStaleCleanup(t *testing.T) { project := projecttest.NewDynamicTestProject( projecttest.AddSpec(spec), projecttest.AddComponent(localComponentConfig("keep-me")), + localComponentLock("keep-me"), projecttest.UseTestDefaultConfigs(), projecttest.WithGitRepo(), projecttest.AddFile("SPECS/s/stale-component/RENDER_FAILED", "Rendering failed.\n"), @@ -297,6 +306,7 @@ func TestRenderRefusesOverwriteWithoutForce(t *testing.T) { project := projecttest.NewDynamicTestProject( projecttest.AddSpec(spec), projecttest.AddComponent(localComponentConfig("no-clobber")), + localComponentLock("no-clobber"), projecttest.UseTestDefaultConfigs(), projecttest.WithGitRepo(), projecttest.AddFile("SPECS/n/no-clobber/existing-file.txt", "do not delete me\n"), @@ -371,6 +381,7 @@ License: MIT project := projecttest.NewDynamicTestProject( projecttest.AddComponent(localComponentConfig("golang-example")), + localComponentLock("golang-example"), // Write the custom spec content directly via AddFile since AddSpec's // TestSpec renderer doesn't support %gometa. projecttest.AddFile("specs/golang-example/golang-example.spec", goSpecContent), @@ -438,6 +449,8 @@ func TestRenderMultipleComponentsParallel(t *testing.T) { projecttest.AddSpec(specB), projecttest.AddComponent(localComponentConfig("comp-alpha")), projecttest.AddComponent(localComponentConfig("comp-beta")), + localComponentLock("comp-alpha"), + localComponentLock("comp-beta"), projecttest.UseTestDefaultConfigs(), projecttest.WithGitRepo(), ) @@ -502,9 +515,11 @@ func TestRenderBrokenSpecWithGoodSpec(t *testing.T) { project := projecttest.NewDynamicTestProject( projecttest.AddSpec(goodSpec), projecttest.AddComponent(localComponentConfig("good-pkg")), + localComponentLock("good-pkg"), // Add a broken spec as a raw file — not valid RPM spec syntax. projecttest.AddFile("specs/broken-pkg/broken-pkg.spec", "this is not a valid spec file\n"), projecttest.AddComponent(localComponentConfig("broken-pkg")), + localComponentLock("broken-pkg"), projecttest.UseTestDefaultConfigs(), projecttest.WithGitRepo(), ) @@ -569,13 +584,6 @@ func TestRenderLocalSpecWithSyntheticHistory(t *testing.T) { projecttest.WithBuildArch(projecttest.NoArch), ) - // Pre-baked lock file with a stale fingerprint. The overlay in the - // component config changes the runtime fingerprint, so dirty detection - // will fire and add a synthetic commit. - const lockFileContent = `version = 1 -input-fingerprint = "pre-baked-for-test" -` - project := projecttest.NewDynamicTestProject( projecttest.AddSpec(spec), projecttest.AddComponent(localComponentConfig("synth-local", @@ -587,7 +595,7 @@ input-fingerprint = "pre-baked-for-test" }, )), projecttest.UseTestDefaultConfigs(), - projecttest.AddFile("locks/synth-local.lock", lockFileContent), + projecttest.AddLock("synth-local", projecttest.WithLockInputFingerprint("pre-baked-for-test")), projecttest.WithGitRepo(), ) diff --git a/scenario/internal/projecttest/dynamictestproject.go b/scenario/internal/projecttest/dynamictestproject.go index d67d774d..72e69a61 100644 --- a/scenario/internal/projecttest/dynamictestproject.go +++ b/scenario/internal/projecttest/dynamictestproject.go @@ -12,6 +12,7 @@ import ( "testing" "github.com/brunoga/deep" + "github.com/microsoft/azure-linux-dev-tools/internal/lockfile" "github.com/microsoft/azure-linux-dev-tools/internal/projectconfig" "github.com/microsoft/azure-linux-dev-tools/internal/projectgen" "github.com/microsoft/azure-linux-dev-tools/internal/utils/fileperms" @@ -29,6 +30,9 @@ type dynamicTestProject struct { // Maps relative file path to file contents (as bytes). otherFiles map[string][]byte + // Maps component name to lock file content. + locks map[string]*lockfile.ComponentLock + // initGitRepo causes [Serialize] to initialize a git repo in the project directory // and commit all files. Required for commands that use synthetic history (e.g., render). initGitRepo bool @@ -41,6 +45,7 @@ func NewDynamicTestProject(options ...DynamicTestProjectOption) *dynamicTestProj project := &dynamicTestProject{ configFile: projectgen.GenerateBasicConfig("test-project"), otherFiles: make(map[string][]byte), + locks: make(map[string]*lockfile.ComponentLock), } // Make sure we have an empty component map so we can easily add to it later. @@ -78,6 +83,11 @@ func (p *dynamicTestProject) Serialize(t *testing.T, projectDir string) { require.NoError(t, os.WriteFile(destFilePath, fileContent, fileperms.PublicFile)) } + // Write out lock files before git initialization so [WithGitRepo] commits them. + for componentName, componentLock := range p.locks { + writeComponentLock(t, projectDir, componentName, componentLock) + } + // Initialize a git repo if requested. if p.initGitRepo { initProjectGitRepo(t, projectDir) @@ -123,6 +133,23 @@ func (p *dynamicTestProject) addComponent(componentConfig *projectconfig.Compone p.configFile.Components[componentConfig.Name] = deep.MustCopy(*componentConfig) } +func (p *dynamicTestProject) addLock(componentName string, componentLock *lockfile.ComponentLock) { + lockRelPath := lockRelativePath(componentName) + if _, exists := p.otherFiles[lockRelPath]; exists { + panic(fmt.Sprintf("AddLock: lock file for component %#q already exists via AddFile", componentName)) + } + + cp := *componentLock + p.locks[componentName] = &cp +} + +// AddLock adds (or updates) a lock file for the component in the project. +func AddLock(componentName string, options ...LockOption) DynamicTestProjectOption { + return func(p *dynamicTestProject) { + p.addLock(componentName, NewLock(options...)) + } +} + // AddFile adds an arbitrary file to the project at the specified relative path. // The path must be relative and must not escape the project directory. func AddFile(relativePath, content string) DynamicTestProjectOption { @@ -132,8 +159,14 @@ func AddFile(relativePath, content string) DynamicTestProjectOption { panic(fmt.Sprintf("AddFile: path %#q is invalid or escapes the project directory", relativePath)) } - return func(p *dynamicTestProject) { - p.otherFiles[cleaned] = []byte(content) + return func(project *dynamicTestProject) { + for componentName := range project.locks { + if cleaned == lockRelativePath(componentName) { + panic(fmt.Sprintf("AddFile: path %#q already exists via AddLock", relativePath)) + } + } + + project.otherFiles[cleaned] = []byte(content) } } diff --git a/scenario/internal/projecttest/lockfile.go b/scenario/internal/projecttest/lockfile.go new file mode 100644 index 00000000..d7fb0a9a --- /dev/null +++ b/scenario/internal/projecttest/lockfile.go @@ -0,0 +1,85 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +package projecttest + +import ( + "path/filepath" + "testing" + + "github.com/microsoft/azure-linux-dev-tools/internal/lockfile" + "github.com/microsoft/azure-linux-dev-tools/internal/projectconfig" + "github.com/spf13/afero" + "github.com/stretchr/testify/require" +) + +// LockOption mutates a component lock used by scenario test projects. +type LockOption func(*lockfile.ComponentLock) + +// NewLock creates a component lock for scenario test fixtures. +func NewLock(options ...LockOption) *lockfile.ComponentLock { + componentLock := lockfile.New() + for _, option := range options { + option(componentLock) + } + + return componentLock +} + +// WithLockInputFingerprint sets the lock's stored input fingerprint. +func WithLockInputFingerprint(inputFingerprint string) LockOption { + return func(componentLock *lockfile.ComponentLock) { + componentLock.InputFingerprint = inputFingerprint + } +} + +// WithLockUpstreamCommit sets the lock's upstream commit. +func WithLockUpstreamCommit(upstreamCommit string) LockOption { + return func(componentLock *lockfile.ComponentLock) { + componentLock.UpstreamCommit = upstreamCommit + } +} + +// WithLockImportCommit sets the lock's import commit. +func WithLockImportCommit(importCommit string) LockOption { + return func(componentLock *lockfile.ComponentLock) { + componentLock.ImportCommit = importCommit + } +} + +// WithLockManualBump sets the lock's manual bump counter. +func WithLockManualBump(manualBump int) LockOption { + return func(componentLock *lockfile.ComponentLock) { + componentLock.ManualBump = manualBump + } +} + +// WithLockResolutionInputHash sets the lock's resolution input hash. +func WithLockResolutionInputHash(resolutionInputHash string) LockOption { + return func(componentLock *lockfile.ComponentLock) { + componentLock.ResolutionInputHash = resolutionInputHash + } +} + +// WriteLock writes a component lock to a serialized scenario test project. +func WriteLock(t *testing.T, projectDir, componentName string, options ...LockOption) { + t.Helper() + + writeComponentLock(t, projectDir, componentName, NewLock(options...)) +} + +func writeComponentLock(t *testing.T, projectDir, componentName string, componentLock *lockfile.ComponentLock) { + t.Helper() + + store := lockfile.NewStore(afero.NewOsFs(), filepath.Join(projectDir, projectconfig.DefaultLockDir)) + require.NoError(t, store.Save(componentName, componentLock)) +} + +func lockRelativePath(componentName string) string { + lockPath, err := lockfile.LockPath(projectconfig.DefaultLockDir, componentName) + if err != nil { + panic(err) + } + + return filepath.Clean(lockPath) +} diff --git a/scenario/internal/projecttest/lockfile_test.go b/scenario/internal/projecttest/lockfile_test.go new file mode 100644 index 00000000..d7b9a731 --- /dev/null +++ b/scenario/internal/projecttest/lockfile_test.go @@ -0,0 +1,96 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +package projecttest_test + +import ( + "path/filepath" + "testing" + + "github.com/microsoft/azure-linux-dev-tools/internal/lockfile" + "github.com/microsoft/azure-linux-dev-tools/internal/projectconfig" + "github.com/microsoft/azure-linux-dev-tools/scenario/internal/projecttest" + "github.com/spf13/afero" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestDynamicTestProjectAddLockSerializesLock(t *testing.T) { + t.Parallel() + + project := projecttest.NewDynamicTestProject( + projecttest.AddLock("curl", + projecttest.WithLockInputFingerprint("sha256:input"), + projecttest.WithLockUpstreamCommit("upstream-commit"), + projecttest.WithLockImportCommit("import-commit"), + projecttest.WithLockManualBump(2), + projecttest.WithLockResolutionInputHash("sha256:resolution"), + ), + ) + + projectDir := t.TempDir() + project.Serialize(t, projectDir) + + componentLock := loadProjectLock(t, projectDir, "curl") + assert.Equal(t, "sha256:input", componentLock.InputFingerprint) + assert.Equal(t, "upstream-commit", componentLock.UpstreamCommit) + assert.Equal(t, "import-commit", componentLock.ImportCommit) + assert.Equal(t, 2, componentLock.ManualBump) + assert.Equal(t, "sha256:resolution", componentLock.ResolutionInputHash) +} + +func TestWriteLockUpdatesSerializedProject(t *testing.T) { + t.Parallel() + + project := projecttest.NewDynamicTestProject() + projectDir := t.TempDir() + project.Serialize(t, projectDir) + + projecttest.WriteLock(t, projectDir, "curl", projecttest.WithLockInputFingerprint("sha256:v1")) + componentLock := loadProjectLock(t, projectDir, "curl") + assert.Equal(t, "sha256:v1", componentLock.InputFingerprint) + + projecttest.WriteLock(t, projectDir, "curl", projecttest.WithLockInputFingerprint("sha256:v2")) + componentLock = loadProjectLock(t, projectDir, "curl") + assert.Equal(t, "sha256:v2", componentLock.InputFingerprint) +} + +func TestDynamicTestProjectAddLockConflictsWithAddFile(t *testing.T) { + t.Parallel() + + lockPath := filepath.Join(projectconfig.DefaultLockDir, "curl.lock") + + require.Panics(t, func() { + projecttest.NewDynamicTestProject( + projecttest.AddFile(lockPath, "version = 1\n"), + projecttest.AddLock("curl"), + ) + }) + + require.Panics(t, func() { + projecttest.NewDynamicTestProject( + projecttest.AddLock("curl"), + projecttest.AddFile(lockPath, "version = 1\n"), + ) + }) +} + +func TestDynamicTestProjectAddLockRejectsInvalidComponentName(t *testing.T) { + t.Parallel() + + require.Panics(t, func() { + projecttest.NewDynamicTestProject(projecttest.AddLock("bad/name")) + }) +} + +func loadProjectLock(t *testing.T, projectDir, componentName string) *lockfile.ComponentLock { + t.Helper() + + lockPath, err := lockfile.LockPath(filepath.Join(projectDir, projectconfig.DefaultLockDir), componentName) + require.NoError(t, err) + + componentLock, err := lockfile.Load(afero.NewOsFs(), lockPath) + require.NoError(t, err) + + return componentLock +} diff --git a/scenario/testdata/simple/locks/a.lock b/scenario/testdata/simple/locks/a.lock new file mode 100644 index 00000000..94ad81ab --- /dev/null +++ b/scenario/testdata/simple/locks/a.lock @@ -0,0 +1,2 @@ +version = 1 +input-fingerprint = "sha256:a-v1" From a6970ed9ac44771e1471ac7652b0b48009fe7646 Mon Sep 17 00:00:00 2001 From: Antonio Salinas Date: Wed, 10 Jun 2026 23:40:24 +0000 Subject: [PATCH 2/2] minor fix --- scenario/internal/projecttest/dynamictestproject.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scenario/internal/projecttest/dynamictestproject.go b/scenario/internal/projecttest/dynamictestproject.go index 72e69a61..7857e5ea 100644 --- a/scenario/internal/projecttest/dynamictestproject.go +++ b/scenario/internal/projecttest/dynamictestproject.go @@ -139,7 +139,7 @@ func (p *dynamicTestProject) addLock(componentName string, componentLock *lockfi panic(fmt.Sprintf("AddLock: lock file for component %#q already exists via AddFile", componentName)) } - cp := *componentLock + cp := deep.MustCopy(*componentLock) p.locks[componentName] = &cp }