diff --git a/Taskfile.yml b/Taskfile.yml index 95430d5db3..d011a8ea82 100644 --- a/Taskfile.yml +++ b/Taskfile.yml @@ -132,6 +132,15 @@ tasks: cmds: - go test ./... + test:e2e: + desc: Runs E2E test suite + aliases: [t] + sources: + - "**/*.go" + - "./cmd/task/testdata/**/*" + cmds: + - go test ./cmd/task/... -tags test_e2e + test:watch: desc: Runs test suite with watch tests included deps: [sleepit:build] diff --git a/cmd/task/task.go b/cmd/task/task.go index d5f2b7baf0..1ee4a70916 100644 --- a/cmd/task/task.go +++ b/cmd/task/task.go @@ -20,6 +20,10 @@ import ( ) func main() { + os.Exit(main_()) +} + +func main_() (rc int) { if err := run(); err != nil { l := &logger.Logger{ Stdout: os.Stdout, @@ -39,6 +43,7 @@ func main() { os.Exit(errors.CodeUnknown) } os.Exit(errors.CodeOk) + return } func run() error { diff --git a/cmd/task/task_test.go b/cmd/task/task_test.go new file mode 100644 index 0000000000..c346b9648f --- /dev/null +++ b/cmd/task/task_test.go @@ -0,0 +1,59 @@ +//go:build test_e2e +// +build test_e2e + +package main + +import ( + "os" + "path/filepath" + "testing" + "time" + + "github.com/rogpeppe/go-internal/testscript" +) + +func TestMain(m *testing.M) { + os.Exit(testscript.RunMain(m, map[string]func() int{ + "task": main_, + })) +} + +func TestE2E(t *testing.T) { + testscript.Run(t, testscript.Params{ + Dir: "testdata", + Cmds: map[string]func(ts *testscript.TestScript, neg bool, args []string){ + "sleep": sleep, + "touch": touch, + }, + Setup: func(e *testscript.Env) error { return nil }, + }) +} + +func sleep(ts *testscript.TestScript, neg bool, args []string) { + duration := time.Second + if len(args) == 1 { + d, err := time.ParseDuration(args[0]) + ts.Check(err) + duration = d + } + time.Sleep(duration) +} + +func touch(ts *testscript.TestScript, neg bool, args []string) { + if len(args) != 1 { + ts.Fatalf("touch ") + } + // Get the relative path to the scripts current directory. + path := ts.MkAbs(args[0]) + // Create the file (if necessary). + err := os.MkdirAll(filepath.Dir(path), 0750) + ts.Check(err) + file, err := os.OpenFile(path, os.O_RDWR|os.O_CREATE, 0644) + ts.Check(err) + err = file.Close() + ts.Check(err) + // Now update the timestamp. + t := time.Now() + err = os.Chtimes(path, t, t) + ts.Check(err) +} diff --git a/cmd/task/testdata/cli.flags.init.txtar b/cmd/task/testdata/cli.flags.init.txtar new file mode 100644 index 0000000000..27ccad391b --- /dev/null +++ b/cmd/task/testdata/cli.flags.init.txtar @@ -0,0 +1,28 @@ +# Test: task init +task --init +exists Taskfile.yml +stdout 'Taskfile created: Taskfile.yml' +cmp Taskfile.yml expect + +# Test: taskfile exists, content not modified +exec sh -c 'echo "foo" > Taskfile.yml' +! task --init +exists Taskfile.yml +stderr 'task: A Taskfile already exists' +exec cat Taskfile.yml +stdout 'foo' + + +-- expect -- +# https://taskfile.dev + +version: '3' + +vars: + GREETING: Hello, World! + +tasks: + default: + cmds: + - echo "{{.GREETING}}" + silent: true diff --git a/cmd/task/testdata/cli.flags.interval.txtar b/cmd/task/testdata/cli.flags.interval.txtar new file mode 100644 index 0000000000..c8ff915605 --- /dev/null +++ b/cmd/task/testdata/cli.flags.interval.txtar @@ -0,0 +1,31 @@ +mkdir src + +# Test with extended interval, change should not be detected. +task --interval=1200ms & +sleep 500ms +touch src/a +sleep 500ms +kill -INT +wait +cmp stdout expect_stdout +cmp stderr expect_stderr + + +-- Taskfile.yml -- +version: '3' +tasks: + default: + watch: true + sources: + - 'src/*' + cmds: + - echo "Task running!" + - sleep 2 + +-- expect_stdout -- +Task running! +task: Signal received: "interrupt" +-- expect_stderr -- +task: Started watching for tasks: default +task: [default] echo "Task running!" +task: [default] sleep 2 \ No newline at end of file diff --git a/cmd/task/testdata/cli.flags.watch.txtar b/cmd/task/testdata/cli.flags.watch.txtar new file mode 100644 index 0000000000..a082adc0e4 --- /dev/null +++ b/cmd/task/testdata/cli.flags.watch.txtar @@ -0,0 +1,31 @@ +mkdir src +task --watch & +sleep 500ms +touch src/a +sleep 500ms +kill -INT +wait +cmp stdout expect_stdout +cmp stderr expect_stderr + + +-- Taskfile.yml -- +version: '3' +tasks: + default: + sources: + - 'src/*' + cmds: + - echo "Task running!" + - sleep 2 + +-- expect_stdout -- +Task running! +Task running! +task: Signal received: "interrupt" +-- expect_stderr -- +task: Started watching for tasks: default +task: [default] echo "Task running!" +task: [default] sleep 2 +task: [default] echo "Task running!" +task: [default] sleep 2 \ No newline at end of file diff --git a/cmd/task/testdata/watch.interval.txtar b/cmd/task/testdata/watch.interval.txtar new file mode 100644 index 0000000000..efb2204e1a --- /dev/null +++ b/cmd/task/testdata/watch.interval.txtar @@ -0,0 +1,32 @@ +mkdir src + +# Test with extended interval, change should not be detected. +task & +sleep 500ms +touch src/a +sleep 500ms +kill -INT +wait +cmp stdout expect_stdout +cmp stderr expect_stderr + + +-- Taskfile.yml -- +version: '3' +interval: 1200ms +tasks: + default: + watch: true + sources: + - "src/*" + cmds: + - echo "Task running!" + - sleep 2 + +-- expect_stdout -- +Task running! +task: Signal received: "interrupt" +-- expect_stderr -- +task: Started watching for tasks: default +task: [default] echo "Task running!" +task: [default] sleep 2 \ No newline at end of file diff --git a/cmd/task/testdata/watch.service.txtar b/cmd/task/testdata/watch.service.txtar new file mode 100644 index 0000000000..fa1d49e569 --- /dev/null +++ b/cmd/task/testdata/watch.service.txtar @@ -0,0 +1,79 @@ +# Build the service. +mkdir $WORK/.cache/go-build +env GOCACHE=$WORK/.cache/go-build +task build + +# Test that the service is restarted when watched file changed. +task run & +sleep 0.5s +touch watch.file +sleep 0.5s +kill -INT +wait +stdout 'Service received signal: interrupt' +stdout 'Starting Service' +stdout 'Wait for signal' +grep -count=1 'Service received signal: interrupt' +grep -count=2 'Starting Service' +grep -count=2 'Wait for signal' + +# Test that the service is _not_ restarted when an excluded file is changed. +task -s run & +sleep 0.5s +touch watch.exclude +sleep 0.5s +kill -INT +wait +! stdout 'Service received signal: interrupt' +stdout 'Starting Service' +stdout 'Wait for signal' +! grep 'Service received signal: interrupt' +grep -count=1 'Starting Service' +grep -count=1 'Wait for signal' + + +-- Taskfile.yml -- + +version: '3' +tasks: + provision: + cmds: + - touch watch.exclude + build: + sources: + - 'service.go' + generates: + - './out/service' + cmds: + - rm -rf ./out + - task: provision + - go build -o ./out/ service.go + run: + deps: [run:service] + watch: true + sources: + - 'watch.file' + - exclude: 'watch.exclude' + method: none + run:service: + cmds: + - './out/service' + + +-- service.go -- +package main + +import ( + "fmt" + "os" + "os/signal" +) + +func main() { + fmt.Println("Starting Service ...") + c := make (chan os.Signal, 1) + signal.Notify(c) + fmt.Println("Wait for signal ...") + s := <-c + fmt.Println("Service received signal:", s) +} diff --git a/cmd/task/testdata/watch.sources.exclude.txtar b/cmd/task/testdata/watch.sources.exclude.txtar new file mode 100644 index 0000000000..6d33303a84 --- /dev/null +++ b/cmd/task/testdata/watch.sources.exclude.txtar @@ -0,0 +1,30 @@ +mkdir src +task & +sleep 500ms +touch src/foo +sleep 500ms +kill -INT +wait +cmp stdout expect_stdout +cmp stderr expect_stderr + + +-- Taskfile.yml -- +version: '3' +tasks: + default: + watch: true + sources: + - "src/*" + - exclude: 'src/foo' + cmds: + - echo "Task running!" + - sleep 2 + +-- expect_stdout -- +Task running! +task: Signal received: "interrupt" +-- expect_stderr -- +task: Started watching for tasks: default +task: [default] echo "Task running!" +task: [default] sleep 2 \ No newline at end of file diff --git a/cmd/task/testdata/watch.sources.txtar b/cmd/task/testdata/watch.sources.txtar new file mode 100644 index 0000000000..75e2c06af7 --- /dev/null +++ b/cmd/task/testdata/watch.sources.txtar @@ -0,0 +1,47 @@ +mkdir src + +# Test with default conditions, change should be detected. +task & +sleep 500ms +touch src/a +sleep 500ms +kill -INT +wait +cmp stdout expect_stdout +cmp stderr expect_stderr + +# Test with duplicate events, task should be restated only once. +task & +sleep 500ms +touch src/a +touch src/b +touch src/c +touch src/d +sleep 500ms +kill -INT +wait +cmp stdout expect_stdout +cmp stderr expect_stderr + + +-- Taskfile.yml -- +version: '3' +tasks: + default: + watch: true + sources: + - 'src/*' + cmds: + - echo "Task running!" + - sleep 2 + +-- expect_stdout -- +Task running! +Task running! +task: Signal received: "interrupt" +-- expect_stderr -- +task: Started watching for tasks: default +task: [default] echo "Task running!" +task: [default] sleep 2 +task: [default] echo "Task running!" +task: [default] sleep 2 \ No newline at end of file diff --git a/go.mod b/go.mod index 80015ded85..d47c89a4c6 100644 --- a/go.mod +++ b/go.mod @@ -50,6 +50,7 @@ require ( github.com/otiai10/mint v1.6.3 // indirect github.com/pjbgf/sha1cd v0.3.2 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/rogpeppe/go-internal v1.14.1 // indirect github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 // indirect github.com/skeema/knownhosts v1.3.1 // indirect github.com/stretchr/objx v0.5.2 // indirect @@ -57,5 +58,6 @@ require ( golang.org/x/crypto v0.37.0 // indirect golang.org/x/net v0.39.0 // indirect golang.org/x/sys v0.33.0 // indirect + golang.org/x/tools v0.27.0 // indirect gopkg.in/warnings.v0 v0.1.2 // indirect ) diff --git a/go.sum b/go.sum index ff69f46845..af31e0c240 100644 --- a/go.sum +++ b/go.sum @@ -161,6 +161,8 @@ golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.24.0 h1:dd5Bzh4yt5KYA8f9CJHCP4FB4D51c2c6JvN37xJJkJ0= golang.org/x/text v0.24.0/go.mod h1:L8rBsPeo2pSS+xqN0d5u2ikmjtmoJbDBT1b7nHvFCdU= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.27.0 h1:qEKojBykQkQ4EynWy4S8Weg69NumxKdn40Fce3uc/8o= +golang.org/x/tools v0.27.0/go.mod h1:sUi0ZgbwW9ZPAq26Ekut+weQPR5eIM6GQLQ1Yjm1H0Q= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=