2017-03-15 21:15:27 -03:00
package task_test
import (
2017-05-17 14:37:11 -03:00
"bytes"
2019-02-09 10:16:13 -02:00
"context"
2017-07-12 11:30:28 +02:00
"fmt"
2021-12-04 17:37:52 +02:00
"io"
2025-07-23 21:57:25 +01:00
"maps"
2017-03-15 21:15:27 -03:00
"os"
2025-03-31 17:53:58 +01:00
"slices"
"sort"
2017-03-25 10:51:30 -03:00
"strings"
2024-03-10 17:21:50 +00:00
"sync"
2017-03-15 21:15:27 -03:00
"testing"
2017-06-16 11:24:01 -03:00
2025-03-31 17:53:58 +01:00
"github.com/sebdah/goldie/v2"
2021-01-07 11:48:33 -03:00
"github.com/stretchr/testify/assert"
2023-04-06 11:18:41 +01:00
"github.com/stretchr/testify/require"
2021-01-07 11:48:33 -03:00
2020-08-16 15:48:19 -03:00
"github.com/go-task/task/v3"
2025-04-21 13:55:24 -03:00
"github.com/go-task/task/v3/experiments"
2022-08-06 18:19:07 -03:00
"github.com/go-task/task/v3/internal/filepathext"
2025-07-24 14:52:36 +00:00
"github.com/go-task/task/v3/taskfile"
2017-03-15 21:15:27 -03:00
)
2021-05-30 22:48:48 -03:00
func init ( ) {
_ = os . Setenv ( "NO_COLOR" , "1" )
}
2025-03-31 17:53:58 +01:00
type (
TestOption interface {
ExecutorTestOption
FormatterTestOption
}
TaskTest struct {
2025-07-23 21:57:25 +01:00
name string
experiments map [ * experiments . Experiment ] int
postProcessFns [ ] PostProcessFn
fixtureTemplateData map [ string ] any
fixtureTemplatingEnabled bool
2025-03-31 17:53:58 +01:00
}
)
// goldenFileName makes the file path for fixture files safe for all well-known
// operating systems. Windows in particular has a lot of restrictions the
// characters that can be used in file paths.
func goldenFileName ( t * testing . T ) string {
t . Helper ( )
name := t . Name ( )
for _ , c := range [ ] string { ` ` , ` < ` , ` > ` , ` : ` , ` " ` , ` / ` , ` \ ` , ` | ` , ` ? ` , ` * ` } {
name = strings . ReplaceAll ( name , c , "-" )
}
return name
}
// writeFixture writes a fixture file for the test. The fixture file is created
// using the [goldie.Goldie] package. The fixture file is created with the
// output of the task, after any post-process functions have been applied.
func ( tt * TaskTest ) writeFixture (
t * testing . T ,
g * goldie . Goldie ,
goldenFileSuffix string ,
b [ ] byte ,
) {
t . Helper ( )
// Apply any post-process functions
for _ , fn := range tt . postProcessFns {
b = fn ( t , b )
}
// Write the fixture file
goldenFileName := goldenFileName ( t )
if goldenFileSuffix != "" {
goldenFileName += "-" + goldenFileSuffix
}
2025-07-23 21:57:25 +01:00
// Create a set of data to be made available to every test fixture
wd , err := os . Getwd ( )
require . NoError ( t , err )
if tt . fixtureTemplatingEnabled {
fixtureTemplateData := map [ string ] any {
"TEST_NAME" : t . Name ( ) ,
"TEST_DIR" : wd ,
}
// If the test has additional template data, copy it into the map
if tt . fixtureTemplateData != nil {
maps . Copy ( fixtureTemplateData , tt . fixtureTemplateData )
}
g . AssertWithTemplate ( t , goldenFileName , fixtureTemplateData , b )
2025-05-20 22:37:57 +02:00
} else {
g . Assert ( t , goldenFileName , b )
}
2025-03-31 17:53:58 +01:00
}
// writeFixtureBuffer is a wrapper for writing the main output of the task to a
// fixture file.
func ( tt * TaskTest ) writeFixtureBuffer (
t * testing . T ,
g * goldie . Goldie ,
buff bytes . Buffer ,
) {
t . Helper ( )
tt . writeFixture ( t , g , "" , buff . Bytes ( ) )
}
2025-07-24 14:27:16 +00:00
// writeFixtureErrSetup is a wrapper for writing the output of an error during
// the setup phase of the task to a fixture file.
func ( tt * TaskTest ) writeFixtureErrReader (
t * testing . T ,
g * goldie . Goldie ,
err error ,
) {
t . Helper ( )
tt . writeFixture ( t , g , "err-reader" , [ ] byte ( err . Error ( ) ) )
}
2025-03-31 17:53:58 +01:00
// writeFixtureErrSetup is a wrapper for writing the output of an error during
// the setup phase of the task to a fixture file.
func ( tt * TaskTest ) writeFixtureErrSetup (
t * testing . T ,
g * goldie . Goldie ,
err error ,
) {
t . Helper ( )
tt . writeFixture ( t , g , "err-setup" , [ ] byte ( err . Error ( ) ) )
}
// Functional options
// WithName gives the test fixture output a name. This should be used when
// running multiple tests in a single test function.
func WithName ( name string ) TestOption {
return & nameTestOption { name : name }
}
type nameTestOption struct {
name string
}
func ( opt * nameTestOption ) applyToExecutorTest ( t * ExecutorTest ) {
t . name = opt . name
}
func ( opt * nameTestOption ) applyToFormatterTest ( t * FormatterTest ) {
t . name = opt . name
}
2025-07-24 14:27:16 +00:00
// WithNodeDir sets the directory to be used for the test node.
func WithNodeDir ( dir string ) TestOption {
return & nodeDirTestOption { dir : dir }
}
type nodeDirTestOption struct {
dir string
}
func ( opt * nodeDirTestOption ) applyToExecutorTest ( t * ExecutorTest ) {
t . nodeDir = opt . dir
}
func ( opt * nodeDirTestOption ) applyToFormatterTest ( t * FormatterTest ) {
t . nodeDir = opt . dir
}
// WithNodeEntrypoint sets the entrypoint to be used for the test node.
func WithNodeEntrypoint ( entrypoint string ) TestOption {
return & nodeEntrypointTestOption { entrypoint : entrypoint }
}
type nodeEntrypointTestOption struct {
entrypoint string
}
func ( opt * nodeEntrypointTestOption ) applyToExecutorTest ( t * ExecutorTest ) {
t . nodeEntrypoint = opt . entrypoint
}
func ( opt * nodeEntrypointTestOption ) applyToFormatterTest ( t * FormatterTest ) {
t . nodeEntrypoint = opt . entrypoint
}
// WithNodeInsecure sets the insecure flag to be used for the test node.
func WithNodeInsecure ( insecure bool ) TestOption {
return & nodeInsecureTestOption { insecure : insecure }
}
type nodeInsecureTestOption struct {
insecure bool
}
func ( opt * nodeInsecureTestOption ) applyToExecutorTest ( t * ExecutorTest ) {
t . nodeInsecure = opt . insecure
}
func ( opt * nodeInsecureTestOption ) applyToFormatterTest ( t * FormatterTest ) {
t . nodeInsecure = opt . insecure
}
2025-03-31 17:53:58 +01:00
// WithTask sets the name of the task to run. This should be used when the task
// to run is not the default task.
func WithTask ( task string ) TestOption {
return & taskTestOption { task : task }
}
type taskTestOption struct {
task string
}
func ( opt * taskTestOption ) applyToExecutorTest ( t * ExecutorTest ) {
t . task = opt . task
}
func ( opt * taskTestOption ) applyToFormatterTest ( t * FormatterTest ) {
t . task = opt . task
}
// WithVar sets a variable to be passed to the task. This can be called multiple
// times to set more than one variable.
func WithVar ( key string , value any ) TestOption {
return & varTestOption { key : key , value : value }
}
type varTestOption struct {
key string
value any
}
func ( opt * varTestOption ) applyToExecutorTest ( t * ExecutorTest ) {
t . vars [ opt . key ] = opt . value
}
func ( opt * varTestOption ) applyToFormatterTest ( t * FormatterTest ) {
t . vars [ opt . key ] = opt . value
}
// WithExecutorOptions sets the [task.ExecutorOption]s to be used when creating
// a [task.Executor].
func WithExecutorOptions ( executorOpts ... task . ExecutorOption ) TestOption {
return & executorOptionsTestOption { executorOpts : executorOpts }
}
type executorOptionsTestOption struct {
executorOpts [ ] task . ExecutorOption
}
func ( opt * executorOptionsTestOption ) applyToExecutorTest ( t * ExecutorTest ) {
t . executorOpts = slices . Concat ( t . executorOpts , opt . executorOpts )
}
func ( opt * executorOptionsTestOption ) applyToFormatterTest ( t * FormatterTest ) {
t . executorOpts = slices . Concat ( t . executorOpts , opt . executorOpts )
}
// WithExperiment sets an experiment to be enabled for the test. This can be
// called multiple times to enable more than one experiment.
func WithExperiment ( experiment * experiments . Experiment , value int ) TestOption {
return & experimentTestOption { experiment : experiment , value : value }
}
type experimentTestOption struct {
experiment * experiments . Experiment
value int
}
func ( opt * experimentTestOption ) applyToExecutorTest ( t * ExecutorTest ) {
t . experiments [ opt . experiment ] = opt . value
}
func ( opt * experimentTestOption ) applyToFormatterTest ( t * FormatterTest ) {
t . experiments [ opt . experiment ] = opt . value
}
// WithPostProcessFn adds a [PostProcessFn] function to the test. Post-process
// functions are run on the output of the task before a fixture is created. This
// can be used to remove absolute paths, sort lines, etc. This can be called
// multiple times to add more than one post-process function.
func WithPostProcessFn ( fn PostProcessFn ) TestOption {
return & postProcessFnTestOption { fn : fn }
}
type postProcessFnTestOption struct {
fn PostProcessFn
}
func ( opt * postProcessFnTestOption ) applyToExecutorTest ( t * ExecutorTest ) {
t . postProcessFns = append ( t . postProcessFns , opt . fn )
}
func ( opt * postProcessFnTestOption ) applyToFormatterTest ( t * FormatterTest ) {
t . postProcessFns = append ( t . postProcessFns , opt . fn )
}
2025-07-24 14:27:16 +00:00
// WithReaderError sets the test to expect an error during the reader phase of the
// task execution. A fixture will be created with the output of any errors.
func WithReaderError ( ) TestOption {
return & readerErrorTestOption { }
}
type readerErrorTestOption struct { }
func ( opt * readerErrorTestOption ) applyToExecutorTest ( t * ExecutorTest ) {
t . wantReaderError = true
}
func ( opt * readerErrorTestOption ) applyToFormatterTest ( t * FormatterTest ) {
t . wantReaderError = true
}
2025-03-31 17:53:58 +01:00
// WithSetupError sets the test to expect an error during the setup phase of the
// task execution. A fixture will be created with the output of any errors.
func WithSetupError ( ) TestOption {
return & setupErrorTestOption { }
}
type setupErrorTestOption struct { }
func ( opt * setupErrorTestOption ) applyToExecutorTest ( t * ExecutorTest ) {
t . wantSetupError = true
}
func ( opt * setupErrorTestOption ) applyToFormatterTest ( t * FormatterTest ) {
t . wantSetupError = true
}
2025-07-23 21:57:25 +01:00
// WithFixtureTemplating enables templating for the golden fixture files with
// the default set of data. This is useful if the golden file is dynamic in some
// way (e.g. contains user-specific directories). To add more data, see
// WithFixtureTemplateData.
func WithFixtureTemplating ( ) TestOption {
return & fixtureTemplatingTestOption { }
}
type fixtureTemplatingTestOption struct { }
func ( opt * fixtureTemplatingTestOption ) applyToExecutorTest ( t * ExecutorTest ) {
t . fixtureTemplatingEnabled = true
}
func ( opt * fixtureTemplatingTestOption ) applyToFormatterTest ( t * FormatterTest ) {
t . fixtureTemplatingEnabled = true
}
// WithFixtureTemplateData adds data to the golden fixture file templates. Keys
// given here will override any existing values. This option will also enable
// global templating, so you do not need to call WithFixtureTemplating as well.
func WithFixtureTemplateData ( key string , value any ) TestOption {
return & fixtureTemplateDataTestOption { key , value }
2025-05-20 22:37:57 +02:00
}
type fixtureTemplateDataTestOption struct {
2025-07-23 21:57:25 +01:00
k string
v any
2025-05-20 22:37:57 +02:00
}
func ( opt * fixtureTemplateDataTestOption ) applyToExecutorTest ( t * ExecutorTest ) {
2025-07-23 21:57:25 +01:00
t . fixtureTemplatingEnabled = true
t . fixtureTemplateData [ opt . k ] = opt . v
2025-05-20 22:37:57 +02:00
}
func ( opt * fixtureTemplateDataTestOption ) applyToFormatterTest ( t * FormatterTest ) {
2025-07-23 21:57:25 +01:00
t . fixtureTemplatingEnabled = true
t . fixtureTemplateData [ opt . k ] = opt . v
2025-05-20 22:37:57 +02:00
}
2025-03-31 17:53:58 +01:00
// Post-processing
// A PostProcessFn is a function that can be applied to the output of a test
// fixture before the file is written.
type PostProcessFn func ( * testing . T , [ ] byte ) [ ] byte
// PPSortedLines sorts the lines of the output of the task. This is useful when
// the order of the output is not important, but the output is expected to be
// the same each time the task is run (e.g. when running tasks in parallel).
func PPSortedLines ( t * testing . T , b [ ] byte ) [ ] byte {
t . Helper ( )
lines := strings . Split ( strings . TrimSpace ( string ( b ) ) , "\n" )
sort . Strings ( lines )
return [ ] byte ( strings . Join ( lines , "\n" ) + "\n" )
}
2024-03-10 17:21:50 +00:00
// SyncBuffer is a threadsafe buffer for testing.
// Some times replace stdout/stderr with a buffer to capture output.
// stdout and stderr are threadsafe, but a regular bytes.Buffer is not.
// Using this instead helps prevents race conditions with output.
type SyncBuffer struct {
buf bytes . Buffer
mu sync . Mutex
}
func ( sb * SyncBuffer ) Write ( p [ ] byte ) ( n int , err error ) {
sb . mu . Lock ( )
defer sb . mu . Unlock ( )
return sb . buf . Write ( p )
}
2017-07-20 09:05:37 +02:00
// fileContentTest provides a basic reusable test-case for running a Taskfile
// and inspect generated files.
type fileContentTest struct {
2021-12-04 17:37:52 +02:00
Dir string
Entrypoint string
Target string
TrimSpace bool
Files map [ string ] string
2017-07-20 09:05:37 +02:00
}
func ( fct fileContentTest ) name ( file string ) string {
return fmt . Sprintf ( "target=%q,file=%q" , fct . Target , file )
}
2025-07-24 14:52:36 +00:00
func deprecatedExecutor (
t * testing . T ,
entrypoint string ,
dir string ,
insecure bool ,
readerOpts [ ] taskfile . ReaderOption ,
opts ... task . ExecutorOption ,
) ( * task . Executor , error ) {
ctx := context . Background ( )
// Create a new root node for the given entrypoint
node , err := taskfile . NewRootNode (
entrypoint ,
dir ,
insecure ,
)
require . NoError ( t , err )
// Set up a temporary directory for the taskfile reader and task executor
tempDir , err := task . NewTempDir ( node . Dir ( ) )
require . NoError ( t , err )
// Set up the taskfile reader
readerOpts = append ( readerOpts , taskfile . WithTempDir ( tempDir . Remote ) )
reader := taskfile . NewReader ( readerOpts ... )
graph , err := reader . Read ( ctx , node )
require . NoError ( t , err )
// Set up the task executor
return task . NewExecutor ( graph , opts ... )
}
2017-07-20 09:05:37 +02:00
func ( fct fileContentTest ) Run ( t * testing . T ) {
2024-12-12 01:47:10 +01:00
t . Helper ( )
2017-07-20 09:05:37 +02:00
for f := range fct . Files {
2022-08-06 18:19:07 -03:00
_ = os . Remove ( filepathext . SmartJoin ( fct . Dir , f ) )
2017-07-20 09:05:37 +02:00
}
2025-03-10 20:38:25 +00:00
2025-07-24 14:52:36 +00:00
e , err := deprecatedExecutor ( t ,
fct . Entrypoint ,
fct . Dir ,
false ,
[ ] taskfile . ReaderOption { } ,
2025-03-31 21:49:00 +01:00
task . WithDir ( fct . Dir ) ,
2025-07-24 14:52:36 +00:00
task . WithTempDir ( & task . TempDir {
2024-06-28 18:01:11 +02:00
Remote : filepathext . SmartJoin ( fct . Dir , ".task" ) ,
Fingerprint : filepathext . SmartJoin ( fct . Dir , ".task" ) ,
2025-03-10 20:38:25 +00:00
} ) ,
2025-03-31 21:49:00 +01:00
task . WithStdout ( io . Discard ) ,
task . WithStderr ( io . Discard ) ,
2025-03-10 20:38:25 +00:00
)
2024-07-17 00:44:34 +02:00
2025-07-24 14:52:36 +00:00
require . NoError ( t , err , "e.Setup()" )
2025-02-23 18:30:42 +00:00
require . NoError ( t , e . Run ( context . Background ( ) , & task . Call { Task : fct . Target } ) , "e.Run(target)" )
2017-07-20 09:05:37 +02:00
for name , expectContent := range fct . Files {
t . Run ( fct . name ( name ) , func ( t * testing . T ) {
2024-02-13 01:07:00 +00:00
path := filepathext . SmartJoin ( e . Dir , name )
2022-07-26 10:10:16 +12:00
b , err := os . ReadFile ( path )
2023-04-06 11:18:41 +01:00
require . NoError ( t , err , "Error reading file" )
2017-07-20 09:05:37 +02:00
s := string ( b )
if fct . TrimSpace {
s = strings . TrimSpace ( s )
}
2022-07-26 10:10:16 +12:00
assert . Equal ( t , expectContent , s , "unexpected file content in %s" , path )
2017-07-20 09:05:37 +02:00
} )
}
}
2025-07-24 14:52:36 +00:00
// func TestGenerates(t *testing.T) {
// t.Parallel()
// const dir = "testdata/generates"
// const (
// srcTask = "sub/src.txt"
// relTask = "rel.txt"
// absTask = "abs.txt"
// fileWithSpaces = "my text file.txt"
// )
// srcFile := filepathext.SmartJoin(dir, srcTask)
// for _, task := range []string{srcTask, relTask, absTask, fileWithSpaces} {
// path := filepathext.SmartJoin(dir, task)
// _ = os.Remove(path)
// if _, err := os.Stat(path); err == nil {
// t.Errorf("File should not exist: %v", err)
// }
// }
// buff := bytes.NewBuffer(nil)
// e := task.NewExecutor(
// task.WithDir(dir),
// task.WithStdout(buff),
// task.WithStderr(buff),
// )
// require.NoError(t, e.Setup())
// for _, theTask := range []string{relTask, absTask, fileWithSpaces} {
// destFile := filepathext.SmartJoin(dir, theTask)
// upToDate := fmt.Sprintf("task: Task \"%s\" is up to date\n", srcTask) +
// fmt.Sprintf("task: Task \"%s\" is up to date\n", theTask)
// // Run task for the first time.
// require.NoError(t, e.Run(context.Background(), &task.Call{Task: theTask}))
// if _, err := os.Stat(srcFile); err != nil {
// t.Errorf("File should exist: %v", err)
// }
// if _, err := os.Stat(destFile); err != nil {
// t.Errorf("File should exist: %v", err)
// }
// // Ensure task was not incorrectly found to be up-to-date on first run.
// if buff.String() == upToDate {
// t.Errorf("Wrong output message: %s", buff.String())
// }
// buff.Reset()
// // Re-run task to ensure it's now found to be up-to-date.
// require.NoError(t, e.Run(context.Background(), &task.Call{Task: theTask}))
// if buff.String() != upToDate {
// t.Errorf("Wrong output message: %s", buff.String())
// }
// buff.Reset()
// }
// }
// func TestStatusChecksum(t *testing.T) { // nolint:paralleltest // cannot run in parallel
// const dir = "testdata/checksum"
// tests := []struct {
// files []string
// task string
// }{
// {[]string{"generated.txt", ".task/checksum/build"}, "build"},
// {[]string{"generated.txt", ".task/checksum/build-with-status"}, "build-with-status"},
// }
// for _, test := range tests { // nolint:paralleltest // cannot run in parallel
// t.Run(test.task, func(t *testing.T) {
// for _, f := range test.files {
// _ = os.Remove(filepathext.SmartJoin(dir, f))
// _, err := os.Stat(filepathext.SmartJoin(dir, f))
// require.Error(t, err)
// }
// var buff bytes.Buffer
// tempDir := task.TempDir{
// Remote: filepathext.SmartJoin(dir, ".task"),
// Fingerprint: filepathext.SmartJoin(dir, ".task"),
// }
// e := task.NewExecutor(
// task.WithDir(dir),
// task.WithStdout(&buff),
// task.WithStderr(&buff),
// task.WithTempDir(tempDir),
// )
// require.NoError(t, e.Setup())
// require.NoError(t, e.Run(context.Background(), &task.Call{Task: test.task}))
// for _, f := range test.files {
// _, err := os.Stat(filepathext.SmartJoin(dir, f))
// require.NoError(t, err)
// }
// // Capture the modification time, so we can ensure the checksum file
// // is not regenerated when the hash hasn't changed.
// s, err := os.Stat(filepathext.SmartJoin(tempDir.Fingerprint, "checksum/"+test.task))
// require.NoError(t, err)
// time := s.ModTime()
// buff.Reset()
// require.NoError(t, e.Run(context.Background(), &task.Call{Task: test.task}))
// assert.Equal(t, `task: Task "`+test.task+`" is up to date`+"\n", buff.String())
// s, err = os.Stat(filepathext.SmartJoin(tempDir.Fingerprint, "checksum/"+test.task))
// require.NoError(t, err)
// assert.Equal(t, time, s.ModTime())
// })
// }
// }
// func TestStatusVariables(t *testing.T) {
// t.Parallel()
// const dir = "testdata/status_vars"
// _ = os.RemoveAll(filepathext.SmartJoin(dir, ".task"))
// _ = os.Remove(filepathext.SmartJoin(dir, "generated.txt"))
// var buff bytes.Buffer
// e := task.NewExecutor(
// task.WithDir(dir),
// task.WithTempDir(task.TempDir{
// Remote: filepathext.SmartJoin(dir, ".task"),
// Fingerprint: filepathext.SmartJoin(dir, ".task"),
// }),
// task.WithStdout(&buff),
// task.WithStderr(&buff),
// task.WithSilent(false),
// task.WithVerbose(true),
// )
// require.NoError(t, e.Setup())
// require.NoError(t, e.Run(context.Background(), &task.Call{Task: "build-checksum"}))
// assert.Contains(t, buff.String(), "3e464c4b03f4b65d740e1e130d4d108a")
// buff.Reset()
// require.NoError(t, e.Run(context.Background(), &task.Call{Task: "build-ts"}))
// inf, err := os.Stat(filepathext.SmartJoin(dir, "source.txt"))
// require.NoError(t, err)
// ts := fmt.Sprintf("%d", inf.ModTime().Unix())
// tf := inf.ModTime().String()
// assert.Contains(t, buff.String(), ts)
// assert.Contains(t, buff.String(), tf)
// }
// func TestCmdsVariables(t *testing.T) {
// t.Parallel()
// const dir = "testdata/cmds_vars"
// _ = os.RemoveAll(filepathext.SmartJoin(dir, ".task"))
// var buff bytes.Buffer
// e := task.NewExecutor(
// task.WithDir(dir),
// task.WithTempDir(task.TempDir{
// Remote: filepathext.SmartJoin(dir, ".task"),
// Fingerprint: filepathext.SmartJoin(dir, ".task"),
// }),
// task.WithStdout(&buff),
// task.WithStderr(&buff),
// task.WithSilent(false),
// task.WithVerbose(true),
// )
// require.NoError(t, e.Setup())
// require.NoError(t, e.Run(context.Background(), &task.Call{Task: "build-checksum"}))
// assert.Contains(t, buff.String(), "3e464c4b03f4b65d740e1e130d4d108a")
// buff.Reset()
// require.NoError(t, e.Run(context.Background(), &task.Call{Task: "build-ts"}))
// inf, err := os.Stat(filepathext.SmartJoin(dir, "source.txt"))
// require.NoError(t, err)
// ts := fmt.Sprintf("%d", inf.ModTime().Unix())
// tf := inf.ModTime().String()
// assert.Contains(t, buff.String(), ts)
// assert.Contains(t, buff.String(), tf)
// }
// func TestCyclicDep(t *testing.T) {
// t.Parallel()
// const dir = "testdata/cyclic"
// e := task.NewExecutor(
// task.WithDir(dir),
// task.WithStdout(io.Discard),
// task.WithStderr(io.Discard),
// )
// require.NoError(t, e.Setup())
// assert.IsType(t, &errors.TaskCalledTooManyTimesError{}, e.Run(context.Background(), &task.Call{Task: "task-1"}))
// }
// func TestTaskVersion(t *testing.T) {
// t.Parallel()
// tests := []struct {
// Dir string
// Version *semver.Version
// wantErr bool
// }{
// {"testdata/version/v1", semver.MustParse("1"), true},
// {"testdata/version/v2", semver.MustParse("2"), true},
// {"testdata/version/v3", semver.MustParse("3"), false},
// }
// for _, test := range tests {
// t.Run(test.Dir, func(t *testing.T) {
// t.Parallel()
// e := task.NewExecutor(
// task.WithDir(test.Dir),
// task.WithStdout(io.Discard),
// task.WithStderr(io.Discard),
// task.WithVersionCheck(true),
// )
// err := e.Setup()
// if test.wantErr {
// require.Error(t, err)
// return
// }
// require.NoError(t, err)
// assert.Equal(t, test.Version, e.Taskfile.Version)
// assert.Equal(t, 2, e.Taskfile.Tasks.Len())
// })
// }
// }
// func TestTaskIgnoreErrors(t *testing.T) {
// t.Parallel()
// const dir = "testdata/ignore_errors"
// e := task.NewExecutor(
// task.WithDir(dir),
// task.WithStdout(io.Discard),
// task.WithStderr(io.Discard),
// )
// require.NoError(t, e.Setup())
// require.NoError(t, e.Run(context.Background(), &task.Call{Task: "task-should-pass"}))
// require.Error(t, e.Run(context.Background(), &task.Call{Task: "task-should-fail"}))
// require.NoError(t, e.Run(context.Background(), &task.Call{Task: "cmd-should-pass"}))
// require.Error(t, e.Run(context.Background(), &task.Call{Task: "cmd-should-fail"}))
// }
// func TestExpand(t *testing.T) {
// t.Parallel()
// const dir = "testdata/expand"
// home, err := os.UserHomeDir()
// if err != nil {
// t.Errorf("Couldn't get $HOME: %v", err)
// }
// var buff bytes.Buffer
// e := task.NewExecutor(
// task.WithDir(dir),
// task.WithStdout(&buff),
// task.WithStderr(&buff),
// )
// require.NoError(t, e.Setup())
// require.NoError(t, e.Run(context.Background(), &task.Call{Task: "pwd"}))
// assert.Equal(t, home, strings.TrimSpace(buff.String()))
// }
// func TestDry(t *testing.T) {
// t.Parallel()
// const dir = "testdata/dry"
// file := filepathext.SmartJoin(dir, "file.txt")
// _ = os.Remove(file)
// var buff bytes.Buffer
// e := task.NewExecutor(
// task.WithDir(dir),
// task.WithStdout(&buff),
// task.WithStderr(&buff),
// task.WithDry(true),
// )
// require.NoError(t, e.Setup())
// require.NoError(t, e.Run(context.Background(), &task.Call{Task: "build"}))
// assert.Equal(t, "task: [build] touch file.txt", strings.TrimSpace(buff.String()))
// if _, err := os.Stat(file); err == nil {
// t.Errorf("File should not exist %s", file)
// }
// }
// // TestDryChecksum tests if the checksum file is not being written to disk
// // if the dry mode is enabled.
// func TestDryChecksum(t *testing.T) {
// t.Parallel()
// const dir = "testdata/dry_checksum"
// checksumFile := filepathext.SmartJoin(dir, ".task/checksum/default")
// _ = os.Remove(checksumFile)
// e := task.NewExecutor(
// task.WithDir(dir),
// task.WithTempDir(task.TempDir{
// Remote: filepathext.SmartJoin(dir, ".task"),
// Fingerprint: filepathext.SmartJoin(dir, ".task"),
// }),
// task.WithStdout(io.Discard),
// task.WithStderr(io.Discard),
// task.WithDry(true),
// )
// require.NoError(t, e.Setup())
// require.NoError(t, e.Run(context.Background(), &task.Call{Task: "default"}))
// _, err := os.Stat(checksumFile)
// require.Error(t, err, "checksum file should not exist")
// e.Dry = false
// require.NoError(t, e.Run(context.Background(), &task.Call{Task: "default"}))
// _, err = os.Stat(checksumFile)
// require.NoError(t, err, "checksum file should exist")
// }
// func TestIncludes(t *testing.T) {
// t.Parallel()
// tt := fileContentTest{
// Dir: "testdata/includes",
// Target: "default",
// TrimSpace: true,
// Files: map[string]string{
// "main.txt": "main",
// "included_directory.txt": "included_directory",
// "included_directory_without_dir.txt": "included_directory_without_dir",
// "included_taskfile_without_dir.txt": "included_taskfile_without_dir",
// "./module2/included_directory_with_dir.txt": "included_directory_with_dir",
// "./module2/included_taskfile_with_dir.txt": "included_taskfile_with_dir",
// "os_include.txt": "os",
// },
// }
// t.Run("", func(t *testing.T) {
// t.Parallel()
// tt.Run(t)
// })
// }
// func TestIncludesMultiLevel(t *testing.T) {
// t.Parallel()
// tt := fileContentTest{
// Dir: "testdata/includes_multi_level",
// Target: "default",
// TrimSpace: true,
// Files: map[string]string{
// "called_one.txt": "one",
// "called_two.txt": "two",
// "called_three.txt": "three",
// },
// }
// t.Run("", func(t *testing.T) {
// t.Parallel()
// tt.Run(t)
// })
// }
// func TestIncludesRemote(t *testing.T) {
// enableExperimentForTest(t, &experiments.RemoteTaskfiles, 1)
// dir := "testdata/includes_remote"
// os.RemoveAll(filepath.Join(dir, ".task", "remote"))
// srv := httptest.NewServer(http.FileServer(http.Dir(dir)))
// defer srv.Close()
// tcs := []struct {
// firstRemote string
// secondRemote string
// }{
// {
// firstRemote: srv.URL + "/first/Taskfile.yml",
// secondRemote: srv.URL + "/first/second/Taskfile.yml",
// },
// {
// firstRemote: srv.URL + "/first/Taskfile.yml",
// secondRemote: "./second/Taskfile.yml",
// },
// {
// firstRemote: srv.URL + "/first/",
// secondRemote: srv.URL + "/first/second/",
// },
// }
// taskCalls := []*task.Call{
// {Task: "first:write-file"},
// {Task: "first:second:write-file"},
// }
// for i, tc := range tcs {
// t.Run(fmt.Sprint(i), func(t *testing.T) {
// t.Setenv("FIRST_REMOTE_URL", tc.firstRemote)
// t.Setenv("SECOND_REMOTE_URL", tc.secondRemote)
// var buff SyncBuffer
// executors := []struct {
// name string
// executor *task.Executor
// }{
// {
// name: "online, always download",
// executor: task.NewExecutor(
// task.WithDir(dir),
// task.WithStdout(&buff),
// task.WithStderr(&buff),
// task.WithTimeout(time.Minute),
// task.WithInsecure(true),
// task.WithStdout(&buff),
// task.WithStderr(&buff),
// task.WithVerbose(true),
// // Without caching
// task.WithAssumeYes(true),
// task.WithDownload(true),
// ),
// },
// {
// name: "offline, use cache",
// executor: task.NewExecutor(
// task.WithDir(dir),
// task.WithStdout(&buff),
// task.WithStderr(&buff),
// task.WithTimeout(time.Minute),
// task.WithInsecure(true),
// task.WithStdout(&buff),
// task.WithStderr(&buff),
// task.WithVerbose(true),
// // With caching
// task.WithAssumeYes(false),
// task.WithDownload(false),
// task.WithOffline(true),
// ),
// },
// }
// for _, e := range executors {
// t.Run(e.name, func(t *testing.T) {
// require.NoError(t, e.executor.Setup())
// for k, taskCall := range taskCalls {
// t.Run(taskCall.Task, func(t *testing.T) {
// expectedContent := fmt.Sprint(rand.Int64())
// t.Setenv("CONTENT", expectedContent)
// outputFile := fmt.Sprintf("%d.%d.txt", i, k)
// t.Setenv("OUTPUT_FILE", outputFile)
// path := filepath.Join(dir, outputFile)
// require.NoError(t, os.RemoveAll(path))
// require.NoError(t, e.executor.Run(context.Background(), taskCall))
// actualContent, err := os.ReadFile(path)
// require.NoError(t, err)
// assert.Equal(t, expectedContent, strings.TrimSpace(string(actualContent)))
// })
// }
// })
// }
// t.Log("\noutput:\n", buff.buf.String())
// })
// }
// }
// func TestIncludeCycle(t *testing.T) {
// t.Parallel()
// const dir = "testdata/includes_cycle"
// var buff bytes.Buffer
// e := task.NewExecutor(
// task.WithDir(dir),
// task.WithStdout(&buff),
// task.WithStderr(&buff),
// task.WithSilent(true),
// )
// err := e.Setup()
// require.Error(t, err)
// assert.Contains(t, err.Error(), "task: include cycle detected between")
// }
// func TestIncludesIncorrect(t *testing.T) {
// t.Parallel()
// const dir = "testdata/includes_incorrect"
// var buff bytes.Buffer
// e := task.NewExecutor(
// task.WithDir(dir),
// task.WithStdout(&buff),
// task.WithStderr(&buff),
// task.WithSilent(true),
// )
// err := e.Setup()
// require.Error(t, err)
// assert.Contains(t, err.Error(), "Failed to parse testdata/includes_incorrect/incomplete.yml:", err.Error())
// }
// func TestIncludesEmptyMain(t *testing.T) {
// t.Parallel()
// tt := fileContentTest{
// Dir: "testdata/includes_empty",
// Target: "included:default",
// TrimSpace: true,
// Files: map[string]string{
// "file.txt": "default",
// },
// }
// t.Run("", func(t *testing.T) {
// t.Parallel()
// tt.Run(t)
// })
// }
// func TestIncludesHttp(t *testing.T) {
// enableExperimentForTest(t, &experiments.RemoteTaskfiles, 1)
// dir, err := filepath.Abs("testdata/includes_http")
// require.NoError(t, err)
// srv := httptest.NewServer(http.FileServer(http.Dir(dir)))
// defer srv.Close()
// t.Cleanup(func() {
// // This test fills the .task/remote directory with cache entries because the include URL
// // is different on every test due to the dynamic nature of the TCP port in srv.URL
// if err := os.RemoveAll(filepath.Join(dir, ".task")); err != nil {
// t.Logf("error cleaning up: %s", err)
// }
// })
// taskfiles, err := fs.Glob(os.DirFS(dir), "root-taskfile-*.yml")
// require.NoError(t, err)
// remotes := []struct {
// name string
// root string
// }{
// {
// name: "local",
// root: ".",
// },
// {
// name: "http-remote",
// root: srv.URL,
// },
// }
// for _, taskfile := range taskfiles {
// t.Run(taskfile, func(t *testing.T) {
// for _, remote := range remotes {
// t.Run(remote.name, func(t *testing.T) {
// t.Setenv("INCLUDE_ROOT", remote.root)
// entrypoint := filepath.Join(dir, taskfile)
// var buff SyncBuffer
// e := task.NewExecutor(
// task.WithEntrypoint(entrypoint),
// task.WithDir(dir),
// task.WithStdout(&buff),
// task.WithStderr(&buff),
// task.WithInsecure(true),
// task.WithDownload(true),
// task.WithAssumeYes(true),
// task.WithStdout(&buff),
// task.WithStderr(&buff),
// task.WithVerbose(true),
// task.WithTimeout(time.Minute),
// )
// require.NoError(t, e.Setup())
// defer func() { t.Log("output:", buff.buf.String()) }()
// tcs := []struct {
// name, dir string
// }{
// {
// name: "second-with-dir-1:third-with-dir-1:default",
// dir: filepath.Join(dir, "dir-1"),
// },
// {
// name: "second-with-dir-1:third-with-dir-2:default",
// dir: filepath.Join(dir, "dir-2"),
// },
// }
// for _, tc := range tcs {
// t.Run(tc.name, func(t *testing.T) {
// t.Parallel()
// task, err := e.CompiledTask(&task.Call{Task: tc.name})
// require.NoError(t, err)
// assert.Equal(t, tc.dir, task.Dir)
// })
// }
// })
// }
// })
// }
// }
// func TestIncludesDependencies(t *testing.T) {
// t.Parallel()
// tt := fileContentTest{
// Dir: "testdata/includes_deps",
// Target: "default",
// TrimSpace: true,
// Files: map[string]string{
// "default.txt": "default",
// "called_dep.txt": "called_dep",
// "called_task.txt": "called_task",
// },
// }
// t.Run("", func(t *testing.T) {
// t.Parallel()
// tt.Run(t)
// })
// }
// func TestIncludesCallingRoot(t *testing.T) {
// t.Parallel()
// tt := fileContentTest{
// Dir: "testdata/includes_call_root_task",
// Target: "included:call-root",
// TrimSpace: true,
// Files: map[string]string{
// "root_task.txt": "root task",
// },
// }
// t.Run("", func(t *testing.T) {
// t.Parallel()
// tt.Run(t)
// })
// }
// func TestIncludesOptional(t *testing.T) {
// t.Parallel()
// tt := fileContentTest{
// Dir: "testdata/includes_optional",
// Target: "default",
// TrimSpace: true,
// Files: map[string]string{
// "called_dep.txt": "called_dep",
// },
// }
// t.Run("", func(t *testing.T) {
// t.Parallel()
// tt.Run(t)
// })
// }
// func TestIncludesOptionalImplicitFalse(t *testing.T) {
// t.Parallel()
// const dir = "testdata/includes_optional_implicit_false"
// wd, _ := os.Getwd()
// message := "stat %s/%s/TaskfileOptional.yml: no such file or directory"
// expected := fmt.Sprintf(message, wd, dir)
// e := task.NewExecutor(
// task.WithDir(dir),
// task.WithStdout(io.Discard),
// task.WithStderr(io.Discard),
// )
// err := e.Setup()
// require.Error(t, err)
// assert.Equal(t, expected, err.Error())
// }
// func TestIncludesOptionalExplicitFalse(t *testing.T) {
// t.Parallel()
// const dir = "testdata/includes_optional_explicit_false"
// wd, _ := os.Getwd()
// message := "stat %s/%s/TaskfileOptional.yml: no such file or directory"
// expected := fmt.Sprintf(message, wd, dir)
// e := task.NewExecutor(
// task.WithDir(dir),
// task.WithStdout(io.Discard),
// task.WithStderr(io.Discard),
// )
// err := e.Setup()
// require.Error(t, err)
// assert.Equal(t, expected, err.Error())
// }
// func TestIncludesFromCustomTaskfile(t *testing.T) {
// t.Parallel()
// tt := fileContentTest{
// Entrypoint: "testdata/includes_yaml/Custom.ext",
// Dir: "testdata/includes_yaml",
// Target: "default",
// TrimSpace: true,
// Files: map[string]string{
// "main.txt": "main",
// "included_with_yaml_extension.txt": "included_with_yaml_extension",
// "included_with_custom_file.txt": "included_with_custom_file",
// },
// }
// t.Run("", func(t *testing.T) {
// t.Parallel()
// tt.Run(t)
// })
// }
// func TestIncludesRelativePath(t *testing.T) {
// t.Parallel()
// const dir = "testdata/includes_rel_path"
// var buff bytes.Buffer
// e := task.NewExecutor(
// task.WithDir(dir),
// task.WithStdout(&buff),
// task.WithStderr(&buff),
// )
// require.NoError(t, e.Setup())
// require.NoError(t, e.Run(context.Background(), &task.Call{Task: "common:pwd"}))
// assert.Contains(t, buff.String(), "testdata/includes_rel_path/common")
// buff.Reset()
// require.NoError(t, e.Run(context.Background(), &task.Call{Task: "included:common:pwd"}))
// assert.Contains(t, buff.String(), "testdata/includes_rel_path/common")
// }
// func TestIncludesInternal(t *testing.T) {
// t.Parallel()
// const dir = "testdata/internal_task"
// tests := []struct {
// name string
// task string
// expectedErr bool
// expectedOutput string
// }{
// {"included internal task via task", "task-1", false, "Hello, World!\n"},
// {"included internal task via dep", "task-2", false, "Hello, World!\n"},
// {"included internal direct", "included:task-3", true, "task: No tasks with description available. Try --list-all to list all tasks\n"},
// }
// for _, test := range tests {
// t.Run(test.name, func(t *testing.T) {
// t.Parallel()
// var buff bytes.Buffer
// e := task.NewExecutor(
// task.WithDir(dir),
// task.WithStdout(&buff),
// task.WithStderr(&buff),
// task.WithSilent(true),
// )
// require.NoError(t, e.Setup())
// err := e.Run(context.Background(), &task.Call{Task: test.task})
// if test.expectedErr {
// require.Error(t, err)
// } else {
// require.NoError(t, err)
// }
// assert.Equal(t, test.expectedOutput, buff.String())
// })
// }
// }
// func TestIncludesFlatten(t *testing.T) {
// t.Parallel()
// const dir = "testdata/includes_flatten"
// tests := []struct {
// name string
// taskfile string
// task string
// expectedErr bool
// expectedOutput string
// }{
// {name: "included flatten", taskfile: "Taskfile.yml", task: "gen", expectedOutput: "gen from included\n"},
// {name: "included flatten with default", taskfile: "Taskfile.yml", task: "default", expectedOutput: "default from included flatten\n"},
// {name: "included flatten can call entrypoint tasks", taskfile: "Taskfile.yml", task: "from_entrypoint", expectedOutput: "from entrypoint\n"},
// {name: "included flatten with deps", taskfile: "Taskfile.yml", task: "with_deps", expectedOutput: "gen from included\nwith_deps from included\n"},
// {name: "included flatten nested", taskfile: "Taskfile.yml", task: "from_nested", expectedOutput: "from nested\n"},
// {name: "included flatten multiple same task", taskfile: "Taskfile.multiple.yml", task: "gen", expectedErr: true, expectedOutput: "task: Found multiple tasks (gen) included by \"included\"\""},
// }
// for _, test := range tests {
// t.Run(test.name, func(t *testing.T) {
// t.Parallel()
// var buff bytes.Buffer
// e := task.NewExecutor(
// task.WithDir(dir),
// task.WithEntrypoint(dir+"/"+test.taskfile),
// task.WithStdout(&buff),
// task.WithStderr(&buff),
// task.WithSilent(true),
// )
// err := e.Setup()
// if test.expectedErr {
// assert.EqualError(t, err, test.expectedOutput)
// } else {
// require.NoError(t, err)
// _ = e.Run(context.Background(), &task.Call{Task: test.task})
// assert.Equal(t, test.expectedOutput, buff.String())
// }
// })
// }
// }
// func TestIncludesInterpolation(t *testing.T) { // nolint:paralleltest // cannot run in parallel
// const dir = "testdata/includes_interpolation"
// tests := []struct {
// name string
// task string
// expectedErr bool
// expectedOutput string
// }{
// {"include", "include", false, "include\n"},
// {"include_with_env_variable", "include-with-env-variable", false, "include_with_env_variable\n"},
// {"include_with_dir", "include-with-dir", false, "included\n"},
// }
// t.Setenv("MODULE", "included")
// for _, test := range tests { // nolint:paralleltest // cannot run in parallel
// t.Run(test.name, func(t *testing.T) {
// var buff bytes.Buffer
// e := task.NewExecutor(
// task.WithDir(filepath.Join(dir, test.name)),
// task.WithStdout(&buff),
// task.WithStderr(&buff),
// task.WithSilent(true),
// )
// require.NoError(t, e.Setup())
// err := e.Run(context.Background(), &task.Call{Task: test.task})
// if test.expectedErr {
// require.Error(t, err)
// } else {
// require.NoError(t, err)
// }
// assert.Equal(t, test.expectedOutput, buff.String())
// })
// }
// }
// func TestIncludesWithExclude(t *testing.T) {
// t.Parallel()
// var buff bytes.Buffer
// e := task.NewExecutor(
// task.WithDir("testdata/includes_with_excludes"),
// task.WithSilent(true),
// task.WithStdout(&buff),
// task.WithStderr(&buff),
// )
// require.NoError(t, e.Setup())
// err := e.Run(context.Background(), &task.Call{Task: "included:bar"})
// require.NoError(t, err)
// assert.Equal(t, "bar\n", buff.String())
// buff.Reset()
// err = e.Run(context.Background(), &task.Call{Task: "included:foo"})
// require.Error(t, err)
// buff.Reset()
// err = e.Run(context.Background(), &task.Call{Task: "bar"})
// require.Error(t, err)
// buff.Reset()
// err = e.Run(context.Background(), &task.Call{Task: "foo"})
// require.NoError(t, err)
// assert.Equal(t, "foo\n", buff.String())
// }
// func TestIncludedTaskfileVarMerging(t *testing.T) {
// t.Parallel()
// const dir = "testdata/included_taskfile_var_merging"
// tests := []struct {
// name string
// task string
// expectedOutput string
// }{
// {"foo", "foo:pwd", "included_taskfile_var_merging/foo\n"},
// {"bar", "bar:pwd", "included_taskfile_var_merging/bar\n"},
// }
// for _, test := range tests {
// t.Run(test.name, func(t *testing.T) {
// t.Parallel()
// var buff bytes.Buffer
// e := task.NewExecutor(
// task.WithDir(dir),
// task.WithStdout(&buff),
// task.WithStderr(&buff),
// task.WithSilent(true),
// )
// require.NoError(t, e.Setup())
// err := e.Run(context.Background(), &task.Call{Task: test.task})
// require.NoError(t, err)
// assert.Contains(t, buff.String(), test.expectedOutput)
// })
// }
// }
// func TestInternalTask(t *testing.T) {
// t.Parallel()
// const dir = "testdata/internal_task"
// tests := []struct {
// name string
// task string
// expectedErr bool
// expectedOutput string
// }{
// {"internal task via task", "task-1", false, "Hello, World!\n"},
// {"internal task via dep", "task-2", false, "Hello, World!\n"},
// {"internal direct", "task-3", true, ""},
// }
// for _, test := range tests {
// t.Run(test.name, func(t *testing.T) {
// t.Parallel()
// var buff bytes.Buffer
// e := task.NewExecutor(
// task.WithDir(dir),
// task.WithStdout(&buff),
// task.WithStderr(&buff),
// task.WithSilent(true),
// )
// require.NoError(t, e.Setup())
// err := e.Run(context.Background(), &task.Call{Task: test.task})
// if test.expectedErr {
// require.Error(t, err)
// } else {
// require.NoError(t, err)
// }
// assert.Equal(t, test.expectedOutput, buff.String())
// })
// }
// }
// func TestIncludesShadowedDefault(t *testing.T) {
// t.Parallel()
// tt := fileContentTest{
// Dir: "testdata/includes_shadowed_default",
// Target: "included",
// TrimSpace: true,
// Files: map[string]string{
// "file.txt": "shadowed",
// },
// }
// t.Run("", func(t *testing.T) {
// t.Parallel()
// tt.Run(t)
// })
// }
// func TestIncludesUnshadowedDefault(t *testing.T) {
// t.Parallel()
// tt := fileContentTest{
// Dir: "testdata/includes_unshadowed_default",
// Target: "included",
// TrimSpace: true,
// Files: map[string]string{
// "file.txt": "included",
// },
// }
// t.Run("", func(t *testing.T) {
// t.Parallel()
// tt.Run(t)
// })
// }
// func TestSupportedFileNames(t *testing.T) {
// t.Parallel()
// fileNames := []string{
// "Taskfile.yml",
// "Taskfile.yaml",
// "Taskfile.dist.yml",
// "Taskfile.dist.yaml",
// }
// for _, fileName := range fileNames {
// t.Run(fileName, func(t *testing.T) {
// t.Parallel()
// tt := fileContentTest{
// Dir: fmt.Sprintf("testdata/file_names/%s", fileName),
// Target: "default",
// TrimSpace: true,
// Files: map[string]string{
// "output.txt": "hello",
// },
// }
// tt.Run(t)
// })
// }
// }
// func TestSummary(t *testing.T) {
// t.Parallel()
// const dir = "testdata/summary"
// var buff bytes.Buffer
// e := task.NewExecutor(
// task.WithDir(dir),
// task.WithStdout(&buff),
// task.WithStderr(&buff),
// task.WithSummary(true),
// task.WithSilent(true),
// )
// require.NoError(t, e.Setup())
// require.NoError(t, e.Run(context.Background(), &task.Call{Task: "task-with-summary"}, &task.Call{Task: "other-task-with-summary"}))
// data, err := os.ReadFile(filepathext.SmartJoin(dir, "task-with-summary.txt"))
// require.NoError(t, err)
// expectedOutput := string(data)
// if runtime.GOOS == "windows" {
// expectedOutput = strings.ReplaceAll(expectedOutput, "\r\n", "\n")
// }
// assert.Equal(t, expectedOutput, buff.String())
// }
// func TestWhenNoDirAttributeItRunsInSameDirAsTaskfile(t *testing.T) {
// t.Parallel()
// const expected = "dir"
// const dir = "testdata/" + expected
// var out bytes.Buffer
// e := task.NewExecutor(
// task.WithDir(dir),
// task.WithStdout(&out),
// task.WithStderr(&out),
// )
// require.NoError(t, e.Setup())
// require.NoError(t, e.Run(context.Background(), &task.Call{Task: "whereami"}))
// // got should be the "dir" part of "testdata/dir"
// got := strings.TrimSuffix(filepath.Base(out.String()), "\n")
// assert.Equal(t, expected, got, "Mismatch in the working directory")
// }
// func TestWhenDirAttributeAndDirExistsItRunsInThatDir(t *testing.T) {
// t.Parallel()
// const expected = "exists"
// const dir = "testdata/dir/explicit_exists"
// var out bytes.Buffer
// e := task.NewExecutor(
// task.WithDir(dir),
// task.WithStdout(&out),
// task.WithStderr(&out),
// )
// require.NoError(t, e.Setup())
// require.NoError(t, e.Run(context.Background(), &task.Call{Task: "whereami"}))
// got := strings.TrimSuffix(filepath.Base(out.String()), "\n")
// assert.Equal(t, expected, got, "Mismatch in the working directory")
// }
// func TestWhenDirAttributeItCreatesMissingAndRunsInThatDir(t *testing.T) {
// t.Parallel()
// const expected = "createme"
// const dir = "testdata/dir/explicit_doesnt_exist/"
// const toBeCreated = dir + expected
// const target = "whereami"
// var out bytes.Buffer
// e := task.NewExecutor(
// task.WithDir(dir),
// task.WithStdout(&out),
// task.WithStderr(&out),
// )
// // Ensure that the directory to be created doesn't actually exist.
// _ = os.RemoveAll(toBeCreated)
// if _, err := os.Stat(toBeCreated); err == nil {
// t.Errorf("Directory should not exist: %v", err)
// }
// require.NoError(t, e.Setup())
// require.NoError(t, e.Run(context.Background(), &task.Call{Task: target}))
// got := strings.TrimSuffix(filepath.Base(out.String()), "\n")
// assert.Equal(t, expected, got, "Mismatch in the working directory")
// // Clean-up after ourselves only if no error.
// _ = os.RemoveAll(toBeCreated)
// }
// func TestDynamicVariablesRunOnTheNewCreatedDir(t *testing.T) {
// t.Parallel()
// const expected = "created"
// const dir = "testdata/dir/dynamic_var_on_created_dir/"
// const toBeCreated = dir + expected
// const target = "default"
// var out bytes.Buffer
// e := task.NewExecutor(
// task.WithDir(dir),
// task.WithStdout(&out),
// task.WithStderr(&out),
// )
// // Ensure that the directory to be created doesn't actually exist.
// _ = os.RemoveAll(toBeCreated)
// if _, err := os.Stat(toBeCreated); err == nil {
// t.Errorf("Directory should not exist: %v", err)
// }
// require.NoError(t, e.Setup())
// require.NoError(t, e.Run(context.Background(), &task.Call{Task: target}))
// got := strings.TrimSuffix(filepath.Base(out.String()), "\n")
// assert.Equal(t, expected, got, "Mismatch in the working directory")
// // Clean-up after ourselves only if no error.
// _ = os.RemoveAll(toBeCreated)
// }
// func TestDynamicVariablesShouldRunOnTheTaskDir(t *testing.T) {
// t.Parallel()
// tt := fileContentTest{
// Dir: "testdata/dir/dynamic_var",
// Target: "default",
// TrimSpace: false,
// Files: map[string]string{
// "subdirectory/from_root_taskfile.txt": "subdirectory\n",
// "subdirectory/from_included_taskfile.txt": "subdirectory\n",
// "subdirectory/from_included_taskfile_task.txt": "subdirectory\n",
// "subdirectory/from_interpolated_dir.txt": "subdirectory\n",
// },
// }
// t.Run("", func(t *testing.T) {
// t.Parallel()
// tt.Run(t)
// })
// }
// func TestDisplaysErrorOnVersion1Schema(t *testing.T) {
// t.Parallel()
// e := task.NewExecutor(
// task.WithDir("testdata/version/v1"),
// task.WithStdout(io.Discard),
// task.WithStderr(io.Discard),
// task.WithVersionCheck(true),
// )
// err := e.Setup()
// require.Error(t, err)
// assert.Regexp(t, regexp.MustCompile(`task: Invalid schema version in Taskfile \".*testdata\/version\/v1\/Taskfile\.yml\":\nSchema version \(1\.0\.0\) no longer supported\. Please use v3 or above`), err.Error())
// }
// func TestDisplaysErrorOnVersion2Schema(t *testing.T) {
// t.Parallel()
// var buff bytes.Buffer
// e := task.NewExecutor(
// task.WithDir("testdata/version/v2"),
// task.WithStdout(io.Discard),
// task.WithStderr(&buff),
// task.WithVersionCheck(true),
// )
// err := e.Setup()
// require.Error(t, err)
// assert.Regexp(t, regexp.MustCompile(`task: Invalid schema version in Taskfile \".*testdata\/version\/v2\/Taskfile\.yml\":\nSchema version \(2\.0\.0\) no longer supported\. Please use v3 or above`), err.Error())
// }
// func TestShortTaskNotation(t *testing.T) {
// t.Parallel()
// const dir = "testdata/short_task_notation"
// var buff bytes.Buffer
// e := task.NewExecutor(
// task.WithDir(dir),
// task.WithStdout(&buff),
// task.WithStderr(&buff),
// task.WithSilent(true),
// )
// require.NoError(t, e.Setup())
// require.NoError(t, e.Run(context.Background(), &task.Call{Task: "default"}))
// assert.Equal(t, "string-slice-1\nstring-slice-2\nstring\n", buff.String())
// }
// func TestDotenvShouldIncludeAllEnvFiles(t *testing.T) {
// t.Parallel()
// tt := fileContentTest{
// Dir: "testdata/dotenv/default",
// Target: "default",
// TrimSpace: false,
// Files: map[string]string{
// "include.txt": "INCLUDE1='from_include1' INCLUDE2='from_include2'\n",
// },
// }
// t.Run("", func(t *testing.T) {
// t.Parallel()
// tt.Run(t)
// })
// }
// func TestDotenvShouldErrorWhenIncludingDependantDotenvs(t *testing.T) {
// t.Parallel()
// var buff bytes.Buffer
// e := task.NewExecutor(
// task.WithDir("testdata/dotenv/error_included_envs"),
// task.WithSummary(true),
// task.WithStdout(&buff),
// task.WithStderr(&buff),
// )
// err := e.Setup()
// require.Error(t, err)
// assert.Contains(t, err.Error(), "move the dotenv")
// }
// func TestDotenvShouldAllowMissingEnv(t *testing.T) {
// t.Parallel()
// tt := fileContentTest{
// Dir: "testdata/dotenv/missing_env",
// Target: "default",
// TrimSpace: false,
// Files: map[string]string{
// "include.txt": "INCLUDE1='' INCLUDE2=''\n",
// },
// }
// t.Run("", func(t *testing.T) {
// t.Parallel()
// tt.Run(t)
// })
// }
// func TestDotenvHasLocalEnvInPath(t *testing.T) {
// t.Parallel()
// tt := fileContentTest{
// Dir: "testdata/dotenv/local_env_in_path",
// Target: "default",
// TrimSpace: false,
// Files: map[string]string{
// "var.txt": "VAR='var_in_dot_env_1'\n",
// },
// }
// t.Run("", func(t *testing.T) {
// t.Parallel()
// tt.Run(t)
// })
// }
// func TestDotenvHasLocalVarInPath(t *testing.T) {
// t.Parallel()
// tt := fileContentTest{
// Dir: "testdata/dotenv/local_var_in_path",
// Target: "default",
// TrimSpace: false,
// Files: map[string]string{
// "var.txt": "VAR='var_in_dot_env_3'\n",
// },
// }
// t.Run("", func(t *testing.T) {
// t.Parallel()
// tt.Run(t)
// })
// }
// func TestDotenvHasEnvVarInPath(t *testing.T) { // nolint:paralleltest // cannot run in parallel
// t.Setenv("ENV_VAR", "testing")
// tt := fileContentTest{
// Dir: "testdata/dotenv/env_var_in_path",
// Target: "default",
// TrimSpace: false,
// Files: map[string]string{
// "var.txt": "VAR='var_in_dot_env_2'\n",
// },
// }
// tt.Run(t)
// }
// func TestTaskDotenvParseErrorMessage(t *testing.T) {
// t.Parallel()
// e := task.NewExecutor(
// task.WithDir("testdata/dotenv/parse_error"),
// )
// path, _ := filepath.Abs(filepath.Join(e.Dir, ".env-with-error"))
// expected := fmt.Sprintf("error reading env file %s:", path)
// err := e.Setup()
// require.ErrorContains(t, err, expected)
// }
// func TestTaskDotenv(t *testing.T) {
// t.Parallel()
// tt := fileContentTest{
// Dir: "testdata/dotenv_task/default",
// Target: "dotenv",
// TrimSpace: true,
// Files: map[string]string{
// "dotenv.txt": "foo",
// },
// }
// t.Run("", func(t *testing.T) {
// t.Parallel()
// tt.Run(t)
// })
// }
// func TestTaskDotenvFail(t *testing.T) {
// t.Parallel()
// tt := fileContentTest{
// Dir: "testdata/dotenv_task/default",
// Target: "no-dotenv",
// TrimSpace: true,
// Files: map[string]string{
// "no-dotenv.txt": "global",
// },
// }
// t.Run("", func(t *testing.T) {
// t.Parallel()
// tt.Run(t)
// })
// }
// func TestTaskDotenvOverriddenByEnv(t *testing.T) {
// t.Parallel()
// tt := fileContentTest{
// Dir: "testdata/dotenv_task/default",
// Target: "dotenv-overridden-by-env",
// TrimSpace: true,
// Files: map[string]string{
// "dotenv-overridden-by-env.txt": "overridden",
// },
// }
// t.Run("", func(t *testing.T) {
// t.Parallel()
// tt.Run(t)
// })
// }
// func TestTaskDotenvWithVarName(t *testing.T) {
// t.Parallel()
// tt := fileContentTest{
// Dir: "testdata/dotenv_task/default",
// Target: "dotenv-with-var-name",
// TrimSpace: true,
// Files: map[string]string{
// "dotenv-with-var-name.txt": "foo",
// },
// }
// t.Run("", func(t *testing.T) {
// t.Parallel()
// tt.Run(t)
// })
// }
// func TestExitImmediately(t *testing.T) {
// t.Parallel()
// const dir = "testdata/exit_immediately"
// var buff bytes.Buffer
// e := task.NewExecutor(
// task.WithDir(dir),
// task.WithStdout(&buff),
// task.WithStderr(&buff),
// task.WithSilent(true),
// )
// require.NoError(t, e.Setup())
// require.Error(t, e.Run(context.Background(), &task.Call{Task: "default"}))
// assert.Contains(t, buff.String(), `"this_should_fail": executable file not found in $PATH`)
// }
// func TestRunOnlyRunsJobsHashOnce(t *testing.T) {
// t.Parallel()
// tt := fileContentTest{
// Dir: "testdata/run",
// Target: "generate-hash",
// Files: map[string]string{
// "hash.txt": "starting 1\n1\n2\n",
// },
// }
// t.Run("", func(t *testing.T) {
// t.Parallel()
// tt.Run(t)
// })
// }
// func TestRunOnceSharedDeps(t *testing.T) {
// t.Parallel()
// const dir = "testdata/run_once_shared_deps"
// var buff bytes.Buffer
// e := task.NewExecutor(
// task.WithDir(dir),
// task.WithStdout(&buff),
// task.WithStderr(&buff),
// task.WithForceAll(true),
// )
// require.NoError(t, e.Setup())
// require.NoError(t, e.Run(context.Background(), &task.Call{Task: "build"}))
// rx := regexp.MustCompile(`task: \[service-[a,b]:library:build\] echo "build library"`)
// matches := rx.FindAllStringSubmatch(buff.String(), -1)
// assert.Len(t, matches, 1)
// assert.Contains(t, buff.String(), `task: [service-a:build] echo "build a"`)
// assert.Contains(t, buff.String(), `task: [service-b:build] echo "build b"`)
// }
// func TestDeferredCmds(t *testing.T) {
// t.Parallel()
// const dir = "testdata/deferred"
// var buff bytes.Buffer
// e := task.NewExecutor(
// task.WithDir(dir),
// task.WithStdout(&buff),
// task.WithStderr(&buff),
// )
// require.NoError(t, e.Setup())
// expectedOutputOrder := strings.TrimSpace(`
// task: [task-2] echo 'cmd ran'
// cmd ran
// task: [task-2] exit 1
// task: [task-2] echo 'failing' && exit 2
// failing
// echo ran
// task-1 ran successfully
// task: [task-1] echo 'task-1 ran successfully'
// task-1 ran successfully
// `)
// require.Error(t, e.Run(context.Background(), &task.Call{Task: "task-2"}))
// assert.Contains(t, buff.String(), expectedOutputOrder)
// buff.Reset()
// require.NoError(t, e.Run(context.Background(), &task.Call{Task: "parent"}))
// assert.Contains(t, buff.String(), "child task deferred value-from-parent")
// }
// func TestExitCodeZero(t *testing.T) {
// t.Parallel()
// const dir = "testdata/exit_code"
// var buff bytes.Buffer
// e := task.NewExecutor(
// task.WithDir(dir),
// task.WithStdout(&buff),
// task.WithStderr(&buff),
// )
// require.NoError(t, e.Setup())
// require.NoError(t, e.Run(context.Background(), &task.Call{Task: "exit-zero"}))
// assert.Equal(t, "FOO=bar - DYNAMIC_FOO=bar - EXIT_CODE=", strings.TrimSpace(buff.String()))
// }
// func TestExitCodeOne(t *testing.T) {
// t.Parallel()
// const dir = "testdata/exit_code"
// var buff bytes.Buffer
// e := task.NewExecutor(
// task.WithDir(dir),
// task.WithStdout(&buff),
// task.WithStderr(&buff),
// )
// require.NoError(t, e.Setup())
// require.Error(t, e.Run(context.Background(), &task.Call{Task: "exit-one"}))
// assert.Equal(t, "FOO=bar - DYNAMIC_FOO=bar - EXIT_CODE=1", strings.TrimSpace(buff.String()))
// }
// func TestIgnoreNilElements(t *testing.T) {
// t.Parallel()
// tests := []struct {
// name string
// dir string
// }{
// {"nil cmd", "testdata/ignore_nil_elements/cmds"},
// {"nil dep", "testdata/ignore_nil_elements/deps"},
// {"nil include", "testdata/ignore_nil_elements/includes"},
// {"nil precondition", "testdata/ignore_nil_elements/preconditions"},
// }
// for _, test := range tests {
// t.Run(test.name, func(t *testing.T) {
// t.Parallel()
// var buff bytes.Buffer
// e := task.NewExecutor(
// task.WithDir(test.dir),
// task.WithStdout(&buff),
// task.WithStderr(&buff),
// task.WithSilent(true),
// )
// require.NoError(t, e.Setup())
// require.NoError(t, e.Run(context.Background(), &task.Call{Task: "default"}))
// assert.Equal(t, "string-slice-1\n", buff.String())
// })
// }
// }
// func TestOutputGroup(t *testing.T) {
// t.Parallel()
// const dir = "testdata/output_group"
// var buff bytes.Buffer
// e := task.NewExecutor(
// task.WithDir(dir),
// task.WithStdout(&buff),
// task.WithStderr(&buff),
// )
// require.NoError(t, e.Setup())
// expectedOutputOrder := strings.TrimSpace(`
// task: [hello] echo 'Hello!'
// ::group::hello
// Hello!
// ::endgroup::
// task: [bye] echo 'Bye!'
// ::group::bye
// Bye!
// ::endgroup::
// `)
// require.NoError(t, e.Run(context.Background(), &task.Call{Task: "bye"}))
// t.Log(buff.String())
// assert.Equal(t, strings.TrimSpace(buff.String()), expectedOutputOrder)
// }
// func TestOutputGroupErrorOnlySwallowsOutputOnSuccess(t *testing.T) {
// t.Parallel()
// const dir = "testdata/output_group_error_only"
// var buff bytes.Buffer
// e := task.NewExecutor(
// task.WithDir(dir),
// task.WithStdout(&buff),
// task.WithStderr(&buff),
// )
// require.NoError(t, e.Setup())
// require.NoError(t, e.Run(context.Background(), &task.Call{Task: "passing"}))
// t.Log(buff.String())
// assert.Empty(t, buff.String())
// }
// func TestOutputGroupErrorOnlyShowsOutputOnFailure(t *testing.T) {
// t.Parallel()
// const dir = "testdata/output_group_error_only"
// var buff bytes.Buffer
// e := task.NewExecutor(
// task.WithDir(dir),
// task.WithStdout(&buff),
// task.WithStderr(&buff),
// )
// require.NoError(t, e.Setup())
// require.Error(t, e.Run(context.Background(), &task.Call{Task: "failing"}))
// t.Log(buff.String())
// assert.Contains(t, "failing-output", strings.TrimSpace(buff.String()))
// assert.NotContains(t, "passing", strings.TrimSpace(buff.String()))
// }
// func TestIncludedVars(t *testing.T) {
// t.Parallel()
// const dir = "testdata/include_with_vars"
// var buff bytes.Buffer
// e := task.NewExecutor(
// task.WithDir(dir),
// task.WithStdout(&buff),
// task.WithStderr(&buff),
// )
// require.NoError(t, e.Setup())
// expectedOutputOrder := strings.TrimSpace(`
// task: [included1:task1] echo "VAR_1 is included1-var1"
// VAR_1 is included1-var1
// task: [included1:task1] echo "VAR_2 is included-default-var2"
// VAR_2 is included-default-var2
// task: [included2:task1] echo "VAR_1 is included2-var1"
// VAR_1 is included2-var1
// task: [included2:task1] echo "VAR_2 is included-default-var2"
// VAR_2 is included-default-var2
// task: [included3:task1] echo "VAR_1 is included-default-var1"
// VAR_1 is included-default-var1
// task: [included3:task1] echo "VAR_2 is included-default-var2"
// VAR_2 is included-default-var2
// `)
// require.NoError(t, e.Run(context.Background(), &task.Call{Task: "task1"}))
// t.Log(buff.String())
// assert.Equal(t, strings.TrimSpace(buff.String()), expectedOutputOrder)
// }
// func TestIncludeWithVarsInInclude(t *testing.T) {
// t.Parallel()
// const dir = "testdata/include_with_vars_inside_include"
// var buff bytes.Buffer
// e := task.Executor{
// Dir: dir,
// Stdout: &buff,
// Stderr: &buff,
// }
// require.NoError(t, e.Setup())
// }
// func TestIncludedVarsMultiLevel(t *testing.T) {
// t.Parallel()
// const dir = "testdata/include_with_vars_multi_level"
// var buff bytes.Buffer
// e := task.NewExecutor(
// task.WithDir(dir),
// task.WithStdout(&buff),
// task.WithStderr(&buff),
// )
// require.NoError(t, e.Setup())
// expectedOutputOrder := strings.TrimSpace(`
// task: [lib:greet] echo 'Hello world'
// Hello world
// task: [foo:lib:greet] echo 'Hello foo'
// Hello foo
// task: [bar:lib:greet] echo 'Hello bar'
// Hello bar
// `)
// require.NoError(t, e.Run(context.Background(), &task.Call{Task: "default"}))
// t.Log(buff.String())
// assert.Equal(t, expectedOutputOrder, strings.TrimSpace(buff.String()))
// }
// func TestErrorCode(t *testing.T) {
// t.Parallel()
// const dir = "testdata/error_code"
// tests := []struct {
// name string
// task string
// expected int
// }{
// {
// name: "direct task",
// task: "direct",
// expected: 42,
// }, {
// name: "indirect task",
// task: "indirect",
// expected: 42,
// },
// }
// for _, test := range tests {
// t.Run(test.name, func(t *testing.T) {
// t.Parallel()
// var buff bytes.Buffer
// e := task.NewExecutor(
// task.WithDir(dir),
// task.WithStdout(&buff),
// task.WithStderr(&buff),
// task.WithSilent(true),
// )
// require.NoError(t, e.Setup())
// err := e.Run(context.Background(), &task.Call{Task: test.task})
// require.Error(t, err)
// taskRunErr, ok := err.(*errors.TaskRunError)
// assert.True(t, ok, "cannot cast returned error to *task.TaskRunError")
// assert.Equal(t, test.expected, taskRunErr.TaskExitCode(), "unexpected exit code from task")
// })
// }
// }
// func TestEvaluateSymlinksInPaths(t *testing.T) { // nolint:paralleltest // cannot run in parallel
// const dir = "testdata/evaluate_symlinks_in_paths"
// var buff bytes.Buffer
// e := task.NewExecutor(
// task.WithDir(dir),
// task.WithStdout(&buff),
// task.WithStderr(&buff),
// task.WithSilent(false),
// )
// tests := []struct {
// name string
// task string
// expected string
// }{
// {
// name: "default (1)",
// task: "default",
// expected: "task: [default] echo \"some job\"\nsome job",
// },
// {
// name: "test-sym (1)",
// task: "test-sym",
// expected: "task: [test-sym] echo \"shared file source changed\" > src/shared/b",
// },
// {
// name: "default (2)",
// task: "default",
// expected: "task: [default] echo \"some job\"\nsome job",
// },
// {
// name: "default (3)",
// task: "default",
// expected: `task: Task "default" is up to date`,
// },
// {
// name: "reset",
// task: "reset",
// expected: "task: [reset] echo \"shared file source\" > src/shared/b\ntask: [reset] echo \"file source\" > src/a",
// },
// }
// for _, test := range tests { // nolint:paralleltest // cannot run in parallel
// t.Run(test.name, func(t *testing.T) {
// require.NoError(t, e.Setup())
// err := e.Run(context.Background(), &task.Call{Task: test.task})
// require.NoError(t, err)
// assert.Equal(t, test.expected, strings.TrimSpace(buff.String()))
// buff.Reset()
// })
// }
// err := os.RemoveAll(dir + "/.task")
// require.NoError(t, err)
// }
// func TestTaskfileWalk(t *testing.T) {
// t.Parallel()
// tests := []struct {
// name string
// dir string
// expected string
// }{
// {
// name: "walk from root directory",
// dir: "testdata/taskfile_walk",
// expected: "foo\n",
// }, {
// name: "walk from sub directory",
// dir: "testdata/taskfile_walk/foo",
// expected: "foo\n",
// }, {
// name: "walk from sub sub directory",
// dir: "testdata/taskfile_walk/foo/bar",
// expected: "foo\n",
// },
// }
// for _, test := range tests {
// t.Run(test.name, func(t *testing.T) {
// t.Parallel()
// var buff bytes.Buffer
// e := task.NewExecutor(
// task.WithDir(test.dir),
// task.WithStdout(&buff),
// task.WithStderr(&buff),
// )
// require.NoError(t, e.Setup())
// require.NoError(t, e.Run(context.Background(), &task.Call{Task: "default"}))
// assert.Equal(t, test.expected, buff.String())
// })
// }
// }
// func TestUserWorkingDirectory(t *testing.T) {
// t.Parallel()
// var buff bytes.Buffer
// e := task.NewExecutor(
// task.WithDir("testdata/user_working_dir"),
// task.WithStdout(&buff),
// task.WithStderr(&buff),
// )
// wd, err := os.Getwd()
// require.NoError(t, err)
// require.NoError(t, e.Setup())
// require.NoError(t, e.Run(context.Background(), &task.Call{Task: "default"}))
// assert.Equal(t, fmt.Sprintf("%s\n", wd), buff.String())
// }
// func TestUserWorkingDirectoryWithIncluded(t *testing.T) {
// t.Parallel()
// wd, err := os.Getwd()
// require.NoError(t, err)
// wd = filepathext.SmartJoin(wd, "testdata/user_working_dir_with_includes/somedir")
// var buff bytes.Buffer
// e := task.NewExecutor(
// task.WithDir("testdata/user_working_dir_with_includes"),
// task.WithStdout(&buff),
// task.WithStderr(&buff),
// )
// e.UserWorkingDir = wd
// require.NoError(t, err)
// require.NoError(t, e.Setup())
// require.NoError(t, e.Run(context.Background(), &task.Call{Task: "included:echo"}))
// assert.Equal(t, fmt.Sprintf("%s\n", wd), buff.String())
// }
// func TestPlatforms(t *testing.T) {
// t.Parallel()
// var buff bytes.Buffer
// e := task.NewExecutor(
// task.WithDir("testdata/platforms"),
// task.WithStdout(&buff),
// task.WithStderr(&buff),
// )
// require.NoError(t, e.Setup())
// require.NoError(t, e.Run(context.Background(), &task.Call{Task: "build-" + runtime.GOOS}))
// assert.Equal(t, fmt.Sprintf("task: [build-%s] echo 'Running task on %s'\nRunning task on %s\n", runtime.GOOS, runtime.GOOS, runtime.GOOS), buff.String())
// }
// func TestPOSIXShellOptsGlobalLevel(t *testing.T) {
// t.Parallel()
// var buff bytes.Buffer
// e := task.NewExecutor(
// task.WithDir("testdata/shopts/global_level"),
// task.WithStdout(&buff),
// task.WithStderr(&buff),
// )
// require.NoError(t, e.Setup())
// err := e.Run(context.Background(), &task.Call{Task: "pipefail"})
// require.NoError(t, err)
// assert.Equal(t, "pipefail\ton\n", buff.String())
// }
// func TestPOSIXShellOptsTaskLevel(t *testing.T) {
// t.Parallel()
// var buff bytes.Buffer
// e := task.NewExecutor(
// task.WithDir("testdata/shopts/task_level"),
// task.WithStdout(&buff),
// task.WithStderr(&buff),
// )
// require.NoError(t, e.Setup())
// err := e.Run(context.Background(), &task.Call{Task: "pipefail"})
// require.NoError(t, err)
// assert.Equal(t, "pipefail\ton\n", buff.String())
// }
// func TestPOSIXShellOptsCommandLevel(t *testing.T) {
// t.Parallel()
// var buff bytes.Buffer
// e := task.NewExecutor(
// task.WithDir("testdata/shopts/command_level"),
// task.WithStdout(&buff),
// task.WithStderr(&buff),
// )
// require.NoError(t, e.Setup())
// err := e.Run(context.Background(), &task.Call{Task: "pipefail"})
// require.NoError(t, err)
// assert.Equal(t, "pipefail\ton\n", buff.String())
// }
// func TestBashShellOptsGlobalLevel(t *testing.T) {
// t.Parallel()
// var buff bytes.Buffer
// e := task.NewExecutor(
// task.WithDir("testdata/shopts/global_level"),
// task.WithStdout(&buff),
// task.WithStderr(&buff),
// )
// require.NoError(t, e.Setup())
// err := e.Run(context.Background(), &task.Call{Task: "globstar"})
// require.NoError(t, err)
// assert.Equal(t, "globstar\ton\n", buff.String())
// }
// func TestBashShellOptsTaskLevel(t *testing.T) {
// t.Parallel()
// var buff bytes.Buffer
// e := task.NewExecutor(
// task.WithDir("testdata/shopts/task_level"),
// task.WithStdout(&buff),
// task.WithStderr(&buff),
// )
// require.NoError(t, e.Setup())
// err := e.Run(context.Background(), &task.Call{Task: "globstar"})
// require.NoError(t, err)
// assert.Equal(t, "globstar\ton\n", buff.String())
// }
// func TestBashShellOptsCommandLevel(t *testing.T) {
// t.Parallel()
// var buff bytes.Buffer
// e := task.NewExecutor(
// task.WithDir("testdata/shopts/command_level"),
// task.WithStdout(&buff),
// task.WithStderr(&buff),
// )
// require.NoError(t, e.Setup())
// err := e.Run(context.Background(), &task.Call{Task: "globstar"})
// require.NoError(t, err)
// assert.Equal(t, "globstar\ton\n", buff.String())
// }
// func TestSplitArgs(t *testing.T) {
// t.Parallel()
// var buff bytes.Buffer
// e := task.NewExecutor(
// task.WithDir("testdata/split_args"),
// task.WithStdout(&buff),
// task.WithStderr(&buff),
// task.WithSilent(true),
// )
// require.NoError(t, e.Setup())
// vars := ast.NewVars()
// vars.Set("CLI_ARGS", ast.Var{Value: "foo bar 'foo bar baz'"})
// err := e.Run(context.Background(), &task.Call{Task: "default", Vars: vars})
// require.NoError(t, err)
// assert.Equal(t, "3\n", buff.String())
// }
// func TestSingleCmdDep(t *testing.T) {
// t.Parallel()
// tt := fileContentTest{
// Dir: "testdata/single_cmd_dep",
// Target: "foo",
// Files: map[string]string{
// "foo.txt": "foo\n",
// "bar.txt": "bar\n",
// },
// }
// t.Run("", func(t *testing.T) {
// t.Parallel()
// tt.Run(t)
// })
// }
// func TestSilence(t *testing.T) {
// t.Parallel()
// var buff bytes.Buffer
// e := task.NewExecutor(
// task.WithDir("testdata/silent"),
// task.WithStdout(&buff),
// task.WithStderr(&buff),
// task.WithSilent(false),
// )
// require.NoError(t, e.Setup())
// // First verify that the silent flag is in place.
// fetchedTask, err := e.GetTask(&task.Call{Task: "task-test-silent-calls-chatty-silenced"})
// require.NoError(t, err, "Unable to look up task task-test-silent-calls-chatty-silenced")
// require.True(t, fetchedTask.Cmds[0].Silent, "The task task-test-silent-calls-chatty-silenced should have a silent call to chatty")
// // Then test the two basic cases where the task is silent or not.
// // A silenced task.
// err = e.Run(context.Background(), &task.Call{Task: "silent"})
// require.NoError(t, err)
// require.Empty(t, buff.String(), "siWhile running lent: Expected not see output, because the task is silent")
// buff.Reset()
// // A chatty (not silent) task.
// err = e.Run(context.Background(), &task.Call{Task: "chatty"})
// require.NoError(t, err)
// require.NotEmpty(t, buff.String(), "chWhile running atty: Expected to see output, because the task is not silent")
// buff.Reset()
// // Then test invoking the two task from other tasks.
// // A silenced task that calls a chatty task.
// err = e.Run(context.Background(), &task.Call{Task: "task-test-silent-calls-chatty-non-silenced"})
// require.NoError(t, err)
// require.NotEmpty(t, buff.String(), "While running task-test-silent-calls-chatty-non-silenced: Expected to see output. The task is silenced, but the called task is not. Silence does not propagate to called tasks.")
// buff.Reset()
// // A silent task that does a silent call to a chatty task.
// err = e.Run(context.Background(), &task.Call{Task: "task-test-silent-calls-chatty-silenced"})
// require.NoError(t, err)
// require.Empty(t, buff.String(), "While running task-test-silent-calls-chatty-silenced: Expected not to see output. The task calls chatty task, but the call is silenced.")
// buff.Reset()
// // A chatty task that does a call to a chatty task.
// err = e.Run(context.Background(), &task.Call{Task: "task-test-chatty-calls-chatty-non-silenced"})
// require.NoError(t, err)
// require.NotEmpty(t, buff.String(), "While running task-test-chatty-calls-chatty-non-silenced: Expected to see output. Both caller and callee are chatty and not silenced.")
// buff.Reset()
// // A chatty task that does a silenced call to a chatty task.
// err = e.Run(context.Background(), &task.Call{Task: "task-test-chatty-calls-chatty-silenced"})
// require.NoError(t, err)
// require.NotEmpty(t, buff.String(), "While running task-test-chatty-calls-chatty-silenced: Expected to see output. Call to a chatty task is silenced, but the parent task is not.")
// buff.Reset()
// // A chatty task with no cmd's of its own that does a silenced call to a chatty task.
// err = e.Run(context.Background(), &task.Call{Task: "task-test-no-cmds-calls-chatty-silenced"})
// require.NoError(t, err)
// require.Empty(t, buff.String(), "While running task-test-no-cmds-calls-chatty-silenced: Expected not to see output. While the task itself is not silenced, it does not have any cmds and only does an invocation of a silenced task.")
// buff.Reset()
// // A chatty task that does a silenced invocation of a task.
// err = e.Run(context.Background(), &task.Call{Task: "task-test-chatty-calls-silenced-cmd"})
// require.NoError(t, err)
// require.Empty(t, buff.String(), "While running task-test-chatty-calls-silenced-cmd: Expected not to see output. While the task itself is not silenced, its call to the chatty task is silent.")
// buff.Reset()
// // Then test calls via dependencies.
// // A silent task that depends on a chatty task.
// err = e.Run(context.Background(), &task.Call{Task: "task-test-is-silent-depends-on-chatty-non-silenced"})
// require.NoError(t, err)
// require.NotEmpty(t, buff.String(), "While running task-test-is-silent-depends-on-chatty-non-silenced: Expected to see output. The task is silent and depends on a chatty task. Dependencies does not inherit silence.")
// buff.Reset()
// // A silent task that depends on a silenced chatty task.
// err = e.Run(context.Background(), &task.Call{Task: "task-test-is-silent-depends-on-chatty-silenced"})
// require.NoError(t, err)
// require.Empty(t, buff.String(), "While running task-test-is-silent-depends-on-chatty-silenced: Expected not to see output. The task is silent and has a silenced dependency on a chatty task.")
// buff.Reset()
// // A chatty task that, depends on a silenced chatty task.
// err = e.Run(context.Background(), &task.Call{Task: "task-test-is-chatty-depends-on-chatty-silenced"})
// require.NoError(t, err)
// require.Empty(t, buff.String(), "While running task-test-is-chatty-depends-on-chatty-silenced: Expected not to see output. The task is chatty but does not have commands and has a silenced dependency on a chatty task.")
// buff.Reset()
// }
// func TestForce(t *testing.T) {
// t.Parallel()
// tests := []struct {
// name string
// env map[string]string
// force bool
// forceAll bool
// }{
// {
// name: "force",
// force: true,
// },
// {
// name: "force-all",
// forceAll: true,
// },
// {
// name: "force with gentle force experiment",
// force: true,
// env: map[string]string{
// "TASK_X_GENTLE_FORCE": "1",
// },
// },
// {
// name: "force-all with gentle force experiment",
// forceAll: true,
// env: map[string]string{
// "TASK_X_GENTLE_FORCE": "1",
// },
// },
// }
// for _, tt := range tests {
// t.Run(tt.name, func(t *testing.T) {
// t.Parallel()
// var buff bytes.Buffer
// e := task.NewExecutor(
// task.WithDir("testdata/force"),
// task.WithStdout(&buff),
// task.WithStderr(&buff),
// task.WithForce(tt.force),
// task.WithForceAll(tt.forceAll),
// )
// require.NoError(t, e.Setup())
// require.NoError(t, e.Run(context.Background(), &task.Call{Task: "task-with-dep"}))
// })
// }
// }
2024-05-16 16:20:59 +01:00
2024-10-05 20:40:22 -04:00
// enableExperimentForTest enables the experiment behind pointer e for the duration of test t and sub-tests,
// with the experiment being restored to its previous state when tests complete.
//
// Typically experiments are controlled via TASK_X_ env vars, but we cannot use those in tests
// because the experiment settings are parsed during experiments.init(), before any tests run.
2025-02-23 10:51:59 +01:00
func enableExperimentForTest ( t * testing . T , e * experiments . Experiment , val int ) {
2024-12-12 01:47:10 +01:00
t . Helper ( )
2024-10-05 20:40:22 -04:00
prev := * e
* e = experiments . Experiment {
2025-02-08 23:02:51 +00:00
Name : prev . Name ,
2025-02-23 10:51:59 +01:00
AllowedValues : [ ] int { val } ,
2025-02-08 23:02:51 +00:00
Value : val ,
2024-10-05 20:40:22 -04:00
}
t . Cleanup ( func ( ) { * e = prev } )
}