Skip to content

feat(2304): update checksum and timestamp file paths to include task … #2305

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

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions internal/fingerprint/sources_checksum.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,9 @@ func (checker *ChecksumChecker) IsUpToDate(t *ast.Task) (bool, error) {
}

if !checker.dry && oldHash != newHash {
_ = os.MkdirAll(filepathext.SmartJoin(checker.tempDir, "checksum"), 0o755)

// if Task is executed in directory different from the task directory,
_ = os.MkdirAll(filepathext.SmartJoin(filepath.Join(checker.tempDir, t.Dir), "checksum"), 0o755)
if err = os.WriteFile(checksumFile, []byte(newHash+"\n"), 0o644); err != nil {
return false, err
}
Expand Down Expand Up @@ -115,7 +117,7 @@ func (c *ChecksumChecker) checksum(t *ast.Task) (string, error) {
}

func (checker *ChecksumChecker) checksumFilePath(t *ast.Task) string {
return filepath.Join(checker.tempDir, "checksum", normalizeFilename(t.Name()))
return filepath.Join(checker.tempDir, t.Dir, "checksum", normalizeFilename(t.Name()))
}

var checksumFilenameRegexp = regexp.MustCompile("[^A-z0-9]")
Expand Down
2 changes: 1 addition & 1 deletion internal/fingerprint/sources_timestamp.go
Original file line number Diff line number Diff line change
Expand Up @@ -147,5 +147,5 @@ func (*TimestampChecker) OnError(t *ast.Task) error {
}

func (checker *TimestampChecker) timestampFilePath(t *ast.Task) string {
return filepath.Join(checker.tempDir, "timestamp", normalizeFilename(t.Task))
return filepath.Join(checker.tempDir, t.Dir, "timestamp", normalizeFilename(t.Task))
}
100 changes: 100 additions & 0 deletions internal/fingerprint/sources_timestamp_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
package fingerprint

import (
"os"
"path/filepath"
"testing"
"time"

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"

"github.com/go-task/task/v3/taskfile/ast"
)

func TestTimestampFileLocation(t *testing.T) {
t.Parallel()

// Create temporary directory for test
tempDir, err := os.MkdirTemp("", "task-test-*")
require.NoError(t, err)
defer os.RemoveAll(tempDir)

// Create test directories
rootDir := filepath.Join(tempDir, "root")
subDir := filepath.Join(rootDir, "subdir")
require.NoError(t, os.MkdirAll(rootDir, 0755))
require.NoError(t, os.MkdirAll(subDir, 0755))

// Create source files
rootSourceFile := filepath.Join(rootDir, "root.txt")
subSourceFile := filepath.Join(subDir, "sub.txt")
require.NoError(t, os.WriteFile(rootSourceFile, []byte("root source"), 0644))
require.NoError(t, os.WriteFile(subSourceFile, []byte("sub source"), 0644))

// Create generate files
rootGenerateFile := filepath.Join(rootDir, "root.txt.processed")
subGenerateFile := filepath.Join(subDir, "sub.txt.processed")
require.NoError(t, os.WriteFile(rootGenerateFile, []byte("Processing root.txt"), 0644))
require.NoError(t, os.WriteFile(subGenerateFile, []byte("Processing sub.txt"), 0644))

// Set file times
now := time.Now()
sourceTime := now.Add(-1 * time.Hour)
generateTime := now
require.NoError(t, os.Chtimes(rootSourceFile, sourceTime, sourceTime))
require.NoError(t, os.Chtimes(subSourceFile, sourceTime, sourceTime))
require.NoError(t, os.Chtimes(rootGenerateFile, generateTime, generateTime))
require.NoError(t, os.Chtimes(subGenerateFile, generateTime, generateTime))

// Create tasks
rootTask := &ast.Task{
Task: "root",
Dir: rootDir,
Sources: []*ast.Glob{{Glob: "*.txt"}},
Generates: []*ast.Glob{{Glob: "*.txt.processed"}},
Method: "timestamp",
}
subTask := &ast.Task{
Task: "sub",
Dir: subDir,
Sources: []*ast.Glob{{Glob: "*.txt"}},
Generates: []*ast.Glob{{Glob: "*.txt.processed"}},
Method: "timestamp",
}

// Create checker
checker := NewTimestampChecker(tempDir, false)

// Test root task
rootUpToDate, err := checker.IsUpToDate(rootTask)
require.NoError(t, err)
assert.True(t, rootUpToDate, "Root task should be up-to-date")

// Test sub task
subUpToDate, err := checker.IsUpToDate(subTask)
require.NoError(t, err)
assert.True(t, subUpToDate, "Sub task should be up-to-date")

// Verify timestamp files were created in the correct locations
rootTimestampFile := filepath.Join(tempDir, rootDir, "timestamp", normalizeFilename(rootTask.Task))
subTimestampFile := filepath.Join(tempDir, subDir, "timestamp", normalizeFilename(subTask.Task))

_, err = os.Stat(rootTimestampFile)
assert.NoError(t, err, "Root timestamp file should exist")
_, err = os.Stat(subTimestampFile)
assert.NoError(t, err, "Sub timestamp file should exist")

// Test that modifying a source file makes the task not up-to-date
newSourceTime := now.Add(1 * time.Hour)
require.NoError(t, os.Chtimes(rootSourceFile, newSourceTime, newSourceTime))

rootUpToDate, err = checker.IsUpToDate(rootTask)
require.NoError(t, err)
assert.False(t, rootUpToDate, "Root task should not be up-to-date after source file modification")

// Sub task should still be up-to-date
subUpToDate, err = checker.IsUpToDate(subTask)
require.NoError(t, err)
assert.True(t, subUpToDate, "Sub task should still be up-to-date")
}
179 changes: 179 additions & 0 deletions internal/fingerprint/timestamp_checker_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,179 @@
package fingerprint

import (
"os"
"path/filepath"
"testing"
"time"

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"

"github.com/go-task/task/v3/taskfile/ast"
)

type TestDefinition struct {
name string
setup func(t *testing.T, dir string) *ast.Task
expected bool
}

func TestTimestampCheckerIsUpToDate(t *testing.T) {
t.Parallel()

tests := []TestDefinition{
{
name: "empty sources",
setup: func(t *testing.T, dir string) *ast.Task {
return &ast.Task{
Dir: dir,
Sources: nil,
Generates: nil,
}
},
expected: false,
},
{
name: "sources newer than generates",
setup: func(t *testing.T, dir string) *ast.Task {
// Create source file
sourceFile := filepath.Join(dir, "source.txt")
err := os.WriteFile(sourceFile, []byte("source"), 0644)
require.NoError(t, err)

// Create generate file with older timestamp
generateFile := filepath.Join(dir, "generate.txt")
err = os.WriteFile(generateFile, []byte("generate"), 0644)
require.NoError(t, err)

// Set source file to be newer than generate file
sourceTime := time.Now()
generateTime := sourceTime.Add(-1 * time.Hour)
err = os.Chtimes(sourceFile, sourceTime, sourceTime)
require.NoError(t, err)
err = os.Chtimes(generateFile, generateTime, generateTime)
require.NoError(t, err)

return &ast.Task{
Dir: dir,
Sources: []*ast.Glob{{Glob: "source.txt"}},
Generates: []*ast.Glob{{Glob: "generate.txt"}},
}
},
expected: false,
},
{
name: "generates newer than sources",
setup: func(t *testing.T, dir string) *ast.Task {
// Create source file
sourceFile := filepath.Join(dir, "source.txt")
err := os.WriteFile(sourceFile, []byte("source"), 0644)
require.NoError(t, err)

// Create generate file with newer timestamp
generateFile := filepath.Join(dir, "generate.txt")
err = os.WriteFile(generateFile, []byte("generate"), 0644)
require.NoError(t, err)

// Set generate file to be newer than source file
sourceTime := time.Now().Add(-1 * time.Hour)
generateTime := time.Now()
err = os.Chtimes(sourceFile, sourceTime, sourceTime)
require.NoError(t, err)
err = os.Chtimes(generateFile, generateTime, generateTime)
require.NoError(t, err)

return &ast.Task{
Dir: dir,
Sources: []*ast.Glob{{Glob: "source.txt"}},
Generates: []*ast.Glob{{Glob: "generate.txt"}},
}
},
expected: true,
},
{
name: "glob pattern directory/**/*",
setup: func(t *testing.T, dir string) *ast.Task {
// Create directory structure
subDir := filepath.Join(dir, "subdir")
nestedDir := filepath.Join(subDir, "nested")
err := os.MkdirAll(nestedDir, 0755)
require.NoError(t, err)

// Create source files
sourceFile1 := filepath.Join(subDir, "source1.txt")
sourceFile2 := filepath.Join(nestedDir, "source2.txt")
err = os.WriteFile(sourceFile1, []byte("source1"), 0644)
require.NoError(t, err)
err = os.WriteFile(sourceFile2, []byte("source2"), 0644)
require.NoError(t, err)

// Create generate file
generateFile := filepath.Join(dir, "generate.txt")
generateFile2 := filepath.Join(dir, "generate2.txt")
err = os.WriteFile(generateFile, []byte("generate"), 0644)
require.NoError(t, err)
err = os.WriteFile(generateFile2, []byte("generate"), 0644)
require.NoError(t, err)

// Set source files to be newer than generate file to simulate a change
generateTime := time.Now().Add(-1 * time.Hour)
sourceTime := time.Now()
err = os.Chtimes(sourceFile1, sourceTime, sourceTime)
require.NoError(t, err)
err = os.Chtimes(sourceFile2, sourceTime, sourceTime)
require.NoError(t, err)
err = os.Chtimes(generateFile, generateTime, generateTime)
require.NoError(t, err)
err = os.Remove(generateFile2) // Also remove one generate file to simulate a change
require.NoError(t, err)

return &ast.Task{
Dir: dir,
Sources: []*ast.Glob{{Glob: "subdir/**/*"}},
Generates: []*ast.Glob{{Glob: "*.txt"}},
Method: "timestamp",
}
},
expected: false,
},
}

for _, tt := range tests {
tt := tt
t.Run(tt.name, func(t *testing.T) {
t.Parallel()

// Create temporary directory for test
tempDir, err := os.MkdirTemp("", "task-test-*")
require.NoError(t, err)
defer os.RemoveAll(tempDir)

// Create test directory
testDir := filepath.Join(tempDir, "test")
err = os.MkdirAll(testDir, 0755)
require.NoError(t, err)

// Setup test
task := tt.setup(t, testDir)

// Create checker
checker := NewTimestampChecker(tempDir, false)

// Run test
result, err := checker.IsUpToDate(task)
require.NoError(t, err)
assert.Equal(t, tt.expected, result)

// Verify timestamp file location if sources exist
if len(task.Sources) > 0 {
timestampFile := filepath.Join(tempDir, task.Dir, "timestamp", normalizeFilename(task.Task))
if !tt.expected {
// If task is not up-to-date, the timestamp file should still exist
_, err := os.Stat(timestampFile)
require.NoError(t, err, "Timestamp file should exist at %s", timestampFile)
}
}
})
}
}
14 changes: 8 additions & 6 deletions task_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -411,12 +411,14 @@ func TestGenerates(t *testing.T) {
func TestStatusChecksum(t *testing.T) { // nolint:paralleltest // cannot run in parallel
const dir = "testdata/checksum"

pwd, _ := os.Getwd()

tests := []struct {
files []string
task string
}{
{[]string{"generated.txt", ".task/checksum/build"}, "build"},
{[]string{"generated.txt", ".task/checksum/build-with-status"}, "build-with-status"},
{[]string{"generated.txt", filepath.Join(".task", pwd, "testdata/checksum/checksum/build")}, "build"},
{[]string{"generated.txt", filepath.Join(".task", pwd, "testdata/checksum/checksum/build-with-status")}, "build-with-status"},
}

for _, test := range tests { // nolint:paralleltest // cannot run in parallel
Expand Down Expand Up @@ -449,15 +451,15 @@ func TestStatusChecksum(t *testing.T) { // nolint:paralleltest // cannot run in

// Capture the modification time, so we can ensure the checksum file
// is not regenerated when the hash hasn't changed.
s, err := os.Stat(filepathext.SmartJoin(tempDir.Fingerprint, "checksum/"+test.task))
s, err := os.Stat(filepath.Join(tempDir.Fingerprint, pwd, dir, "checksum/"+test.task))
require.NoError(t, err)
time := s.ModTime()

buff.Reset()
require.NoError(t, e.Run(context.Background(), &task.Call{Task: test.task}))
assert.Equal(t, `task: Task "`+test.task+`" is up to date`+"\n", buff.String())

s, err = os.Stat(filepathext.SmartJoin(tempDir.Fingerprint, "checksum/"+test.task))
s, err = os.Stat(filepath.Join(tempDir.Fingerprint, pwd, dir, "checksum/"+test.task))
require.NoError(t, err)
assert.Equal(t, time, s.ModTime())
})
Expand Down Expand Up @@ -655,8 +657,8 @@ func TestDryChecksum(t *testing.T) {
t.Parallel()

const dir = "testdata/dry_checksum"

checksumFile := filepathext.SmartJoin(dir, ".task/checksum/default")
pwd, _ := os.Getwd()
checksumFile := filepath.Join(dir, filepath.Join(".task", pwd, dir, "/checksum/default"))
_ = os.Remove(checksumFile)

e := task.NewExecutor(
Expand Down