From 096c9294e33d696c72af61c529165e506b752d42 Mon Sep 17 00:00:00 2001 From: Oscar Reyes Date: Tue, 24 Jan 2023 12:36:58 -0600 Subject: [PATCH] feature: updating binary to support non-zero status codes and environment variables --- README.md | 77 ++++------------------- examples/test-from-id.js | 45 ------------- models/outputConfig.go | 34 ++++++++++ models/tracetestRun.go | 15 ++++- modules/output/config.go | 28 --------- modules/output/output.go | 7 ++- modules/tracetest/api.go | 34 ++++++---- modules/tracetest/tracetest.go | 22 +++++++ {examples => tests}/test-from-id-queue.js | 0 tests/test-from-id.js | 61 ++++++++++++++++++ 10 files changed, 168 insertions(+), 155 deletions(-) delete mode 100644 examples/test-from-id.js create mode 100644 models/outputConfig.go delete mode 100644 modules/output/config.go rename {examples => tests}/test-from-id-queue.js (100%) create mode 100644 tests/test-from-id.js diff --git a/README.md b/README.md index 9f884a7..fd09b1a 100644 --- a/README.md +++ b/README.md @@ -29,72 +29,17 @@ $ go install go.k6.io/xk6/cmd/xk6@latest $ xk6 build --with github.com/kubeshop/xk6-tracetest@latest ``` -## Example +## Available Variables -```javascript -import { Http, Tracetest } from "k6/x/tracetest"; -import { sleep } from "k6"; - -export const options = { - vus: 1, - duration: "5s", -}; - -const http = new Http(); -const tracetest = Tracetest({ - serverUrl: "", -}); -const testId = ""; - -export default function () { - /// successful test run - http.get("http://localhost:8081/pokemon?take=5", { - tracetest: { - testId, - }, - }); - - sleep(1); -} - -export function handleSummary() { - return { - stdout: tracetest.summary(), - 'tracetest.json': tracetest.json(), - }; -} -``` +If you want to configure the tracetest k6 binary you can do it by using any of the following environment variables -Result output: +- **XK6_TRACETEST_SERVER_URL:** Updates the tracetest server url for API interactions (can be overwritten by the script config) +- **XK6_TRACETEST_SERVER_PATH:** Updates the tracetest server path for API interactions (can be overwritten by the script config) -```bash -$ ./k6 run examples/test-from-id.js -o xk6-tracetest - - /\ |‾‾| /‾‾/ /‾‾/ - /\ / \ | |/ / / / - / \/ \ | ( / ‾‾\ - / \ | |\ \ | (‾) | - / __________ \ |__| \__\ \_____/ .io - - execution: local - script: examples/test-from-id.js - output: xk6-tracetest-output (TestRunID: 79680) - - scenarios: (100.00%) 1 scenario, 1 max VUs, 35s max duration (incl. graceful stop): - * default: 1 looping VUs for 5s (gracefulStop: 30s) - - -running (05.1s), 0/1 VUs, 5 complete and 0 interrupted iterations -default ✓ [======================================] 1 VUs 5s -[TotalRuns=8, SuccessfulRus=4, FailedRuns=4] -[FAILED] -[Request=GET - http://localhost:8081/pokemon?take=10, TraceID=dc0718f6bd99b3dc30cc624b154beb23, RunState=FINISHED FailingSpecs=false, TracetestURL= http://localhost:11633/test/nDdBCnoVg/run/28] -[Request=GET - http://localhost:8081/pokemon?take=10, TraceID=dc0718dacd99b3dc30dc0ed028659513, RunState=FINISHED FailingSpecs=false, TracetestURL= http://localhost:11633/test/nDdBCnoVg/run/25] -[Request=GET - http://localhost:8081/pokemon?take=10, TraceID=dc071882b699b3dc30f993c5e0a8b330, RunState=FINISHED FailingSpecs=false, TracetestURL= http://localhost:11633/test/nDdBCnoVg/run/26] -[Request=GET - http://localhost:8081/pokemon?take=10, TraceID=dc0718e3c599b3dc30c09b0fff1e83d7, RunState=FINISHED FailingSpecs=false, TracetestURL= http://localhost:11633/test/nDdBCnoVg/run/27] -[SUCCESSFUL] -[Request=GET - http://localhost:8081/pokemon?take=5, TraceID=dc0718cfcd99b3dc301f7fc40fa024a8, RunState=FINISHED FailingSpecs=false, TracetestURL= http://localhost:11633/test/J0d887oVR/run/145] -[Request=GET - http://localhost:8081/pokemon?take=5, TraceID=dc0718f2bd99b3dc300da669a9c1d4b5, RunState=FINISHED FailingSpecs=false, TracetestURL= http://localhost:11633/test/J0d887oVR/run/142] -[Request=GET - http://localhost:8081/pokemon?take=5, TraceID=dc0718e0c599b3dc30e774886605729d, RunState=FINISHED FailingSpecs=false, TracetestURL= http://localhost:11633/test/J0d887oVR/run/143] -[Request=GET - http://localhost:8081/pokemon?take=5, TraceID=dc0718f7b599b3dc30f5fab29c1b01f4, RunState=FINISHED FailingSpecs=false, TracetestURL= http://localhost:11633/test/J0d887oVR/run/144] -``` +You can also set a default tracetest endpoint when running the k6 binary by using the following option: + +`./k6 run examples/test-from-id.js -o xk6-tracetest=` + +## Example + +To run a full example take a look at the fully flesh demo we have for you in the Tracetest main mono repo: [examples/tracetest-k6](https://github.com/kubeshop/tracetest/tree/main/examples/tracetest-k6) diff --git a/examples/test-from-id.js b/examples/test-from-id.js deleted file mode 100644 index 7e33ea8..0000000 --- a/examples/test-from-id.js +++ /dev/null @@ -1,45 +0,0 @@ -import { check } from 'k6'; -import { Http, Tracetest } from "k6/x/tracetest"; -import { sleep } from "k6"; - -export const options = { - vus: 1, - duration: "6s", -}; - -const tracetest = Tracetest({ - serverUrl: "http://localhost:3000", -}); -const testId = "kc_MgKoVR"; -const pokemonId = 6; -const http = new Http(); - -export default function () { - const url = "http://localhost:8081/pokemon/import"; - const payload = JSON.stringify({ - id: pokemonId, - }); - const params = { - tracetest: { - testId, - }, - headers: { - 'Content-Type': 'application/json', - }, - }; - - const response = http.post(url, payload, params); - - check(response, { - 'is status 200': (r) => r.status === 200, - 'body matches de id': (r) => JSON.parse(r.body).id === pokemonId, - }); - sleep(1); -} - -// export function handleSummary() { -// return { -// stdout: tracetest.summary(), -// "tracetest.json": tracetest.json(), -// }; -// } diff --git a/models/outputConfig.go b/models/outputConfig.go new file mode 100644 index 0000000..7776222 --- /dev/null +++ b/models/outputConfig.go @@ -0,0 +1,34 @@ +package models + +import ( + "go.k6.io/k6/output" +) + +var ( + ENV_SERVER_URL = "XK6_TRACETEST_SERVER_URL" + ENV_SERVER_PATH = "XK6_TRACETEST_SERVER_PATH" +) + +type OutputConfig struct { + ServerUrl string + ServerPath string +} + +func NewConfig(params output.Params) (OutputConfig, error) { + cfg := OutputConfig{ + ServerUrl: ServerURL, + ServerPath: ServerPath, + } + + if params.ConfigArgument != "" { + cfg.ServerUrl = params.ConfigArgument + } else if val, ok := params.Environment[ENV_SERVER_URL]; ok { + cfg.ServerUrl = val + } + + if val, ok := params.Environment[ENV_SERVER_PATH]; ok { + cfg.ServerPath = val + } + + return cfg, nil +} diff --git a/models/tracetestRun.go b/models/tracetestRun.go index b933115..85b5c33 100644 --- a/models/tracetestRun.go +++ b/models/tracetestRun.go @@ -19,12 +19,23 @@ type TracetestRun struct { func (tr *TracetestRun) Summary(baseUrl string) string { runUrl := fmt.Sprintf("%s/test/%s/run/%s", baseUrl, tr.TestId, *tr.TestRun.Id) - failingSpecs := false + failingSpecs := true if tr.TestRun != nil && tr.TestRun.Result != nil && tr.TestRun.Result.AllPassed != nil { failingSpecs = !*tr.TestRun.Result.AllPassed } - return fmt.Sprintf("RunState=%s FailingSpecs=%t, TracetestURL= %s", *tr.TestRun.State, failingSpecs, runUrl) + lastError := "" + if tr.TestRun != nil && tr.TestRun.LastErrorState != nil { + lastError = *tr.TestRun.LastErrorState + } + + summary := fmt.Sprintf("RunState=%s FailingSpecs=%t, TracetestURL= %s", *tr.TestRun.State, failingSpecs, runUrl) + + if lastError != "" { + summary += fmt.Sprintf(", LastError=%s", lastError) + } + + return summary } func (tr *TracetestRun) IsSuccessful() bool { diff --git a/modules/output/config.go b/modules/output/config.go deleted file mode 100644 index 17804d0..0000000 --- a/modules/output/config.go +++ /dev/null @@ -1,28 +0,0 @@ -package output - -import ( - "fmt" - "time" - - "go.k6.io/k6/output" -) - -type Config struct { - PushInterval time.Duration -} - -func NewConfig(params output.Params) (Config, error) { - cfg := Config{ - PushInterval: 1 * time.Second, - } - - if val, ok := params.Environment["XK6_TRACETEST_PUSH_INTERVAL"]; ok { - var err error - cfg.PushInterval, err = time.ParseDuration(val) - if err != nil { - return cfg, fmt.Errorf("error parsing environment variable 'XK6_CROCOSPANS_PUSH_INTERVAL': %w", err) - } - } - - return cfg, nil -} diff --git a/modules/output/output.go b/modules/output/output.go index 1241936..5e03b31 100644 --- a/modules/output/output.go +++ b/modules/output/output.go @@ -4,6 +4,7 @@ import ( "fmt" "math/rand" + "github.com/kubeshop/xk6-tracetest/models" "github.com/kubeshop/xk6-tracetest/modules/tracetest" "github.com/sirupsen/logrus" @@ -12,7 +13,7 @@ import ( ) type Output struct { - config Config + config models.OutputConfig testRunID int64 logger logrus.FieldLogger tracetest *tracetest.Tracetest @@ -21,11 +22,13 @@ type Output struct { var _ output.Output = new(Output) func New(params output.Params, tracetest *tracetest.Tracetest) (*Output, error) { - config, err := NewConfig(params) + config, err := models.NewConfig(params) if err != nil { return nil, err } + tracetest.UpdateFromConfig(config) + return &Output{ config: config, tracetest: tracetest, diff --git a/modules/tracetest/api.go b/modules/tracetest/api.go index 8b6a7cb..ec2923a 100644 --- a/modules/tracetest/api.go +++ b/modules/tracetest/api.go @@ -100,28 +100,38 @@ func (t *Tracetest) getIsTestReady(ctx context.Context, testID, testRunId string return nil, nil } -func (t *Tracetest) stringSummary() string { - failedSummary := "[FAILED] \n" - successfulSummary := "[SUCCESSFUL] \n" - totalRuns := 0 - failedRuns := 0 - successfulRuns := 0 - +func (t *Tracetest) jobSummary() (successfulJobs, failedJobs []models.Job) { t.processedBuffer.Range(func(_, value interface{}) bool { if job, ok := value.(models.Job); ok { - totalRuns += 1 if job.IsSuccessful() { - successfulSummary += fmt.Sprintf("[%s] \n", job.Summary(t.apiOptions.ServerUrl)) - successfulRuns += 1 + successfulJobs = append(successfulJobs, job) } else { - failedSummary += fmt.Sprintf("[%s] \n", job.Summary(t.apiOptions.ServerUrl)) - failedRuns += 1 + failedJobs = append(failedJobs, job) } } return true }) + return +} + +func (t *Tracetest) stringSummary() string { + successfulJobs, failedJobs := t.jobSummary() + failedSummary := "[FAILED] \n" + successfulSummary := "[SUCCESSFUL] \n" + totalRuns := len(successfulJobs) + len(failedJobs) + failedRuns := len(failedJobs) + successfulRuns := len(successfulJobs) + + for _, job := range failedJobs { + failedSummary += fmt.Sprintf("[%s] \n", job.Summary(t.apiOptions.ServerUrl)) + } + + for _, job := range successfulJobs { + successfulSummary += fmt.Sprintf("[%s] \n", job.Summary(t.apiOptions.ServerUrl)) + } + totalResults := fmt.Sprintf("[TotalRuns=%d, SuccessfulRus=%d, FailedRuns=%d] \n", totalRuns, successfulRuns, failedRuns) if failedRuns == 0 { diff --git a/modules/tracetest/tracetest.go b/modules/tracetest/tracetest.go index e94fd10..4f34d53 100644 --- a/modules/tracetest/tracetest.go +++ b/modules/tracetest/tracetest.go @@ -2,6 +2,7 @@ package tracetest import ( "encoding/json" + "fmt" "sync" "time" @@ -41,6 +42,15 @@ func New() *Tracetest { return tracetest } +func (t *Tracetest) UpdateFromConfig(config models.OutputConfig) { + apiOptions := models.ApiOptions{ + ServerUrl: config.ServerUrl, + ServerPath: config.ServerPath, + } + + t.client = NewAPIClient(apiOptions) +} + func (t *Tracetest) Constructor(call goja.ConstructorCall) *goja.Object { rt := t.Vu.Runtime() apiOptions, err := models.NewApiOptions(t.Vu, call.Argument(0)) @@ -66,6 +76,18 @@ func (t *Tracetest) Summary() string { return t.stringSummary() } +func (t *Tracetest) ValidateResult() { + if len(t.buffer) != 0 { + t.processQueue() + } + + _, failedJobs := t.jobSummary() + + if len(failedJobs) > 0 { + panic(fmt.Sprintf("Tracetest: %d jobs failed", len(failedJobs))) + } +} + func (t *Tracetest) Json() string { rt := t.Vu.Runtime() jsonString, err := json.Marshal(t.jsonSummary()) diff --git a/examples/test-from-id-queue.js b/tests/test-from-id-queue.js similarity index 100% rename from examples/test-from-id-queue.js rename to tests/test-from-id-queue.js diff --git a/tests/test-from-id.js b/tests/test-from-id.js new file mode 100644 index 0000000..0f4795c --- /dev/null +++ b/tests/test-from-id.js @@ -0,0 +1,61 @@ +import { check } from "k6"; +import { textSummary } from "https://jslib.k6.io/k6-summary/0.0.2/index.js"; +import { Http, Tracetest } from "k6/x/tracetest"; +import { sleep } from "k6"; + +export const options = { + vus: 1, + duration: "5s", + thresholds: { + http_req_duration: ["p(95)<1"], // 95% of requests should be below 200ms + }, +}; +let pokemonId = 6; //charizard +const http = new Http(); +const testId = "kc_MgKoVR"; +const tracetest = Tracetest(); + +export default function () { + const url = "http://localhost:8081/pokemon/import"; + const payload = JSON.stringify({ + id: pokemonId, + }); + const params = { + headers: { + "Content-Type": "application/json", + }, + tracetest: { + testId, + }, + }; + + const response = http.post(url, payload, params); + + check(response, { + "is status 200": (r) => r.status === 200, + "body matches de id": (r) => JSON.parse(r.body).id === pokemonId, + }); + + pokemonId += 1; + sleep(1); +} + +// enable this to return a non-zero status code if a tracetest test fails +export function teardown() { + tracetest.validateResult(); +} + +export function handleSummary(data) { + // combine the default summary with the tracetest summary + const tracetestSummary = tracetest.summary(); + const defaultSummary = textSummary(data); + const summary = ` + ${defaultSummary} + ${tracetestSummary} + `; + + return { + stderr: summary, + "tracetest.json": tracetest.json(), + }; +}