Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[2/2] Make k6 cloud run --local-execution upload test archive #3931

Merged
merged 12 commits into from
Sep 13, 2024
74 changes: 67 additions & 7 deletions cloudapi/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ type TestRun struct {
Thresholds map[string][]string `json:"thresholds"`
// Duration of test in seconds. -1 for unknown length, 0 for continuous running.
Duration int64 `json:"duration"`
// Archive is the test script archive to (maybe) upload to the cloud.
Archive *lib.Archive `json:"-"`
}

// LogEntry can be used by the cloud to tell k6 to log something to the console,
Expand Down Expand Up @@ -81,26 +83,84 @@ func (c *Client) handleLogEntriesFromCloud(ctrr CreateTestRunResponse) {
}

// CreateTestRun is used when a test run is being executed locally, while the
// results are streamed to the cloud, i.e. `k6 run --out cloud script.js`.
// results are streamed to the cloud, i.e. `k6 cloud run --local-execution` or `k6 run --out cloud script.js`.
func (c *Client) CreateTestRun(testRun *TestRun) (*CreateTestRunResponse, error) {
url := fmt.Sprintf("%s/tests", c.baseURL)
req, err := c.NewRequest("POST", url, testRun)

// Because the kind of request we make can vary depending on the test run configuration, we delegate
// its creation to a helper.
request, err := c.makeCreateTestRunRequest(url, testRun)
if err != nil {
return nil, err
}

ctrr := CreateTestRunResponse{}
err = c.Do(req, &ctrr)
response := CreateTestRunResponse{}
err = c.Do(request, &response)
if err != nil {
return nil, err
}

c.handleLogEntriesFromCloud(ctrr)
if ctrr.ReferenceID == "" {
c.handleLogEntriesFromCloud(response)
if response.ReferenceID == "" {
return nil, fmt.Errorf("failed to get a reference ID")
}

return &ctrr, nil
return &response, nil
}

// makeCreateTestRunRequest creates a new HTTP request for creating a test run.
//
// If the test run archive isn't set, the request will be a regular JSON request with the test run information.
// Otherwise, the request will be a multipart form request containing the test run information and the archive file.
func (c *Client) makeCreateTestRunRequest(url string, testRun *TestRun) (*http.Request, error) {
// If the test run archive isn't set, we are not uploading an archive and can use the regular request JSON format.
if testRun.Archive == nil {
return c.NewRequest(http.MethodPost, url, testRun)
}

// Otherwise, we need to create a multipart form request containing the test run information as
// well as the archive file.
fields := [][2]string{
{"name", testRun.Name},
{"project_id", strconv.FormatInt(testRun.ProjectID, 10)},
{"vus", strconv.FormatInt(testRun.VUsMax, 10)},
{"duration", strconv.FormatInt(testRun.Duration, 10)},
}

var buffer bytes.Buffer
multipartWriter := multipart.NewWriter(&buffer)

for _, field := range fields {
if err := multipartWriter.WriteField(field[0], field[1]); err != nil {
return nil, err
}
}

fw, err := multipartWriter.CreateFormFile("file", "archive.tar")
if err != nil {
return nil, err
}

if err = testRun.Archive.Write(fw); err != nil {
return nil, err
}

// Close the multipart writer to finalize the form data
err = multipartWriter.Close()
if err != nil {
return nil, err
}

// Create a new POST request with the multipart form data
req, err := http.NewRequest(http.MethodPost, url, &buffer) //nolint:noctx
if err != nil {
return nil, err
}

// Set the content type to the one generated by the multipart writer
req.Header.Set("Content-Type", multipartWriter.FormDataContentType())

return req, nil
}

// StartCloudTestRun starts a cloud test run, i.e. `k6 cloud script.js`.
Expand Down
Loading