task_test.go 74 KB

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