Compare commits

...

5 Commits

Author SHA1 Message Date
Pete Davison
5128a98ee2 feat: test wildcards 2025-08-11 12:00:34 +00:00
Pete Davison
8353dffc7a fix: executor and formatter tests 2025-08-11 12:00:34 +00:00
Pete Davison
6e80b401e6 feat: remove entrypoint from the executor 2025-08-11 12:00:34 +00:00
Pete Davison
1402e2baaf refactor: merge Setup into NewExecutor 2025-08-11 12:00:34 +00:00
Pete Davison
4b99f60039 refactor: split executor and reader 2025-08-11 12:00:34 +00:00
27 changed files with 2701 additions and 2646 deletions

View File

@@ -16,6 +16,7 @@ import (
"github.com/go-task/task/v3/internal/flags" "github.com/go-task/task/v3/internal/flags"
"github.com/go-task/task/v3/internal/logger" "github.com/go-task/task/v3/internal/logger"
"github.com/go-task/task/v3/internal/version" "github.com/go-task/task/v3/internal/version"
"github.com/go-task/task/v3/taskfile"
"github.com/go-task/task/v3/taskfile/ast" "github.com/go-task/task/v3/taskfile/ast"
) )
@@ -110,16 +111,63 @@ func run() error {
return nil return nil
} }
e := task.NewExecutor( if err := experiments.Validate(); err != nil {
flags.WithFlags(), log.Warnf("%s\n", err.Error())
task.WithVersionCheck(true), }
// Create a new root node for the given entrypoint
node, err := taskfile.NewRootNode(
flags.Entrypoint,
flags.Dir,
flags.Insecure,
) )
if err := e.Setup(); err != nil { if err != nil {
return err return err
} }
tempDir, err := task.NewTempDir(node.Dir())
if err != nil {
return err
}
reader := taskfile.NewReader(
flags.WithFlags(),
taskfile.WithTempDir(tempDir.Remote),
taskfile.WithDebugFunc(func(s string) {
log.VerboseOutf(logger.Magenta, s)
}),
taskfile.WithPromptFunc(func(s string) error {
return log.Prompt(logger.Yellow, s, "n", "y", "yes")
}),
)
ctx, cf := context.WithTimeout(context.Background(), flags.Timeout)
defer cf()
graph, err := reader.Read(ctx, node)
if err != nil {
if errors.Is(err, context.DeadlineExceeded) {
return &errors.TaskfileNetworkTimeoutError{URI: node.Location(), Timeout: flags.Timeout}
}
return err
}
executor, err := task.NewExecutor(graph,
flags.WithFlags(),
task.WithDir(node.Dir()),
task.WithTempDir(tempDir),
)
if err != nil {
return err
}
// If the download flag is specified, we should stop execution as soon as
// taskfile is downloaded
if flags.Download {
return nil
}
if flags.ClearCache { if flags.ClearCache {
cachePath := filepath.Join(e.TempDir.Remote, "remote") cachePath := filepath.Join(executor.TempDir.Remote, "remote")
return os.RemoveAll(cachePath) return os.RemoveAll(cachePath)
} }
@@ -131,9 +179,9 @@ func run() error {
) )
if listOptions.ShouldListTasks() { if listOptions.ShouldListTasks() {
if flags.Silent { if flags.Silent {
return e.ListTaskNames(flags.ListAll) return executor.ListTaskNames(flags.ListAll)
} }
foundTasks, err := e.ListTasks(listOptions) foundTasks, err := executor.ListTasks(listOptions)
if err != nil { if err != nil {
return err return err
} }
@@ -165,17 +213,17 @@ func run() error {
globals.Set("CLI_SILENT", ast.Var{Value: flags.Silent}) globals.Set("CLI_SILENT", ast.Var{Value: flags.Silent})
globals.Set("CLI_VERBOSE", ast.Var{Value: flags.Verbose}) globals.Set("CLI_VERBOSE", ast.Var{Value: flags.Verbose})
globals.Set("CLI_OFFLINE", ast.Var{Value: flags.Offline}) globals.Set("CLI_OFFLINE", ast.Var{Value: flags.Offline})
e.Taskfile.Vars.Merge(globals, nil) executor.Taskfile.Vars.Merge(globals, nil)
if !flags.Watch { if !flags.Watch {
e.InterceptInterruptSignals() executor.InterceptInterruptSignals()
} }
ctx := context.Background() ctx = context.Background()
if flags.Status { if flags.Status {
return e.Status(ctx, calls...) return executor.Status(ctx, calls...)
} }
return e.Run(ctx, calls...) return executor.Run(ctx, calls...)
} }

View File

@@ -4,6 +4,7 @@ import (
"context" "context"
"io" "io"
"os" "os"
"path/filepath"
"sync" "sync"
"time" "time"
@@ -26,27 +27,21 @@ type (
// within them. // within them.
Executor struct { Executor struct {
// Flags // Flags
Dir string Dir string
Entrypoint string TempDir *TempDir
TempDir TempDir Force bool
Force bool ForceAll bool
ForceAll bool Watch bool
Insecure bool Verbose bool
Download bool Silent bool
Offline bool AssumeYes bool
Timeout time.Duration AssumeTerm bool // Used for testing
CacheExpiryDuration time.Duration Dry bool
Watch bool Summary bool
Verbose bool Parallel bool
Silent bool Color bool
AssumeYes bool Concurrency int
AssumeTerm bool // Used for testing Interval time.Duration
Dry bool
Summary bool
Parallel bool
Color bool
Concurrency int
Interval time.Duration
// I/O // I/O
Stdin io.Reader Stdin io.Reader
@@ -72,17 +67,17 @@ type (
executionHashesMutex sync.Mutex executionHashesMutex sync.Mutex
watchedDirs *xsync.MapOf[string, bool] watchedDirs *xsync.MapOf[string, bool]
} }
TempDir struct {
Remote string
Fingerprint string
}
) )
// NewExecutor creates a new [Executor] and applies the given functional options // NewExecutor creates a new [Executor] and applies the given functional options
// to it. // to it.
func NewExecutor(opts ...ExecutorOption) *Executor { func NewExecutor(graph *ast.TaskfileGraph, opts ...ExecutorOption) (*Executor, error) {
tf, err := graph.Merge()
if err != nil {
return nil, err
}
e := &Executor{ e := &Executor{
Timeout: time.Second * 10, Taskfile: tf,
Stdin: os.Stdin, Stdin: os.Stdin,
Stdout: os.Stdout, Stdout: os.Stdout,
Stderr: os.Stderr, Stderr: os.Stderr,
@@ -100,7 +95,10 @@ func NewExecutor(opts ...ExecutorOption) *Executor {
executionHashesMutex: sync.Mutex{}, executionHashesMutex: sync.Mutex{},
} }
e.Options(opts...) e.Options(opts...)
return e if err := e.setup(); err != nil {
return nil, err
}
return e, nil
} }
// Options loops through the given [ExecutorOption] functions and applies them // Options loops through the given [ExecutorOption] functions and applies them
@@ -122,33 +120,23 @@ type dirOption struct {
} }
func (o *dirOption) ApplyToExecutor(e *Executor) { func (o *dirOption) ApplyToExecutor(e *Executor) {
e.Dir = o.dir absDir, err := filepath.Abs(o.dir)
} if err != nil {
e.Dir = o.dir
// WithEntrypoint sets the entrypoint (main Taskfile) of the [Executor]. By return
// default, Task will search for one of the default Taskfiles in the given }
// directory. e.Dir = absDir
func WithEntrypoint(entrypoint string) ExecutorOption {
return &entrypointOption{entrypoint}
}
type entrypointOption struct {
entrypoint string
}
func (o *entrypointOption) ApplyToExecutor(e *Executor) {
e.Entrypoint = o.entrypoint
} }
// WithTempDir sets the temporary directory that will be used by [Executor] for // WithTempDir sets the temporary directory that will be used by [Executor] for
// storing temporary files like checksums and cached remote files. By default, // storing temporary files like checksums and cached remote files. By default,
// the temporary directory is set to the user's temporary directory. // the temporary directory is set to the user's temporary directory.
func WithTempDir(tempDir TempDir) ExecutorOption { func WithTempDir(tempDir *TempDir) ExecutorOption {
return &tempDirOption{tempDir} return &tempDirOption{tempDir}
} }
type tempDirOption struct { type tempDirOption struct {
tempDir TempDir tempDir *TempDir
} }
func (o *tempDirOption) ApplyToExecutor(e *Executor) { func (o *tempDirOption) ApplyToExecutor(e *Executor) {
@@ -183,76 +171,6 @@ func (o *forceAllOption) ApplyToExecutor(e *Executor) {
e.ForceAll = o.forceAll e.ForceAll = o.forceAll
} }
// WithInsecure allows the [Executor] to make insecure connections when reading
// remote taskfiles. By default, insecure connections are rejected.
func WithInsecure(insecure bool) ExecutorOption {
return &insecureOption{insecure}
}
type insecureOption struct {
insecure bool
}
func (o *insecureOption) ApplyToExecutor(e *Executor) {
e.Insecure = o.insecure
}
// WithDownload forces the [Executor] to download a fresh copy of the taskfile
// from the remote source.
func WithDownload(download bool) ExecutorOption {
return &downloadOption{download}
}
type downloadOption struct {
download bool
}
func (o *downloadOption) ApplyToExecutor(e *Executor) {
e.Download = o.download
}
// WithOffline stops the [Executor] from being able to make network connections.
// It will still be able to read local files and cached copies of remote files.
func WithOffline(offline bool) ExecutorOption {
return &offlineOption{offline}
}
type offlineOption struct {
offline bool
}
func (o *offlineOption) ApplyToExecutor(e *Executor) {
e.Offline = o.offline
}
// WithTimeout sets the [Executor]'s timeout for fetching remote taskfiles. By
// default, the timeout is set to 10 seconds.
func WithTimeout(timeout time.Duration) ExecutorOption {
return &timeoutOption{timeout}
}
type timeoutOption struct {
timeout time.Duration
}
func (o *timeoutOption) ApplyToExecutor(e *Executor) {
e.Timeout = o.timeout
}
// WithCacheExpiryDuration sets the duration after which the cache is considered
// expired. By default, the cache is considered expired after 24 hours.
func WithCacheExpiryDuration(duration time.Duration) ExecutorOption {
return &cacheExpiryDurationOption{duration: duration}
}
type cacheExpiryDurationOption struct {
duration time.Duration
}
func (o *cacheExpiryDurationOption) ApplyToExecutor(r *Executor) {
r.CacheExpiryDuration = o.duration
}
// WithWatch tells the [Executor] to keep running in the background and watch // WithWatch tells the [Executor] to keep running in the background and watch
// for changes to the fingerprint of the tasks that are run. When changes are // for changes to the fingerprint of the tasks that are run. When changes are
// detected, a new task run is triggered. // detected, a new task run is triggered.

View File

@@ -4,18 +4,13 @@ import (
"context" "context"
"fmt" "fmt"
"os" "os"
"path/filepath"
"slices" "slices"
"strings"
"sync" "sync"
"github.com/Masterminds/semver/v3" "github.com/Masterminds/semver/v3"
"github.com/sajari/fuzzy" "github.com/sajari/fuzzy"
"github.com/go-task/task/v3/errors" "github.com/go-task/task/v3/errors"
"github.com/go-task/task/v3/internal/env"
"github.com/go-task/task/v3/internal/execext"
"github.com/go-task/task/v3/internal/filepathext"
"github.com/go-task/task/v3/internal/logger" "github.com/go-task/task/v3/internal/logger"
"github.com/go-task/task/v3/internal/output" "github.com/go-task/task/v3/internal/output"
"github.com/go-task/task/v3/internal/version" "github.com/go-task/task/v3/internal/version"
@@ -23,18 +18,8 @@ import (
"github.com/go-task/task/v3/taskfile/ast" "github.com/go-task/task/v3/taskfile/ast"
) )
func (e *Executor) Setup() error { func (e *Executor) setup() error {
e.setupLogger() e.setupLogger()
node, err := e.getRootNode()
if err != nil {
return err
}
if err := e.setupTempDir(); err != nil {
return err
}
if err := e.readTaskfile(node); err != nil {
return err
}
e.setupFuzzyModel() e.setupFuzzyModel()
e.setupStdFiles() e.setupStdFiles()
if err := e.setupOutput(); err != nil { if err := e.setupOutput(); err != nil {
@@ -54,46 +39,6 @@ func (e *Executor) Setup() error {
return nil return nil
} }
func (e *Executor) getRootNode() (taskfile.Node, error) {
node, err := taskfile.NewRootNode(e.Entrypoint, e.Dir, e.Insecure, e.Timeout)
if err != nil {
return nil, err
}
e.Dir = node.Dir()
return node, err
}
func (e *Executor) readTaskfile(node taskfile.Node) error {
ctx, cf := context.WithTimeout(context.Background(), e.Timeout)
defer cf()
debugFunc := func(s string) {
e.Logger.VerboseOutf(logger.Magenta, s)
}
promptFunc := func(s string) error {
return e.Logger.Prompt(logger.Yellow, s, "n", "y", "yes")
}
reader := taskfile.NewReader(
taskfile.WithInsecure(e.Insecure),
taskfile.WithDownload(e.Download),
taskfile.WithOffline(e.Offline),
taskfile.WithTempDir(e.TempDir.Remote),
taskfile.WithCacheExpiryDuration(e.CacheExpiryDuration),
taskfile.WithDebugFunc(debugFunc),
taskfile.WithPromptFunc(promptFunc),
)
graph, err := reader.Read(ctx, node)
if err != nil {
if errors.Is(err, context.DeadlineExceeded) {
return &errors.TaskfileNetworkTimeoutError{URI: node.Location(), Timeout: e.Timeout}
}
return err
}
if e.Taskfile, err = graph.Merge(); err != nil {
return err
}
return nil
}
func (e *Executor) setupFuzzyModel() { func (e *Executor) setupFuzzyModel() {
if e.Taskfile == nil { if e.Taskfile == nil {
return return
@@ -115,52 +60,6 @@ func (e *Executor) setupFuzzyModel() {
e.fuzzyModel = model e.fuzzyModel = model
} }
func (e *Executor) setupTempDir() error {
if e.TempDir != (TempDir{}) {
return nil
}
tempDir := env.GetTaskEnv("TEMP_DIR")
if tempDir == "" {
e.TempDir = TempDir{
Remote: filepathext.SmartJoin(e.Dir, ".task"),
Fingerprint: filepathext.SmartJoin(e.Dir, ".task"),
}
} else if filepath.IsAbs(tempDir) || strings.HasPrefix(tempDir, "~") {
tempDir, err := execext.ExpandLiteral(tempDir)
if err != nil {
return err
}
projectDir, _ := filepath.Abs(e.Dir)
projectName := filepath.Base(projectDir)
e.TempDir = TempDir{
Remote: tempDir,
Fingerprint: filepathext.SmartJoin(tempDir, projectName),
}
} else {
e.TempDir = TempDir{
Remote: filepathext.SmartJoin(e.Dir, tempDir),
Fingerprint: filepathext.SmartJoin(e.Dir, tempDir),
}
}
remoteDir := env.GetTaskEnv("REMOTE_DIR")
if remoteDir != "" {
if filepath.IsAbs(remoteDir) || strings.HasPrefix(remoteDir, "~") {
remoteTempDir, err := execext.ExpandLiteral(remoteDir)
if err != nil {
return err
}
e.TempDir.Remote = remoteTempDir
} else {
e.TempDir.Remote = filepathext.SmartJoin(e.Dir, ".task")
}
}
return nil
}
func (e *Executor) setupStdFiles() { func (e *Executor) setupStdFiles() {
if e.Stdin == nil { if e.Stdin == nil {
e.Stdin = os.Stdin e.Stdin = os.Stdin
@@ -206,7 +105,7 @@ func (e *Executor) setupCompiler() error {
e.Compiler = &Compiler{ e.Compiler = &Compiler{
Dir: e.Dir, Dir: e.Dir,
Entrypoint: e.Entrypoint, Entrypoint: e.Taskfile.Location,
UserWorkingDir: e.UserWorkingDir, UserWorkingDir: e.UserWorkingDir,
TaskfileEnv: e.Taskfile.Env, TaskfileEnv: e.Taskfile.Env,
TaskfileVars: e.Taskfile.Vars, TaskfileVars: e.Taskfile.Vars,

View File

@@ -7,6 +7,7 @@ import (
"fmt" "fmt"
"os" "os"
"path/filepath" "path/filepath"
"slices"
"testing" "testing"
"github.com/sebdah/goldie/v2" "github.com/sebdah/goldie/v2"
@@ -15,6 +16,7 @@ import (
"github.com/go-task/task/v3" "github.com/go-task/task/v3"
"github.com/go-task/task/v3/experiments" "github.com/go-task/task/v3/experiments"
"github.com/go-task/task/v3/internal/filepathext" "github.com/go-task/task/v3/internal/filepathext"
"github.com/go-task/task/v3/taskfile"
"github.com/go-task/task/v3/taskfile/ast" "github.com/go-task/task/v3/taskfile/ast"
) )
@@ -34,7 +36,12 @@ type (
task string task string
vars map[string]any vars map[string]any
input string input string
nodeDir string
nodeEntrypoint string
nodeInsecure bool
readerOpts []taskfile.ReaderOption
executorOpts []task.ExecutorOption executorOpts []task.ExecutorOption
wantReaderError bool
wantSetupError bool wantSetupError bool
wantRunError bool wantRunError bool
wantStatusError bool wantStatusError bool
@@ -47,8 +54,9 @@ type (
func NewExecutorTest(t *testing.T, opts ...ExecutorTestOption) { func NewExecutorTest(t *testing.T, opts ...ExecutorTestOption) {
t.Helper() t.Helper()
tt := &ExecutorTest{ tt := &ExecutorTest{
task: "default", task: "default",
vars: map[string]any{}, vars: map[string]any{},
nodeDir: ".",
TaskTest: TaskTest{ TaskTest: TaskTest{
experiments: map[*experiments.Experiment]int{}, experiments: map[*experiments.Experiment]int{},
fixtureTemplateData: map[string]any{}, fixtureTemplateData: map[string]any{},
@@ -145,11 +153,52 @@ func (tt *ExecutorTest) run(t *testing.T) {
f := func(t *testing.T) { f := func(t *testing.T) {
t.Helper() t.Helper()
var buf bytes.Buffer var buf bytes.Buffer
ctx := context.Background()
opts := append( // Create a new root node for the given entrypoint
node, err := taskfile.NewRootNode(
tt.nodeEntrypoint,
tt.nodeDir,
tt.nodeInsecure,
)
require.NoError(t, err)
// Create a golden fixture file for the output
g := goldie.New(t,
goldie.WithFixtureDir(filepath.Join(node.Dir(), "testdata")),
)
// Set up a temporary directory for the taskfile reader and task executor
tempDir, err := task.NewTempDir(node.Dir())
require.NoError(t, err)
tt.readerOpts = append(tt.readerOpts, taskfile.WithTempDir(tempDir.Remote))
// Set up the taskfile reader
reader := taskfile.NewReader(tt.readerOpts...)
graph, err := reader.Read(ctx, node)
if tt.wantReaderError {
require.Error(t, err)
tt.writeFixtureErrReader(t, g, err)
tt.writeFixtureBuffer(t, g, buf)
return
} else {
require.NoError(t, err)
}
executorOpts := slices.Concat(
// Apply the node directory and temp directory to the executor options
// by default, but allow them to by overridden by the test options
[]task.ExecutorOption{
task.WithDir(node.Dir()),
task.WithTempDir(tempDir),
},
// Apply the executor options from the test
tt.executorOpts, tt.executorOpts,
task.WithStdout(&buf), // Force the input/output streams to be set to the test buffer
task.WithStderr(&buf), []task.ExecutorOption{
task.WithStdout(&buf),
task.WithStderr(&buf),
},
) )
// If the test has input, create a reader for it and add it to the // If the test has input, create a reader for it and add it to the
@@ -157,19 +206,12 @@ func (tt *ExecutorTest) run(t *testing.T) {
if tt.input != "" { if tt.input != "" {
var reader bytes.Buffer var reader bytes.Buffer
reader.WriteString(tt.input) reader.WriteString(tt.input)
opts = append(opts, task.WithStdin(&reader)) executorOpts = append(executorOpts, task.WithStdin(&reader))
} }
// Set up the task executor // Set up the task executor
e := task.NewExecutor(opts...) executor, err := task.NewExecutor(graph, executorOpts...)
if tt.wantSetupError {
// Create a golden fixture file for the output
g := goldie.New(t,
goldie.WithFixtureDir(filepath.Join(e.Dir, "testdata")),
)
// Call setup and check for errors
if err := e.Setup(); tt.wantSetupError {
require.Error(t, err) require.Error(t, err)
tt.writeFixtureErrSetup(t, g, err) tt.writeFixtureErrSetup(t, g, err)
tt.writeFixtureBuffer(t, g, buf) tt.writeFixtureBuffer(t, g, buf)
@@ -189,8 +231,7 @@ func (tt *ExecutorTest) run(t *testing.T) {
} }
// Run the task and check for errors // Run the task and check for errors
ctx := context.Background() if err := executor.Run(ctx, call); tt.wantRunError {
if err := e.Run(ctx, call); tt.wantRunError {
require.Error(t, err) require.Error(t, err)
tt.writeFixtureErrRun(t, g, err) tt.writeFixtureErrRun(t, g, err)
tt.writeFixtureBuffer(t, g, buf) tt.writeFixtureBuffer(t, g, buf)
@@ -201,7 +242,7 @@ func (tt *ExecutorTest) run(t *testing.T) {
// If the status flag is set, run the status check // If the status flag is set, run the status check
if tt.wantStatusError { if tt.wantStatusError {
if err := e.Status(ctx, call); err != nil { if err := executor.Status(ctx, call); err != nil {
tt.writeFixtureStatus(t, g, err.Error()) tt.writeFixtureStatus(t, g, err.Error())
} }
} }
@@ -220,19 +261,16 @@ func (tt *ExecutorTest) run(t *testing.T) {
func TestEmptyTask(t *testing.T) { func TestEmptyTask(t *testing.T) {
t.Parallel() t.Parallel()
NewExecutorTest(t, NewExecutorTest(t,
WithExecutorOptions( WithNodeDir("testdata/empty_task"),
task.WithDir("testdata/empty_task"), WithExecutorOptions(),
),
) )
} }
func TestEmptyTaskfile(t *testing.T) { func TestEmptyTaskfile(t *testing.T) {
t.Parallel() t.Parallel()
NewExecutorTest(t, NewExecutorTest(t,
WithExecutorOptions( WithNodeDir("testdata/empty_taskfile"),
task.WithDir("testdata/empty_taskfile"), WithReaderError(),
),
WithSetupError(),
WithFixtureTemplating(), WithFixtureTemplating(),
) )
} }
@@ -241,15 +279,15 @@ func TestEnv(t *testing.T) {
t.Setenv("QUX", "from_os") t.Setenv("QUX", "from_os")
NewExecutorTest(t, NewExecutorTest(t,
WithName("env precedence disabled"), WithName("env precedence disabled"),
WithNodeDir("testdata/env"),
WithExecutorOptions( WithExecutorOptions(
task.WithDir("testdata/env"),
task.WithSilent(true), task.WithSilent(true),
), ),
) )
NewExecutorTest(t, NewExecutorTest(t,
WithName("env precedence enabled"), WithName("env precedence enabled"),
WithNodeDir("testdata/env"),
WithExecutorOptions( WithExecutorOptions(
task.WithDir("testdata/env"),
task.WithSilent(true), task.WithSilent(true),
), ),
WithExperiment(&experiments.EnvPrecedence, 1), WithExperiment(&experiments.EnvPrecedence, 1),
@@ -259,8 +297,8 @@ func TestEnv(t *testing.T) {
func TestVars(t *testing.T) { func TestVars(t *testing.T) {
t.Parallel() t.Parallel()
NewExecutorTest(t, NewExecutorTest(t,
WithNodeDir("testdata/vars"),
WithExecutorOptions( WithExecutorOptions(
task.WithDir("testdata/vars"),
task.WithSilent(true), task.WithSilent(true),
), ),
) )
@@ -270,25 +308,19 @@ func TestRequires(t *testing.T) {
t.Parallel() t.Parallel()
NewExecutorTest(t, NewExecutorTest(t,
WithName("required var missing"), WithName("required var missing"),
WithExecutorOptions( WithNodeDir("testdata/requires"),
task.WithDir("testdata/requires"),
),
WithTask("missing-var"), WithTask("missing-var"),
WithRunError(), WithRunError(),
) )
NewExecutorTest(t, NewExecutorTest(t,
WithName("required var ok"), WithName("required var ok"),
WithExecutorOptions( WithNodeDir("testdata/requires"),
task.WithDir("testdata/requires"),
),
WithTask("missing-var"), WithTask("missing-var"),
WithVar("FOO", "bar"), WithVar("FOO", "bar"),
) )
NewExecutorTest(t, NewExecutorTest(t,
WithName("fails validation"), WithName("fails validation"),
WithExecutorOptions( WithNodeDir("testdata/requires"),
task.WithDir("testdata/requires"),
),
WithTask("validation-var"), WithTask("validation-var"),
WithVar("ENV", "dev"), WithVar("ENV", "dev"),
WithVar("FOO", "bar"), WithVar("FOO", "bar"),
@@ -296,48 +328,37 @@ func TestRequires(t *testing.T) {
) )
NewExecutorTest(t, NewExecutorTest(t,
WithName("passes validation"), WithName("passes validation"),
WithExecutorOptions( WithNodeDir("testdata/requires"),
task.WithDir("testdata/requires"),
),
WithTask("validation-var"), WithTask("validation-var"),
WithVar("FOO", "one"), WithVar("FOO", "one"),
WithVar("ENV", "dev"), WithVar("ENV", "dev"),
) )
NewExecutorTest(t, NewExecutorTest(t,
WithName("required var missing + fails validation"), WithName("required var missing + fails validation"),
WithExecutorOptions( WithNodeDir("testdata/requires"),
task.WithDir("testdata/requires"),
),
WithTask("validation-var"), WithTask("validation-var"),
WithRunError(), WithRunError(),
) )
NewExecutorTest(t, NewExecutorTest(t,
WithName("required var missing + fails validation"), WithName("required var missing + fails validation"),
WithExecutorOptions( WithNodeDir("testdata/requires"),
task.WithDir("testdata/requires"),
),
WithTask("validation-var-dynamic"), WithTask("validation-var-dynamic"),
WithVar("FOO", "one"), WithVar("FOO", "one"),
WithVar("ENV", "dev"), WithVar("ENV", "dev"),
) )
NewExecutorTest(t, NewExecutorTest(t,
WithName("require before compile"), WithName("require before compile"),
WithExecutorOptions( WithNodeDir("testdata/requires"),
task.WithDir("testdata/requires"),
),
WithTask("require-before-compile"), WithTask("require-before-compile"),
WithRunError(), WithRunError(),
) )
NewExecutorTest(t, NewExecutorTest(t,
WithName("var defined in task"), WithName("var defined in task"),
WithExecutorOptions( WithNodeDir("testdata/requires"),
task.WithDir("testdata/requires"),
),
WithTask("var-defined-in-task"), WithTask("var-defined-in-task"),
) )
} }
// TODO: mock fs
func TestSpecialVars(t *testing.T) { func TestSpecialVars(t *testing.T) {
t.Parallel() t.Parallel()
@@ -358,12 +379,13 @@ func TestSpecialVars(t *testing.T) {
"included:print-taskfile-dir", "included:print-taskfile-dir",
} }
for _, dir := range []string{dir, subdir} { for _, executorDir := range []string{dir, subdir} {
for _, test := range tests { for _, test := range tests {
name := fmt.Sprintf("%s-%s", executorDir, test)
NewExecutorTest(t, NewExecutorTest(t,
WithName(fmt.Sprintf("%s-%s", dir, test)), WithName(name),
WithNodeDir(executorDir),
WithExecutorOptions( WithExecutorOptions(
task.WithDir(dir),
task.WithSilent(true), task.WithSilent(true),
task.WithVersionCheck(true), task.WithVersionCheck(true),
), ),
@@ -377,8 +399,8 @@ func TestSpecialVars(t *testing.T) {
func TestConcurrency(t *testing.T) { func TestConcurrency(t *testing.T) {
t.Parallel() t.Parallel()
NewExecutorTest(t, NewExecutorTest(t,
WithNodeDir("testdata/concurrency"),
WithExecutorOptions( WithExecutorOptions(
task.WithDir("testdata/concurrency"),
task.WithConcurrency(1), task.WithConcurrency(1),
), ),
WithPostProcessFn(PPSortedLines), WithPostProcessFn(PPSortedLines),
@@ -388,8 +410,8 @@ func TestConcurrency(t *testing.T) {
func TestParams(t *testing.T) { func TestParams(t *testing.T) {
t.Parallel() t.Parallel()
NewExecutorTest(t, NewExecutorTest(t,
WithNodeDir("testdata/params"),
WithExecutorOptions( WithExecutorOptions(
task.WithDir("testdata/params"),
task.WithSilent(true), task.WithSilent(true),
), ),
WithPostProcessFn(PPSortedLines), WithPostProcessFn(PPSortedLines),
@@ -399,15 +421,14 @@ func TestParams(t *testing.T) {
func TestDeps(t *testing.T) { func TestDeps(t *testing.T) {
t.Parallel() t.Parallel()
NewExecutorTest(t, NewExecutorTest(t,
WithNodeDir("testdata/deps"),
WithExecutorOptions( WithExecutorOptions(
task.WithDir("testdata/deps"),
task.WithSilent(true), task.WithSilent(true),
), ),
WithPostProcessFn(PPSortedLines), WithPostProcessFn(PPSortedLines),
) )
} }
// TODO: mock fs
func TestStatus(t *testing.T) { func TestStatus(t *testing.T) {
t.Parallel() t.Parallel()
@@ -430,8 +451,8 @@ func TestStatus(t *testing.T) {
// gen-foo creates foo.txt, and will always fail it's status check. // gen-foo creates foo.txt, and will always fail it's status check.
NewExecutorTest(t, NewExecutorTest(t,
WithName("run gen-foo 1 silent"), WithName("run gen-foo 1 silent"),
WithNodeDir(dir),
WithExecutorOptions( WithExecutorOptions(
task.WithDir(dir),
task.WithSilent(true), task.WithSilent(true),
), ),
WithTask("gen-foo"), WithTask("gen-foo"),
@@ -442,8 +463,8 @@ func TestStatus(t *testing.T) {
// only exists after the first run. // only exists after the first run.
NewExecutorTest(t, NewExecutorTest(t,
WithName("run gen-bar 1 silent"), WithName("run gen-bar 1 silent"),
WithNodeDir(dir),
WithExecutorOptions( WithExecutorOptions(
task.WithDir(dir),
task.WithSilent(true), task.WithSilent(true),
), ),
WithTask("gen-bar"), WithTask("gen-bar"),
@@ -452,8 +473,8 @@ func TestStatus(t *testing.T) {
// if e.Verbose is set to true. // if e.Verbose is set to true.
NewExecutorTest(t, NewExecutorTest(t,
WithName("run gen-baz silent"), WithName("run gen-baz silent"),
WithNodeDir(dir),
WithExecutorOptions( WithExecutorOptions(
task.WithDir(dir),
task.WithSilent(true), task.WithSilent(true),
), ),
WithTask("gen-silent-baz"), WithTask("gen-silent-baz"),
@@ -468,8 +489,8 @@ func TestStatus(t *testing.T) {
// Run gen-bar a second time to produce a checksum file that matches bar.txt // Run gen-bar a second time to produce a checksum file that matches bar.txt
NewExecutorTest(t, NewExecutorTest(t,
WithName("run gen-bar 2 silent"), WithName("run gen-bar 2 silent"),
WithNodeDir(dir),
WithExecutorOptions( WithExecutorOptions(
task.WithDir(dir),
task.WithSilent(true), task.WithSilent(true),
), ),
WithTask("gen-bar"), WithTask("gen-bar"),
@@ -477,8 +498,8 @@ func TestStatus(t *testing.T) {
// Run gen-bar a third time, to make sure we've triggered the status check. // Run gen-bar a third time, to make sure we've triggered the status check.
NewExecutorTest(t, NewExecutorTest(t,
WithName("run gen-bar 3 silent"), WithName("run gen-bar 3 silent"),
WithNodeDir(dir),
WithExecutorOptions( WithExecutorOptions(
task.WithDir(dir),
task.WithSilent(true), task.WithSilent(true),
), ),
WithTask("gen-bar"), WithTask("gen-bar"),
@@ -490,8 +511,8 @@ func TestStatus(t *testing.T) {
require.NoError(t, err) require.NoError(t, err)
NewExecutorTest(t, NewExecutorTest(t,
WithName("run gen-bar 4 silent"), WithName("run gen-bar 4 silent"),
WithNodeDir(dir),
WithExecutorOptions( WithExecutorOptions(
task.WithDir(dir),
task.WithSilent(true), task.WithSilent(true),
), ),
WithTask("gen-bar"), WithTask("gen-bar"),
@@ -499,56 +520,44 @@ func TestStatus(t *testing.T) {
// all: not up-to-date // all: not up-to-date
NewExecutorTest(t, NewExecutorTest(t,
WithName("run gen-foo 2"), WithName("run gen-foo 2"),
WithExecutorOptions( WithNodeDir(dir),
task.WithDir(dir),
),
WithTask("gen-foo"), WithTask("gen-foo"),
) )
// status: not up-to-date // status: not up-to-date
NewExecutorTest(t, NewExecutorTest(t,
WithName("run gen-foo 3"), WithName("run gen-foo 3"),
WithExecutorOptions( WithNodeDir(dir),
task.WithDir(dir),
),
WithTask("gen-foo"), WithTask("gen-foo"),
) )
// sources: not up-to-date // sources: not up-to-date
NewExecutorTest(t, NewExecutorTest(t,
WithName("run gen-bar 5"), WithName("run gen-bar 5"),
WithExecutorOptions( WithNodeDir(dir),
task.WithDir(dir),
),
WithTask("gen-bar"), WithTask("gen-bar"),
) )
// all: up-to-date // all: up-to-date
NewExecutorTest(t, NewExecutorTest(t,
WithName("run gen-bar 6"), WithName("run gen-bar 6"),
WithExecutorOptions( WithNodeDir(dir),
task.WithDir(dir),
),
WithTask("gen-bar"), WithTask("gen-bar"),
) )
// sources: not up-to-date, no output produced. // sources: not up-to-date, no output produced.
NewExecutorTest(t, NewExecutorTest(t,
WithName("run gen-baz 2"), WithName("run gen-baz 2"),
WithExecutorOptions( WithNodeDir(dir),
task.WithDir(dir),
),
WithTask("gen-silent-baz"), WithTask("gen-silent-baz"),
) )
// up-to-date, no output produced // up-to-date, no output produced
NewExecutorTest(t, NewExecutorTest(t,
WithName("run gen-baz 3"), WithName("run gen-baz 3"),
WithExecutorOptions( WithNodeDir(dir),
task.WithDir(dir),
),
WithTask("gen-silent-baz"), WithTask("gen-silent-baz"),
) )
// up-to-date, output produced due to Verbose mode. // up-to-date, output produced due to Verbose mode.
NewExecutorTest(t, NewExecutorTest(t,
WithName("run gen-baz 4 verbose"), WithName("run gen-baz 4 verbose"),
WithNodeDir(dir),
WithExecutorOptions( WithExecutorOptions(
task.WithDir(dir),
task.WithVerbose(true), task.WithVerbose(true),
), ),
WithTask("gen-silent-baz"), WithTask("gen-silent-baz"),
@@ -561,32 +570,24 @@ func TestPrecondition(t *testing.T) {
const dir = "testdata/precondition" const dir = "testdata/precondition"
NewExecutorTest(t, NewExecutorTest(t,
WithName("a precondition has been met"), WithName("a precondition has been met"),
WithExecutorOptions( WithNodeDir(dir),
task.WithDir(dir),
),
WithTask("foo"), WithTask("foo"),
) )
NewExecutorTest(t, NewExecutorTest(t,
WithName("a precondition was not met"), WithName("a precondition was not met"),
WithExecutorOptions( WithNodeDir(dir),
task.WithDir(dir),
),
WithTask("impossible"), WithTask("impossible"),
WithRunError(), WithRunError(),
) )
NewExecutorTest(t, NewExecutorTest(t,
WithName("precondition in dependency fails the task"), WithName("precondition in dependency fails the task"),
WithExecutorOptions( WithNodeDir(dir),
task.WithDir(dir),
),
WithTask("depends_on_impossible"), WithTask("depends_on_impossible"),
WithRunError(), WithRunError(),
) )
NewExecutorTest(t, NewExecutorTest(t,
WithName("precondition in cmd fails the task"), WithName("precondition in cmd fails the task"),
WithExecutorOptions( WithNodeDir(dir),
task.WithDir(dir),
),
WithTask("executes_failing_task_as_cmd"), WithTask("executes_failing_task_as_cmd"),
WithRunError(), WithRunError(),
) )
@@ -597,25 +598,21 @@ func TestAlias(t *testing.T) {
NewExecutorTest(t, NewExecutorTest(t,
WithName("alias"), WithName("alias"),
WithExecutorOptions( WithNodeDir("testdata/alias"),
task.WithDir("testdata/alias"),
),
WithTask("f"), WithTask("f"),
) )
NewExecutorTest(t, NewExecutorTest(t,
WithName("duplicate alias"), WithName("duplicate alias"),
WithExecutorOptions( WithNodeDir("testdata/alias"),
task.WithDir("testdata/alias"),
),
WithTask("x"), WithTask("x"),
WithRunError(), WithRunError(),
) )
NewExecutorTest(t, NewExecutorTest(t,
WithName("alias summary"), WithName("alias summary"),
WithNodeDir("testdata/alias"),
WithExecutorOptions( WithExecutorOptions(
task.WithDir("testdata/alias"),
task.WithSummary(true), task.WithSummary(true),
), ),
WithTask("f"), WithTask("f"),
@@ -627,16 +624,14 @@ func TestLabel(t *testing.T) {
NewExecutorTest(t, NewExecutorTest(t,
WithName("up to date"), WithName("up to date"),
WithExecutorOptions( WithNodeDir("testdata/label_uptodate"),
task.WithDir("testdata/label_uptodate"),
),
WithTask("foo"), WithTask("foo"),
) )
NewExecutorTest(t, NewExecutorTest(t,
WithName("summary"), WithName("summary"),
WithNodeDir("testdata/label_summary"),
WithExecutorOptions( WithExecutorOptions(
task.WithDir("testdata/label_summary"),
task.WithSummary(true), task.WithSummary(true),
), ),
WithTask("foo"), WithTask("foo"),
@@ -644,26 +639,20 @@ func TestLabel(t *testing.T) {
NewExecutorTest(t, NewExecutorTest(t,
WithName("status"), WithName("status"),
WithExecutorOptions( WithNodeDir("testdata/label_status"),
task.WithDir("testdata/label_status"),
),
WithTask("foo"), WithTask("foo"),
WithStatusError(), WithStatusError(),
) )
NewExecutorTest(t, NewExecutorTest(t,
WithName("var"), WithName("var"),
WithExecutorOptions( WithNodeDir("testdata/label_var"),
task.WithDir("testdata/label_var"),
),
WithTask("foo"), WithTask("foo"),
) )
NewExecutorTest(t, NewExecutorTest(t,
WithName("label in summary"), WithName("label in summary"),
WithExecutorOptions( WithNodeDir("testdata/label_summary"),
task.WithDir("testdata/label_summary"),
),
WithTask("foo"), WithTask("foo"),
) )
} }
@@ -690,8 +679,8 @@ func TestPromptInSummary(t *testing.T) {
opts := []ExecutorTestOption{ opts := []ExecutorTestOption{
WithName(test.name), WithName(test.name),
WithNodeDir("testdata/prompt"),
WithExecutorOptions( WithExecutorOptions(
task.WithDir("testdata/prompt"),
task.WithAssumeTerm(true), task.WithAssumeTerm(true),
), ),
WithTask("foo"), WithTask("foo"),
@@ -709,8 +698,8 @@ func TestPromptWithIndirectTask(t *testing.T) {
t.Parallel() t.Parallel()
NewExecutorTest(t, NewExecutorTest(t,
WithNodeDir("testdata/prompt"),
WithExecutorOptions( WithExecutorOptions(
task.WithDir("testdata/prompt"),
task.WithAssumeTerm(true), task.WithAssumeTerm(true),
), ),
WithTask("bar"), WithTask("bar"),
@@ -723,8 +712,8 @@ func TestPromptAssumeYes(t *testing.T) {
NewExecutorTest(t, NewExecutorTest(t,
WithName("--yes flag should skip prompt"), WithName("--yes flag should skip prompt"),
WithNodeDir("testdata/prompt"),
WithExecutorOptions( WithExecutorOptions(
task.WithDir("testdata/prompt"),
task.WithAssumeTerm(true), task.WithAssumeTerm(true),
task.WithAssumeYes(true), task.WithAssumeYes(true),
), ),
@@ -734,8 +723,8 @@ func TestPromptAssumeYes(t *testing.T) {
NewExecutorTest(t, NewExecutorTest(t,
WithName("task should raise errors.TaskCancelledError"), WithName("task should raise errors.TaskCancelledError"),
WithNodeDir("testdata/prompt"),
WithExecutorOptions( WithExecutorOptions(
task.WithDir("testdata/prompt"),
task.WithAssumeTerm(true), task.WithAssumeTerm(true),
), ),
WithTask("foo"), WithTask("foo"),
@@ -772,8 +761,8 @@ func TestForCmds(t *testing.T) {
for _, test := range tests { for _, test := range tests {
opts := []ExecutorTestOption{ opts := []ExecutorTestOption{
WithName(test.name), WithName(test.name),
WithNodeDir("testdata/for/cmds"),
WithExecutorOptions( WithExecutorOptions(
task.WithDir("testdata/for/cmds"),
task.WithSilent(true), task.WithSilent(true),
task.WithForce(true), task.WithForce(true),
), ),
@@ -815,8 +804,8 @@ func TestForDeps(t *testing.T) {
for _, test := range tests { for _, test := range tests {
opts := []ExecutorTestOption{ opts := []ExecutorTestOption{
WithName(test.name), WithName(test.name),
WithNodeDir("testdata/for/deps"),
WithExecutorOptions( WithExecutorOptions(
task.WithDir("testdata/for/deps"),
task.WithSilent(true), task.WithSilent(true),
task.WithForce(true), task.WithForce(true),
// Force output of each dep to be grouped together to prevent interleaving // Force output of each dep to be grouped together to prevent interleaving
@@ -861,8 +850,8 @@ func TestReference(t *testing.T) {
for _, test := range tests { for _, test := range tests {
NewExecutorTest(t, NewExecutorTest(t,
WithName(test.name), WithName(test.name),
WithNodeDir("testdata/var_references"),
WithExecutorOptions( WithExecutorOptions(
task.WithDir("testdata/var_references"),
task.WithSilent(true), task.WithSilent(true),
task.WithForce(true), task.WithForce(true),
), ),
@@ -929,8 +918,8 @@ func TestVarInheritance(t *testing.T) {
for _, test := range tests { for _, test := range tests {
NewExecutorTest(t, NewExecutorTest(t,
WithName(test.name), WithName(test.name),
WithNodeDir(fmt.Sprintf("testdata/var_inheritance/v3/%s", test.name)),
WithExecutorOptions( WithExecutorOptions(
task.WithDir(fmt.Sprintf("testdata/var_inheritance/v3/%s", test.name)),
task.WithSilent(true), task.WithSilent(true),
task.WithForce(true), task.WithForce(true),
), ),
@@ -944,26 +933,20 @@ func TestFuzzyModel(t *testing.T) {
NewExecutorTest(t, NewExecutorTest(t,
WithName("fuzzy"), WithName("fuzzy"),
WithExecutorOptions( WithNodeDir("testdata/fuzzy"),
task.WithDir("testdata/fuzzy"),
),
WithTask("instal"), WithTask("instal"),
WithRunError(), WithRunError(),
) )
NewExecutorTest(t, NewExecutorTest(t,
WithName("not-fuzzy"), WithName("not-fuzzy"),
WithExecutorOptions( WithNodeDir("testdata/fuzzy"),
task.WithDir("testdata/fuzzy"),
),
WithTask("install"), WithTask("install"),
) )
NewExecutorTest(t, NewExecutorTest(t,
WithName("intern"), WithName("intern"),
WithExecutorOptions( WithNodeDir("testdata/fuzzy"),
task.WithDir("testdata/fuzzy"),
),
WithTask("intern"), WithTask("intern"),
WithRunError(), WithRunError(),
) )
@@ -974,17 +957,65 @@ func TestIncludeChecksum(t *testing.T) {
NewExecutorTest(t, NewExecutorTest(t,
WithName("correct"), WithName("correct"),
WithExecutorOptions( WithNodeDir("testdata/includes_checksum/correct"),
task.WithDir("testdata/includes_checksum/correct"),
),
) )
NewExecutorTest(t, NewExecutorTest(t,
WithName("incorrect"), WithName("incorrect"),
WithExecutorOptions( WithNodeDir("testdata/includes_checksum/incorrect"),
task.WithDir("testdata/includes_checksum/incorrect"), WithReaderError(),
),
WithSetupError(),
WithFixtureTemplating(), WithFixtureTemplating(),
) )
} }
func TestWildcard(t *testing.T) {
t.Parallel()
tests := []struct {
name string
call string
wantErr bool
}{
{
name: "basic wildcard",
call: "wildcard-foo",
},
{
name: "double wildcard",
call: "foo-wildcard-bar",
},
{
name: "store wildcard",
call: "start-foo",
},
{
name: "matches exactly",
call: "matches-exactly-*",
},
{
name: "no matches",
call: "no-match",
wantErr: true,
},
{
name: "multiple matches",
call: "wildcard-foo-bar",
},
}
for _, test := range tests {
opts := []ExecutorTestOption{
WithName(test.name),
WithNodeDir("testdata/wildcards"),
WithExecutorOptions(
task.WithSilent(true),
task.WithForce(true),
),
WithTask(test.call),
}
if test.wantErr {
opts = append(opts, WithRunError())
}
NewExecutorTest(t, opts...)
}
}

View File

@@ -2,7 +2,9 @@ package task_test
import ( import (
"bytes" "bytes"
"context"
"path/filepath" "path/filepath"
"slices"
"testing" "testing"
"github.com/sebdah/goldie/v2" "github.com/sebdah/goldie/v2"
@@ -10,6 +12,7 @@ import (
"github.com/go-task/task/v3" "github.com/go-task/task/v3"
"github.com/go-task/task/v3/experiments" "github.com/go-task/task/v3/experiments"
"github.com/go-task/task/v3/taskfile"
"github.com/go-task/task/v3/taskfile/ast" "github.com/go-task/task/v3/taskfile/ast"
) )
@@ -26,12 +29,17 @@ type (
// running `task gen:fixtures`. // running `task gen:fixtures`.
FormatterTest struct { FormatterTest struct {
TaskTest TaskTest
task string task string
vars map[string]any vars map[string]any
executorOpts []task.ExecutorOption nodeDir string
listOptions task.ListOptions nodeEntrypoint string
wantSetupError bool nodeInsecure bool
wantListError bool readerOpts []taskfile.ReaderOption
executorOpts []task.ExecutorOption
listOptions task.ListOptions
wantReaderError bool
wantSetupError bool
wantListError bool
} }
) )
@@ -41,8 +49,9 @@ type (
func NewFormatterTest(t *testing.T, opts ...FormatterTestOption) { func NewFormatterTest(t *testing.T, opts ...FormatterTestOption) {
t.Helper() t.Helper()
tt := &FormatterTest{ tt := &FormatterTest{
task: "default", task: "default",
vars: map[string]any{}, vars: map[string]any{},
nodeDir: ".",
TaskTest: TaskTest{ TaskTest: TaskTest{
experiments: map[*experiments.Experiment]int{}, experiments: map[*experiments.Experiment]int{},
fixtureTemplateData: map[string]any{}, fixtureTemplateData: map[string]any{},
@@ -114,23 +123,57 @@ func (tt *FormatterTest) run(t *testing.T) {
f := func(t *testing.T) { f := func(t *testing.T) {
t.Helper() t.Helper()
var buf bytes.Buffer var buf bytes.Buffer
ctx := context.Background()
opts := append( // Create a new root node for the given entrypoint
tt.executorOpts, node, err := taskfile.NewRootNode(
task.WithStdout(&buf), tt.nodeEntrypoint,
task.WithStderr(&buf), tt.nodeDir,
tt.nodeInsecure,
) )
require.NoError(t, err)
// Set up the task executor
e := task.NewExecutor(opts...)
// Create a golden fixture file for the output // Create a golden fixture file for the output
g := goldie.New(t, g := goldie.New(t,
goldie.WithFixtureDir(filepath.Join(e.Dir, "testdata")), goldie.WithFixtureDir(filepath.Join(node.Dir(), "testdata")),
) )
// Call setup and check for errors // Set up a temporary directory for the taskfile reader and task executor
if err := e.Setup(); tt.wantSetupError { tempDir, err := task.NewTempDir(node.Dir())
require.NoError(t, err)
tt.readerOpts = append(tt.readerOpts, taskfile.WithTempDir(tempDir.Remote))
// Set up the taskfile reader
reader := taskfile.NewReader(tt.readerOpts...)
graph, err := reader.Read(ctx, node)
if tt.wantReaderError {
require.Error(t, err)
tt.writeFixtureErrReader(t, g, err)
tt.writeFixtureBuffer(t, g, buf)
return
} else {
require.NoError(t, err)
}
executorOpts := slices.Concat(
// Apply the node directory and temp directory to the executor options
// by default, but allow them to by overridden by the test options
[]task.ExecutorOption{
task.WithDir(node.Dir()),
task.WithTempDir(tempDir),
},
// Apply the executor options from the test
tt.executorOpts,
// Force the input/output streams to be set to the test buffer
[]task.ExecutorOption{
task.WithStdout(&buf),
task.WithStderr(&buf),
},
)
// Set up the task executor
executor, err := task.NewExecutor(graph, executorOpts...)
if tt.wantSetupError {
require.Error(t, err) require.Error(t, err)
tt.writeFixtureErrSetup(t, g, err) tt.writeFixtureErrSetup(t, g, err)
tt.writeFixtureBuffer(t, g, buf) tt.writeFixtureBuffer(t, g, buf)
@@ -146,7 +189,7 @@ func (tt *FormatterTest) run(t *testing.T) {
} }
// Run the formatter and check for errors // Run the formatter and check for errors
if _, err := e.ListTasks(tt.listOptions); tt.wantListError { if _, err := executor.ListTasks(tt.listOptions); tt.wantListError {
require.Error(t, err) require.Error(t, err)
tt.writeFixtureErrList(t, g, err) tt.writeFixtureErrList(t, g, err)
tt.writeFixtureBuffer(t, g, buf) tt.writeFixtureBuffer(t, g, buf)
@@ -170,9 +213,7 @@ func TestNoLabelInList(t *testing.T) {
t.Parallel() t.Parallel()
NewFormatterTest(t, NewFormatterTest(t,
WithExecutorOptions( WithNodeDir("testdata/label_list"),
task.WithDir("testdata/label_list"),
),
WithListOptions(task.ListOptions{ WithListOptions(task.ListOptions{
ListOnlyTasksWithDescriptions: true, ListOnlyTasksWithDescriptions: true,
}), }),
@@ -184,9 +225,7 @@ func TestListAllShowsNoDesc(t *testing.T) {
t.Parallel() t.Parallel()
NewFormatterTest(t, NewFormatterTest(t,
WithExecutorOptions( WithNodeDir("testdata/list_mixed_desc"),
task.WithDir("testdata/list_mixed_desc"),
),
WithListOptions(task.ListOptions{ WithListOptions(task.ListOptions{
ListAllTasks: true, ListAllTasks: true,
}), }),
@@ -198,9 +237,7 @@ func TestListCanListDescOnly(t *testing.T) {
t.Parallel() t.Parallel()
NewFormatterTest(t, NewFormatterTest(t,
WithExecutorOptions( WithNodeDir("testdata/list_mixed_desc"),
task.WithDir("testdata/list_mixed_desc"),
),
WithListOptions(task.ListOptions{ WithListOptions(task.ListOptions{
ListOnlyTasksWithDescriptions: true, ListOnlyTasksWithDescriptions: true,
}), }),
@@ -211,9 +248,7 @@ func TestListDescInterpolation(t *testing.T) {
t.Parallel() t.Parallel()
NewFormatterTest(t, NewFormatterTest(t,
WithExecutorOptions( WithNodeDir("testdata/list_desc_interpolation"),
task.WithDir("testdata/list_desc_interpolation"),
),
WithListOptions(task.ListOptions{ WithListOptions(task.ListOptions{
ListOnlyTasksWithDescriptions: true, ListOnlyTasksWithDescriptions: true,
}), }),
@@ -224,9 +259,7 @@ func TestJsonListFormat(t *testing.T) {
t.Parallel() t.Parallel()
NewFormatterTest(t, NewFormatterTest(t,
WithExecutorOptions( WithNodeDir("testdata/json_list_format"),
task.WithDir("testdata/json_list_format"),
),
WithListOptions(task.ListOptions{ WithListOptions(task.ListOptions{
FormatTaskListAsJSON: true, FormatTaskListAsJSON: true,
}), }),

View File

@@ -15,6 +15,7 @@ import (
"github.com/go-task/task/v3/experiments" "github.com/go-task/task/v3/experiments"
"github.com/go-task/task/v3/internal/env" "github.com/go-task/task/v3/internal/env"
"github.com/go-task/task/v3/internal/sort" "github.com/go-task/task/v3/internal/sort"
"github.com/go-task/task/v3/taskfile"
"github.com/go-task/task/v3/taskfile/ast" "github.com/go-task/task/v3/taskfile/ast"
) )
@@ -201,7 +202,7 @@ func Validate() error {
// WithFlags is a special internal functional option that is used to pass flags // WithFlags is a special internal functional option that is used to pass flags
// from the CLI into any constructor that accepts functional options. // from the CLI into any constructor that accepts functional options.
func WithFlags() task.ExecutorOption { func WithFlags() *flagsOption {
return &flagsOption{} return &flagsOption{}
} }
@@ -228,14 +229,8 @@ func (o *flagsOption) ApplyToExecutor(e *task.Executor) {
e.Options( e.Options(
task.WithDir(dir), task.WithDir(dir),
task.WithEntrypoint(Entrypoint),
task.WithForce(Force), task.WithForce(Force),
task.WithForceAll(ForceAll), task.WithForceAll(ForceAll),
task.WithInsecure(Insecure),
task.WithDownload(Download),
task.WithOffline(Offline),
task.WithTimeout(Timeout),
task.WithCacheExpiryDuration(CacheExpiryDuration),
task.WithWatch(Watch), task.WithWatch(Watch),
task.WithVerbose(Verbose), task.WithVerbose(Verbose),
task.WithSilent(Silent), task.WithSilent(Silent),
@@ -251,3 +246,12 @@ func (o *flagsOption) ApplyToExecutor(e *task.Executor) {
task.WithVersionCheck(true), task.WithVersionCheck(true),
) )
} }
func (o *flagsOption) ApplyToReader(r *taskfile.Reader) {
r.Options(
taskfile.WithInsecure(Insecure),
taskfile.WithDownload(Download),
taskfile.WithOffline(Offline),
taskfile.WithCacheExpiryDuration(CacheExpiryDuration),
)
}

File diff suppressed because it is too large Load Diff

View File

@@ -3,7 +3,6 @@ package taskfile
import ( import (
"context" "context"
"strings" "strings"
"time"
giturls "github.com/chainguard-dev/git-urls" giturls "github.com/chainguard-dev/git-urls"
@@ -33,7 +32,6 @@ func NewRootNode(
entrypoint string, entrypoint string,
dir string, dir string,
insecure bool, insecure bool,
timeout time.Duration,
) (Node, error) { ) (Node, error) {
dir = fsext.DefaultDir(entrypoint, dir) dir = fsext.DefaultDir(entrypoint, dir)
// If the entrypoint is "-", we read from stdin // If the entrypoint is "-", we read from stdin

78
temp_dir.go Normal file
View File

@@ -0,0 +1,78 @@
package task
import (
"path/filepath"
"strings"
"github.com/go-task/task/v3/internal/env"
"github.com/go-task/task/v3/internal/execext"
"github.com/go-task/task/v3/internal/filepathext"
)
type TempDir struct {
Remote string
Fingerprint string
}
func NewTempDir(dir string) (*TempDir, error) {
tempDir, err := setupTempDirFingerprint(dir)
if err != nil {
return nil, err
}
err = setupTempDirRemote(dir, tempDir)
if err != nil {
return nil, err
}
return tempDir, nil
}
func setupTempDirFingerprint(dir string) (*TempDir, error) {
tempDir := env.GetTaskEnv("TEMP_DIR")
if tempDir == "" {
return &TempDir{
Remote: filepathext.SmartJoin(dir, ".task"),
Fingerprint: filepathext.SmartJoin(dir, ".task"),
}, nil
}
if filepath.IsAbs(tempDir) || strings.HasPrefix(tempDir, "~") {
tempDir, err := execext.ExpandLiteral(tempDir)
if err != nil {
return nil, err
}
projectDir, _ := filepath.Abs(dir)
projectName := filepath.Base(projectDir)
return &TempDir{
Remote: tempDir,
Fingerprint: filepathext.SmartJoin(tempDir, projectName),
}, nil
}
return &TempDir{
Remote: filepathext.SmartJoin(dir, tempDir),
Fingerprint: filepathext.SmartJoin(dir, tempDir),
}, nil
}
func setupTempDirRemote(dir string, tempDir *TempDir) error {
remoteDir := env.GetTaskEnv("REMOTE_DIR")
if remoteDir == "" {
return nil
}
if filepath.IsAbs(remoteDir) || strings.HasPrefix(remoteDir, "~") {
remoteTempDir, err := execext.ExpandLiteral(remoteDir)
if err != nil {
return err
}
tempDir.Remote = remoteTempDir
return nil
}
tempDir.Remote = filepathext.SmartJoin(dir, ".task")
return nil
}

View File

@@ -0,0 +1 @@
Hello foo

View File

@@ -0,0 +1 @@
Hello foo bar

View File

@@ -0,0 +1 @@
I don't consume matches: []

View File

@@ -0,0 +1 @@
Hello foo-bar

View File

@@ -0,0 +1 @@
task: Task "no-match" does not exist

View File

@@ -0,0 +1 @@
task: No tasks with description available. Try --list-all to list all tasks

View File

@@ -0,0 +1 @@
Starting foo