From 24bb26a0425fa0081d67f1917d588091811a9965 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joan=20L=C3=B3pez=20de=20la=20Franca=20Beltran?= Date: Fri, 12 May 2023 03:20:18 +0200 Subject: [PATCH 1/7] Baseline for acceptance tests --- acceptance/acceptance_test.go | 205 ++++++++++++++++++++++ acceptance/tests/simple/config.txtar | 81 +++++++++ acceptance/tests/simple/http/simple.txtar | 24 +++ go.mod | 19 +- go.sum | 35 +++- 5 files changed, 349 insertions(+), 15 deletions(-) create mode 100644 acceptance/acceptance_test.go create mode 100644 acceptance/tests/simple/config.txtar create mode 100644 acceptance/tests/simple/http/simple.txtar diff --git a/acceptance/acceptance_test.go b/acceptance/acceptance_test.go new file mode 100644 index 0000000..bab2d04 --- /dev/null +++ b/acceptance/acceptance_test.go @@ -0,0 +1,205 @@ +package acceptance + +import ( + "bufio" + "bytes" + "fmt" + "io" + "net/http" + "net/url" + "os" + "os/exec" + "path/filepath" + "strings" + "testing" + "time" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "golang.org/x/tools/txtar" +) + +const addr = "http://localhost:3000" + +func Test(t *testing.T) { + // For every test directory + tcs := collectTestCases(t) + + for _, tc := range tcs { + tc := tc + t.Run(tc.name, func(t *testing.T) { + // Prepare the config directory + path, clean := createTmpCfgDir(t, tc) + t.Cleanup(clean) + + // Start the application + stop := runApplication(t, path) + t.Cleanup(stop) + + // For every request and response pair + rrs := collectRequestResponses(t, tc.path) + for _, rr := range rrs { + rr := rr + t.Run(rr.name, func(t *testing.T) { + // Send the request + res, err := http.DefaultClient.Do(rr.req) + require.NoError(t, err) + + // Assert the res + rr.assertResponse(res) + }) + } + }) + } +} + +type tc struct { + name string + path string +} + +func collectTestCases(t *testing.T) (cases []tc) { + cwd, err := os.Getwd() + require.NoError(t, err) + + testsDir := filepath.Join(cwd, "tests") + entries, err := os.ReadDir(testsDir) + require.NoError(t, err) + + for _, entry := range entries { + if entry.IsDir() { + cases = append(cases, tc{ + name: entry.Name(), + path: filepath.Join(testsDir, entry.Name()), + }) + } + } + + return +} + +type rr struct { + *testing.T + name string + req *http.Request + res []byte +} + +func (rr rr) assertResponse(response *http.Response) { + // Read the response body + body, err := io.ReadAll(response.Body) + require.NoError(rr, err) + + // Format the status line + statusLine := fmt.Sprintf("HTTP/%d.%d %s", response.ProtoMajor, response.ProtoMinor, response.Status) + + // Strip dynamic headers (to prevent false negatives) + response.Header.Del("Date") + + // Format the headers + var headersBuilder strings.Builder + err = response.Header.Write(&headersBuilder) + require.NoError(rr, err) + headers := strings.ReplaceAll(headersBuilder.String(), "\r\n", "\n") + + // Format the response + res := fmt.Sprintf("%s\n%s\n\n%s", statusLine, headers, body) + assert.Equal(rr, string(rr.res), res) +} + +func collectRequestResponses(t *testing.T, path string) (rrs []rr) { + httpDir := filepath.Join(path, "http") + entries, err := os.ReadDir(httpDir) + require.NoError(t, err) + + for _, entry := range entries { + if entry.IsDir() { + continue + } + + rrFilePath := filepath.Join(httpDir, entry.Name()) + + contents, err := os.ReadFile(rrFilePath) + require.NoError(t, err) + + archive := txtar.Parse(contents) + + rrs = append(rrs, rr{ + T: t, + name: entry.Name(), + req: readRequest(t, find(archive.Files, "req.http")), + res: find(archive.Files, "res.http"), + }) + } + + return +} + +func find(ff []txtar.File, name string) []byte { + for _, f := range ff { + if f.Name == name { + return f.Data + } + } + + return nil +} + +func readRequest(t *testing.T, raw []byte) *http.Request { + req, err := http.ReadRequest(bufio.NewReader(bytes.NewReader(raw))) + require.NoError(t, err) + + baseURL, err := url.Parse(addr) + require.NoError(t, err) + + req.RequestURI = "" + req.URL = baseURL.ResolveReference(req.URL) + + return req +} + +func createTmpCfgDir(t *testing.T, tc tc) (string, func()) { + tmpCfgDir := filepath.Join(os.TempDir(), tc.name) + + cfgFilePath := filepath.Join(tc.path, "config.txtar") + + contents, err := os.ReadFile(cfgFilePath) + require.NoError(t, err) + + archive := txtar.Parse(contents) + + for _, f := range archive.Files { + filePath := filepath.Join(tmpCfgDir, f.Name) + fileDir := filepath.Dir(filePath) + + err := os.MkdirAll(fileDir, os.ModePerm) + require.NoError(t, err) + + err = os.WriteFile(filePath, f.Data, os.ModePerm) + require.NoError(t, err) + } + + return tmpCfgDir, func() { + err := os.RemoveAll(tmpCfgDir) + require.NoError(t, err) + } +} + +func runApplication(t *testing.T, from string) func() { + cmd := exec.Command("killgrave", "--imposters", filepath.Join(from, "imposters")) + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + err := cmd.Start() + require.NoError(t, err) + + // Trick to give time to the app to start + time.Sleep(1 * time.Second) + + return func() { + err := cmd.Process.Signal(os.Interrupt) + require.NoError(t, err) + + err = cmd.Wait() + require.NoError(t, err) + } +} diff --git a/acceptance/tests/simple/config.txtar b/acceptance/tests/simple/config.txtar new file mode 100644 index 0000000..b0aee7c --- /dev/null +++ b/acceptance/tests/simple/config.txtar @@ -0,0 +1,81 @@ +-- imposters/create_gopher.imp.json -- +[ + { + "request": { + "method": "POST", + "endpoint": "/gophers", + "schemaFile": "schemas/create_gopher_request.json", + "headers": { + "Content-Type": "application/json" + }, + "params": { + "gopherColor": "{v:[a-z]+}" + } + }, + "response": { + "status": 200, + "headers": { + "Content-Type": "application/json" + }, + "bodyFile": "responses/create_gopher_response.json" + } + }, + { + "t": "random_text" + } +] +-- imposters/schemas/create_gopher_request.json -- +{ + "type": "object", + "properties": { + "data": { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": [ + "gophers" + ] + }, + "attributes": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "color": { + "type": "string" + }, + "age": { + "type": "integer" + } + }, + "required": [ + "name", + "color", + "age" + ] + } + }, + "required": [ + "type", + "attributes" + ] + } + }, + "required": [ + "data" + ] +} +-- imposters/responses/create_gopher_response.json -- +{ + "data": { + "type": "gophers", + "id": "01D8EMQ185CA8PRGE20DKZTGSR", + "attributes": { + "name": "Zebediah", + "color": "Purple", + "age": 54 + } + } +} \ No newline at end of file diff --git a/acceptance/tests/simple/http/simple.txtar b/acceptance/tests/simple/http/simple.txtar new file mode 100644 index 0000000..ca39c6a --- /dev/null +++ b/acceptance/tests/simple/http/simple.txtar @@ -0,0 +1,24 @@ +-- req.http -- +POST /gophers?gopherColor=red HTTP/1.1 +Content-Length: 95 +Content-Type: application/json + +{"data": {"type": "gophers", "attributes": {"name": "Zebediah", "color": "Purple", "age": 54}}} + +-- res.http -- +HTTP/1.1 200 OK +Content-Length: 214 +Content-Type: application/json + + +{ + "data": { + "type": "gophers", + "id": "01D8EMQ185CA8PRGE20DKZTGSR", + "attributes": { + "name": "Zebediah", + "color": "Purple", + "age": 54 + } + } +} diff --git a/go.mod b/go.mod index 3c8a7c5..bfa119b 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module github.com/friendsofgo/killgrave -go 1.20 +go 1.16 require ( github.com/gorilla/handlers v1.5.1 @@ -8,20 +8,11 @@ require ( github.com/radovskyb/watcher v1.0.7 github.com/spf13/afero v1.6.0 github.com/spf13/cobra v1.0.0 - github.com/stretchr/testify v1.7.0 - github.com/xeipuuv/gojsonschema v1.2.0 - gopkg.in/yaml.v2 v2.4.0 -) - -require ( - github.com/davecgh/go-spew v1.1.1 // indirect - github.com/felixge/httpsnoop v1.0.1 // indirect - github.com/inconshreveable/mousetrap v1.0.0 // indirect - github.com/pmezard/go-difflib v1.0.0 // indirect github.com/spf13/pflag v1.0.5 // indirect + github.com/stretchr/testify v1.7.0 github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect - github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect - golang.org/x/text v0.3.3 // indirect + github.com/xeipuuv/gojsonschema v1.2.0 + golang.org/x/tools v0.9.1 gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 // indirect - gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c // indirect + gopkg.in/yaml.v2 v2.4.0 ) diff --git a/go.sum b/go.sum index 097b429..f1b314b 100644 --- a/go.sum +++ b/go.sum @@ -117,6 +117,7 @@ github.com/xeipuuv/gojsonschema v1.2.0 h1:LhYJRs+L4fBtjZUfuSZIKGeVu0QRy8e5Xi7D17 github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y= github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= +github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= @@ -124,32 +125,64 @@ go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= +golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.10.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= +golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.2.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= +golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE= +golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= +golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= +golang.org/x/tools v0.9.1 h1:8WMNJAz3zrtPmnYC7ISf5dEn3MT0gY7jBJfw27yrrLo= +golang.org/x/tools v0.9.1/go.mod h1:owI94Op576fPu3cIGQeHs3joujW/2Oc6MtlxbF5dfNc= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= From 35b05d6d15833526d53dfd754aa1a5836616a120 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joan=20L=C3=B3pez=20de=20la=20Franca=20Beltran?= Date: Mon, 11 Nov 2024 00:01:31 +0100 Subject: [PATCH 2/7] Refine the acceptance testing framework and the test cases --- Makefile | 8 +- acceptance/acceptance_test.go | 219 +++++++++++++++--- acceptance/tests/simple/config.txtar | 76 +++--- .../http/{simple.txtar => create.txtar} | 2 +- .../tests/simple/http/fetchExisting.txtar | 21 ++ .../tests/simple/http/fetchNotFound.txtar | 9 + acceptance/utils/network/network.go | 19 ++ go.mod | 11 +- go.sum | 17 +- 9 files changed, 285 insertions(+), 97 deletions(-) rename acceptance/tests/simple/http/{simple.txtar => create.txtar} (95%) create mode 100644 acceptance/tests/simple/http/fetchExisting.txtar create mode 100644 acceptance/tests/simple/http/fetchNotFound.txtar create mode 100644 acceptance/utils/network/network.go diff --git a/Makefile b/Makefile index b773558..60bc9ce 100644 --- a/Makefile +++ b/Makefile @@ -1,3 +1,7 @@ -.PHONY: build +.PHONY: build acceptance + build: - go build -ldflags "-s -w -X 'github.com/friendsofgo/killgrave/internal/app/cmd._version=`git rev-parse --abbrev-ref HEAD`-`git rev-parse --short HEAD`'" -o bin/killgrave cmd/killgrave/main.go \ No newline at end of file + go build -ldflags "-s -w -X 'github.com/friendsofgo/killgrave/internal/app/cmd._version=`git rev-parse --abbrev-ref HEAD`-`git rev-parse --short HEAD`'" -o bin/killgrave cmd/killgrave/main.go + +acceptance: build + @(cd acceptance && go test -count=1 -v ./...) \ No newline at end of file diff --git a/acceptance/acceptance_test.go b/acceptance/acceptance_test.go index bab2d04..a8d665c 100644 --- a/acceptance/acceptance_test.go +++ b/acceptance/acceptance_test.go @@ -3,13 +3,18 @@ package acceptance import ( "bufio" "bytes" + "errors" "fmt" "io" + "io/fs" + "log" "net/http" "net/url" "os" "os/exec" "path/filepath" + "regexp" + "strconv" "strings" "testing" "time" @@ -17,78 +22,150 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "golang.org/x/tools/txtar" + + "github.com/friendsofgo/killgrave/acceptance/utils/network" ) -const addr = "http://localhost:3000" +const ( + // testsDir is the directory, within the `acceptance` folder, + // where the acceptance tests are located at. + testsDir = "tests" + + // addr is the address where the Killgrave binary + addr = "localhost" + + // bin is the path to where the Killgrave binary + // is expected to run acceptance tests. + bin = "../bin/killgrave" +) +// Test is the entry point for the acceptance tests. func Test(t *testing.T) { - // For every test directory + // First of all, we extract the Killgrave version by running `killgrave version`. + // This is useful, not only to write a log that can serve as metadata for the test results, + // but also to ensure that the Killgrave binary is available. + version, err := extractKillgraveVersion(t) + if err != nil { + var pathErr *fs.PathError + if errors.As(err, &pathErr) || strings.Contains(err.Error(), "executable file not found in $PATH") { + log.Fatalf("Attention! It looks like you haven't compiled Killgrave, the execution of the Killgrave "+ + "binary has failed with: %v", err) + } + log.Fatalf("The execution of `killgrave version` has failed with: %v", err) + } + t.Logf("Running acceptance tests with Killgrave version: %s", version) + + // Once we now that the Killgrave binary is available, we can proceed with the acceptance tests. + // The first step is to collect all test cases from the `tests` directory. For each test: + // + // 0. The test case self-contain on each directory, which name is used as the test name. + // 1. Requires a `config.txtar` file at the root level of the test case directory, + // it is used to initialize a file system with all the configuration-related files, + // which not only includes the Killgrave configuration file but also the imposters. + // 2. Runs Killgrave in any available port (so we can run multiple test cases at the same + // time), using the configuration files from the previous step. + // 3. Requires an `http` directory which contains a set of request and response pairs. + // Each pair is defined by two files: `req.http` and `res.http`, where the request + // is the HTTP request that will be performed as part of the test, and the response + // is the response expected from Killgrave (which will be asserted). + // Each pair is considered one of the test cases that compose the acceptance test, + // defined by the aforementioned parent directory. tcs := collectTestCases(t) for _, tc := range tcs { tc := tc t.Run(tc.name, func(t *testing.T) { - // Prepare the config directory - path, clean := createTmpCfgDir(t, tc) - t.Cleanup(clean) + t.Parallel() - // Start the application - stop := runApplication(t, path) - t.Cleanup(stop) + // 1. Create a temporary directory with the configuration files. + path := createTmpCfgDir(t, tc) - // For every request and response pair + // 2. Start the Killgrave process. + address := runApplication(t, path) + + // 3. Collect the request and response pairs + // and iterate over them to perform the tests. rrs := collectRequestResponses(t, tc.path) for _, rr := range rrs { rr := rr t.Run(rr.name, func(t *testing.T) { + // Override the address + rr.overrideAddress(address) + // Send the request res, err := http.DefaultClient.Do(rr.req) require.NoError(t, err) // Assert the res - rr.assertResponse(res) + rr.assertResponse(t, res) }) } }) } } -type tc struct { +// testCase represents a test case to be run. +// It is defined by the name of the test case and the path +// to the directory where the testCase files live in. +type testCase struct { name string path string } -func collectTestCases(t *testing.T) (cases []tc) { +// collectTestCases walks over the `tests` directory and +// constructs all the testCase's from the directories found. +func collectTestCases(t *testing.T) []testCase { + var tcs []testCase + cwd, err := os.Getwd() require.NoError(t, err) - testsDir := filepath.Join(cwd, "tests") + testsDir := filepath.Join(cwd, testsDir) entries, err := os.ReadDir(testsDir) require.NoError(t, err) for _, entry := range entries { if entry.IsDir() { - cases = append(cases, tc{ + tcs = append(tcs, testCase{ name: entry.Name(), path: filepath.Join(testsDir, entry.Name()), }) } } - return + return tcs } -type rr struct { - *testing.T +// reqRes is a data structure that holds the information required to run +// each of the test cases that compose each acceptance test: +// - the name of the test case. +// - the request to be sent to Killgrave, as *http.Request. +// - the expected response from Killgrave, as []byte. +type reqRes struct { name string req *http.Request res []byte } -func (rr rr) assertResponse(response *http.Response) { +// overrideAddress changes the request's URL to use the provided address. +// This is useful to run the tests against different addresses, e.g. different ports, +// which is a requirement to be able to run the acceptance tests concurrently. +func (rr reqRes) overrideAddress(address string) { + rr.req.URL.Scheme = "http" + rr.req.URL.Host = address +} + +// assertResponse is a self-contained function that can be used to assert +// that the response received from Killgrave matches the expected response. +// +// It builds the response string from the response object, and then it +// compares it with the expected response (from the test definition). +func (rr reqRes) assertResponse(t *testing.T, response *http.Response) { + t.Helper() + // Read the response body body, err := io.ReadAll(response.Body) - require.NoError(rr, err) + require.NoError(t, err) // Format the status line statusLine := fmt.Sprintf("HTTP/%d.%d %s", response.ProtoMajor, response.ProtoMinor, response.Status) @@ -99,15 +176,17 @@ func (rr rr) assertResponse(response *http.Response) { // Format the headers var headersBuilder strings.Builder err = response.Header.Write(&headersBuilder) - require.NoError(rr, err) + require.NoError(t, err) headers := strings.ReplaceAll(headersBuilder.String(), "\r\n", "\n") // Format the response res := fmt.Sprintf("%s\n%s\n\n%s", statusLine, headers, body) - assert.Equal(rr, string(rr.res), res) + assert.Equal(t, string(rr.res), res) } -func collectRequestResponses(t *testing.T, path string) (rrs []rr) { +// collectRequestResponses walks over the `http` directory of the test case +// and collects all the reqRes pairs. In other words, it collects all the test cases. +func collectRequestResponses(t *testing.T, path string) (rrs []reqRes) { httpDir := filepath.Join(path, "http") entries, err := os.ReadDir(httpDir) require.NoError(t, err) @@ -124,8 +203,7 @@ func collectRequestResponses(t *testing.T, path string) (rrs []rr) { archive := txtar.Parse(contents) - rrs = append(rrs, rr{ - T: t, + rrs = append(rrs, reqRes{ name: entry.Name(), req: readRequest(t, find(archive.Files, "req.http")), res: find(archive.Files, "res.http"), @@ -141,10 +219,11 @@ func find(ff []txtar.File, name string) []byte { return f.Data } } - return nil } +// readRequest reads a raw HTTP request from a []byte (e.g. read from a file), +// and instantiates the equivalent *http.Request object from it. func readRequest(t *testing.T, raw []byte) *http.Request { req, err := http.ReadRequest(bufio.NewReader(bytes.NewReader(raw))) require.NoError(t, err) @@ -158,16 +237,24 @@ func readRequest(t *testing.T, raw []byte) *http.Request { return req } -func createTmpCfgDir(t *testing.T, tc tc) (string, func()) { +// createTmpCfgDir creates a temporary directory with the configuration files defined +// in the `config.txtar` file, replicating the structure to be used by Killgrave, when executed. +// +// We follow this approach because this way we can test the application assuming the binary +// already exists, so these tests can be run with a recently generated binary (e.g. a release). +// +// Additionally, in the future we might explore ways to reuse this setup to run these tests +// as "integration tests", so faking using a fake, likely in-memory, file system but directly +// calling app.Run(). +func createTmpCfgDir(t *testing.T, tc testCase) string { + // First, we read the `config.txtar` file and initialize a txtar.Archive with its contents. tmpCfgDir := filepath.Join(os.TempDir(), tc.name) - cfgFilePath := filepath.Join(tc.path, "config.txtar") - contents, err := os.ReadFile(cfgFilePath) require.NoError(t, err) - archive := txtar.Parse(contents) + // Then, we create the temporary directory and write the files. for _, f := range archive.Files { filePath := filepath.Join(tmpCfgDir, f.Name) fileDir := filepath.Dir(filePath) @@ -179,27 +266,83 @@ func createTmpCfgDir(t *testing.T, tc tc) (string, func()) { require.NoError(t, err) } - return tmpCfgDir, func() { + // Tell the testing framework to clean up the temporary directory after the test is done. + t.Cleanup(func() { err := os.RemoveAll(tmpCfgDir) require.NoError(t, err) - } + }) + + return tmpCfgDir } -func runApplication(t *testing.T, from string) func() { - cmd := exec.Command("killgrave", "--imposters", filepath.Join(from, "imposters")) +// runApplication runs Killgrave assuming the binary already exists. +// It uses the imposters located at `from`, which path must be absolute. +// It runs the application on any available port, so we can run multiple +// tests concurrently. It returns the address as the first return value. +// +// For now, it redirects the application's output (stdout and stderr) +// to the test's output, but in the future, we might want to capture +// the output to assert the logs, and or use it in a smarter way. +func runApplication(t *testing.T, from string) string { + // Look for any available port. + port, err := network.AnyAvailablePort() + address := addr + ":" + strconv.Itoa(port) + require.NoError(t, err, "failed to find an available port") + + // Prepare the `killgrave` command, and start it. + cmd := exec.Command(bin, "-P", strconv.Itoa(port), "--imposters", filepath.Join(from, "imposters")) cmd.Stdout = os.Stdout cmd.Stderr = os.Stderr - err := cmd.Start() + err = cmd.Start() require.NoError(t, err) - // Trick to give time to the app to start - time.Sleep(1 * time.Second) - - return func() { + // Tell the testing framework to stop the process after the test is done. + t.Cleanup(func() { err := cmd.Process.Signal(os.Interrupt) require.NoError(t, err) err = cmd.Wait() require.NoError(t, err) + }) + + // Wait for the application to be ready. + const ( + maxWaitTime = 2 * time.Second + checkEvery = 100 * time.Millisecond + ) + require.Eventually(t, func() bool { + res, err := http.Get("http://" + address + "/nonExistingEndpoint") + return err == nil && res != nil && res.StatusCode == http.StatusNotFound + }, maxWaitTime, checkEvery) + + return address +} + +// extractKillgraveVersion runs the `killgrave version` command and uses a regular expression +// to extract the version from the output. In case there's any error (e.g. the binary is not +// available), it returns the error. +func extractKillgraveVersion(t *testing.T) (string, error) { + t.Helper() + + // Prepare the `killgrave version` command. + cmd := exec.Command(bin, "version", "-v") + + // Capture the command's output. + out := new(bytes.Buffer) + cmd.Stdout = out + + // Run the command, and check for errors. + err := cmd.Run() + if err != nil { + return "", err } + + // Extract the Killgrave version from the output. + re := regexp.MustCompile(`Killgrave version:\s*([a-zA-Z0-9\-]+)`) + match := re.FindStringSubmatch(out.String()) + if len(match) == 2 { + return match[1], nil + } + + return "", errors.New("version not found") } diff --git a/acceptance/tests/simple/config.txtar b/acceptance/tests/simple/config.txtar index b0aee7c..a857747 100644 --- a/acceptance/tests/simple/config.txtar +++ b/acceptance/tests/simple/config.txtar @@ -1,10 +1,25 @@ --- imposters/create_gopher.imp.json -- +-- imposters/gophers.imp.json -- [ + { + "request": { + "method": "GET", + "endpoint": "/gophers/01D8EMQ185CA8PRGE20DKZTGSR", + "headers": { + "Content-Type": "application/json" + } + }, + "response": { + "status": 200, + "headers": { + "Content-Type": "application/json" + }, + "bodyFile": "responses/create_gopher_response.json" + } + }, { "request": { "method": "POST", "endpoint": "/gophers", - "schemaFile": "schemas/create_gopher_request.json", "headers": { "Content-Type": "application/json" }, @@ -13,60 +28,29 @@ } }, "response": { - "status": 200, + "status": 201, "headers": { "Content-Type": "application/json" }, "bodyFile": "responses/create_gopher_response.json" } }, + { + "request": { + "method": "GET", + "endpoint": "/gophers/{_id:[\\w]{26}}", + "headers": { + "Content-Type": "application/json" + } + }, + "response": { + "status": 404 + } + }, { "t": "random_text" } ] --- imposters/schemas/create_gopher_request.json -- -{ - "type": "object", - "properties": { - "data": { - "type": "object", - "properties": { - "type": { - "type": "string", - "enum": [ - "gophers" - ] - }, - "attributes": { - "type": "object", - "properties": { - "name": { - "type": "string" - }, - "color": { - "type": "string" - }, - "age": { - "type": "integer" - } - }, - "required": [ - "name", - "color", - "age" - ] - } - }, - "required": [ - "type", - "attributes" - ] - } - }, - "required": [ - "data" - ] -} -- imposters/responses/create_gopher_response.json -- { "data": { diff --git a/acceptance/tests/simple/http/simple.txtar b/acceptance/tests/simple/http/create.txtar similarity index 95% rename from acceptance/tests/simple/http/simple.txtar rename to acceptance/tests/simple/http/create.txtar index ca39c6a..4444642 100644 --- a/acceptance/tests/simple/http/simple.txtar +++ b/acceptance/tests/simple/http/create.txtar @@ -6,7 +6,7 @@ Content-Type: application/json {"data": {"type": "gophers", "attributes": {"name": "Zebediah", "color": "Purple", "age": 54}}} -- res.http -- -HTTP/1.1 200 OK +HTTP/1.1 201 Created Content-Length: 214 Content-Type: application/json diff --git a/acceptance/tests/simple/http/fetchExisting.txtar b/acceptance/tests/simple/http/fetchExisting.txtar new file mode 100644 index 0000000..4a78d3f --- /dev/null +++ b/acceptance/tests/simple/http/fetchExisting.txtar @@ -0,0 +1,21 @@ +-- req.http -- +GET /gophers/01D8EMQ185CA8PRGE20DKZTGSR HTTP/1.1 +Content-Type: application/json + +-- res.http -- +HTTP/1.1 200 OK +Content-Length: 214 +Content-Type: application/json + + +{ + "data": { + "type": "gophers", + "id": "01D8EMQ185CA8PRGE20DKZTGSR", + "attributes": { + "name": "Zebediah", + "color": "Purple", + "age": 54 + } + } +} diff --git a/acceptance/tests/simple/http/fetchNotFound.txtar b/acceptance/tests/simple/http/fetchNotFound.txtar new file mode 100644 index 0000000..702a1e0 --- /dev/null +++ b/acceptance/tests/simple/http/fetchNotFound.txtar @@ -0,0 +1,9 @@ +-- req.http -- +GET /gophers/01D8EMQ185CA8PRGE20DKZT404 HTTP/1.1 +Content-Type: application/json + +-- res.http -- +HTTP/1.1 404 Not Found +Content-Length: 0 + + diff --git a/acceptance/utils/network/network.go b/acceptance/utils/network/network.go new file mode 100644 index 0000000..2b5f77c --- /dev/null +++ b/acceptance/utils/network/network.go @@ -0,0 +1,19 @@ +package network + +import "net" + +func AnyAvailablePort() (int, error) { + // Create a new TCP listener on port 0 (which means "any available port") + listener, err := net.Listen("tcp", ":0") + if err != nil { + return 0, err + } + + // Extract the port number from the listener address + port := listener.Addr().(*net.TCPAddr).Port + + // Close the listener to free up the port + err = listener.Close() + + return port, err +} diff --git a/go.mod b/go.mod index a14f6b3..96ef1bf 100644 --- a/go.mod +++ b/go.mod @@ -1,26 +1,29 @@ module github.com/friendsofgo/killgrave -go 1.21 +go 1.22.0 + +toolchain go1.22.3 require ( - github.com/gorilla/handlers v1.5.1 + github.com/gorilla/handlers v1.5.2 github.com/gorilla/mux v1.8.1 github.com/radovskyb/watcher v1.0.7 github.com/spf13/cobra v1.8.1 github.com/stretchr/testify v1.9.0 github.com/xeipuuv/gojsonschema v1.2.0 + golang.org/x/tools v0.27.0 gopkg.in/yaml.v2 v2.4.0 ) require ( github.com/davecgh/go-spew v1.1.1 // indirect - github.com/felixge/httpsnoop v1.0.1 // indirect + github.com/felixge/httpsnoop v1.0.4 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/kr/pretty v0.3.1 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/spf13/pflag v1.0.5 // indirect github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect - gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 // indirect + gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index aa67312..58d599a 100644 --- a/go.sum +++ b/go.sum @@ -3,16 +3,19 @@ github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ3 github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/felixge/httpsnoop v1.0.1 h1:lvB5Jl89CsZtGIWuTcDM1E/vkVs49/Ml7JJe07l8SPQ= -github.com/felixge/httpsnoop v1.0.1/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= -github.com/gorilla/handlers v1.5.1 h1:9lRY6j8DEeeBT10CvO9hGW0gmky0BprnvDI5vfhUHH4= -github.com/gorilla/handlers v1.5.1/go.mod h1:t8XrUpc4KVXb7HGyJ4/cEnwQiaxrX/hz1Zv/4g96P1Q= +github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= +github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= +github.com/gorilla/handlers v1.5.2 h1:cLTUSsNkgcwhgRqvCNmdbRWG0A3N4F+M2nWKdScwyEE= +github.com/gorilla/handlers v1.5.2/go.mod h1:dX+xVpaxdSw+q0Qek8SSsl3dfMk3jNddUkMzo0GtH0w= github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY= github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= +github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= +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/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= @@ -38,9 +41,11 @@ github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 h1:EzJWgHo github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ= github.com/xeipuuv/gojsonschema v1.2.0 h1:LhYJRs+L4fBtjZUfuSZIKGeVu0QRy8e5Xi7D17UxZ74= github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y= +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 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= -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= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= From 5f01090ebbd1f3cef5d9d3b8d2ee4217ea5b41da Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joan=20L=C3=B3pez=20de=20la=20Franca=20Beltran?= Date: Mon, 11 Nov 2024 00:07:43 +0100 Subject: [PATCH 3/7] Run acceptance tests as part of the CI --- .github/workflows/main.yaml | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/.github/workflows/main.yaml b/.github/workflows/main.yaml index 7ba8526..85c78c9 100644 --- a/.github/workflows/main.yaml +++ b/.github/workflows/main.yaml @@ -1,23 +1,27 @@ name: Test & Build on: + pull_request: push: paths: - 'cmd/**' - 'internal/**' + - 'acceptance/**' jobs: build: runs-on: ubuntu-latest strategy: matrix: - go: ['1.20'] + go: [ '1.20' ] steps: - - uses: actions/checkout@v3 - - uses: actions/setup-go@v4 + - name: Check out the source code + uses: actions/checkout@v3 + - name: Set up Go + uses: actions/setup-go@v4 with: go-version: ${{ matrix.go }} - - name: Run Tests... + - name: Run unit tests run: go test -v -vet=off -race ./... - - name: Build... - run: go build -race cmd/killgrave/main.go \ No newline at end of file + - name: Run acceptance tests + run: make acceptance \ No newline at end of file From d913a8c62998fed0d3e70ca69c1517b45594cb0e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joan=20L=C3=B3pez=20de=20la=20Franca=20Beltran?= Date: Mon, 11 Nov 2024 00:09:16 +0100 Subject: [PATCH 4/7] Undo go toolchain set --- go.mod | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 96ef1bf..baaf053 100644 --- a/go.mod +++ b/go.mod @@ -1,8 +1,6 @@ module github.com/friendsofgo/killgrave -go 1.22.0 - -toolchain go1.22.3 +go 1.21 require ( github.com/gorilla/handlers v1.5.2 From f7d35b415219ec4268c574b3b79b37336e0ec50e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joan=20L=C3=B3pez=20de=20la=20Franca=20Beltran?= Date: Mon, 11 Nov 2024 00:12:40 +0100 Subject: [PATCH 5/7] Update to Go v1.21 (CI) --- .github/workflows/main.yaml | 6 +++--- .github/workflows/release.yml | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/main.yaml b/.github/workflows/main.yaml index 85c78c9..2a66f8e 100644 --- a/.github/workflows/main.yaml +++ b/.github/workflows/main.yaml @@ -13,10 +13,10 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - go: [ '1.20' ] + go: [ '1.21' ] steps: - - name: Check out the source code - uses: actions/checkout@v3 + - name: Checkout + uses: actions/checkout@v4 - name: Set up Go uses: actions/setup-go@v4 with: diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 54f8a17..f018cc5 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -10,13 +10,13 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: fetch-depth: 0 - name: Set up Go - uses: actions/setup-go@v4 + uses: actions/setup-go@v5 with: - go-version: '1.20' + go-version: '1.21' - name: Run GoReleaser uses: goreleaser/goreleaser-action@v4 if: startsWith(github.ref, 'refs/tags/') From e149c49b8e5c8bdfcf03f79d0a1389133e2e0abd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joan=20L=C3=B3pez=20de=20la=20Franca=20Beltran?= Date: Mon, 11 Nov 2024 00:17:03 +0100 Subject: [PATCH 6/7] Downgrade to golang.org/x/tools@v0.24.0 --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index baaf053..549f841 100644 --- a/go.mod +++ b/go.mod @@ -9,7 +9,7 @@ require ( github.com/spf13/cobra v1.8.1 github.com/stretchr/testify v1.9.0 github.com/xeipuuv/gojsonschema v1.2.0 - golang.org/x/tools v0.27.0 + golang.org/x/tools v0.24.0 gopkg.in/yaml.v2 v2.4.0 ) diff --git a/go.sum b/go.sum index 58d599a..4590e07 100644 --- a/go.sum +++ b/go.sum @@ -41,8 +41,8 @@ github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 h1:EzJWgHo github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ= github.com/xeipuuv/gojsonschema v1.2.0 h1:LhYJRs+L4fBtjZUfuSZIKGeVu0QRy8e5Xi7D17UxZ74= github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y= -golang.org/x/tools v0.27.0 h1:qEKojBykQkQ4EynWy4S8Weg69NumxKdn40Fce3uc/8o= -golang.org/x/tools v0.27.0/go.mod h1:sUi0ZgbwW9ZPAq26Ekut+weQPR5eIM6GQLQ1Yjm1H0Q= +golang.org/x/tools v0.24.0 h1:J1shsA93PJUEVaUSaay7UXAyE8aimq3GW0pjlolpa24= +golang.org/x/tools v0.24.0/go.mod h1:YhNqVBIfWHdzvTLs0d8LCuMhkKUgSUKldakyV7W/WDQ= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= From 347d0230a9ee9d4c655c912939bea605ac96c2b3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joan=20L=C3=B3pez=20de=20la=20Franca=20Beltran?= Date: Mon, 11 Nov 2024 00:21:33 +0100 Subject: [PATCH 7/7] Use specific go build flag for acceptance tests --- Makefile | 2 +- acceptance/acceptance_test.go | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 60bc9ce..a193783 100644 --- a/Makefile +++ b/Makefile @@ -4,4 +4,4 @@ build: go build -ldflags "-s -w -X 'github.com/friendsofgo/killgrave/internal/app/cmd._version=`git rev-parse --abbrev-ref HEAD`-`git rev-parse --short HEAD`'" -o bin/killgrave cmd/killgrave/main.go acceptance: build - @(cd acceptance && go test -count=1 -v ./...) \ No newline at end of file + @(cd acceptance && go test -count=1 -tags=acceptance -v ./...) \ No newline at end of file diff --git a/acceptance/acceptance_test.go b/acceptance/acceptance_test.go index a8d665c..d2a1b6b 100644 --- a/acceptance/acceptance_test.go +++ b/acceptance/acceptance_test.go @@ -1,3 +1,5 @@ +//go:build acceptance + package acceptance import (