Skip to content

Commit ba615fa

Browse files
committed
test: cover fingerprint method resolution and its variable
Table the two Taskfile-level method tests, assert on an actual timestamp rather than on the "ts=" prefix an unresolved variable also satisfies, and add a --force case over an invalid method, which no test caught. On the unit side, check that Kind and SourceValue agree on the resolved method, and that an invalid one is rejected by every entry point that needs a checker.
1 parent e34e69a commit ba615fa

4 files changed

Lines changed: 151 additions & 57 deletions

File tree

‎internal/fingerprint/fingerprinter_test.go‎

Lines changed: 84 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
11
package fingerprint
22

33
import (
4+
"os"
5+
"path/filepath"
46
"testing"
7+
"time"
58

69
"github.com/stretchr/testify/assert"
710
"github.com/stretchr/testify/mock"
@@ -10,19 +13,7 @@ import (
1013
"github.com/go-task/task/v3/taskfile/ast"
1114
)
1215

13-
// TruthTable
14-
//
15-
// | Status up-to-date | Sources up-to-date | Task is up-to-date |
16-
// | ----------------- | ------------------ | ------------------ |
17-
// | not set | not set | false |
18-
// | not set | true | true |
19-
// | not set | false | false |
20-
// | true | not set | true |
21-
// | true | true | true |
22-
// | true | false | false |
23-
// | false | not set | false |
24-
// | false | true | false |
25-
// | false | false | false |
16+
// The cases below walk the truth table documented on [Fingerprinter.UpToDate].
2617
func TestFingerprinterUpToDate(t *testing.T) {
2718
t.Parallel()
2819

@@ -172,3 +163,83 @@ func TestFingerprinterUpToDate(t *testing.T) {
172163
})
173164
}
174165
}
166+
167+
// The task's own method wins over the Taskfile default, for the injected
168+
// variable as much as for the up-to-date check.
169+
func TestFingerprinterMethodResolution(t *testing.T) {
170+
t.Parallel()
171+
172+
tests := []struct {
173+
name string
174+
defaultMethod string
175+
method string
176+
expectedKind string
177+
expectedValue any
178+
}{
179+
{
180+
name: "task method wins over the default",
181+
defaultMethod: "checksum",
182+
method: "timestamp",
183+
expectedKind: "timestamp",
184+
expectedValue: time.Time{},
185+
},
186+
{
187+
name: "default method is inherited when the task declares none",
188+
defaultMethod: "timestamp",
189+
expectedKind: "timestamp",
190+
expectedValue: time.Time{},
191+
},
192+
{
193+
name: "checksum is inherited too",
194+
defaultMethod: "checksum",
195+
expectedKind: "checksum",
196+
expectedValue: "",
197+
},
198+
{
199+
name: "none is inherited too",
200+
defaultMethod: "none",
201+
expectedKind: "none",
202+
expectedValue: "",
203+
},
204+
}
205+
for _, tt := range tests {
206+
t.Run(tt.name, func(t *testing.T) {
207+
t.Parallel()
208+
209+
dir := t.TempDir()
210+
require.NoError(t, os.WriteFile(filepath.Join(dir, "source.txt"), []byte("content"), 0o644))
211+
task := &ast.Task{
212+
Dir: dir,
213+
Method: tt.method,
214+
Sources: []*ast.Glob{{Glob: "source.txt"}},
215+
}
216+
217+
f := NewFingerprinter(tt.defaultMethod, t.TempDir(), true, nil)
218+
219+
assert.Equal(t, tt.expectedKind, f.Kind(task))
220+
221+
// A timestamp checker yields a time, the other two a string.
222+
value, err := f.SourceValue(task)
223+
require.NoError(t, err)
224+
assert.IsType(t, tt.expectedValue, value)
225+
})
226+
}
227+
}
228+
229+
// Only the entry points that need a checker reject an invalid method; Kind
230+
// tolerates it, so that --force runs still compile.
231+
func TestFingerprinterInvalidMethod(t *testing.T) {
232+
t.Parallel()
233+
234+
const wantErr = `task: invalid method "Checksum"`
235+
task := &ast.Task{Sources: []*ast.Glob{{Glob: "source.txt"}}}
236+
f := NewFingerprinter("Checksum", t.TempDir(), true, nil)
237+
238+
assert.Equal(t, "checksum", f.Kind(task))
239+
240+
_, err := f.SourceValue(task)
241+
require.EqualError(t, err, wantErr)
242+
_, err = f.UpToDate(t.Context(), task)
243+
require.EqualError(t, err, wantErr)
244+
require.EqualError(t, f.OnError(task), wantErr)
245+
}

‎task_test.go‎

Lines changed: 57 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -653,55 +653,68 @@ func TestStatusChecksumMissingGenerated(t *testing.T) { // nolint:paralleltest /
653653
require.NoError(t, err, "generated.txt should be recreated after third run")
654654
}
655655

656-
// TestFingerprintVarMethodInheritedFromTaskfile asserts that the fingerprint
657-
// variable injected into a task follows the Taskfile-level method when the
658-
// task doesn't declare one, like the up-to-date check does.
659-
func TestFingerprintVarMethodInheritedFromTaskfile(t *testing.T) {
656+
// The injected fingerprint variable follows the method the up-to-date check
657+
// uses, including when that method comes from the Taskfile level.
658+
func TestFingerprintVarMethod(t *testing.T) {
660659
t.Parallel()
661660

662-
const dir = "testdata/method_taskfile_timestamp"
663-
_ = os.RemoveAll(filepathext.SmartJoin(dir, ".task"))
664-
665-
var buff bytes.Buffer
666-
e := task.NewExecutor(
667-
task.WithDir(dir),
668-
task.WithStdout(&buff),
669-
task.WithStderr(&buff),
670-
task.WithTempDir(task.TempDir{
671-
Remote: filepathext.SmartJoin(dir, ".task"),
672-
Fingerprint: filepathext.SmartJoin(dir, ".task"),
673-
}),
674-
)
675-
require.NoError(t, e.Setup())
676-
677-
require.NoError(t, e.Run(t.Context(), &task.Call{Task: "build"}))
678-
assert.Contains(t, buff.String(), "ts=")
679-
assert.NotContains(t, buff.String(), "<no value>", "TIMESTAMP should be injected when method is inherited from the Taskfile")
680-
}
681-
682-
// TestFingerprintVarMethodNone asserts that no fingerprint variable is
683-
// injected when the effective method is "none", including when it is
684-
// inherited from the Taskfile level.
685-
func TestFingerprintVarMethodNone(t *testing.T) {
686-
t.Parallel()
661+
tests := []struct {
662+
name string
663+
dir string
664+
executorOpts []task.ExecutorOption
665+
assertOutput func(t *testing.T, output string)
666+
}{
667+
{
668+
name: "TIMESTAMP is injected when the method is inherited from the Taskfile",
669+
dir: "testdata/method_taskfile_timestamp",
670+
assertOutput: func(t *testing.T, output string) {
671+
t.Helper()
672+
// An unresolved variable renders as an empty string, so this
673+
// has to match an actual timestamp, not just the prefix.
674+
assert.Regexp(t, `ts=\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}`, output)
675+
},
676+
},
677+
{
678+
name: "no variable is injected when the effective method is none",
679+
dir: "testdata/method_taskfile_none",
680+
assertOutput: func(t *testing.T, output string) {
681+
t.Helper()
682+
assert.Contains(t, output, "cs=\n")
683+
},
684+
},
685+
{
686+
name: "an invalid method doesn't fail a run that skips fingerprinting",
687+
dir: "testdata/method_invalid",
688+
executorOpts: []task.ExecutorOption{task.WithForce(true)},
689+
assertOutput: func(t *testing.T, output string) {
690+
t.Helper()
691+
assert.Contains(t, output, "hello\n")
692+
},
693+
},
694+
}
695+
for _, tt := range tests {
696+
t.Run(tt.name, func(t *testing.T) {
697+
t.Parallel()
687698

688-
const dir = "testdata/method_taskfile_none"
689-
_ = os.RemoveAll(filepathext.SmartJoin(dir, ".task"))
699+
_ = os.RemoveAll(filepathext.SmartJoin(tt.dir, ".task"))
690700

691-
var buff bytes.Buffer
692-
e := task.NewExecutor(
693-
task.WithDir(dir),
694-
task.WithStdout(&buff),
695-
task.WithStderr(&buff),
696-
task.WithTempDir(task.TempDir{
697-
Remote: filepathext.SmartJoin(dir, ".task"),
698-
Fingerprint: filepathext.SmartJoin(dir, ".task"),
699-
}),
700-
)
701-
require.NoError(t, e.Setup())
701+
var buff bytes.Buffer
702+
opts := append([]task.ExecutorOption{
703+
task.WithDir(tt.dir),
704+
task.WithStdout(&buff),
705+
task.WithStderr(&buff),
706+
task.WithTempDir(task.TempDir{
707+
Remote: filepathext.SmartJoin(tt.dir, ".task"),
708+
Fingerprint: filepathext.SmartJoin(tt.dir, ".task"),
709+
}),
710+
}, tt.executorOpts...)
711+
e := task.NewExecutor(opts...)
712+
require.NoError(t, e.Setup())
702713

703-
require.NoError(t, e.Run(t.Context(), &task.Call{Task: "build"}))
704-
assert.Contains(t, buff.String(), "cs=\n", "CHECKSUM should not be injected when the effective method is none")
714+
require.NoError(t, e.Run(t.Context(), &task.Call{Task: "build"}))
715+
tt.assertOutput(t, buff.String())
716+
})
717+
}
705718
}
706719

707720
func writeFile(t *testing.T, dir, name, content string) {
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
version: '3'
2+
3+
tasks:
4+
build:
5+
method: checksums # typo: not a valid method
6+
cmds:
7+
- echo "hello"
8+
sources:
9+
- ./source.txt

‎testdata/method_invalid/source.txt‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
x

0 commit comments

Comments
 (0)