Skip to content

[TE-5303] Add location prefix support#453

Open
nprizal wants to merge 8 commits intomainfrom
te-5303-add-location-prefix-support
Open

[TE-5303] Add location prefix support#453
nprizal wants to merge 8 commits intomainfrom
te-5303-add-location-prefix-support

Conversation

@nprizal
Copy link
Contributor

@nprizal nprizal commented Mar 3, 2026

When using bktec with a custom runner for minitest, test plans weren't being optimally bin-packed. All test durations were coming back as 1000 (the default). The root cause is that the minitest collector uploads file paths with a ./ prefix (e.g., ./spec/models/user_spec.rb), but bktec sends paths without it (e.g., spec/models/user_spec.rb). Since the paths don't match, the API can't look up actual durations.

This is the same class of problem that affects customers using the JS collector with a custom prefix (e.g., frontend/ in a monorepo). The collector knows about the prefix, but bktec doesn't.

I've added a --location-prefix flag (sourced from both BUILDKITE_TEST_ENGINE_LOCATION_PREFIX and BUILDKITE_ANALYTICS_LOCATION_PREFIX) that prepends a prefix to file paths when requesting a test plan from the API, and strips it before passing paths to the test runner for execution.

Usage

bktec run \
  --test-runner custom \
  --test-command "bundle exec rake test TEST_FILES='{{testExamples}}'" \
  --test-file-pattern "test/**/*_test.rb" \
  --location-prefix "./"

Or via environment variable:

export BUILDKITE_TEST_ENGINE_LOCATION_PREFIX="./"

Name: "location-prefix",
Category: "TEST RUNNER",
Usage: "Prefix to prepend to test file paths when requesting a test plan, used to match the test files reported by the test collector",
Sources: cli.EnvVars("BUILDKITE_TEST_ENGINE_LOCATION_PREFIX", "BUILDKITE_ANALYTICS_LOCATION_PREFIX"),
Copy link
Contributor Author

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_PREFIX is inconsistent with other environment variables namespace in bktec. 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. Supporting BUILDKITE_TEST_ENGINE_LOCATION_PREFIX will make it consistent.

Copy link
Contributor

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 of BUILDKITE_TEST_ENGINE_LOCATION_PREFIX in our collectors?

@nprizal nprizal self-assigned this Mar 3, 2026
@nprizal nprizal marked this pull request as ready for review March 3, 2026 21:19
@nprizal nprizal requested a review from a team as a code owner March 3, 2026 21:19
Copy link
Contributor

@gchan gchan left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks good and I've left some comments and questions.

While we did planned to use locationPrefix to handle both ./ and /path/to/dir cases, I wonder if we are overloading it given that I noticed we treat both cases differently (one we trim, and the other we don't).

I wonder if we can make it clearer how we are handling location prefixes and why (with examples) because I'm afraid that this knowledge might slowly disappear.

TagFilters string
TestRunner string

locationPrefix string
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nipick - should this attribute be capitalised?
Is there a reason to not export this field?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some test runner (e.g. rspec) will set a default value to this attribute, so the value should be accessed from the runner interface. In golang, you can't have a static attribute on an interface and it should be a getter function. The options are:

  1. have a public attribute LocationPrefix and create a getter GetLocationPrefix() on the runner interface
  2. have a private attribute locationPrefix and create a getter LocationPrefix() on the runner interface

I chose 2 as it is recommended by golang https://go.dev/doc/effective_go#Getters, and since other package should not access the raw locationPrefix directly (without default value that runner set)

filteredFilesPath = append(filteredFilesPath, file.Path)
// File paths sent to the API for filtering tests include the location prefix to match Test Engine records.
// However, the test runner expects file paths without the prefix, so we need to remove it before running the tests.
trimmedPath, err := trimFilePathPrefix(file.Path, prefix)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To clarify what's going on in this class:

  • createRequestParam prepends the prefixes
  • splitAllFiles calls runner.GetExamples with the prefixes and doesn't remove the prefixes from the examples it returns
  • filterAndSplitFiles trims the prefixes (unless it is ./) and then calls runner.GetExamples with the prefixes removed. It appears the examples returned has it's prefixes removed?

What's the reason behind this difference it handling?

}
}

func TestCreateRequestParams_WithLocationPrefix(t *testing.T) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This test and the next one are the new tests, the other tests have been moved from run_test.go

{
Identifier: "./testdata/rspec/spec/fruits/banana_spec.rb[1:1]",
Name: "is yellow",
Path: "./testdata/rspec/spec/fruits/banana_spec.rb[1:1]",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What's the reason behind the different treatment of examples vs files?

Copy link
Contributor Author

@nprizal nprizal Mar 4, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We don't identify examples by filename, but with name and scope. So bktec doesn't need to prepend the fixes when requesting the test plan. Do you think we should add the prefix as well for consistency, even if we don't need it?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yep, for consistency and to reduce confusion, I think we should!

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I handle the examples' Path, but I'm leaving Identifier as is because Identifier might not be a path for other runners.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we want to add a brief comment in getExamplesWithPrefix to explain the divergence for future travellers?

Name: "location-prefix",
Category: "TEST RUNNER",
Usage: "Prefix to prepend to test file paths when requesting a test plan, used to match the test files reported by the test collector",
Sources: cli.EnvVars("BUILDKITE_TEST_ENGINE_LOCATION_PREFIX", "BUILDKITE_ANALYTICS_LOCATION_PREFIX"),
Copy link
Contributor

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 of BUILDKITE_TEST_ENGINE_LOCATION_PREFIX in our collectors?

for _, c := range cases {
got := NewJest(c.input)
if diff := cmp.Diff(got.RunnerConfig, c.want); diff != "" {
if diff := cmp.Diff(got.RunnerConfig, c.want, cmp.AllowUnexported(RunnerConfig{})); diff != "" {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My understanding is that this is so we can compare for the unexported field locationPrefix.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice sorting of the attributes.

Nitpick - I wonder if it makes sense to continue grouping common attributes together similar to how we display them in the cli --help command?

Copy link
Contributor

@gchan gchan left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Giving this an early approval given my major questions about consistency has (or will) been handled.

@nprizal nprizal force-pushed the te-5303-add-location-prefix-support branch from 8c27f10 to a1721ea Compare March 5, 2026 01:05
@nprizal nprizal requested a review from gchan March 5, 2026 01:07
}
}

func getExamplesWithPrefix(filePaths []string, runner TestRunner) ([]plan.TestCase, error) {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this function is called by filterAndSplitFiles and splitAllFiles since we need to handle it the same way.

Copy link
Contributor

@gchan gchan left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice work! Thanks for getting rid of the special ./ case. :shipit:

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants