Skip to content

Commit

Permalink
[2/2] Make k6 cloud run --local-execution upload test archive (#3931)
Browse files Browse the repository at this point in the history
  • Loading branch information
oleiade committed Sep 13, 2024
1 parent 18e0bdc commit 31e3db7
Show file tree
Hide file tree
Showing 8 changed files with 493 additions and 131 deletions.
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

0 comments on commit 31e3db7

Please sign in to comment.