-
Notifications
You must be signed in to change notification settings - Fork 12
feat: Add location prefix support #453
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
Changes from all commits
98b8d63
e4f0609
48e5b48
72887f3
a5ece80
c1aee15
a1721ea
10a1e5c
6b0a0eb
ba4c1f6
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,35 @@ | ||
| package command | ||
|
|
||
| import ( | ||
| "fmt" | ||
| "path/filepath" | ||
| ) | ||
|
|
||
| // prefixFilePaths prepends the given prefix to the file paths of the test cases. | ||
| func prefixPath(path string, prefix string) string { | ||
| if prefix == "" { | ||
| return path | ||
| } | ||
|
|
||
| var prefixedPath string | ||
| // Some test collectors (e.g. Rspec) report file paths with a "./" by default. | ||
| // Since `filepath.Join` ignore "./", we need to handle this case separately to avoid losing the "./" prefix. | ||
| if prefix == "./" { | ||
| prefixedPath = prefix + path | ||
| } else { | ||
| prefixedPath = filepath.Join(prefix, path) | ||
| } | ||
| return prefixedPath | ||
| } | ||
|
|
||
| func trimFilePathPrefix(path string, prefix string) (string, error) { | ||
| if prefix == "" { | ||
| return path, nil | ||
| } | ||
|
|
||
| relPath, err := filepath.Rel(prefix, path) | ||
| if err != nil { | ||
| return "", fmt.Errorf("failed to trim prefix from file path: %w", err) | ||
| } | ||
| return relPath, nil | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,90 @@ | ||
| package command | ||
|
|
||
| import ( | ||
| "testing" | ||
|
|
||
| "github.com/google/go-cmp/cmp" | ||
| ) | ||
|
|
||
| func TestPrefixFilePath(t *testing.T) { | ||
| path := "spec/models/user_spec.rb" | ||
|
|
||
| cases := []struct { | ||
| prefix string | ||
| expected string | ||
| }{ | ||
| { | ||
| prefix: "", | ||
| expected: "spec/models/user_spec.rb", | ||
| }, | ||
| { | ||
| prefix: "my/project", | ||
| expected: "my/project/spec/models/user_spec.rb", | ||
| }, | ||
|
Comment on lines
+20
to
+23
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should we clarify the expected behaviour for this case - a custom prefix that starts with I think we'll get |
||
| { | ||
| prefix: "/home/user/my/project", | ||
| expected: "/home/user/my/project/spec/models/user_spec.rb", | ||
| }, | ||
| { | ||
| prefix: "./", | ||
| expected: "./spec/models/user_spec.rb", | ||
| }, | ||
| } | ||
|
|
||
| for _, c := range cases { | ||
| t.Run(c.prefix, func(t *testing.T) { | ||
| got := prefixPath(path, c.prefix) | ||
| if diff := cmp.Diff(got, c.expected); diff != "" { | ||
| t.Errorf("prefixPath() diff (-got +want):\n%s", diff) | ||
| } | ||
| }) | ||
| } | ||
| } | ||
|
|
||
| func TestTrimFilePathPrefix(t *testing.T) { | ||
| cases := []struct { | ||
| prefix string | ||
| filePath string | ||
| expected string | ||
| }{ | ||
| { | ||
| prefix: "my/project", | ||
| filePath: "my/project/spec/models/user_spec.rb", | ||
| expected: "spec/models/user_spec.rb", | ||
| }, | ||
| { | ||
| filePath: "/home/user/my/project/spec/models/user_spec.rb", | ||
| prefix: "/home/user/my/project", | ||
| expected: "spec/models/user_spec.rb", | ||
| }, | ||
| { | ||
| filePath: "./spec/models/user_spec.rb", | ||
| prefix: "./", | ||
| expected: "spec/models/user_spec.rb", | ||
| }, | ||
| } | ||
|
|
||
| for _, c := range cases { | ||
| t.Run(c.prefix, func(t *testing.T) { | ||
| got, err := trimFilePathPrefix(c.filePath, c.prefix) | ||
| if err != nil { | ||
| t.Errorf("trimFilePathPrefix() error = %v", err) | ||
| } | ||
|
|
||
| if diff := cmp.Diff(got, c.expected); diff != "" { | ||
| t.Errorf("trimFilePathPrefix() diff (-got +want):\n%s", diff) | ||
| } | ||
| }) | ||
| } | ||
| } | ||
|
|
||
| func TestTrimFilePathPrefix_Error(t *testing.T) { | ||
| path := "spec/foo.rb" | ||
| got, err := trimFilePathPrefix(path, "/absolute/path") | ||
| if err == nil { | ||
| t.Errorf("expected error, got nil") | ||
| } | ||
| if got != "" { | ||
| t.Errorf("expected empty string, got %q", got) | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change | ||||||
|---|---|---|---|---|---|---|---|---|
|
|
@@ -16,13 +16,17 @@ import ( | |||||||
| // that are slow or contain skipped tests. These files are then split into examples | ||||||||
| // The remaining files are sent as is. | ||||||||
| // | ||||||||
| // If location prefix is configured, the file paths are prefixed when making the request to the Test Engine API, | ||||||||
| // so that it can correctly identify the files. | ||||||||
| // | ||||||||
| // If tag filtering is enabled, all files are split into examples to support filtering. | ||||||||
| // Currently only the Pytest runner supports tag filtering. | ||||||||
| func createRequestParam(ctx context.Context, cfg *config.Config, files []string, client api.Client, runner TestRunner) (api.TestPlanParams, error) { | ||||||||
| testFiles := []plan.TestCase{} | ||||||||
|
|
||||||||
| for _, file := range files { | ||||||||
| testFiles = append(testFiles, plan.TestCase{ | ||||||||
| Path: file, | ||||||||
| Path: prefixPath(file, runner.LocationPrefix()), | ||||||||
| }) | ||||||||
| } | ||||||||
|
|
||||||||
|
|
@@ -100,6 +104,39 @@ func buildSelectionParams(strategy string, params map[string]string) *api.Select | |||||||
| } | ||||||||
| } | ||||||||
|
|
||||||||
| func getExamplesWithPrefix(filePaths []string, runner TestRunner) ([]plan.TestCase, error) { | ||||||||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. this function is called by |
||||||||
| prefix := runner.LocationPrefix() | ||||||||
|
Comment on lines
+107
to
+108
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit
Suggested change
|
||||||||
| trimmedPaths := make([]string, len(filePaths)) | ||||||||
|
|
||||||||
| // runner.GetExamples will call the test runner with the file paths. | ||||||||
| // Because the test runner expects the file paths without the prefix (it doesn't know about the prefix), | ||||||||
| // we need to trim the prefix before passing the file paths to the runner. | ||||||||
| for i, filePath := range filePaths { | ||||||||
| path, err := trimFilePathPrefix(filePath, prefix) | ||||||||
| if err != nil { | ||||||||
| return nil, fmt.Errorf("trim file path prefix: %w", err) | ||||||||
| } | ||||||||
| trimmedPaths[i] = path | ||||||||
| } | ||||||||
|
|
||||||||
| examples, err := runner.GetExamples(trimmedPaths) | ||||||||
| if err != nil { | ||||||||
| return nil, fmt.Errorf("get examples: %w", err) | ||||||||
| } | ||||||||
|
|
||||||||
| // After getting the examples from the runner, we need to re-apply the prefix to the example paths | ||||||||
| // before sending them to the Test Engine API. | ||||||||
| if prefix != "" { | ||||||||
| for i := range examples { | ||||||||
| // The 'Identifier' field in an example may not always be a file path. | ||||||||
| // Since the Test Engine API only uses the 'Path' field, we only apply the prefix to 'Path'. | ||||||||
| examples[i].Path = prefixPath(examples[i].Path, prefix) | ||||||||
| } | ||||||||
| } | ||||||||
|
|
||||||||
| return examples, nil | ||||||||
| } | ||||||||
|
|
||||||||
| // Splits all the test files into examples to support tag filtering. | ||||||||
| func splitAllFiles(files []plan.TestCase, runner TestRunner) (api.TestPlanParamsTest, error) { | ||||||||
| debug.Printf("Splitting all %d files", len(files)) | ||||||||
|
|
@@ -108,9 +145,9 @@ func splitAllFiles(files []plan.TestCase, runner TestRunner) (api.TestPlanParams | |||||||
| filePaths = append(filePaths, file.Path) | ||||||||
| } | ||||||||
|
|
||||||||
| examples, err := runner.GetExamples(filePaths) | ||||||||
| examples, err := getExamplesWithPrefix(filePaths, runner) | ||||||||
| if err != nil { | ||||||||
| return api.TestPlanParamsTest{}, fmt.Errorf("get examples: %w", err) | ||||||||
| return api.TestPlanParamsTest{}, err | ||||||||
| } | ||||||||
|
|
||||||||
| debug.Printf("Got %d examples from all files", len(examples)) | ||||||||
|
|
@@ -121,24 +158,24 @@ func splitAllFiles(files []plan.TestCase, runner TestRunner) (api.TestPlanParams | |||||||
| } | ||||||||
|
|
||||||||
| // filterAndSplitFiles filters the test files through the Test Engine API and splits the filtered files into examples. | ||||||||
| // It returns the test plan parameters with the examples from the filtered files and the remaining files. | ||||||||
| // It returns the test plan parameters with the examples from the filtered files and the remaining files that are not filtered. | ||||||||
| // An error is returned if there is a failure in any of the process. | ||||||||
| func filterAndSplitFiles(ctx context.Context, cfg *config.Config, client api.Client, files []plan.TestCase, runner TestRunner) (api.TestPlanParamsTest, error) { | ||||||||
| func filterAndSplitFiles(ctx context.Context, cfg *config.Config, client api.Client, allTestFiles []plan.TestCase, runner TestRunner) (api.TestPlanParamsTest, error) { | ||||||||
| // Filter files that need to be split. | ||||||||
| debug.Printf("Filtering %d files", len(files)) | ||||||||
| debug.Printf("Filtering %d files", len(allTestFiles)) | ||||||||
| filteredFiles, err := client.FilterTests(ctx, cfg.SuiteSlug, api.FilterTestsParams{ | ||||||||
| Files: files, | ||||||||
| Files: allTestFiles, | ||||||||
| Env: cfg, | ||||||||
| }) | ||||||||
| if err != nil { | ||||||||
| return api.TestPlanParamsTest{}, fmt.Errorf("filter tests: %w", err) | ||||||||
| } | ||||||||
|
|
||||||||
| // If no files are filtered, return the all files. | ||||||||
| // If no files are filtered, return all the files. | ||||||||
| if len(filteredFiles) == 0 { | ||||||||
| debug.Println("No filtered files found") | ||||||||
| return api.TestPlanParamsTest{ | ||||||||
| Files: files, | ||||||||
| Files: allTestFiles, | ||||||||
| }, nil | ||||||||
| } | ||||||||
|
|
||||||||
|
|
@@ -152,16 +189,19 @@ func filterAndSplitFiles(ctx context.Context, cfg *config.Config, client api.Cli | |||||||
| filteredFilesPath = append(filteredFilesPath, file.Path) | ||||||||
| } | ||||||||
|
|
||||||||
| examples, err := runner.GetExamples(filteredFilesPath) | ||||||||
| // The filtered files returned by the API include the location prefix in their paths, | ||||||||
| // so we should trim the prefix before passing the file paths to the runner to get the examples, | ||||||||
| // then re-apply the prefix to the example paths collected by the runner. | ||||||||
| examples, err := getExamplesWithPrefix(filteredFilesPath, runner) | ||||||||
| if err != nil { | ||||||||
| return api.TestPlanParamsTest{}, fmt.Errorf("get examples: %w", err) | ||||||||
| return api.TestPlanParamsTest{}, err | ||||||||
| } | ||||||||
|
|
||||||||
| debug.Printf("Got %d examples within the filtered files", len(examples)) | ||||||||
|
|
||||||||
| // Get the remaining files that are not filtered. | ||||||||
| remainingFiles := make([]plan.TestCase, 0, len(files)-len(filteredFiles)) | ||||||||
| for _, file := range files { | ||||||||
| remainingFiles := make([]plan.TestCase, 0, len(allTestFiles)-len(filteredFiles)) | ||||||||
| for _, file := range allTestFiles { | ||||||||
| if _, ok := filteredFilesMap[file.Path]; !ok { | ||||||||
| remainingFiles = append(remainingFiles, file) | ||||||||
| } | ||||||||
|
|
||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Using only
BUILDKITE_ANALYTICS_LOCATION_PREFIXis inconsistent with other environment variables namespace inbktec. The location prefix can be configured for the test collector without that environment variable (e.g via collector config object) so it might not be present. SupportingBUILDKITE_TEST_ENGINE_LOCATION_PREFIXwill make it consistent.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should be encouraging the deprecation of
BUILDKITE_ANALYTICS_LOCATION_PREFIXin favour ofBUILDKITE_TEST_ENGINE_LOCATION_PREFIXin our collectors?