diff --git a/atlasaction/action_test.go b/atlasaction/action_test.go index 47a70619..7c84c91c 100644 --- a/atlasaction/action_test.go +++ b/atlasaction/action_test.go @@ -32,6 +32,8 @@ import ( "ariga.io/atlas/sql/sqlcheck" "ariga.io/atlas/sql/sqlclient" _ "github.com/mattn/go-sqlite3" + "github.com/rogpeppe/go-internal/diff" + "github.com/rogpeppe/go-internal/testscript" "github.com/stretchr/testify/require" ) @@ -2163,3 +2165,137 @@ func (m *mockAction) WithFieldsMap(args map[string]string) atlasaction.Logger { logger: m.logger.With(argPairs...), } } + +func TestGitHubActions(t *testing.T) { + var ( + actions = "actions" + output = filepath.Join(actions, "output.txt") + summary = filepath.Join(actions, "summary.txt") + ) + testscript.Run(t, testscript.Params{ + Dir: filepath.Join("testdata", "github"), + Setup: func(e *testscript.Env) (err error) { + dir := filepath.Join(e.WorkDir, actions) + if err := os.Mkdir(dir, 0700); err != nil { + return err + } + e.Setenv("GITHUB_ACTIONS", "true") + e.Setenv("GITHUB_ENV", filepath.Join(dir, "env.txt")) + e.Setenv("GITHUB_OUTPUT", filepath.Join(dir, "output.txt")) + e.Setenv("GITHUB_STEP_SUMMARY", filepath.Join(dir, "summary.txt")) + c, err := atlasexec.NewClient(e.WorkDir, "atlas") + if err != nil { + return err + } + // Create a new actions for each test. + e.Values[atlasKey{}] = &atlasClient{c} + return nil + }, + Cmds: map[string]func(ts *testscript.TestScript, neg bool, args []string){ + "atlas-action": atlasAction, + "mock-atlas": mockAtlasOutput, + "summary": func(ts *testscript.TestScript, neg bool, args []string) { + if len(args) == 0 { + _, err := os.Stat(ts.MkAbs(summary)) + if neg { + if !os.IsNotExist(err) { + ts.Fatalf("expected no summary, but got some") + } + return + } + if err != nil { + ts.Fatalf("expected summary, but got none") + return + } + return + } + cmpFiles(ts, neg, args[0], summary) + }, + "output": func(ts *testscript.TestScript, neg bool, args []string) { + if len(args) == 0 { + _, err := os.Stat(ts.MkAbs(output)) + if neg { + if !os.IsNotExist(err) { + ts.Fatalf("expected no output, but got some") + } + return + } + if err != nil { + ts.Fatalf("expected output, but got none") + return + } + return + } + cmpFiles(ts, neg, args[0], output) + }, + }, + }) +} + +type ( + atlasKey struct{} + atlasClient struct { + atlasaction.AtlasExec + } +) + +func atlasAction(ts *testscript.TestScript, neg bool, args []string) { + if len(args) != 1 { + ts.Fatalf("usage: atlas-action ") + } + client, ok := ts.Value(atlasKey{}).(*atlasClient) + if !ok || client == nil { + ts.Fatalf("client not found") + } + // The action need to be create for each call to read correct inputs + act, err := atlasaction.New(ts.Getenv, ts.Stdout()) + ts.Check(err) + act.Atlas = client.AtlasExec + act.Version = "testscript" + // Run the action! + switch err := act.Run(context.Background(), args[0]); { + case !neg: + ts.Check(err) + case err == nil: + ts.Fatalf("expected fail") + case neg: + // Print the error to asserting on the testscript + fmt.Fprint(ts.Stderr(), err.Error()) + } +} + +func mockAtlasOutput(ts *testscript.TestScript, neg bool, args []string) { + if len(args) != 1 { + ts.Fatalf("usage: mock-atlas ") + } + client, ok := ts.Value(atlasKey{}).(*atlasClient) + if !ok || client == nil { + ts.Fatalf("client not found") + } + m, err := atlasexec.NewClient("", "./mock-atlas.sh") + ts.Check(err) + ts.Check(m.SetEnv(map[string]string{ + "TEST_BATCH": args[0], + })) + // Replace the atlas client with a mock client. + client.AtlasExec = m +} + +func cmpFiles(ts *testscript.TestScript, neg bool, name1, name2 string) { + text1 := ts.ReadFile(name1) + data, err := os.ReadFile(ts.MkAbs(name2)) + ts.Check(err) + eq := text1 == string(data) + if neg { + if eq { + ts.Fatalf("%s and %s do not differ", name1, name2) + } + return // they differ, as expected + } + if eq { + return // they are equal, as expected + } + unifiedDiff := diff.Diff(name1, []byte(text1), name2, data) + ts.Logf("%s", unifiedDiff) + ts.Fatalf("%s and %s differ", name1, name2) +} diff --git a/atlasaction/mock-atlas.sh b/atlasaction/mock-atlas.sh new file mode 100755 index 00000000..8d1a440a --- /dev/null +++ b/atlasaction/mock-atlas.sh @@ -0,0 +1,47 @@ +#!/bin/bash + +# TEST_BATCH provide the directory containts all +# outputs for multiple runs. The path should be absolulate +# or related to current working directory. +if [[ "$TEST_BATCH" != "" ]]; then + COUNTER_FILE=$TEST_BATCH/counter + COUNTER=$(cat $COUNTER_FILE 2>/dev/null) + COUNTER=$((COUNTER+1)) + DIR_CUR="$TEST_BATCH/$COUNTER" + if [ ! -d "$DIR_CUR" ]; then + >&2 echo -n "$DIR_CUR does not exist, quitting..." + exit 1 + fi + # Save counter for the next runs + echo -n $COUNTER > $COUNTER_FILE + if [ -f "$DIR_CUR/args" ]; then + TEST_ARGS=$(cat $DIR_CUR/args) + fi + if [ -f "$DIR_CUR/stderr" ]; then + TEST_STDERR=$(cat $DIR_CUR/stderr) + fi + if [ -f "$DIR_CUR/stdout" ]; then + TEST_STDOUT=$(cat $DIR_CUR/stdout) + fi +fi + +if [[ "$TEST_ARGS" != "$@" ]]; then + >&2 echo "Receive unexpected args: $@" + exit 1 +fi + +if [[ "$TEST_STDOUT" != "" ]]; then + echo -n $TEST_STDOUT + if [[ "$TEST_STDERR" == "" ]]; then + exit 0 # No stderr + fi + # In some cases, Atlas will write the error in stderr + # when if the command is partially successful. + # eg. Run the apply commands with multiple environments. + >&2 echo -n $TEST_STDERR + exit 1 +fi + +TEST_STDERR="${TEST_STDERR:-Missing stderr either stdout input for the test}" +>&2 echo -n $TEST_STDERR +exit 1 diff --git a/atlasaction/testdata/github/migrate-test.txtar b/atlasaction/testdata/github/migrate-test.txtar new file mode 100644 index 00000000..67cc6a0f --- /dev/null +++ b/atlasaction/testdata/github/migrate-test.txtar @@ -0,0 +1,28 @@ +# Mock the atlas command outputs +mock-atlas $WORK/migrate-test +# Setup the action input variables +env INPUT_CONFIG=file://testdata/config/atlas.hcl +env INPUT_ENV=test +env INPUT_VARS='{"var1":"value1","var2":"value2"}' +env INPUT_DIR=file://testdata/migrations +env INPUT_DEV_URL=sqlite://file?mode=memory +env INPUT_RUN=example + +atlas-action migrate/test +stdout '`atlas migrate test` completed successfully, no issues found' +stdout 'Success' +! output + +! atlas-action migrate/test +stderr '`atlas migrate test` completed with errors:' +stderr 'Failure' +! output + +-- migrate-test/1/args -- +migrate test --env test --config file://testdata/config/atlas.hcl --dir file://testdata/migrations --run example --var var1=value1 --var var2=value2 +-- migrate-test/1/stdout -- +Success +-- migrate-test/2/args -- +migrate test --env test --config file://testdata/config/atlas.hcl --dir file://testdata/migrations --run example --var var1=value1 --var var2=value2 +-- migrate-test/2/stderr -- +Failure diff --git a/atlasaction/testdata/github/schema-test.txtar b/atlasaction/testdata/github/schema-test.txtar new file mode 100644 index 00000000..8405ea20 --- /dev/null +++ b/atlasaction/testdata/github/schema-test.txtar @@ -0,0 +1,27 @@ +# Mock the atlas command outputs +mock-atlas $WORK/schema-test +# Setup the action input variables +env INPUT_CONFIG=file://testdata/config/atlas.hcl +env INPUT_ENV=test +env INPUT_VARS='{"var1":"value1","var2":"value2"}' +env INPUT_DEV_URL=sqlite://file?mode=memory +env INPUT_RUN=example + +atlas-action schema/test +stdout '`atlas schema test` completed successfully, no issues found' +stdout 'Success' +! output + +! atlas-action schema/test +stderr '`atlas schema test` completed with errors:' +stderr 'Failure' +! output + +-- schema-test/1/args -- +schema test --env test --config file://testdata/config/atlas.hcl --run example --var var1=value1 --var var2=value2 +-- schema-test/1/stdout -- +Success +-- schema-test/2/args -- +schema test --env test --config file://testdata/config/atlas.hcl --run example --var var1=value1 --var var2=value2 +-- schema-test/2/stderr -- +Failure diff --git a/go.mod b/go.mod index 9d3f9a99..150aae6d 100644 --- a/go.mod +++ b/go.mod @@ -8,6 +8,7 @@ require ( github.com/alecthomas/kong v0.8.0 github.com/mattn/go-sqlite3 v1.14.17 github.com/mitchellh/mapstructure v1.1.2 + github.com/rogpeppe/go-internal v1.12.1-0.20240709150035-ccf4b4329d21 github.com/sethvargo/go-githubactions v1.3.0 github.com/stretchr/testify v1.8.4 golang.org/x/oauth2 v0.22.0 @@ -22,8 +23,9 @@ require ( github.com/hashicorp/hcl/v2 v2.18.1 // indirect github.com/mitchellh/go-wordwrap v1.0.1 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect - github.com/rogpeppe/go-internal v1.6.1 // indirect github.com/zclconf/go-cty v1.14.1 // indirect + golang.org/x/sys v0.21.0 // indirect golang.org/x/text v0.14.0 // indirect + golang.org/x/tools v0.22.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index 09a06aee..d082733f 100644 --- a/go.sum +++ b/go.sum @@ -26,11 +26,8 @@ github.com/hashicorp/hcl/v2 v2.18.1 h1:6nxnOJFku1EuSawSD81fuviYUV8DxFr3fp2dUi3ZY github.com/hashicorp/hcl/v2 v2.18.1/go.mod h1:ThLC89FV4p9MPW804KVbe/cEXoQ8NZEh+JtMeeGErHE= github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM= github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg= -github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= -github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= -github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= @@ -45,8 +42,8 @@ github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWb github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/rogpeppe/go-internal v1.6.1 h1:/FiVV8dS/e+YqF2JvO3yXRFbBLTIuSDkuC7aBOAvL+k= -github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= +github.com/rogpeppe/go-internal v1.12.1-0.20240709150035-ccf4b4329d21 h1:igWZJluD8KtEtAgRyF4x6lqcxDry1ULztksMJh2mnQE= +github.com/rogpeppe/go-internal v1.12.1-0.20240709150035-ccf4b4329d21/go.mod h1:RMRJLmBOqWacUkmJHRMiPKh1S1m3PA7Zh4W80/kWPpg= github.com/sergi/go-diff v1.3.1 h1:xkr+Oxo4BOQKmkn/B9eMK0g5Kg/983T9DqqPHwYqD+8= github.com/sergi/go-diff v1.3.1/go.mod h1:aMJSSKb2lpPvRNec0+w3fl7LP9IOFzdc9Pa4NFbPK1I= github.com/sethvargo/go-githubactions v1.3.0 h1:Kg633LIUV2IrJsqy2MfveiED/Ouo+H2P0itWS0eLh8A= @@ -57,12 +54,14 @@ github.com/zclconf/go-cty v1.14.1 h1:t9fyA35fwjjUMcmL5hLER+e/rEPqrbCK1/OSE4SI9KA github.com/zclconf/go-cty v1.14.1/go.mod h1:VvMs5i0vgZdhYawQNq5kePSpLAoz8u1xvZgrPIxfnZE= golang.org/x/oauth2 v0.22.0 h1:BzDx2FehcG7jJwgWLELCdmLuxk2i+x9UDpSiss2u0ZA= golang.org/x/oauth2 v0.22.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= +golang.org/x/sys v0.21.0 h1:rF+pYz3DAGSQAxAu1CbC7catZg4ebC4UIeIhKxBZvws= +golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/tools v0.22.0 h1:gqSGLZqv+AI9lIQzniJ0nZDRG5GBPsSi+DRNHWNz6yA= +golang.org/x/tools v0.22.0/go.mod h1:aCwcsjqvq7Yqt6TNyX7QMU2enbQ/Gt0bo6krSeEri+c= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU= gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=