Compare commits

...

9 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
Valentin Maerten
4bdfe5ce3b fix: publish nightly draft and title (#2358) 2025-08-09 16:03:46 +02:00
Valentin Maerten
26ef693417 chore: publish nightly (#2246)
Co-authored-by: Andrey Nering <andreynering@users.noreply.github.com>
2025-08-06 20:29:36 +02:00
renovate[bot]
952f32d388 chore(deps): update all non-major dependencies (#2351)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-08-06 10:51:19 -03:00
Andrey Nering
e72c35f79f fix(goreleaser): fix automatic submission of winget pr 2025-07-28 10:59:45 -03:00
35 changed files with 2779 additions and 2675 deletions

29
.github/workflows/release-nightly.yml vendored Normal file
View File

@@ -0,0 +1,29 @@
name: Realease nightly
on:
workflow_dispatch:
schedule:
- cron: 0 0 * * *
jobs:
goreleaser:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Set up Go
uses: actions/setup-go@v5
with:
go-version: 1.24.x
- name: Run GoReleaser
uses: goreleaser/goreleaser-action@v6
with:
distribution: goreleaser-pro
version: latest
args: release --clean --nightly -f .goreleaser-nightly.yml
env:
GITHUB_TOKEN: ${{secrets.GH_PAT}}
GORELEASER_KEY: ${{secrets.GORELEASER_KEY}}

View File

@@ -24,7 +24,7 @@ jobs:
with: with:
distribution: goreleaser-pro distribution: goreleaser-pro
version: latest version: latest
args: release --clean args: release --clean --draft
env: env:
GITHUB_TOKEN: ${{secrets.GH_PAT}} GITHUB_TOKEN: ${{secrets.GH_PAT}}
GORELEASER_KEY: ${{secrets.GORELEASER_KEY}} GORELEASER_KEY: ${{secrets.GORELEASER_KEY}}

15
.goreleaser-nightly.yml Normal file
View File

@@ -0,0 +1,15 @@
# yaml-language-server: $schema=https://goreleaser.com/static/schema.json
version: 2
pro: true
release:
name_template: 'v{{.Version}}'
nightly:
publish_release: true
keep_single_release: true
version_template: "{{incminor .Version}}-nightly"
includes:
- from_file:
path: ./.goreleaser.yml

View File

@@ -30,13 +30,14 @@ builds:
flags: flags:
- -trimpath - -trimpath
ldflags: ldflags:
- -s -w # Don't set main.version. - "-s -w"
- "{{if .IsNightly}}-X github.com/go-task/task/v3/internal/version.version={{.Version}}{{end}}"
gomod: gomod:
proxy: true proxy: true
archives: archives:
- name_template: "{{.Binary}}_{{.Os}}_{{.Arch}}" - name_template: '{{.Binary}}_{{.Os}}_{{.Arch}}'
files: files:
- README.md - README.md
- LICENSE - LICENSE
@@ -45,14 +46,15 @@ archives:
- goos: windows - goos: windows
formats: [zip] formats: [zip]
release: git:
draft: true ignore_tags:
- "{{if not .IsNightly}}nightly{{end}}"
snapshot: snapshot:
version_template: "{{.Version}}" version_template: '{{.Version}}'
checksum: checksum:
name_template: "task_checksums.txt" name_template: 'task_checksums.txt'
nfpms: nfpms:
- vendor: Task - vendor: Task
@@ -66,7 +68,7 @@ nfpms:
formats: formats:
- deb - deb
- rpm - rpm
file_name_template: "{{.ProjectName}}_{{.Os}}_{{.Arch}}" file_name_template: '{{.ProjectName}}_{{.Os}}_{{.Arch}}'
contents: contents:
- src: completion/bash/task.bash - src: completion/bash/task.bash
dst: /etc/bash_completion.d/task dst: /etc/bash_completion.d/task
@@ -84,8 +86,7 @@ brews:
repository: repository:
owner: go-task owner: go-task
name: homebrew-tap name: homebrew-tap
test: test: system "#{bin}/task", "--help"
system "#{bin}/task", "--help"
install: |- install: |-
bin.install "task" bin.install "task"
bash_completion.install "completion/bash/task.bash" => "task" bash_completion.install "completion/bash/task.bash" => "task"
@@ -108,7 +109,7 @@ winget:
commit_author: commit_author:
name: task-bot name: task-bot
email: 106601941+task-bot@users.noreply.github.com email: 106601941+task-bot@users.noreply.github.com
commit_msg_template: "chore: bump {{.PackageIdentifier}} to {{.Tag}}" commit_msg_template: 'chore: release {{.PackageIdentifier}} {{.Tag}}'
release_notes_url: https://github.com/go-task/task/releases/tag/{{.Tag}} release_notes_url: https://github.com/go-task/task/releases/tag/{{.Tag}}
tags: tags:
- build - build
@@ -122,13 +123,15 @@ winget:
- task-runner - task-runner
- taskfile - taskfile
- tool - tool
skip_upload: true
repository: repository:
owner: microsoft
name: winget-pkgs
pull_request:
enabled: true
base:
owner: go-task owner: go-task
name: winget-pkgs name: winget-pkgs
branch: "bump-task-to-{{.Tag}}" branch: 'chore/task-{{.Version}}'
pull_request:
enabled: true
draft: false
check_boxes: true
base:
owner: microsoft
name: winget-pkgs
branch: master

2
.nvmrc
View File

@@ -1 +1 @@
22.17.1 22.18.0

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"
@@ -27,15 +28,9 @@ type (
Executor struct { Executor struct {
// Flags // Flags
Dir string Dir string
Entrypoint string TempDir *TempDir
TempDir TempDir
Force bool Force bool
ForceAll bool ForceAll bool
Insecure bool
Download bool
Offline bool
Timeout time.Duration
CacheExpiryDuration time.Duration
Watch bool Watch bool
Verbose bool Verbose bool
Silent bool Silent bool
@@ -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) {
absDir, err := filepath.Abs(o.dir)
if err != nil {
e.Dir = o.dir e.Dir = o.dir
} return
}
// WithEntrypoint sets the entrypoint (main Taskfile) of the [Executor]. By e.Dir = absDir
// default, Task will search for one of the default Taskfiles in the given
// directory.
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
@@ -49,6 +56,7 @@ func NewExecutorTest(t *testing.T, opts ...ExecutorTestOption) {
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,
// Force the input/output streams to be set to the test buffer
[]task.ExecutorOption{
task.WithStdout(&buf), task.WithStdout(&buf),
task.WithStderr(&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"
) )
@@ -28,8 +31,13 @@ type (
TaskTest TaskTest
task string task string
vars map[string]any vars map[string]any
nodeDir string
nodeEntrypoint string
nodeInsecure bool
readerOpts []taskfile.ReaderOption
executorOpts []task.ExecutorOption executorOpts []task.ExecutorOption
listOptions task.ListOptions listOptions task.ListOptions
wantReaderError bool
wantSetupError bool wantSetupError bool
wantListError bool wantListError bool
} }
@@ -43,6 +51,7 @@ func NewFormatterTest(t *testing.T, opts ...FormatterTestOption) {
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,
}), }),

2
go.mod
View File

@@ -5,7 +5,7 @@ go 1.23.0
require ( require (
github.com/Ladicle/tabwriter v1.0.0 github.com/Ladicle/tabwriter v1.0.0
github.com/Masterminds/semver/v3 v3.4.0 github.com/Masterminds/semver/v3 v3.4.0
github.com/alecthomas/chroma/v2 v2.19.0 github.com/alecthomas/chroma/v2 v2.20.0
github.com/chainguard-dev/git-urls v1.0.2 github.com/chainguard-dev/git-urls v1.0.2
github.com/davecgh/go-spew v1.1.1 github.com/davecgh/go-spew v1.1.1
github.com/dominikbraun/graph v0.23.0 github.com/dominikbraun/graph v0.23.0

2
go.sum
View File

@@ -13,6 +13,8 @@ github.com/alecthomas/assert/v2 v2.11.0 h1:2Q9r3ki8+JYXvGsDyBXwH3LcJ+WK5D0gc5E8v
github.com/alecthomas/assert/v2 v2.11.0/go.mod h1:Bze95FyfUr7x34QZrjL+XP+0qgp/zg8yS+TtBj1WA3k= github.com/alecthomas/assert/v2 v2.11.0/go.mod h1:Bze95FyfUr7x34QZrjL+XP+0qgp/zg8yS+TtBj1WA3k=
github.com/alecthomas/chroma/v2 v2.19.0 h1:Im+SLRgT8maArxv81mULDWN8oKxkzboH07CHesxElq4= github.com/alecthomas/chroma/v2 v2.19.0 h1:Im+SLRgT8maArxv81mULDWN8oKxkzboH07CHesxElq4=
github.com/alecthomas/chroma/v2 v2.19.0/go.mod h1:RVX6AvYm4VfYe/zsk7mjHueLDZor3aWCNE14TFlepBk= github.com/alecthomas/chroma/v2 v2.19.0/go.mod h1:RVX6AvYm4VfYe/zsk7mjHueLDZor3aWCNE14TFlepBk=
github.com/alecthomas/chroma/v2 v2.20.0 h1:sfIHpxPyR07/Oylvmcai3X/exDlE8+FA820NTz+9sGw=
github.com/alecthomas/chroma/v2 v2.20.0/go.mod h1:e7tViK0xh/Nf4BYHl00ycY6rV7b8iXBksI9E359yNmA=
github.com/alecthomas/repr v0.4.0 h1:GhI2A8MACjfegCPVq9f1FLvIBS+DrQ2KQBFZP1iFzXc= github.com/alecthomas/repr v0.4.0 h1:GhI2A8MACjfegCPVq9f1FLvIBS+DrQ2KQBFZP1iFzXc=
github.com/alecthomas/repr v0.4.0/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4= github.com/alecthomas/repr v0.4.0/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4=
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFIImctFaOjnTIavg87rW78vTPkQqLI8= github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFIImctFaOjnTIavg87rW78vTPkQqLI8=

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

View File

@@ -2445,9 +2445,9 @@
"@types/react" "*" "@types/react" "*"
"@types/react@*", "@types/react@^19.0.0": "@types/react@*", "@types/react@^19.0.0":
version "19.1.8" version "19.1.9"
resolved "https://registry.yarnpkg.com/@types/react/-/react-19.1.8.tgz#ff8395f2afb764597265ced15f8dddb0720ae1c3" resolved "https://registry.yarnpkg.com/@types/react/-/react-19.1.9.tgz#f42b24f35474566a39b5c3a98e4d0c425b79a849"
integrity sha512-AwAfQ2Wa5bCx9WP8nZL2uMZWod7J7/JSplxbTmBQ5ms6QpqNYm672H0Vu9ZVKVngQ+ii4R/byguVEUZQyeg44g== integrity sha512-WmdoynAX8Stew/36uTSVMcLJJ1KRh6L3IZRx1PZ7qJtBqT3dYTgyDTx8H1qoRghErydW7xw9mSJ3wS//tCRpFA==
dependencies: dependencies:
csstype "^3.0.2" csstype "^3.0.2"
@@ -7219,9 +7219,9 @@ rc@1.2.8:
strip-json-comments "~2.0.1" strip-json-comments "~2.0.1"
react-dom@^19.0.0: react-dom@^19.0.0:
version "19.1.0" version "19.1.1"
resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-19.1.0.tgz#133558deca37fa1d682708df8904b25186793623" resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-19.1.1.tgz#2daa9ff7f3ae384aeb30e76d5ee38c046dc89893"
integrity sha512-Xs1hdnE+DyKgeHJeJznQmYMIBG3TKIHJJT95Q58nHLSrElKlGQqDTR2HQ9fx5CN/Gk6Vh/kupBTDLU11/nDk/g== integrity sha512-Dlq/5LAZgF0Gaz6yiqZCf6VCcZs1ghAJyrsu84Q/GT0gV+mCxbfmKNoGRKBYMJ8IEdGPqu49YWXD02GCknEDkw==
dependencies: dependencies:
scheduler "^0.26.0" scheduler "^0.26.0"
@@ -7301,9 +7301,9 @@ react-router@5.3.4, react-router@^5.3.4:
tiny-warning "^1.0.0" tiny-warning "^1.0.0"
react@^19.0.0: react@^19.0.0:
version "19.1.0" version "19.1.1"
resolved "https://registry.yarnpkg.com/react/-/react-19.1.0.tgz#926864b6c48da7627f004795d6cce50e90793b75" resolved "https://registry.yarnpkg.com/react/-/react-19.1.1.tgz#06d9149ec5e083a67f9a1e39ce97b06a03b644af"
integrity sha512-FS+XFBNvn3GTAWq26joslQgWNoFu08F4kl0J4CgdNKADkdSGXQyTCnKteIAJy96Br6YbpEU1LSzV5dYtjMkMDg== integrity sha512-w8nqGImo45dmMIfljjMwOGtbmC/mk4CMYhWIicdSflH91J9TyCyczcPFXJzrZ/ZXcgGRFeP6BU0BEJTw6tZdfQ==
readable-stream@^2.0.1: readable-stream@^2.0.1:
version "2.3.8" version "2.3.8"
@@ -8278,9 +8278,9 @@ typedarray-to-buffer@^3.1.5:
is-typedarray "^1.0.0" is-typedarray "^1.0.0"
typescript@^5.3.3: typescript@^5.3.3:
version "5.8.3" version "5.9.2"
resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.8.3.tgz#92f8a3e5e3cf497356f4178c34cd65a7f5e8440e" resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.9.2.tgz#d93450cddec5154a2d5cabe3b8102b83316fb2a6"
integrity sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ== integrity sha512-CWBzXQrc/qOkhidw1OzBTQuYRbfyxDXJMVJ1XNwUHGROVmuaeiEm3OslpZ1RV96d7SKKjZKrSJu3+t/xlw3R9A==
undici-types@~6.21.0: undici-types@~6.21.0:
version "6.21.0" version "6.21.0"