mirror of
https://github.com/go-task/task.git
synced 2026-06-11 09:51:50 +00:00
fix: re-run task when generated files are missing with method: timestamp (#2716)
This commit is contained in:
@@ -53,6 +53,7 @@ func (checker *ChecksumChecker) IsUpToDate(t *ast.Task) (bool, error) {
|
|||||||
if len(t.Generates) > 0 {
|
if len(t.Generates) > 0 {
|
||||||
// For each specified 'generates' field, check whether the files actually exist
|
// For each specified 'generates' field, check whether the files actually exist
|
||||||
for _, g := range t.Generates {
|
for _, g := range t.Generates {
|
||||||
|
// Exclusion patterns don't represent output files; skip them.
|
||||||
if g.Negate {
|
if g.Negate {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -32,6 +32,28 @@ func (checker *TimestampChecker) IsUpToDate(t *ast.Task) (bool, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return false, nil
|
return false, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If generates are declared, ensure they all exist. A missing generated
|
||||||
|
// file means the task must run regardless of timestamps.
|
||||||
|
if len(t.Generates) > 0 {
|
||||||
|
for _, g := range t.Generates {
|
||||||
|
// Exclusion patterns don't represent output files; skip them.
|
||||||
|
if g.Negate {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
files, err := glob(t.Dir, g.Glob)
|
||||||
|
if os.IsNotExist(err) {
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
if len(files) == 0 {
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
generates, err := Globs(t.Dir, t.Generates)
|
generates, err := Globs(t.Dir, t.Generates)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, nil
|
return false, nil
|
||||||
|
|||||||
98
task_test.go
98
task_test.go
@@ -555,6 +555,104 @@ func TestStatusChecksum(t *testing.T) { // nolint:paralleltest // cannot run in
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TestStatusTimestamp is a regression test for https://github.com/go-task/task/issues/1230.
|
||||||
|
// When using method: timestamp, deleting a generated file should cause the task to re-run,
|
||||||
|
// not be skipped because the timestamp file is still present.
|
||||||
|
func TestStatusTimestamp(t *testing.T) { // nolint:paralleltest // cannot run in parallel
|
||||||
|
const dir = "testdata/timestamp"
|
||||||
|
|
||||||
|
generatedFile := filepathext.SmartJoin(dir, "generated.txt")
|
||||||
|
tempDir := task.TempDir{
|
||||||
|
Remote: filepathext.SmartJoin(dir, ".task"),
|
||||||
|
Fingerprint: filepathext.SmartJoin(dir, ".task"),
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clean up any state from previous runs.
|
||||||
|
_ = os.Remove(generatedFile)
|
||||||
|
_ = os.RemoveAll(filepathext.SmartJoin(dir, ".task"))
|
||||||
|
|
||||||
|
var buff bytes.Buffer
|
||||||
|
e := task.NewExecutor(
|
||||||
|
task.WithDir(dir),
|
||||||
|
task.WithStdout(&buff),
|
||||||
|
task.WithStderr(&buff),
|
||||||
|
task.WithTempDir(tempDir),
|
||||||
|
)
|
||||||
|
require.NoError(t, e.Setup())
|
||||||
|
|
||||||
|
// First run: task should execute and create generated.txt.
|
||||||
|
require.NoError(t, e.Run(t.Context(), &task.Call{Task: "build"}))
|
||||||
|
_, err := os.Stat(generatedFile)
|
||||||
|
require.NoError(t, err, "generated.txt should exist after first run")
|
||||||
|
buff.Reset()
|
||||||
|
|
||||||
|
// Second run: task should be up to date.
|
||||||
|
require.NoError(t, e.Run(t.Context(), &task.Call{Task: "build"}))
|
||||||
|
assert.Equal(t, `task: Task "build" is up to date`+"\n", buff.String())
|
||||||
|
buff.Reset()
|
||||||
|
|
||||||
|
// Delete the generated file (simulate a clean), but leave the timestamp file.
|
||||||
|
require.NoError(t, os.Remove(generatedFile))
|
||||||
|
_, err = os.Stat(generatedFile)
|
||||||
|
require.Error(t, err, "generated.txt should be gone")
|
||||||
|
|
||||||
|
// Third run: task MUST re-run because generated.txt is missing.
|
||||||
|
// This is the regression: previously the task was incorrectly skipped.
|
||||||
|
require.NoError(t, e.Run(t.Context(), &task.Call{Task: "build"}))
|
||||||
|
assert.NotContains(t, buff.String(), "is up to date", "task should re-run when generated file is missing")
|
||||||
|
_, err = os.Stat(generatedFile)
|
||||||
|
require.NoError(t, err, "generated.txt should be recreated after third run")
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestStatusChecksumMissingGenerated is a regression test for https://github.com/go-task/task/issues/1230.
|
||||||
|
// When using method: checksum, deleting a generated file should cause the task to re-run,
|
||||||
|
// not be skipped because the checksum file still matches.
|
||||||
|
func TestStatusChecksumMissingGenerated(t *testing.T) { // nolint:paralleltest // cannot run in parallel
|
||||||
|
const dir = "testdata/checksum"
|
||||||
|
|
||||||
|
generatedFile := filepathext.SmartJoin(dir, "generated.txt")
|
||||||
|
tempDir := task.TempDir{
|
||||||
|
Remote: filepathext.SmartJoin(dir, ".task"),
|
||||||
|
Fingerprint: filepathext.SmartJoin(dir, ".task"),
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clean up any state from previous runs.
|
||||||
|
_ = os.Remove(generatedFile)
|
||||||
|
_ = os.RemoveAll(filepathext.SmartJoin(dir, ".task"))
|
||||||
|
|
||||||
|
var buff bytes.Buffer
|
||||||
|
e := task.NewExecutor(
|
||||||
|
task.WithDir(dir),
|
||||||
|
task.WithStdout(&buff),
|
||||||
|
task.WithStderr(&buff),
|
||||||
|
task.WithTempDir(tempDir),
|
||||||
|
)
|
||||||
|
require.NoError(t, e.Setup())
|
||||||
|
|
||||||
|
// First run: task should execute and create generated.txt.
|
||||||
|
require.NoError(t, e.Run(t.Context(), &task.Call{Task: "build"}))
|
||||||
|
_, err := os.Stat(generatedFile)
|
||||||
|
require.NoError(t, err, "generated.txt should exist after first run")
|
||||||
|
buff.Reset()
|
||||||
|
|
||||||
|
// Second run: task should be up to date.
|
||||||
|
require.NoError(t, e.Run(t.Context(), &task.Call{Task: "build"}))
|
||||||
|
assert.Equal(t, `task: Task "build" is up to date`+"\n", buff.String())
|
||||||
|
buff.Reset()
|
||||||
|
|
||||||
|
// Delete the generated file (simulate a clean), but leave the checksum file.
|
||||||
|
require.NoError(t, os.Remove(generatedFile))
|
||||||
|
_, err = os.Stat(generatedFile)
|
||||||
|
require.Error(t, err, "generated.txt should be gone")
|
||||||
|
|
||||||
|
// Third run: task MUST re-run because generated.txt is missing.
|
||||||
|
// This is the regression: previously the task was incorrectly skipped.
|
||||||
|
require.NoError(t, e.Run(t.Context(), &task.Call{Task: "build"}))
|
||||||
|
assert.NotContains(t, buff.String(), "is up to date", "task should re-run when generated file is missing")
|
||||||
|
_, err = os.Stat(generatedFile)
|
||||||
|
require.NoError(t, err, "generated.txt should be recreated after third run")
|
||||||
|
}
|
||||||
|
|
||||||
func TestStatusVariables(t *testing.T) {
|
func TestStatusVariables(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
|
|||||||
2
testdata/timestamp/.gitignore
vendored
Normal file
2
testdata/timestamp/.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
.task
|
||||||
|
generated.txt
|
||||||
11
testdata/timestamp/Taskfile.yml
vendored
Normal file
11
testdata/timestamp/Taskfile.yml
vendored
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
version: '3'
|
||||||
|
|
||||||
|
tasks:
|
||||||
|
build:
|
||||||
|
cmds:
|
||||||
|
- cp ./source.txt ./generated.txt
|
||||||
|
sources:
|
||||||
|
- ./source.txt
|
||||||
|
generates:
|
||||||
|
- ./generated.txt
|
||||||
|
method: timestamp
|
||||||
1
testdata/timestamp/source.txt
vendored
Normal file
1
testdata/timestamp/source.txt
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
hello from source
|
||||||
Reference in New Issue
Block a user