task_test.go 67 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256225722582259226022612262226322642265226622672268226922702271227222732274227522762277227822792280228122822283228422852286228722882289229022912292229322942295229622972298229923002301230223032304230523062307230823092310231123122313231423152316231723182319232023212322232323242325232623272328232923302331233223332334233523362337233823392340234123422343234423452346234723482349235023512352235323542355235623572358235923602361236223632364236523662367236823692370237123722373237423752376237723782379238023812382238323842385238623872388238923902391239223932394239523962397239823992400240124022403240424052406240724082409241024112412241324142415241624172418241924202421242224232424242524262427242824292430243124322433243424352436243724382439244024412442244324442445244624472448244924502451245224532454245524562457245824592460246124622463246424652466246724682469247024712472247324742475247624772478247924802481248224832484248524862487248824892490249124922493249424952496249724982499250025012502250325042505250625072508250925102511251225132514251525162517251825192520252125222523252425252526252725282529253025312532253325342535253625372538253925402541254225432544254525462547254825492550255125522553255425552556255725582559256025612562256325642565256625672568256925702571257225732574257525762577257825792580258125822583258425852586258725882589259025912592259325942595259625972598259926002601260226032604
  1. package task_test
  2. import (
  3. "bytes"
  4. "context"
  5. "fmt"
  6. "io"
  7. "os"
  8. "path/filepath"
  9. "regexp"
  10. "runtime"
  11. "strings"
  12. "sync"
  13. "testing"
  14. "github.com/Masterminds/semver/v3"
  15. "github.com/stretchr/testify/assert"
  16. "github.com/stretchr/testify/require"
  17. "github.com/go-task/task/v3"
  18. "github.com/go-task/task/v3/errors"
  19. "github.com/go-task/task/v3/internal/experiments"
  20. "github.com/go-task/task/v3/internal/filepathext"
  21. "github.com/go-task/task/v3/taskfile/ast"
  22. )
  23. func init() {
  24. _ = os.Setenv("NO_COLOR", "1")
  25. }
  26. // SyncBuffer is a threadsafe buffer for testing.
  27. // Some times replace stdout/stderr with a buffer to capture output.
  28. // stdout and stderr are threadsafe, but a regular bytes.Buffer is not.
  29. // Using this instead helps prevents race conditions with output.
  30. type SyncBuffer struct {
  31. buf bytes.Buffer
  32. mu sync.Mutex
  33. }
  34. func (sb *SyncBuffer) Write(p []byte) (n int, err error) {
  35. sb.mu.Lock()
  36. defer sb.mu.Unlock()
  37. return sb.buf.Write(p)
  38. }
  39. // fileContentTest provides a basic reusable test-case for running a Taskfile
  40. // and inspect generated files.
  41. type fileContentTest struct {
  42. Dir string
  43. Entrypoint string
  44. Target string
  45. TrimSpace bool
  46. Files map[string]string
  47. }
  48. func (fct fileContentTest) name(file string) string {
  49. return fmt.Sprintf("target=%q,file=%q", fct.Target, file)
  50. }
  51. func (fct fileContentTest) Run(t *testing.T) {
  52. for f := range fct.Files {
  53. _ = os.Remove(filepathext.SmartJoin(fct.Dir, f))
  54. }
  55. e := &task.Executor{
  56. Dir: fct.Dir,
  57. TempDir: task.TempDir{
  58. Remote: filepathext.SmartJoin(fct.Dir, ".task"),
  59. Fingerprint: filepathext.SmartJoin(fct.Dir, ".task"),
  60. },
  61. Entrypoint: fct.Entrypoint,
  62. Stdout: io.Discard,
  63. Stderr: io.Discard,
  64. }
  65. require.NoError(t, e.Setup(), "e.Setup()")
  66. require.NoError(t, e.Run(context.Background(), &ast.Call{Task: fct.Target}), "e.Run(target)")
  67. for name, expectContent := range fct.Files {
  68. t.Run(fct.name(name), func(t *testing.T) {
  69. path := filepathext.SmartJoin(e.Dir, name)
  70. b, err := os.ReadFile(path)
  71. require.NoError(t, err, "Error reading file")
  72. s := string(b)
  73. if fct.TrimSpace {
  74. s = strings.TrimSpace(s)
  75. }
  76. assert.Equal(t, expectContent, s, "unexpected file content in %s", path)
  77. })
  78. }
  79. }
  80. func TestEmptyTask(t *testing.T) {
  81. e := &task.Executor{
  82. Dir: "testdata/empty_task",
  83. Stdout: io.Discard,
  84. Stderr: io.Discard,
  85. }
  86. require.NoError(t, e.Setup(), "e.Setup()")
  87. require.NoError(t, e.Run(context.Background(), &ast.Call{Task: "default"}))
  88. }
  89. func TestEmptyTaskfile(t *testing.T) {
  90. e := &task.Executor{
  91. Dir: "testdata/empty_taskfile",
  92. Stdout: io.Discard,
  93. Stderr: io.Discard,
  94. }
  95. require.Error(t, e.Setup(), "e.Setup()")
  96. }
  97. func TestEnv(t *testing.T) {
  98. t.Setenv("QUX", "from_os")
  99. tt := fileContentTest{
  100. Dir: "testdata/env",
  101. Target: "default",
  102. TrimSpace: false,
  103. Files: map[string]string{
  104. "local.txt": "GOOS='linux' GOARCH='amd64' CGO_ENABLED='0'\n",
  105. "global.txt": "FOO='foo' BAR='overriden' BAZ='baz'\n",
  106. "multiple_type.txt": "FOO='1' BAR='true' BAZ='1.1'\n",
  107. "not-overriden.txt": "QUX='from_os'\n",
  108. },
  109. }
  110. tt.Run(t)
  111. t.Setenv("TASK_X_ENV_PRECEDENCE", "1")
  112. experiments.EnvPrecedence = experiments.New("ENV_PRECEDENCE")
  113. ttt := fileContentTest{
  114. Dir: "testdata/env",
  115. Target: "overriden",
  116. TrimSpace: false,
  117. Files: map[string]string{
  118. "overriden.txt": "QUX='from_taskfile'\n",
  119. },
  120. }
  121. ttt.Run(t)
  122. }
  123. func TestVars(t *testing.T) {
  124. tt := fileContentTest{
  125. Dir: "testdata/vars",
  126. Target: "default",
  127. Files: map[string]string{
  128. "missing-var.txt": "\n",
  129. "var-order.txt": "ABCDEF\n",
  130. "dependent-sh.txt": "123456\n",
  131. "with-call.txt": "Hi, ABC123!\n",
  132. "from-dot-env.txt": "From .env file\n",
  133. },
  134. }
  135. tt.Run(t)
  136. }
  137. func TestSpecialVars(t *testing.T) {
  138. const dir = "testdata/special_vars"
  139. const subdir = "testdata/special_vars/subdir"
  140. toAbs := func(rel string) string {
  141. abs, err := filepath.Abs(rel)
  142. assert.NoError(t, err)
  143. return abs
  144. }
  145. tests := []struct {
  146. target string
  147. expected string
  148. }{
  149. // Root
  150. {target: "print-task", expected: "print-task"},
  151. {target: "print-root-dir", expected: toAbs(dir)},
  152. {target: "print-taskfile", expected: toAbs(dir) + "/Taskfile.yml"},
  153. {target: "print-taskfile-dir", expected: toAbs(dir)},
  154. {target: "print-task-version", expected: "unknown"},
  155. // Included
  156. {target: "included:print-task", expected: "included:print-task"},
  157. {target: "included:print-root-dir", expected: toAbs(dir)},
  158. {target: "included:print-taskfile", expected: toAbs(dir) + "/included/Taskfile.yml"},
  159. {target: "included:print-taskfile-dir", expected: toAbs(dir) + "/included"},
  160. {target: "included:print-task-version", expected: "unknown"},
  161. }
  162. for _, dir := range []string{dir, subdir} {
  163. for _, test := range tests {
  164. t.Run(test.target, func(t *testing.T) {
  165. var buff bytes.Buffer
  166. e := &task.Executor{
  167. Dir: dir,
  168. Stdout: &buff,
  169. Stderr: &buff,
  170. Silent: true,
  171. }
  172. require.NoError(t, e.Setup())
  173. require.NoError(t, e.Run(context.Background(), &ast.Call{Task: test.target}))
  174. assert.Equal(t, test.expected+"\n", buff.String())
  175. })
  176. }
  177. }
  178. }
  179. func TestConcurrency(t *testing.T) {
  180. const (
  181. dir = "testdata/concurrency"
  182. target = "default"
  183. )
  184. e := &task.Executor{
  185. Dir: dir,
  186. Stdout: io.Discard,
  187. Stderr: io.Discard,
  188. Concurrency: 1,
  189. }
  190. require.NoError(t, e.Setup(), "e.Setup()")
  191. require.NoError(t, e.Run(context.Background(), &ast.Call{Task: target}), "e.Run(target)")
  192. }
  193. func TestParams(t *testing.T) {
  194. tt := fileContentTest{
  195. Dir: "testdata/params",
  196. Target: "default",
  197. TrimSpace: false,
  198. Files: map[string]string{
  199. "hello.txt": "Hello\n",
  200. "world.txt": "World\n",
  201. "exclamation.txt": "!\n",
  202. "dep1.txt": "Dependence1\n",
  203. "dep2.txt": "Dependence2\n",
  204. "spanish.txt": "¡Holla mundo!\n",
  205. "spanish-dep.txt": "¡Holla dependencia!\n",
  206. "portuguese.txt": "Olá, mundo!\n",
  207. "portuguese2.txt": "Olá, mundo!\n",
  208. "german.txt": "Welt!\n",
  209. },
  210. }
  211. tt.Run(t)
  212. }
  213. func TestDeps(t *testing.T) {
  214. const dir = "testdata/deps"
  215. files := []string{
  216. "d1.txt",
  217. "d2.txt",
  218. "d3.txt",
  219. "d11.txt",
  220. "d12.txt",
  221. "d13.txt",
  222. "d21.txt",
  223. "d22.txt",
  224. "d23.txt",
  225. "d31.txt",
  226. "d32.txt",
  227. "d33.txt",
  228. }
  229. for _, f := range files {
  230. _ = os.Remove(filepathext.SmartJoin(dir, f))
  231. }
  232. e := &task.Executor{
  233. Dir: dir,
  234. Stdout: io.Discard,
  235. Stderr: io.Discard,
  236. }
  237. require.NoError(t, e.Setup())
  238. require.NoError(t, e.Run(context.Background(), &ast.Call{Task: "default"}))
  239. for _, f := range files {
  240. f = filepathext.SmartJoin(dir, f)
  241. if _, err := os.Stat(f); err != nil {
  242. t.Errorf("File %s should exist", f)
  243. }
  244. }
  245. }
  246. func TestStatus(t *testing.T) {
  247. const dir = "testdata/status"
  248. files := []string{
  249. "foo.txt",
  250. "bar.txt",
  251. "baz.txt",
  252. }
  253. for _, f := range files {
  254. path := filepathext.SmartJoin(dir, f)
  255. _ = os.Remove(path)
  256. if _, err := os.Stat(path); err == nil {
  257. t.Errorf("File should not exist: %v", err)
  258. }
  259. }
  260. var buff bytes.Buffer
  261. e := &task.Executor{
  262. Dir: dir,
  263. TempDir: task.TempDir{
  264. Remote: filepathext.SmartJoin(dir, ".task"),
  265. Fingerprint: filepathext.SmartJoin(dir, ".task"),
  266. },
  267. Stdout: &buff,
  268. Stderr: &buff,
  269. Silent: true,
  270. }
  271. require.NoError(t, e.Setup())
  272. // gen-foo creates foo.txt, and will always fail it's status check.
  273. require.NoError(t, e.Run(context.Background(), &ast.Call{Task: "gen-foo"}))
  274. // gen-foo creates bar.txt, and will pass its status-check the 3. time it
  275. // is run. It creates bar.txt, but also lists it as its source. So, the checksum
  276. // for the file won't match before after the second run as we the file
  277. // only exists after the first run.
  278. require.NoError(t, e.Run(context.Background(), &ast.Call{Task: "gen-bar"}))
  279. // gen-silent-baz is marked as being silent, and should only produce output
  280. // if e.Verbose is set to true.
  281. require.NoError(t, e.Run(context.Background(), &ast.Call{Task: "gen-silent-baz"}))
  282. for _, f := range files {
  283. if _, err := os.Stat(filepathext.SmartJoin(dir, f)); err != nil {
  284. t.Errorf("File should exist: %v", err)
  285. }
  286. }
  287. // Run gen-bar a second time to produce a checksum file that matches bar.txt
  288. require.NoError(t, e.Run(context.Background(), &ast.Call{Task: "gen-bar"}))
  289. // Run gen-bar a third time, to make sure we've triggered the status check.
  290. require.NoError(t, e.Run(context.Background(), &ast.Call{Task: "gen-bar"}))
  291. // We're silent, so no output should have been produced.
  292. assert.Empty(t, buff.String())
  293. // Now, let's remove source file, and run the task again to to prepare
  294. // for the next test.
  295. err := os.Remove(filepathext.SmartJoin(dir, "bar.txt"))
  296. require.NoError(t, err)
  297. require.NoError(t, e.Run(context.Background(), &ast.Call{Task: "gen-bar"}))
  298. buff.Reset()
  299. // Global silence switched of, so we should see output unless the task itself
  300. // is silent.
  301. e.Silent = false
  302. // all: not up-to-date
  303. require.NoError(t, e.Run(context.Background(), &ast.Call{Task: "gen-foo"}))
  304. assert.Equal(t, "task: [gen-foo] touch foo.txt", strings.TrimSpace(buff.String()))
  305. buff.Reset()
  306. // status: not up-to-date
  307. require.NoError(t, e.Run(context.Background(), &ast.Call{Task: "gen-foo"}))
  308. assert.Equal(t, "task: [gen-foo] touch foo.txt", strings.TrimSpace(buff.String()))
  309. buff.Reset()
  310. // sources: not up-to-date
  311. require.NoError(t, e.Run(context.Background(), &ast.Call{Task: "gen-bar"}))
  312. assert.Equal(t, "task: [gen-bar] touch bar.txt", strings.TrimSpace(buff.String()))
  313. buff.Reset()
  314. // all: up-to-date
  315. require.NoError(t, e.Run(context.Background(), &ast.Call{Task: "gen-bar"}))
  316. assert.Equal(t, `task: Task "gen-bar" is up to date`, strings.TrimSpace(buff.String()))
  317. buff.Reset()
  318. // sources: not up-to-date, no output produced.
  319. require.NoError(t, e.Run(context.Background(), &ast.Call{Task: "gen-silent-baz"}))
  320. assert.Empty(t, buff.String())
  321. // up-to-date, no output produced
  322. require.NoError(t, e.Run(context.Background(), &ast.Call{Task: "gen-silent-baz"}))
  323. assert.Empty(t, buff.String())
  324. e.Verbose = true
  325. // up-to-date, output produced due to Verbose mode.
  326. require.NoError(t, e.Run(context.Background(), &ast.Call{Task: "gen-silent-baz"}))
  327. assert.Equal(t, `task: Task "gen-silent-baz" is up to date`, strings.TrimSpace(buff.String()))
  328. buff.Reset()
  329. }
  330. func TestPrecondition(t *testing.T) {
  331. const dir = "testdata/precondition"
  332. var buff bytes.Buffer
  333. e := &task.Executor{
  334. Dir: dir,
  335. Stdout: &buff,
  336. Stderr: &buff,
  337. }
  338. // A precondition that has been met
  339. require.NoError(t, e.Setup())
  340. require.NoError(t, e.Run(context.Background(), &ast.Call{Task: "foo"}))
  341. if buff.String() != "" {
  342. t.Errorf("Got Output when none was expected: %s", buff.String())
  343. }
  344. // A precondition that was not met
  345. require.Error(t, e.Run(context.Background(), &ast.Call{Task: "impossible"}))
  346. if buff.String() != "task: 1 != 0 obviously!\n" {
  347. t.Errorf("Wrong output message: %s", buff.String())
  348. }
  349. buff.Reset()
  350. // Calling a task with a precondition in a dependency fails the task
  351. require.Error(t, e.Run(context.Background(), &ast.Call{Task: "depends_on_impossible"}))
  352. if buff.String() != "task: 1 != 0 obviously!\n" {
  353. t.Errorf("Wrong output message: %s", buff.String())
  354. }
  355. buff.Reset()
  356. // Calling a task with a precondition in a cmd fails the task
  357. require.Error(t, e.Run(context.Background(), &ast.Call{Task: "executes_failing_task_as_cmd"}))
  358. if buff.String() != "task: 1 != 0 obviously!\n" {
  359. t.Errorf("Wrong output message: %s", buff.String())
  360. }
  361. buff.Reset()
  362. }
  363. func TestGenerates(t *testing.T) {
  364. const dir = "testdata/generates"
  365. const (
  366. srcTask = "sub/src.txt"
  367. relTask = "rel.txt"
  368. absTask = "abs.txt"
  369. fileWithSpaces = "my text file.txt"
  370. )
  371. srcFile := filepathext.SmartJoin(dir, srcTask)
  372. for _, task := range []string{srcTask, relTask, absTask, fileWithSpaces} {
  373. path := filepathext.SmartJoin(dir, task)
  374. _ = os.Remove(path)
  375. if _, err := os.Stat(path); err == nil {
  376. t.Errorf("File should not exist: %v", err)
  377. }
  378. }
  379. buff := bytes.NewBuffer(nil)
  380. e := &task.Executor{
  381. Dir: dir,
  382. Stdout: buff,
  383. Stderr: buff,
  384. }
  385. require.NoError(t, e.Setup())
  386. for _, theTask := range []string{relTask, absTask, fileWithSpaces} {
  387. destFile := filepathext.SmartJoin(dir, theTask)
  388. upToDate := fmt.Sprintf("task: Task \"%s\" is up to date\n", srcTask) +
  389. fmt.Sprintf("task: Task \"%s\" is up to date\n", theTask)
  390. // Run task for the first time.
  391. require.NoError(t, e.Run(context.Background(), &ast.Call{Task: theTask}))
  392. if _, err := os.Stat(srcFile); err != nil {
  393. t.Errorf("File should exist: %v", err)
  394. }
  395. if _, err := os.Stat(destFile); err != nil {
  396. t.Errorf("File should exist: %v", err)
  397. }
  398. // Ensure task was not incorrectly found to be up-to-date on first run.
  399. if buff.String() == upToDate {
  400. t.Errorf("Wrong output message: %s", buff.String())
  401. }
  402. buff.Reset()
  403. // Re-run task to ensure it's now found to be up-to-date.
  404. require.NoError(t, e.Run(context.Background(), &ast.Call{Task: theTask}))
  405. if buff.String() != upToDate {
  406. t.Errorf("Wrong output message: %s", buff.String())
  407. }
  408. buff.Reset()
  409. }
  410. }
  411. func TestStatusChecksum(t *testing.T) {
  412. const dir = "testdata/checksum"
  413. tests := []struct {
  414. files []string
  415. task string
  416. }{
  417. {[]string{"generated.txt", ".task/checksum/build"}, "build"},
  418. {[]string{"generated.txt", ".task/checksum/build-with-status"}, "build-with-status"},
  419. }
  420. for _, test := range tests {
  421. t.Run(test.task, func(t *testing.T) {
  422. for _, f := range test.files {
  423. _ = os.Remove(filepathext.SmartJoin(dir, f))
  424. _, err := os.Stat(filepathext.SmartJoin(dir, f))
  425. require.Error(t, err)
  426. }
  427. var buff bytes.Buffer
  428. tempdir := task.TempDir{
  429. Remote: filepathext.SmartJoin(dir, ".task"),
  430. Fingerprint: filepathext.SmartJoin(dir, ".task"),
  431. }
  432. e := task.Executor{
  433. Dir: dir,
  434. TempDir: tempdir,
  435. Stdout: &buff,
  436. Stderr: &buff,
  437. }
  438. require.NoError(t, e.Setup())
  439. require.NoError(t, e.Run(context.Background(), &ast.Call{Task: test.task}))
  440. for _, f := range test.files {
  441. _, err := os.Stat(filepathext.SmartJoin(dir, f))
  442. require.NoError(t, err)
  443. }
  444. // Capture the modification time, so we can ensure the checksum file
  445. // is not regenerated when the hash hasn't changed.
  446. s, err := os.Stat(filepathext.SmartJoin(tempdir.Fingerprint, "checksum/"+test.task))
  447. require.NoError(t, err)
  448. time := s.ModTime()
  449. buff.Reset()
  450. require.NoError(t, e.Run(context.Background(), &ast.Call{Task: test.task}))
  451. assert.Equal(t, `task: Task "`+test.task+`" is up to date`+"\n", buff.String())
  452. s, err = os.Stat(filepathext.SmartJoin(tempdir.Fingerprint, "checksum/"+test.task))
  453. require.NoError(t, err)
  454. assert.Equal(t, time, s.ModTime())
  455. })
  456. }
  457. }
  458. func TestAlias(t *testing.T) {
  459. const dir = "testdata/alias"
  460. data, err := os.ReadFile(filepathext.SmartJoin(dir, "alias.txt"))
  461. require.NoError(t, err)
  462. var buff bytes.Buffer
  463. e := task.Executor{
  464. Dir: dir,
  465. Stdout: &buff,
  466. Stderr: &buff,
  467. }
  468. require.NoError(t, e.Setup())
  469. require.NoError(t, e.Run(context.Background(), &ast.Call{Task: "f"}))
  470. assert.Equal(t, string(data), buff.String())
  471. }
  472. func TestDuplicateAlias(t *testing.T) {
  473. const dir = "testdata/alias"
  474. var buff bytes.Buffer
  475. e := task.Executor{
  476. Dir: dir,
  477. Stdout: &buff,
  478. Stderr: &buff,
  479. }
  480. require.NoError(t, e.Setup())
  481. require.Error(t, e.Run(context.Background(), &ast.Call{Task: "x"}))
  482. assert.Equal(t, "", buff.String())
  483. }
  484. func TestAliasSummary(t *testing.T) {
  485. const dir = "testdata/alias"
  486. data, err := os.ReadFile(filepathext.SmartJoin(dir, "alias-summary.txt"))
  487. require.NoError(t, err)
  488. var buff bytes.Buffer
  489. e := task.Executor{
  490. Dir: dir,
  491. Summary: true,
  492. Stdout: &buff,
  493. Stderr: &buff,
  494. }
  495. require.NoError(t, e.Setup())
  496. require.NoError(t, e.Run(context.Background(), &ast.Call{Task: "f"}))
  497. assert.Equal(t, string(data), buff.String())
  498. }
  499. func TestLabelUpToDate(t *testing.T) {
  500. const dir = "testdata/label_uptodate"
  501. var buff bytes.Buffer
  502. e := task.Executor{
  503. Dir: dir,
  504. Stdout: &buff,
  505. Stderr: &buff,
  506. }
  507. require.NoError(t, e.Setup())
  508. require.NoError(t, e.Run(context.Background(), &ast.Call{Task: "foo"}))
  509. assert.Contains(t, buff.String(), "foobar")
  510. }
  511. func TestLabelSummary(t *testing.T) {
  512. const dir = "testdata/label_summary"
  513. var buff bytes.Buffer
  514. e := task.Executor{
  515. Dir: dir,
  516. Summary: true,
  517. Stdout: &buff,
  518. Stderr: &buff,
  519. }
  520. require.NoError(t, e.Setup())
  521. require.NoError(t, e.Run(context.Background(), &ast.Call{Task: "foo"}))
  522. assert.Contains(t, buff.String(), "foobar")
  523. }
  524. func TestLabelInStatus(t *testing.T) {
  525. const dir = "testdata/label_status"
  526. e := task.Executor{
  527. Dir: dir,
  528. }
  529. require.NoError(t, e.Setup())
  530. err := e.Status(context.Background(), &ast.Call{Task: "foo"})
  531. assert.ErrorContains(t, err, "foobar")
  532. }
  533. func TestLabelWithVariableExpansion(t *testing.T) {
  534. const dir = "testdata/label_var"
  535. var buff bytes.Buffer
  536. e := task.Executor{
  537. Dir: dir,
  538. Stdout: &buff,
  539. Stderr: &buff,
  540. }
  541. require.NoError(t, e.Setup())
  542. require.NoError(t, e.Run(context.Background(), &ast.Call{Task: "foo"}))
  543. assert.Contains(t, buff.String(), "foobaz")
  544. }
  545. func TestLabelInSummary(t *testing.T) {
  546. const dir = "testdata/label_summary"
  547. var buff bytes.Buffer
  548. e := task.Executor{
  549. Dir: dir,
  550. Stdout: &buff,
  551. Stderr: &buff,
  552. }
  553. require.NoError(t, e.Setup())
  554. require.NoError(t, e.Run(context.Background(), &ast.Call{Task: "foo"}))
  555. assert.Contains(t, buff.String(), "foobar")
  556. }
  557. func TestPromptInSummary(t *testing.T) {
  558. const dir = "testdata/prompt"
  559. tests := []struct {
  560. name string
  561. input string
  562. wantError bool
  563. }{
  564. {"test short approval", "y\n", false},
  565. {"test long approval", "yes\n", false},
  566. {"test uppercase approval", "Y\n", false},
  567. {"test stops task", "n\n", true},
  568. {"test junk value stops task", "foobar\n", true},
  569. {"test Enter stops task", "\n", true},
  570. }
  571. for _, test := range tests {
  572. t.Run(test.name, func(t *testing.T) {
  573. var inBuff bytes.Buffer
  574. var outBuff bytes.Buffer
  575. var errBuff bytes.Buffer
  576. inBuff.Write([]byte(test.input))
  577. e := task.Executor{
  578. Dir: dir,
  579. Stdin: &inBuff,
  580. Stdout: &outBuff,
  581. Stderr: &errBuff,
  582. AssumeTerm: true,
  583. }
  584. require.NoError(t, e.Setup())
  585. err := e.Run(context.Background(), &ast.Call{Task: "foo"})
  586. if test.wantError {
  587. require.Error(t, err)
  588. } else {
  589. require.NoError(t, err)
  590. }
  591. })
  592. }
  593. }
  594. func TestPromptWithIndirectTask(t *testing.T) {
  595. const dir = "testdata/prompt"
  596. var inBuff bytes.Buffer
  597. var outBuff bytes.Buffer
  598. var errBuff bytes.Buffer
  599. inBuff.Write([]byte("y\n"))
  600. e := task.Executor{
  601. Dir: dir,
  602. Stdin: &inBuff,
  603. Stdout: &outBuff,
  604. Stderr: &errBuff,
  605. AssumeTerm: true,
  606. }
  607. require.NoError(t, e.Setup())
  608. err := e.Run(context.Background(), &ast.Call{Task: "bar"})
  609. assert.Contains(t, outBuff.String(), "show-prompt")
  610. require.NoError(t, err)
  611. }
  612. func TestPromptAssumeYes(t *testing.T) {
  613. const dir = "testdata/prompt"
  614. tests := []struct {
  615. name string
  616. assumeYes bool
  617. }{
  618. {"--yes flag should skip prompt", true},
  619. {"task should raise errors.TaskCancelledError", false},
  620. }
  621. for _, test := range tests {
  622. t.Run(test.name, func(t *testing.T) {
  623. var inBuff bytes.Buffer
  624. var outBuff bytes.Buffer
  625. var errBuff bytes.Buffer
  626. // always cancel the prompt so we can require.Error
  627. inBuff.Write([]byte("\n"))
  628. e := task.Executor{
  629. Dir: dir,
  630. Stdin: &inBuff,
  631. Stdout: &outBuff,
  632. Stderr: &errBuff,
  633. AssumeYes: test.assumeYes,
  634. }
  635. require.NoError(t, e.Setup())
  636. err := e.Run(context.Background(), &ast.Call{Task: "foo"})
  637. if !test.assumeYes {
  638. require.Error(t, err)
  639. return
  640. }
  641. })
  642. }
  643. }
  644. func TestNoLabelInList(t *testing.T) {
  645. const dir = "testdata/label_list"
  646. var buff bytes.Buffer
  647. e := task.Executor{
  648. Dir: dir,
  649. Stdout: &buff,
  650. Stderr: &buff,
  651. }
  652. require.NoError(t, e.Setup())
  653. if _, err := e.ListTasks(task.ListOptions{ListOnlyTasksWithDescriptions: true}); err != nil {
  654. t.Error(err)
  655. }
  656. assert.Contains(t, buff.String(), "foo")
  657. }
  658. // task -al case 1: listAll list all tasks
  659. func TestListAllShowsNoDesc(t *testing.T) {
  660. const dir = "testdata/list_mixed_desc"
  661. var buff bytes.Buffer
  662. e := task.Executor{
  663. Dir: dir,
  664. Stdout: &buff,
  665. Stderr: &buff,
  666. }
  667. require.NoError(t, e.Setup())
  668. var title string
  669. if _, err := e.ListTasks(task.ListOptions{ListAllTasks: true}); err != nil {
  670. t.Error(err)
  671. }
  672. for _, title = range []string{
  673. "foo",
  674. "voo",
  675. "doo",
  676. } {
  677. assert.Contains(t, buff.String(), title)
  678. }
  679. }
  680. // task -al case 2: !listAll list some tasks (only those with desc)
  681. func TestListCanListDescOnly(t *testing.T) {
  682. const dir = "testdata/list_mixed_desc"
  683. var buff bytes.Buffer
  684. e := task.Executor{
  685. Dir: dir,
  686. Stdout: &buff,
  687. Stderr: &buff,
  688. }
  689. require.NoError(t, e.Setup())
  690. if _, err := e.ListTasks(task.ListOptions{ListOnlyTasksWithDescriptions: true}); err != nil {
  691. t.Error(err)
  692. }
  693. var title string
  694. assert.Contains(t, buff.String(), "foo")
  695. for _, title = range []string{
  696. "voo",
  697. "doo",
  698. } {
  699. assert.NotContains(t, buff.String(), title)
  700. }
  701. }
  702. func TestListDescInterpolation(t *testing.T) {
  703. const dir = "testdata/list_desc_interpolation"
  704. var buff bytes.Buffer
  705. e := task.Executor{
  706. Dir: dir,
  707. Stdout: &buff,
  708. Stderr: &buff,
  709. }
  710. require.NoError(t, e.Setup())
  711. if _, err := e.ListTasks(task.ListOptions{ListOnlyTasksWithDescriptions: true}); err != nil {
  712. t.Error(err)
  713. }
  714. assert.Contains(t, buff.String(), "foo-var")
  715. assert.Contains(t, buff.String(), "bar-var")
  716. }
  717. func TestStatusVariables(t *testing.T) {
  718. const dir = "testdata/status_vars"
  719. _ = os.RemoveAll(filepathext.SmartJoin(dir, ".task"))
  720. _ = os.Remove(filepathext.SmartJoin(dir, "generated.txt"))
  721. var buff bytes.Buffer
  722. e := task.Executor{
  723. Dir: dir,
  724. TempDir: task.TempDir{
  725. Remote: filepathext.SmartJoin(dir, ".task"),
  726. Fingerprint: filepathext.SmartJoin(dir, ".task"),
  727. },
  728. Stdout: &buff,
  729. Stderr: &buff,
  730. Silent: false,
  731. Verbose: true,
  732. }
  733. require.NoError(t, e.Setup())
  734. require.NoError(t, e.Run(context.Background(), &ast.Call{Task: "build"}))
  735. assert.Contains(t, buff.String(), "3e464c4b03f4b65d740e1e130d4d108a")
  736. inf, err := os.Stat(filepathext.SmartJoin(dir, "source.txt"))
  737. require.NoError(t, err)
  738. ts := fmt.Sprintf("%d", inf.ModTime().Unix())
  739. tf := inf.ModTime().String()
  740. assert.Contains(t, buff.String(), ts)
  741. assert.Contains(t, buff.String(), tf)
  742. }
  743. func TestInit(t *testing.T) {
  744. const dir = "testdata/init"
  745. file := filepathext.SmartJoin(dir, "Taskfile.yml")
  746. _ = os.Remove(file)
  747. if _, err := os.Stat(file); err == nil {
  748. t.Errorf("Taskfile.yml should not exist")
  749. }
  750. if err := task.InitTaskfile(io.Discard, dir); err != nil {
  751. t.Error(err)
  752. }
  753. if _, err := os.Stat(file); err != nil {
  754. t.Errorf("Taskfile.yml should exist")
  755. }
  756. _ = os.Remove(file)
  757. }
  758. func TestCyclicDep(t *testing.T) {
  759. const dir = "testdata/cyclic"
  760. e := task.Executor{
  761. Dir: dir,
  762. Stdout: io.Discard,
  763. Stderr: io.Discard,
  764. }
  765. require.NoError(t, e.Setup())
  766. assert.IsType(t, &errors.TaskCalledTooManyTimesError{}, e.Run(context.Background(), &ast.Call{Task: "task-1"}))
  767. }
  768. func TestTaskVersion(t *testing.T) {
  769. tests := []struct {
  770. Dir string
  771. Version *semver.Version
  772. wantErr bool
  773. }{
  774. {"testdata/version/v1", semver.MustParse("1"), true},
  775. {"testdata/version/v2", semver.MustParse("2"), true},
  776. {"testdata/version/v3", semver.MustParse("3"), false},
  777. }
  778. for _, test := range tests {
  779. t.Run(test.Dir, func(t *testing.T) {
  780. e := task.Executor{
  781. Dir: test.Dir,
  782. Stdout: io.Discard,
  783. Stderr: io.Discard,
  784. }
  785. err := e.Setup()
  786. if test.wantErr {
  787. require.Error(t, err)
  788. return
  789. }
  790. require.NoError(t, err)
  791. assert.Equal(t, test.Version, e.Taskfile.Version)
  792. assert.Equal(t, 2, e.Taskfile.Tasks.Len())
  793. })
  794. }
  795. }
  796. func TestTaskIgnoreErrors(t *testing.T) {
  797. const dir = "testdata/ignore_errors"
  798. e := task.Executor{
  799. Dir: dir,
  800. Stdout: io.Discard,
  801. Stderr: io.Discard,
  802. }
  803. require.NoError(t, e.Setup())
  804. require.NoError(t, e.Run(context.Background(), &ast.Call{Task: "task-should-pass"}))
  805. require.Error(t, e.Run(context.Background(), &ast.Call{Task: "task-should-fail"}))
  806. require.NoError(t, e.Run(context.Background(), &ast.Call{Task: "cmd-should-pass"}))
  807. require.Error(t, e.Run(context.Background(), &ast.Call{Task: "cmd-should-fail"}))
  808. }
  809. func TestExpand(t *testing.T) {
  810. const dir = "testdata/expand"
  811. home, err := os.UserHomeDir()
  812. if err != nil {
  813. t.Errorf("Couldn't get $HOME: %v", err)
  814. }
  815. var buff bytes.Buffer
  816. e := task.Executor{
  817. Dir: dir,
  818. Stdout: &buff,
  819. Stderr: &buff,
  820. }
  821. require.NoError(t, e.Setup())
  822. require.NoError(t, e.Run(context.Background(), &ast.Call{Task: "pwd"}))
  823. assert.Equal(t, home, strings.TrimSpace(buff.String()))
  824. }
  825. func TestDry(t *testing.T) {
  826. const dir = "testdata/dry"
  827. file := filepathext.SmartJoin(dir, "file.txt")
  828. _ = os.Remove(file)
  829. var buff bytes.Buffer
  830. e := task.Executor{
  831. Dir: dir,
  832. Stdout: &buff,
  833. Stderr: &buff,
  834. Dry: true,
  835. }
  836. require.NoError(t, e.Setup())
  837. require.NoError(t, e.Run(context.Background(), &ast.Call{Task: "build"}))
  838. assert.Equal(t, "task: [build] touch file.txt", strings.TrimSpace(buff.String()))
  839. if _, err := os.Stat(file); err == nil {
  840. t.Errorf("File should not exist %s", file)
  841. }
  842. }
  843. // TestDryChecksum tests if the checksum file is not being written to disk
  844. // if the dry mode is enabled.
  845. func TestDryChecksum(t *testing.T) {
  846. const dir = "testdata/dry_checksum"
  847. checksumFile := filepathext.SmartJoin(dir, ".task/checksum/default")
  848. _ = os.Remove(checksumFile)
  849. e := task.Executor{
  850. Dir: dir,
  851. TempDir: task.TempDir{
  852. Remote: filepathext.SmartJoin(dir, ".task"),
  853. Fingerprint: filepathext.SmartJoin(dir, ".task"),
  854. },
  855. Stdout: io.Discard,
  856. Stderr: io.Discard,
  857. Dry: true,
  858. }
  859. require.NoError(t, e.Setup())
  860. require.NoError(t, e.Run(context.Background(), &ast.Call{Task: "default"}))
  861. _, err := os.Stat(checksumFile)
  862. require.Error(t, err, "checksum file should not exist")
  863. e.Dry = false
  864. require.NoError(t, e.Run(context.Background(), &ast.Call{Task: "default"}))
  865. _, err = os.Stat(checksumFile)
  866. require.NoError(t, err, "checksum file should exist")
  867. }
  868. func TestIncludes(t *testing.T) {
  869. tt := fileContentTest{
  870. Dir: "testdata/includes",
  871. Target: "default",
  872. TrimSpace: true,
  873. Files: map[string]string{
  874. "main.txt": "main",
  875. "included_directory.txt": "included_directory",
  876. "included_directory_without_dir.txt": "included_directory_without_dir",
  877. "included_taskfile_without_dir.txt": "included_taskfile_without_dir",
  878. "./module2/included_directory_with_dir.txt": "included_directory_with_dir",
  879. "./module2/included_taskfile_with_dir.txt": "included_taskfile_with_dir",
  880. "os_include.txt": "os",
  881. },
  882. }
  883. tt.Run(t)
  884. }
  885. func TestIncludesMultiLevel(t *testing.T) {
  886. tt := fileContentTest{
  887. Dir: "testdata/includes_multi_level",
  888. Target: "default",
  889. TrimSpace: true,
  890. Files: map[string]string{
  891. "called_one.txt": "one",
  892. "called_two.txt": "two",
  893. "called_three.txt": "three",
  894. },
  895. }
  896. tt.Run(t)
  897. }
  898. func TestIncludeCycle(t *testing.T) {
  899. const dir = "testdata/includes_cycle"
  900. var buff bytes.Buffer
  901. e := task.Executor{
  902. Dir: dir,
  903. Stdout: &buff,
  904. Stderr: &buff,
  905. Silent: true,
  906. }
  907. err := e.Setup()
  908. require.Error(t, err)
  909. assert.Contains(t, err.Error(), "task: include cycle detected between")
  910. }
  911. func TestIncludesIncorrect(t *testing.T) {
  912. const dir = "testdata/includes_incorrect"
  913. var buff bytes.Buffer
  914. e := task.Executor{
  915. Dir: dir,
  916. Stdout: &buff,
  917. Stderr: &buff,
  918. Silent: true,
  919. }
  920. err := e.Setup()
  921. require.Error(t, err)
  922. assert.Contains(t, err.Error(), "Failed to parse testdata/includes_incorrect/incomplete.yml:", err.Error())
  923. }
  924. func TestIncludesEmptyMain(t *testing.T) {
  925. tt := fileContentTest{
  926. Dir: "testdata/includes_empty",
  927. Target: "included:default",
  928. TrimSpace: true,
  929. Files: map[string]string{
  930. "file.txt": "default",
  931. },
  932. }
  933. tt.Run(t)
  934. }
  935. func TestIncludesDependencies(t *testing.T) {
  936. tt := fileContentTest{
  937. Dir: "testdata/includes_deps",
  938. Target: "default",
  939. TrimSpace: true,
  940. Files: map[string]string{
  941. "default.txt": "default",
  942. "called_dep.txt": "called_dep",
  943. "called_task.txt": "called_task",
  944. },
  945. }
  946. tt.Run(t)
  947. }
  948. func TestIncludesCallingRoot(t *testing.T) {
  949. tt := fileContentTest{
  950. Dir: "testdata/includes_call_root_task",
  951. Target: "included:call-root",
  952. TrimSpace: true,
  953. Files: map[string]string{
  954. "root_task.txt": "root task",
  955. },
  956. }
  957. tt.Run(t)
  958. }
  959. func TestIncludesOptional(t *testing.T) {
  960. tt := fileContentTest{
  961. Dir: "testdata/includes_optional",
  962. Target: "default",
  963. TrimSpace: true,
  964. Files: map[string]string{
  965. "called_dep.txt": "called_dep",
  966. },
  967. }
  968. tt.Run(t)
  969. }
  970. func TestIncludesOptionalImplicitFalse(t *testing.T) {
  971. const dir = "testdata/includes_optional_implicit_false"
  972. wd, _ := os.Getwd()
  973. message := "stat %s/%s/TaskfileOptional.yml: no such file or directory"
  974. expected := fmt.Sprintf(message, wd, dir)
  975. e := task.Executor{
  976. Dir: dir,
  977. Stdout: io.Discard,
  978. Stderr: io.Discard,
  979. }
  980. err := e.Setup()
  981. require.Error(t, err)
  982. assert.Equal(t, expected, err.Error())
  983. }
  984. func TestIncludesOptionalExplicitFalse(t *testing.T) {
  985. const dir = "testdata/includes_optional_explicit_false"
  986. wd, _ := os.Getwd()
  987. message := "stat %s/%s/TaskfileOptional.yml: no such file or directory"
  988. expected := fmt.Sprintf(message, wd, dir)
  989. e := task.Executor{
  990. Dir: dir,
  991. Stdout: io.Discard,
  992. Stderr: io.Discard,
  993. }
  994. err := e.Setup()
  995. require.Error(t, err)
  996. assert.Equal(t, expected, err.Error())
  997. }
  998. func TestIncludesFromCustomTaskfile(t *testing.T) {
  999. tt := fileContentTest{
  1000. Entrypoint: "testdata/includes_yaml/Custom.ext",
  1001. Dir: "testdata/includes_yaml",
  1002. Target: "default",
  1003. TrimSpace: true,
  1004. Files: map[string]string{
  1005. "main.txt": "main",
  1006. "included_with_yaml_extension.txt": "included_with_yaml_extension",
  1007. "included_with_custom_file.txt": "included_with_custom_file",
  1008. },
  1009. }
  1010. tt.Run(t)
  1011. }
  1012. func TestIncludesRelativePath(t *testing.T) {
  1013. const dir = "testdata/includes_rel_path"
  1014. var buff bytes.Buffer
  1015. e := task.Executor{
  1016. Dir: dir,
  1017. Stdout: &buff,
  1018. Stderr: &buff,
  1019. }
  1020. require.NoError(t, e.Setup())
  1021. require.NoError(t, e.Run(context.Background(), &ast.Call{Task: "common:pwd"}))
  1022. assert.Contains(t, buff.String(), "testdata/includes_rel_path/common")
  1023. buff.Reset()
  1024. require.NoError(t, e.Run(context.Background(), &ast.Call{Task: "included:common:pwd"}))
  1025. assert.Contains(t, buff.String(), "testdata/includes_rel_path/common")
  1026. }
  1027. func TestIncludesInternal(t *testing.T) {
  1028. const dir = "testdata/internal_task"
  1029. tests := []struct {
  1030. name string
  1031. task string
  1032. expectedErr bool
  1033. expectedOutput string
  1034. }{
  1035. {"included internal task via task", "task-1", false, "Hello, World!\n"},
  1036. {"included internal task via dep", "task-2", false, "Hello, World!\n"},
  1037. {"included internal direct", "included:task-3", true, "task: No tasks with description available. Try --list-all to list all tasks\n"},
  1038. }
  1039. for _, test := range tests {
  1040. t.Run(test.name, func(t *testing.T) {
  1041. var buff bytes.Buffer
  1042. e := task.Executor{
  1043. Dir: dir,
  1044. Stdout: &buff,
  1045. Stderr: &buff,
  1046. Silent: true,
  1047. }
  1048. require.NoError(t, e.Setup())
  1049. err := e.Run(context.Background(), &ast.Call{Task: test.task})
  1050. if test.expectedErr {
  1051. require.Error(t, err)
  1052. } else {
  1053. require.NoError(t, err)
  1054. }
  1055. assert.Equal(t, test.expectedOutput, buff.String())
  1056. })
  1057. }
  1058. }
  1059. func TestIncludesFlatten(t *testing.T) {
  1060. const dir = "testdata/includes_flatten"
  1061. tests := []struct {
  1062. name string
  1063. taskfile string
  1064. task string
  1065. expectedErr bool
  1066. expectedOutput string
  1067. }{
  1068. {name: "included flatten", taskfile: "Taskfile.yml", task: "gen", expectedOutput: "gen from included\n"},
  1069. {name: "included flatten with default", taskfile: "Taskfile.yml", task: "default", expectedOutput: "default from included flatten\n"},
  1070. {name: "included flatten can call entrypoint tasks", taskfile: "Taskfile.yml", task: "from_entrypoint", expectedOutput: "from entrypoint\n"},
  1071. {name: "included flatten with deps", taskfile: "Taskfile.yml", task: "with_deps", expectedOutput: "gen from included\nwith_deps from included\n"},
  1072. {name: "included flatten nested", taskfile: "Taskfile.yml", task: "from_nested", expectedOutput: "from nested\n"},
  1073. {name: "included flatten multiple same task", taskfile: "Taskfile.multiple.yml", task: "gen", expectedErr: true, expectedOutput: "task: Found multiple tasks (gen) included by \"included\"\""},
  1074. }
  1075. for _, test := range tests {
  1076. t.Run(test.name, func(t *testing.T) {
  1077. var buff bytes.Buffer
  1078. e := task.Executor{
  1079. Dir: dir,
  1080. Entrypoint: dir + "/" + test.taskfile,
  1081. Stdout: &buff,
  1082. Stderr: &buff,
  1083. Silent: true,
  1084. }
  1085. err := e.Setup()
  1086. if test.expectedErr {
  1087. assert.EqualError(t, err, test.expectedOutput)
  1088. } else {
  1089. require.NoError(t, err)
  1090. _ = e.Run(context.Background(), &ast.Call{Task: test.task})
  1091. assert.Equal(t, test.expectedOutput, buff.String())
  1092. }
  1093. })
  1094. }
  1095. }
  1096. func TestIncludesInterpolation(t *testing.T) {
  1097. const dir = "testdata/includes_interpolation"
  1098. tests := []struct {
  1099. name string
  1100. task string
  1101. expectedErr bool
  1102. expectedOutput string
  1103. }{
  1104. {"include", "include", false, "include\n"},
  1105. {"include_with_env_variable", "include-with-env-variable", false, "include_with_env_variable\n"},
  1106. {"include_with_dir", "include-with-dir", false, "included\n"},
  1107. }
  1108. t.Setenv("MODULE", "included")
  1109. for _, test := range tests {
  1110. t.Run(test.name, func(t *testing.T) {
  1111. var buff bytes.Buffer
  1112. e := task.Executor{
  1113. Dir: filepath.Join(dir, test.name),
  1114. Stdout: &buff,
  1115. Stderr: &buff,
  1116. Silent: true,
  1117. }
  1118. require.NoError(t, e.Setup())
  1119. err := e.Run(context.Background(), &ast.Call{Task: test.task})
  1120. if test.expectedErr {
  1121. require.Error(t, err)
  1122. } else {
  1123. require.NoError(t, err)
  1124. }
  1125. assert.Equal(t, test.expectedOutput, buff.String())
  1126. })
  1127. }
  1128. }
  1129. func TestIncludedTaskfileVarMerging(t *testing.T) {
  1130. const dir = "testdata/included_taskfile_var_merging"
  1131. tests := []struct {
  1132. name string
  1133. task string
  1134. expectedOutput string
  1135. }{
  1136. {"foo", "foo:pwd", "included_taskfile_var_merging/foo\n"},
  1137. {"bar", "bar:pwd", "included_taskfile_var_merging/bar\n"},
  1138. }
  1139. for _, test := range tests {
  1140. t.Run(test.name, func(t *testing.T) {
  1141. var buff bytes.Buffer
  1142. e := task.Executor{
  1143. Dir: dir,
  1144. Stdout: &buff,
  1145. Stderr: &buff,
  1146. Silent: true,
  1147. }
  1148. require.NoError(t, e.Setup())
  1149. err := e.Run(context.Background(), &ast.Call{Task: test.task})
  1150. require.NoError(t, err)
  1151. assert.Contains(t, buff.String(), test.expectedOutput)
  1152. })
  1153. }
  1154. }
  1155. func TestInternalTask(t *testing.T) {
  1156. const dir = "testdata/internal_task"
  1157. tests := []struct {
  1158. name string
  1159. task string
  1160. expectedErr bool
  1161. expectedOutput string
  1162. }{
  1163. {"internal task via task", "task-1", false, "Hello, World!\n"},
  1164. {"internal task via dep", "task-2", false, "Hello, World!\n"},
  1165. {"internal direct", "task-3", true, ""},
  1166. }
  1167. for _, test := range tests {
  1168. t.Run(test.name, func(t *testing.T) {
  1169. var buff bytes.Buffer
  1170. e := task.Executor{
  1171. Dir: dir,
  1172. Stdout: &buff,
  1173. Stderr: &buff,
  1174. Silent: true,
  1175. }
  1176. require.NoError(t, e.Setup())
  1177. err := e.Run(context.Background(), &ast.Call{Task: test.task})
  1178. if test.expectedErr {
  1179. require.Error(t, err)
  1180. } else {
  1181. require.NoError(t, err)
  1182. }
  1183. assert.Equal(t, test.expectedOutput, buff.String())
  1184. })
  1185. }
  1186. }
  1187. func TestIncludesShadowedDefault(t *testing.T) {
  1188. tt := fileContentTest{
  1189. Dir: "testdata/includes_shadowed_default",
  1190. Target: "included",
  1191. TrimSpace: true,
  1192. Files: map[string]string{
  1193. "file.txt": "shadowed",
  1194. },
  1195. }
  1196. tt.Run(t)
  1197. }
  1198. func TestIncludesUnshadowedDefault(t *testing.T) {
  1199. tt := fileContentTest{
  1200. Dir: "testdata/includes_unshadowed_default",
  1201. Target: "included",
  1202. TrimSpace: true,
  1203. Files: map[string]string{
  1204. "file.txt": "included",
  1205. },
  1206. }
  1207. tt.Run(t)
  1208. }
  1209. func TestSupportedFileNames(t *testing.T) {
  1210. fileNames := []string{
  1211. "Taskfile.yml",
  1212. "Taskfile.yaml",
  1213. "Taskfile.dist.yml",
  1214. "Taskfile.dist.yaml",
  1215. }
  1216. for _, fileName := range fileNames {
  1217. t.Run(fileName, func(t *testing.T) {
  1218. tt := fileContentTest{
  1219. Dir: fmt.Sprintf("testdata/file_names/%s", fileName),
  1220. Target: "default",
  1221. TrimSpace: true,
  1222. Files: map[string]string{
  1223. "output.txt": "hello",
  1224. },
  1225. }
  1226. tt.Run(t)
  1227. })
  1228. }
  1229. }
  1230. func TestSummary(t *testing.T) {
  1231. const dir = "testdata/summary"
  1232. var buff bytes.Buffer
  1233. e := task.Executor{
  1234. Dir: dir,
  1235. Stdout: &buff,
  1236. Stderr: &buff,
  1237. Summary: true,
  1238. Silent: true,
  1239. }
  1240. require.NoError(t, e.Setup())
  1241. require.NoError(t, e.Run(context.Background(), &ast.Call{Task: "task-with-summary"}, &ast.Call{Task: "other-task-with-summary"}))
  1242. data, err := os.ReadFile(filepathext.SmartJoin(dir, "task-with-summary.txt"))
  1243. require.NoError(t, err)
  1244. expectedOutput := string(data)
  1245. if runtime.GOOS == "windows" {
  1246. expectedOutput = strings.ReplaceAll(expectedOutput, "\r\n", "\n")
  1247. }
  1248. assert.Equal(t, expectedOutput, buff.String())
  1249. }
  1250. func TestWhenNoDirAttributeItRunsInSameDirAsTaskfile(t *testing.T) {
  1251. const expected = "dir"
  1252. const dir = "testdata/" + expected
  1253. var out bytes.Buffer
  1254. e := &task.Executor{
  1255. Dir: dir,
  1256. Stdout: &out,
  1257. Stderr: &out,
  1258. }
  1259. require.NoError(t, e.Setup())
  1260. require.NoError(t, e.Run(context.Background(), &ast.Call{Task: "whereami"}))
  1261. // got should be the "dir" part of "testdata/dir"
  1262. got := strings.TrimSuffix(filepath.Base(out.String()), "\n")
  1263. assert.Equal(t, expected, got, "Mismatch in the working directory")
  1264. }
  1265. func TestWhenDirAttributeAndDirExistsItRunsInThatDir(t *testing.T) {
  1266. const expected = "exists"
  1267. const dir = "testdata/dir/explicit_exists"
  1268. var out bytes.Buffer
  1269. e := &task.Executor{
  1270. Dir: dir,
  1271. Stdout: &out,
  1272. Stderr: &out,
  1273. }
  1274. require.NoError(t, e.Setup())
  1275. require.NoError(t, e.Run(context.Background(), &ast.Call{Task: "whereami"}))
  1276. got := strings.TrimSuffix(filepath.Base(out.String()), "\n")
  1277. assert.Equal(t, expected, got, "Mismatch in the working directory")
  1278. }
  1279. func TestWhenDirAttributeItCreatesMissingAndRunsInThatDir(t *testing.T) {
  1280. const expected = "createme"
  1281. const dir = "testdata/dir/explicit_doesnt_exist/"
  1282. const toBeCreated = dir + expected
  1283. const target = "whereami"
  1284. var out bytes.Buffer
  1285. e := &task.Executor{
  1286. Dir: dir,
  1287. Stdout: &out,
  1288. Stderr: &out,
  1289. }
  1290. // Ensure that the directory to be created doesn't actually exist.
  1291. _ = os.RemoveAll(toBeCreated)
  1292. if _, err := os.Stat(toBeCreated); err == nil {
  1293. t.Errorf("Directory should not exist: %v", err)
  1294. }
  1295. require.NoError(t, e.Setup())
  1296. require.NoError(t, e.Run(context.Background(), &ast.Call{Task: target}))
  1297. got := strings.TrimSuffix(filepath.Base(out.String()), "\n")
  1298. assert.Equal(t, expected, got, "Mismatch in the working directory")
  1299. // Clean-up after ourselves only if no error.
  1300. _ = os.RemoveAll(toBeCreated)
  1301. }
  1302. func TestDynamicVariablesRunOnTheNewCreatedDir(t *testing.T) {
  1303. const expected = "created"
  1304. const dir = "testdata/dir/dynamic_var_on_created_dir/"
  1305. const toBeCreated = dir + expected
  1306. const target = "default"
  1307. var out bytes.Buffer
  1308. e := &task.Executor{
  1309. Dir: dir,
  1310. Stdout: &out,
  1311. Stderr: &out,
  1312. }
  1313. // Ensure that the directory to be created doesn't actually exist.
  1314. _ = os.RemoveAll(toBeCreated)
  1315. if _, err := os.Stat(toBeCreated); err == nil {
  1316. t.Errorf("Directory should not exist: %v", err)
  1317. }
  1318. require.NoError(t, e.Setup())
  1319. require.NoError(t, e.Run(context.Background(), &ast.Call{Task: target}))
  1320. got := strings.TrimSuffix(filepath.Base(out.String()), "\n")
  1321. assert.Equal(t, expected, got, "Mismatch in the working directory")
  1322. // Clean-up after ourselves only if no error.
  1323. _ = os.RemoveAll(toBeCreated)
  1324. }
  1325. func TestDynamicVariablesShouldRunOnTheTaskDir(t *testing.T) {
  1326. tt := fileContentTest{
  1327. Dir: "testdata/dir/dynamic_var",
  1328. Target: "default",
  1329. TrimSpace: false,
  1330. Files: map[string]string{
  1331. "subdirectory/from_root_taskfile.txt": "subdirectory\n",
  1332. "subdirectory/from_included_taskfile.txt": "subdirectory\n",
  1333. "subdirectory/from_included_taskfile_task.txt": "subdirectory\n",
  1334. "subdirectory/from_interpolated_dir.txt": "subdirectory\n",
  1335. },
  1336. }
  1337. tt.Run(t)
  1338. }
  1339. func TestDisplaysErrorOnVersion1Schema(t *testing.T) {
  1340. e := task.Executor{
  1341. Dir: "testdata/version/v1",
  1342. Stdout: io.Discard,
  1343. Stderr: io.Discard,
  1344. }
  1345. err := e.Setup()
  1346. require.Error(t, err)
  1347. 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())
  1348. }
  1349. func TestDisplaysErrorOnVersion2Schema(t *testing.T) {
  1350. var buff bytes.Buffer
  1351. e := task.Executor{
  1352. Dir: "testdata/version/v2",
  1353. Stdout: io.Discard,
  1354. Stderr: &buff,
  1355. }
  1356. err := e.Setup()
  1357. require.Error(t, err)
  1358. 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())
  1359. }
  1360. func TestShortTaskNotation(t *testing.T) {
  1361. const dir = "testdata/short_task_notation"
  1362. var buff bytes.Buffer
  1363. e := task.Executor{
  1364. Dir: dir,
  1365. Stdout: &buff,
  1366. Stderr: &buff,
  1367. Silent: true,
  1368. }
  1369. require.NoError(t, e.Setup())
  1370. require.NoError(t, e.Run(context.Background(), &ast.Call{Task: "default"}))
  1371. assert.Equal(t, "string-slice-1\nstring-slice-2\nstring\n", buff.String())
  1372. }
  1373. func TestDotenvShouldIncludeAllEnvFiles(t *testing.T) {
  1374. tt := fileContentTest{
  1375. Dir: "testdata/dotenv/default",
  1376. Target: "default",
  1377. TrimSpace: false,
  1378. Files: map[string]string{
  1379. "include.txt": "INCLUDE1='from_include1' INCLUDE2='from_include2'\n",
  1380. },
  1381. }
  1382. tt.Run(t)
  1383. }
  1384. func TestDotenvShouldErrorWhenIncludingDependantDotenvs(t *testing.T) {
  1385. var buff bytes.Buffer
  1386. e := task.Executor{
  1387. Dir: "testdata/dotenv/error_included_envs",
  1388. Summary: true,
  1389. Stdout: &buff,
  1390. Stderr: &buff,
  1391. }
  1392. err := e.Setup()
  1393. require.Error(t, err)
  1394. assert.Contains(t, err.Error(), "move the dotenv")
  1395. }
  1396. func TestDotenvShouldAllowMissingEnv(t *testing.T) {
  1397. tt := fileContentTest{
  1398. Dir: "testdata/dotenv/missing_env",
  1399. Target: "default",
  1400. TrimSpace: false,
  1401. Files: map[string]string{
  1402. "include.txt": "INCLUDE1='' INCLUDE2=''\n",
  1403. },
  1404. }
  1405. tt.Run(t)
  1406. }
  1407. func TestDotenvHasLocalEnvInPath(t *testing.T) {
  1408. tt := fileContentTest{
  1409. Dir: "testdata/dotenv/local_env_in_path",
  1410. Target: "default",
  1411. TrimSpace: false,
  1412. Files: map[string]string{
  1413. "var.txt": "VAR='var_in_dot_env_1'\n",
  1414. },
  1415. }
  1416. tt.Run(t)
  1417. }
  1418. func TestDotenvHasLocalVarInPath(t *testing.T) {
  1419. tt := fileContentTest{
  1420. Dir: "testdata/dotenv/local_var_in_path",
  1421. Target: "default",
  1422. TrimSpace: false,
  1423. Files: map[string]string{
  1424. "var.txt": "VAR='var_in_dot_env_3'\n",
  1425. },
  1426. }
  1427. tt.Run(t)
  1428. }
  1429. func TestDotenvHasEnvVarInPath(t *testing.T) {
  1430. os.Setenv("ENV_VAR", "testing")
  1431. tt := fileContentTest{
  1432. Dir: "testdata/dotenv/env_var_in_path",
  1433. Target: "default",
  1434. TrimSpace: false,
  1435. Files: map[string]string{
  1436. "var.txt": "VAR='var_in_dot_env_2'\n",
  1437. },
  1438. }
  1439. tt.Run(t)
  1440. }
  1441. func TestTaskDotenv(t *testing.T) {
  1442. tt := fileContentTest{
  1443. Dir: "testdata/dotenv_task/default",
  1444. Target: "dotenv",
  1445. TrimSpace: true,
  1446. Files: map[string]string{
  1447. "dotenv.txt": "foo",
  1448. },
  1449. }
  1450. tt.Run(t)
  1451. }
  1452. func TestTaskDotenvFail(t *testing.T) {
  1453. tt := fileContentTest{
  1454. Dir: "testdata/dotenv_task/default",
  1455. Target: "no-dotenv",
  1456. TrimSpace: true,
  1457. Files: map[string]string{
  1458. "no-dotenv.txt": "global",
  1459. },
  1460. }
  1461. tt.Run(t)
  1462. }
  1463. func TestTaskDotenvOverriddenByEnv(t *testing.T) {
  1464. tt := fileContentTest{
  1465. Dir: "testdata/dotenv_task/default",
  1466. Target: "dotenv-overridden-by-env",
  1467. TrimSpace: true,
  1468. Files: map[string]string{
  1469. "dotenv-overridden-by-env.txt": "overridden",
  1470. },
  1471. }
  1472. tt.Run(t)
  1473. }
  1474. func TestTaskDotenvWithVarName(t *testing.T) {
  1475. tt := fileContentTest{
  1476. Dir: "testdata/dotenv_task/default",
  1477. Target: "dotenv-with-var-name",
  1478. TrimSpace: true,
  1479. Files: map[string]string{
  1480. "dotenv-with-var-name.txt": "foo",
  1481. },
  1482. }
  1483. tt.Run(t)
  1484. }
  1485. func TestExitImmediately(t *testing.T) {
  1486. const dir = "testdata/exit_immediately"
  1487. var buff bytes.Buffer
  1488. e := task.Executor{
  1489. Dir: dir,
  1490. Stdout: &buff,
  1491. Stderr: &buff,
  1492. Silent: true,
  1493. }
  1494. require.NoError(t, e.Setup())
  1495. require.Error(t, e.Run(context.Background(), &ast.Call{Task: "default"}))
  1496. assert.Contains(t, buff.String(), `"this_should_fail": executable file not found in $PATH`)
  1497. }
  1498. func TestRunOnlyRunsJobsHashOnce(t *testing.T) {
  1499. tt := fileContentTest{
  1500. Dir: "testdata/run",
  1501. Target: "generate-hash",
  1502. Files: map[string]string{
  1503. "hash.txt": "starting 1\n1\n2\n",
  1504. },
  1505. }
  1506. tt.Run(t)
  1507. }
  1508. func TestRunOnceSharedDeps(t *testing.T) {
  1509. const dir = "testdata/run_once_shared_deps"
  1510. var buff bytes.Buffer
  1511. e := task.Executor{
  1512. Dir: dir,
  1513. Stdout: &buff,
  1514. Stderr: &buff,
  1515. ForceAll: true,
  1516. }
  1517. require.NoError(t, e.Setup())
  1518. require.NoError(t, e.Run(context.Background(), &ast.Call{Task: "build"}))
  1519. rx := regexp.MustCompile(`task: \[service-[a,b]:library:build\] echo "build library"`)
  1520. matches := rx.FindAllStringSubmatch(buff.String(), -1)
  1521. assert.Len(t, matches, 1)
  1522. assert.Contains(t, buff.String(), `task: [service-a:build] echo "build a"`)
  1523. assert.Contains(t, buff.String(), `task: [service-b:build] echo "build b"`)
  1524. }
  1525. func TestDeferredCmds(t *testing.T) {
  1526. const dir = "testdata/deferred"
  1527. var buff bytes.Buffer
  1528. e := task.Executor{
  1529. Dir: dir,
  1530. Stdout: &buff,
  1531. Stderr: &buff,
  1532. }
  1533. require.NoError(t, e.Setup())
  1534. expectedOutputOrder := strings.TrimSpace(`
  1535. task: [task-2] echo 'cmd ran'
  1536. cmd ran
  1537. task: [task-2] exit 1
  1538. task: [task-2] echo 'failing' && exit 2
  1539. failing
  1540. task: [task-2] echo 'echo ran'
  1541. echo ran
  1542. task: [task-1] echo 'task-1 ran successfully'
  1543. task-1 ran successfully
  1544. `)
  1545. require.Error(t, e.Run(context.Background(), &ast.Call{Task: "task-2"}))
  1546. assert.Contains(t, buff.String(), expectedOutputOrder)
  1547. }
  1548. func TestExitCodeZero(t *testing.T) {
  1549. const dir = "testdata/exit_code"
  1550. var buff bytes.Buffer
  1551. e := task.Executor{
  1552. Dir: dir,
  1553. Stdout: &buff,
  1554. Stderr: &buff,
  1555. }
  1556. require.NoError(t, e.Setup())
  1557. require.NoError(t, e.Run(context.Background(), &ast.Call{Task: "exit-zero"}))
  1558. assert.Equal(t, "FOO=bar - DYNAMIC_FOO=bar - EXIT_CODE=", strings.TrimSpace(buff.String()))
  1559. }
  1560. func TestExitCodeOne(t *testing.T) {
  1561. const dir = "testdata/exit_code"
  1562. var buff bytes.Buffer
  1563. e := task.Executor{
  1564. Dir: dir,
  1565. Stdout: &buff,
  1566. Stderr: &buff,
  1567. }
  1568. require.NoError(t, e.Setup())
  1569. require.Error(t, e.Run(context.Background(), &ast.Call{Task: "exit-one"}))
  1570. assert.Equal(t, "FOO=bar - DYNAMIC_FOO=bar - EXIT_CODE=1", strings.TrimSpace(buff.String()))
  1571. }
  1572. func TestIgnoreNilElements(t *testing.T) {
  1573. tests := []struct {
  1574. name string
  1575. dir string
  1576. }{
  1577. {"nil cmd", "testdata/ignore_nil_elements/cmds"},
  1578. {"nil dep", "testdata/ignore_nil_elements/deps"},
  1579. {"nil include", "testdata/ignore_nil_elements/includes"},
  1580. {"nil precondition", "testdata/ignore_nil_elements/preconditions"},
  1581. }
  1582. for _, test := range tests {
  1583. t.Run(test.name, func(t *testing.T) {
  1584. var buff bytes.Buffer
  1585. e := task.Executor{
  1586. Dir: test.dir,
  1587. Stdout: &buff,
  1588. Stderr: &buff,
  1589. Silent: true,
  1590. }
  1591. require.NoError(t, e.Setup())
  1592. require.NoError(t, e.Run(context.Background(), &ast.Call{Task: "default"}))
  1593. assert.Equal(t, "string-slice-1\n", buff.String())
  1594. })
  1595. }
  1596. }
  1597. func TestOutputGroup(t *testing.T) {
  1598. const dir = "testdata/output_group"
  1599. var buff bytes.Buffer
  1600. e := task.Executor{
  1601. Dir: dir,
  1602. Stdout: &buff,
  1603. Stderr: &buff,
  1604. }
  1605. require.NoError(t, e.Setup())
  1606. expectedOutputOrder := strings.TrimSpace(`
  1607. task: [hello] echo 'Hello!'
  1608. ::group::hello
  1609. Hello!
  1610. ::endgroup::
  1611. task: [bye] echo 'Bye!'
  1612. ::group::bye
  1613. Bye!
  1614. ::endgroup::
  1615. `)
  1616. require.NoError(t, e.Run(context.Background(), &ast.Call{Task: "bye"}))
  1617. t.Log(buff.String())
  1618. assert.Equal(t, strings.TrimSpace(buff.String()), expectedOutputOrder)
  1619. }
  1620. func TestOutputGroupErrorOnlySwallowsOutputOnSuccess(t *testing.T) {
  1621. const dir = "testdata/output_group_error_only"
  1622. var buff bytes.Buffer
  1623. e := task.Executor{
  1624. Dir: dir,
  1625. Stdout: &buff,
  1626. Stderr: &buff,
  1627. }
  1628. require.NoError(t, e.Setup())
  1629. require.NoError(t, e.Run(context.Background(), &ast.Call{Task: "passing"}))
  1630. t.Log(buff.String())
  1631. assert.Empty(t, buff.String())
  1632. }
  1633. func TestOutputGroupErrorOnlyShowsOutputOnFailure(t *testing.T) {
  1634. const dir = "testdata/output_group_error_only"
  1635. var buff bytes.Buffer
  1636. e := task.Executor{
  1637. Dir: dir,
  1638. Stdout: &buff,
  1639. Stderr: &buff,
  1640. }
  1641. require.NoError(t, e.Setup())
  1642. require.Error(t, e.Run(context.Background(), &ast.Call{Task: "failing"}))
  1643. t.Log(buff.String())
  1644. assert.Contains(t, "failing-output", strings.TrimSpace(buff.String()))
  1645. assert.NotContains(t, "passing", strings.TrimSpace(buff.String()))
  1646. }
  1647. func TestIncludedVars(t *testing.T) {
  1648. const dir = "testdata/include_with_vars"
  1649. var buff bytes.Buffer
  1650. e := task.Executor{
  1651. Dir: dir,
  1652. Stdout: &buff,
  1653. Stderr: &buff,
  1654. }
  1655. require.NoError(t, e.Setup())
  1656. expectedOutputOrder := strings.TrimSpace(`
  1657. task: [included1:task1] echo "VAR_1 is included1-var1"
  1658. VAR_1 is included1-var1
  1659. task: [included1:task1] echo "VAR_2 is included-default-var2"
  1660. VAR_2 is included-default-var2
  1661. task: [included2:task1] echo "VAR_1 is included2-var1"
  1662. VAR_1 is included2-var1
  1663. task: [included2:task1] echo "VAR_2 is included-default-var2"
  1664. VAR_2 is included-default-var2
  1665. task: [included3:task1] echo "VAR_1 is included-default-var1"
  1666. VAR_1 is included-default-var1
  1667. task: [included3:task1] echo "VAR_2 is included-default-var2"
  1668. VAR_2 is included-default-var2
  1669. `)
  1670. require.NoError(t, e.Run(context.Background(), &ast.Call{Task: "task1"}))
  1671. t.Log(buff.String())
  1672. assert.Equal(t, strings.TrimSpace(buff.String()), expectedOutputOrder)
  1673. }
  1674. func TestIncludedVarsMultiLevel(t *testing.T) {
  1675. const dir = "testdata/include_with_vars_multi_level"
  1676. var buff bytes.Buffer
  1677. e := task.Executor{
  1678. Dir: dir,
  1679. Stdout: &buff,
  1680. Stderr: &buff,
  1681. }
  1682. require.NoError(t, e.Setup())
  1683. expectedOutputOrder := strings.TrimSpace(`
  1684. task: [lib:greet] echo 'Hello world'
  1685. Hello world
  1686. task: [foo:lib:greet] echo 'Hello foo'
  1687. Hello foo
  1688. task: [bar:lib:greet] echo 'Hello bar'
  1689. Hello bar
  1690. `)
  1691. require.NoError(t, e.Run(context.Background(), &ast.Call{Task: "default"}))
  1692. t.Log(buff.String())
  1693. assert.Equal(t, expectedOutputOrder, strings.TrimSpace(buff.String()))
  1694. }
  1695. func TestErrorCode(t *testing.T) {
  1696. const dir = "testdata/error_code"
  1697. tests := []struct {
  1698. name string
  1699. task string
  1700. expected int
  1701. }{
  1702. {
  1703. name: "direct task",
  1704. task: "direct",
  1705. expected: 42,
  1706. }, {
  1707. name: "indirect task",
  1708. task: "indirect",
  1709. expected: 42,
  1710. },
  1711. }
  1712. for _, test := range tests {
  1713. t.Run(test.name, func(t *testing.T) {
  1714. var buff bytes.Buffer
  1715. e := &task.Executor{
  1716. Dir: dir,
  1717. Stdout: &buff,
  1718. Stderr: &buff,
  1719. Silent: true,
  1720. }
  1721. require.NoError(t, e.Setup())
  1722. err := e.Run(context.Background(), &ast.Call{Task: test.task})
  1723. require.Error(t, err)
  1724. taskRunErr, ok := err.(*errors.TaskRunError)
  1725. assert.True(t, ok, "cannot cast returned error to *task.TaskRunError")
  1726. assert.Equal(t, test.expected, taskRunErr.TaskExitCode(), "unexpected exit code from task")
  1727. })
  1728. }
  1729. }
  1730. func TestEvaluateSymlinksInPaths(t *testing.T) {
  1731. const dir = "testdata/evaluate_symlinks_in_paths"
  1732. var buff bytes.Buffer
  1733. e := &task.Executor{
  1734. Dir: dir,
  1735. Stdout: &buff,
  1736. Stderr: &buff,
  1737. Silent: false,
  1738. }
  1739. tests := []struct {
  1740. name string
  1741. task string
  1742. expected string
  1743. }{
  1744. {
  1745. name: "default (1)",
  1746. task: "default",
  1747. expected: "task: [default] echo \"some job\"\nsome job",
  1748. },
  1749. {
  1750. name: "test-sym (1)",
  1751. task: "test-sym",
  1752. expected: "task: [test-sym] echo \"shared file source changed\" > src/shared/b",
  1753. },
  1754. {
  1755. name: "default (2)",
  1756. task: "default",
  1757. expected: "task: [default] echo \"some job\"\nsome job",
  1758. },
  1759. {
  1760. name: "default (3)",
  1761. task: "default",
  1762. expected: `task: Task "default" is up to date`,
  1763. },
  1764. {
  1765. name: "reset",
  1766. task: "reset",
  1767. expected: "task: [reset] echo \"shared file source\" > src/shared/b\ntask: [reset] echo \"file source\" > src/a",
  1768. },
  1769. }
  1770. for _, test := range tests {
  1771. t.Run(test.name, func(t *testing.T) {
  1772. require.NoError(t, e.Setup())
  1773. err := e.Run(context.Background(), &ast.Call{Task: test.task})
  1774. require.NoError(t, err)
  1775. assert.Equal(t, test.expected, strings.TrimSpace(buff.String()))
  1776. buff.Reset()
  1777. })
  1778. }
  1779. err := os.RemoveAll(dir + "/.task")
  1780. require.NoError(t, err)
  1781. }
  1782. func TestTaskfileWalk(t *testing.T) {
  1783. tests := []struct {
  1784. name string
  1785. dir string
  1786. expected string
  1787. }{
  1788. {
  1789. name: "walk from root directory",
  1790. dir: "testdata/taskfile_walk",
  1791. expected: "foo\n",
  1792. }, {
  1793. name: "walk from sub directory",
  1794. dir: "testdata/taskfile_walk/foo",
  1795. expected: "foo\n",
  1796. }, {
  1797. name: "walk from sub sub directory",
  1798. dir: "testdata/taskfile_walk/foo/bar",
  1799. expected: "foo\n",
  1800. },
  1801. }
  1802. for _, test := range tests {
  1803. t.Run(test.name, func(t *testing.T) {
  1804. var buff bytes.Buffer
  1805. e := task.Executor{
  1806. Dir: test.dir,
  1807. Stdout: &buff,
  1808. Stderr: &buff,
  1809. }
  1810. require.NoError(t, e.Setup())
  1811. require.NoError(t, e.Run(context.Background(), &ast.Call{Task: "default"}))
  1812. assert.Equal(t, test.expected, buff.String())
  1813. })
  1814. }
  1815. }
  1816. func TestUserWorkingDirectory(t *testing.T) {
  1817. var buff bytes.Buffer
  1818. e := task.Executor{
  1819. Dir: "testdata/user_working_dir",
  1820. Stdout: &buff,
  1821. Stderr: &buff,
  1822. }
  1823. wd, err := os.Getwd()
  1824. require.NoError(t, err)
  1825. require.NoError(t, e.Setup())
  1826. require.NoError(t, e.Run(context.Background(), &ast.Call{Task: "default"}))
  1827. assert.Equal(t, fmt.Sprintf("%s\n", wd), buff.String())
  1828. }
  1829. func TestUserWorkingDirectoryWithIncluded(t *testing.T) {
  1830. wd, err := os.Getwd()
  1831. require.NoError(t, err)
  1832. wd = filepathext.SmartJoin(wd, "testdata/user_working_dir_with_includes/somedir")
  1833. var buff bytes.Buffer
  1834. e := task.Executor{
  1835. UserWorkingDir: wd,
  1836. Dir: "testdata/user_working_dir_with_includes",
  1837. Stdout: &buff,
  1838. Stderr: &buff,
  1839. }
  1840. require.NoError(t, err)
  1841. require.NoError(t, e.Setup())
  1842. require.NoError(t, e.Run(context.Background(), &ast.Call{Task: "included:echo"}))
  1843. assert.Equal(t, fmt.Sprintf("%s\n", wd), buff.String())
  1844. }
  1845. func TestPlatforms(t *testing.T) {
  1846. var buff bytes.Buffer
  1847. e := task.Executor{
  1848. Dir: "testdata/platforms",
  1849. Stdout: &buff,
  1850. Stderr: &buff,
  1851. }
  1852. require.NoError(t, e.Setup())
  1853. require.NoError(t, e.Run(context.Background(), &ast.Call{Task: "build-" + runtime.GOOS}))
  1854. 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())
  1855. }
  1856. func TestPOSIXShellOptsGlobalLevel(t *testing.T) {
  1857. var buff bytes.Buffer
  1858. e := task.Executor{
  1859. Dir: "testdata/shopts/global_level",
  1860. Stdout: &buff,
  1861. Stderr: &buff,
  1862. }
  1863. require.NoError(t, e.Setup())
  1864. err := e.Run(context.Background(), &ast.Call{Task: "pipefail"})
  1865. require.NoError(t, err)
  1866. assert.Equal(t, "pipefail\ton\n", buff.String())
  1867. }
  1868. func TestPOSIXShellOptsTaskLevel(t *testing.T) {
  1869. var buff bytes.Buffer
  1870. e := task.Executor{
  1871. Dir: "testdata/shopts/task_level",
  1872. Stdout: &buff,
  1873. Stderr: &buff,
  1874. }
  1875. require.NoError(t, e.Setup())
  1876. err := e.Run(context.Background(), &ast.Call{Task: "pipefail"})
  1877. require.NoError(t, err)
  1878. assert.Equal(t, "pipefail\ton\n", buff.String())
  1879. }
  1880. func TestPOSIXShellOptsCommandLevel(t *testing.T) {
  1881. var buff bytes.Buffer
  1882. e := task.Executor{
  1883. Dir: "testdata/shopts/command_level",
  1884. Stdout: &buff,
  1885. Stderr: &buff,
  1886. }
  1887. require.NoError(t, e.Setup())
  1888. err := e.Run(context.Background(), &ast.Call{Task: "pipefail"})
  1889. require.NoError(t, err)
  1890. assert.Equal(t, "pipefail\ton\n", buff.String())
  1891. }
  1892. func TestBashShellOptsGlobalLevel(t *testing.T) {
  1893. var buff bytes.Buffer
  1894. e := task.Executor{
  1895. Dir: "testdata/shopts/global_level",
  1896. Stdout: &buff,
  1897. Stderr: &buff,
  1898. }
  1899. require.NoError(t, e.Setup())
  1900. err := e.Run(context.Background(), &ast.Call{Task: "globstar"})
  1901. require.NoError(t, err)
  1902. assert.Equal(t, "globstar\ton\n", buff.String())
  1903. }
  1904. func TestBashShellOptsTaskLevel(t *testing.T) {
  1905. var buff bytes.Buffer
  1906. e := task.Executor{
  1907. Dir: "testdata/shopts/task_level",
  1908. Stdout: &buff,
  1909. Stderr: &buff,
  1910. }
  1911. require.NoError(t, e.Setup())
  1912. err := e.Run(context.Background(), &ast.Call{Task: "globstar"})
  1913. require.NoError(t, err)
  1914. assert.Equal(t, "globstar\ton\n", buff.String())
  1915. }
  1916. func TestBashShellOptsCommandLevel(t *testing.T) {
  1917. var buff bytes.Buffer
  1918. e := task.Executor{
  1919. Dir: "testdata/shopts/command_level",
  1920. Stdout: &buff,
  1921. Stderr: &buff,
  1922. }
  1923. require.NoError(t, e.Setup())
  1924. err := e.Run(context.Background(), &ast.Call{Task: "globstar"})
  1925. require.NoError(t, err)
  1926. assert.Equal(t, "globstar\ton\n", buff.String())
  1927. }
  1928. func TestSplitArgs(t *testing.T) {
  1929. var buff bytes.Buffer
  1930. e := task.Executor{
  1931. Dir: "testdata/split_args",
  1932. Stdout: &buff,
  1933. Stderr: &buff,
  1934. Silent: true,
  1935. }
  1936. require.NoError(t, e.Setup())
  1937. vars := &ast.Vars{}
  1938. vars.Set("CLI_ARGS", ast.Var{Value: "foo bar 'foo bar baz'"})
  1939. err := e.Run(context.Background(), &ast.Call{Task: "default", Vars: vars})
  1940. require.NoError(t, err)
  1941. assert.Equal(t, "3\n", buff.String())
  1942. }
  1943. func TestSingleCmdDep(t *testing.T) {
  1944. tt := fileContentTest{
  1945. Dir: "testdata/single_cmd_dep",
  1946. Target: "foo",
  1947. Files: map[string]string{
  1948. "foo.txt": "foo\n",
  1949. "bar.txt": "bar\n",
  1950. },
  1951. }
  1952. tt.Run(t)
  1953. }
  1954. func TestSilence(t *testing.T) {
  1955. var buff bytes.Buffer
  1956. e := task.Executor{
  1957. Dir: "testdata/silent",
  1958. Stdout: &buff,
  1959. Stderr: &buff,
  1960. Silent: false,
  1961. }
  1962. require.NoError(t, e.Setup())
  1963. // First verify that the silent flag is in place.
  1964. task, err := e.GetTask(&ast.Call{Task: "task-test-silent-calls-chatty-silenced"})
  1965. require.NoError(t, err, "Unable to look up task task-test-silent-calls-chatty-silenced")
  1966. require.True(t, task.Cmds[0].Silent, "The task task-test-silent-calls-chatty-silenced should have a silent call to chatty")
  1967. // Then test the two basic cases where the task is silent or not.
  1968. // A silenced task.
  1969. err = e.Run(context.Background(), &ast.Call{Task: "silent"})
  1970. require.NoError(t, err)
  1971. require.Empty(t, buff.String(), "siWhile running lent: Expected not see output, because the task is silent")
  1972. buff.Reset()
  1973. // A chatty (not silent) task.
  1974. err = e.Run(context.Background(), &ast.Call{Task: "chatty"})
  1975. require.NoError(t, err)
  1976. require.NotEmpty(t, buff.String(), "chWhile running atty: Expected to see output, because the task is not silent")
  1977. buff.Reset()
  1978. // Then test invoking the two task from other tasks.
  1979. // A silenced task that calls a chatty task.
  1980. err = e.Run(context.Background(), &ast.Call{Task: "task-test-silent-calls-chatty-non-silenced"})
  1981. require.NoError(t, err)
  1982. 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.")
  1983. buff.Reset()
  1984. // A silent task that does a silent call to a chatty task.
  1985. err = e.Run(context.Background(), &ast.Call{Task: "task-test-silent-calls-chatty-silenced"})
  1986. require.NoError(t, err)
  1987. 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.")
  1988. buff.Reset()
  1989. // A chatty task that does a call to a chatty task.
  1990. err = e.Run(context.Background(), &ast.Call{Task: "task-test-chatty-calls-chatty-non-silenced"})
  1991. require.NoError(t, err)
  1992. 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.")
  1993. buff.Reset()
  1994. // A chatty task that does a silenced call to a chatty task.
  1995. err = e.Run(context.Background(), &ast.Call{Task: "task-test-chatty-calls-chatty-silenced"})
  1996. require.NoError(t, err)
  1997. 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.")
  1998. buff.Reset()
  1999. // A chatty task with no cmd's of its own that does a silenced call to a chatty task.
  2000. err = e.Run(context.Background(), &ast.Call{Task: "task-test-no-cmds-calls-chatty-silenced"})
  2001. require.NoError(t, err)
  2002. 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.")
  2003. buff.Reset()
  2004. // A chatty task that does a silenced invocation of a task.
  2005. err = e.Run(context.Background(), &ast.Call{Task: "task-test-chatty-calls-silenced-cmd"})
  2006. require.NoError(t, err)
  2007. 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.")
  2008. buff.Reset()
  2009. // Then test calls via dependencies.
  2010. // A silent task that depends on a chatty task.
  2011. err = e.Run(context.Background(), &ast.Call{Task: "task-test-is-silent-depends-on-chatty-non-silenced"})
  2012. require.NoError(t, err)
  2013. 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.")
  2014. buff.Reset()
  2015. // A silent task that depends on a silenced chatty task.
  2016. err = e.Run(context.Background(), &ast.Call{Task: "task-test-is-silent-depends-on-chatty-silenced"})
  2017. require.NoError(t, err)
  2018. 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.")
  2019. buff.Reset()
  2020. // A chatty task that, depends on a silenced chatty task.
  2021. err = e.Run(context.Background(), &ast.Call{Task: "task-test-is-chatty-depends-on-chatty-silenced"})
  2022. require.NoError(t, err)
  2023. 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.")
  2024. buff.Reset()
  2025. }
  2026. func TestForce(t *testing.T) {
  2027. tests := []struct {
  2028. name string
  2029. env map[string]string
  2030. force bool
  2031. forceAll bool
  2032. }{
  2033. {
  2034. name: "force",
  2035. force: true,
  2036. },
  2037. {
  2038. name: "force-all",
  2039. forceAll: true,
  2040. },
  2041. {
  2042. name: "force with gentle force experiment",
  2043. force: true,
  2044. env: map[string]string{
  2045. "TASK_X_GENTLE_FORCE": "1",
  2046. },
  2047. },
  2048. {
  2049. name: "force-all with gentle force experiment",
  2050. forceAll: true,
  2051. env: map[string]string{
  2052. "TASK_X_GENTLE_FORCE": "1",
  2053. },
  2054. },
  2055. }
  2056. for _, tt := range tests {
  2057. t.Run(tt.name, func(t *testing.T) {
  2058. var buff bytes.Buffer
  2059. e := task.Executor{
  2060. Dir: "testdata/force",
  2061. Stdout: &buff,
  2062. Stderr: &buff,
  2063. Force: tt.force,
  2064. ForceAll: tt.forceAll,
  2065. }
  2066. require.NoError(t, e.Setup())
  2067. require.NoError(t, e.Run(context.Background(), &ast.Call{Task: "task-with-dep"}))
  2068. })
  2069. }
  2070. }
  2071. func TestForCmds(t *testing.T) {
  2072. tests := []struct {
  2073. name string
  2074. expectedOutput string
  2075. }{
  2076. {
  2077. name: "loop-explicit",
  2078. expectedOutput: "a\nb\nc\n",
  2079. },
  2080. {
  2081. name: "loop-matrix",
  2082. expectedOutput: "windows/amd64\nwindows/arm64\nlinux/amd64\nlinux/arm64\ndarwin/amd64\ndarwin/arm64\n",
  2083. },
  2084. {
  2085. name: "loop-sources",
  2086. expectedOutput: "bar\nfoo\n",
  2087. },
  2088. {
  2089. name: "loop-sources-glob",
  2090. expectedOutput: "bar\nfoo\n",
  2091. },
  2092. {
  2093. name: "loop-vars",
  2094. expectedOutput: "foo\nbar\n",
  2095. },
  2096. {
  2097. name: "loop-vars-sh",
  2098. expectedOutput: "bar\nfoo\n",
  2099. },
  2100. {
  2101. name: "loop-task",
  2102. expectedOutput: "foo\nbar\n",
  2103. },
  2104. {
  2105. name: "loop-task-as",
  2106. expectedOutput: "foo\nbar\n",
  2107. },
  2108. {
  2109. name: "loop-different-tasks",
  2110. expectedOutput: "1\n2\n3\n",
  2111. },
  2112. }
  2113. for _, test := range tests {
  2114. t.Run(test.name, func(t *testing.T) {
  2115. var stdOut bytes.Buffer
  2116. var stdErr bytes.Buffer
  2117. e := task.Executor{
  2118. Dir: "testdata/for/cmds",
  2119. Stdout: &stdOut,
  2120. Stderr: &stdErr,
  2121. Silent: true,
  2122. Force: true,
  2123. }
  2124. require.NoError(t, e.Setup())
  2125. require.NoError(t, e.Run(context.Background(), &ast.Call{Task: test.name}))
  2126. assert.Equal(t, test.expectedOutput, stdOut.String())
  2127. })
  2128. }
  2129. }
  2130. func TestForDeps(t *testing.T) {
  2131. tests := []struct {
  2132. name string
  2133. expectedOutputContains []string
  2134. }{
  2135. {
  2136. name: "loop-explicit",
  2137. expectedOutputContains: []string{"a\n", "b\n", "c\n"},
  2138. },
  2139. {
  2140. name: "loop-matrix",
  2141. expectedOutputContains: []string{
  2142. "windows/amd64\n",
  2143. "windows/arm64\n",
  2144. "linux/amd64\n",
  2145. "linux/arm64\n",
  2146. "darwin/amd64\n",
  2147. "darwin/arm64\n",
  2148. },
  2149. },
  2150. {
  2151. name: "loop-sources",
  2152. expectedOutputContains: []string{"bar\n", "foo\n"},
  2153. },
  2154. {
  2155. name: "loop-sources-glob",
  2156. expectedOutputContains: []string{"bar\n", "foo\n"},
  2157. },
  2158. {
  2159. name: "loop-vars",
  2160. expectedOutputContains: []string{"foo\n", "bar\n"},
  2161. },
  2162. {
  2163. name: "loop-vars-sh",
  2164. expectedOutputContains: []string{"bar\n", "foo\n"},
  2165. },
  2166. {
  2167. name: "loop-task",
  2168. expectedOutputContains: []string{"foo\n", "bar\n"},
  2169. },
  2170. {
  2171. name: "loop-task-as",
  2172. expectedOutputContains: []string{"foo\n", "bar\n"},
  2173. },
  2174. {
  2175. name: "loop-different-tasks",
  2176. expectedOutputContains: []string{"1\n", "2\n", "3\n"},
  2177. },
  2178. }
  2179. for _, test := range tests {
  2180. t.Run(test.name, func(t *testing.T) {
  2181. // We need to use a sync buffer here as deps are run concurrently
  2182. var buff SyncBuffer
  2183. e := task.Executor{
  2184. Dir: "testdata/for/deps",
  2185. Stdout: &buff,
  2186. Stderr: &buff,
  2187. Silent: true,
  2188. Force: true,
  2189. }
  2190. require.NoError(t, e.Setup())
  2191. require.NoError(t, e.Run(context.Background(), &ast.Call{Task: test.name}))
  2192. for _, expectedOutputContains := range test.expectedOutputContains {
  2193. assert.Contains(t, buff.buf.String(), expectedOutputContains)
  2194. }
  2195. })
  2196. }
  2197. }
  2198. func TestWildcard(t *testing.T) {
  2199. tests := []struct {
  2200. name string
  2201. call string
  2202. expectedOutput string
  2203. wantErr bool
  2204. }{
  2205. {
  2206. name: "basic wildcard",
  2207. call: "wildcard-foo",
  2208. expectedOutput: "Hello foo\n",
  2209. },
  2210. {
  2211. name: "double wildcard",
  2212. call: "foo-wildcard-bar",
  2213. expectedOutput: "Hello foo bar\n",
  2214. },
  2215. {
  2216. name: "store wildcard",
  2217. call: "start-foo",
  2218. expectedOutput: "Starting foo\n",
  2219. },
  2220. {
  2221. name: "matches exactly",
  2222. call: "matches-exactly-*",
  2223. expectedOutput: "I don't consume matches: []\n",
  2224. },
  2225. {
  2226. name: "no matches",
  2227. call: "no-match",
  2228. wantErr: true,
  2229. },
  2230. {
  2231. name: "multiple matches",
  2232. call: "wildcard-foo-bar",
  2233. wantErr: true,
  2234. },
  2235. }
  2236. for _, test := range tests {
  2237. t.Run(test.call, func(t *testing.T) {
  2238. var buff bytes.Buffer
  2239. e := task.Executor{
  2240. Dir: "testdata/wildcards",
  2241. Stdout: &buff,
  2242. Stderr: &buff,
  2243. Silent: true,
  2244. Force: true,
  2245. }
  2246. require.NoError(t, e.Setup())
  2247. if test.wantErr {
  2248. require.Error(t, e.Run(context.Background(), &ast.Call{Task: test.call}))
  2249. return
  2250. }
  2251. require.NoError(t, e.Run(context.Background(), &ast.Call{Task: test.call}))
  2252. assert.Equal(t, test.expectedOutput, buff.String())
  2253. })
  2254. }
  2255. }
  2256. func TestReference(t *testing.T) {
  2257. tests := []struct {
  2258. name string
  2259. call string
  2260. expectedOutput string
  2261. }{
  2262. {
  2263. name: "reference in command",
  2264. call: "ref-cmd",
  2265. expectedOutput: "1\n",
  2266. },
  2267. {
  2268. name: "reference in dependency",
  2269. call: "ref-dep",
  2270. expectedOutput: "1\n",
  2271. },
  2272. {
  2273. name: "reference using templating resolver",
  2274. call: "ref-resolver",
  2275. expectedOutput: "1\n",
  2276. },
  2277. {
  2278. name: "reference using templating resolver and dynamic var",
  2279. call: "ref-resolver-sh",
  2280. expectedOutput: "Alice has 3 children called Bob, Charlie, and Diane\n",
  2281. },
  2282. }
  2283. for _, test := range tests {
  2284. t.Run(test.call, func(t *testing.T) {
  2285. var buff bytes.Buffer
  2286. e := task.Executor{
  2287. Dir: "testdata/var_references",
  2288. Stdout: &buff,
  2289. Stderr: &buff,
  2290. Silent: true,
  2291. Force: true,
  2292. }
  2293. require.NoError(t, e.Setup())
  2294. require.NoError(t, e.Run(context.Background(), &ast.Call{Task: test.call}))
  2295. assert.Equal(t, test.expectedOutput, buff.String())
  2296. })
  2297. }
  2298. }