From 6d15c094dce6083e83c111d831acd4ae1ab7c599 Mon Sep 17 00:00:00 2001 From: Tony Redondo Date: Tue, 7 Jul 2026 20:23:20 +0200 Subject: [PATCH 01/23] rules_go: expose Orchestrion pin files to Stardoc Add the missing bzl_library metadata for pin_files.bzl and wire it into the actions that load the provider so Stardoc can traverse the rules_go Starlark graph. Regenerate the v0.60.0 base patch metadata and include the BUILD file in the workspace_runtime profile. Testing: python3 tools/dev/materialize_rules_go_fork.py check --all Testing: PYTHONUNBUFFERED=1 timeout 600 python3 tools/dev/verify_rules_go_profiles.py --upstream v0_60_0 --public-denylist tools/dev/private_leak_public_denylist.txt Testing: PYTHONUNBUFFERED=1 timeout 600 python3 tools/dev/verify_rules_go_profiles.py --upstream v0_61_1 --public-denylist tools/dev/private_leak_public_denylist.txt Testing: python3 tools/dev/verify_rules_go_profiles.py --skip-functional-smoke --public-denylist tools/dev/private_leak_public_denylist.txt --- third_party/rgo/v0_60_0/base.CHANGED_FILES.md | 5 ++- .../base/go/private/actions/BUILD.bazel | 3 ++ .../v0_60_0/base/go/private/orchestrion/BUILD | 7 ++++ .../v0_60_0/base/0001-full-delta.patch | 39 ++++++++++++++++++- .../profiles/workspace_runtime.json | 1 + 5 files changed, 51 insertions(+), 4 deletions(-) diff --git a/third_party/rgo/v0_60_0/base.CHANGED_FILES.md b/third_party/rgo/v0_60_0/base.CHANGED_FILES.md index f11018e0..af16f01f 100644 --- a/third_party/rgo/v0_60_0/base.CHANGED_FILES.md +++ b/third_party/rgo/v0_60_0/base.CHANGED_FILES.md @@ -12,8 +12,8 @@ This file is generated. Do not edit by hand. ## Summary -- Total changed paths: `52` -- Modified files: `28` +- Total changed paths: `53` +- Modified files: `29` - Added files: `24` - Removed files: `0` @@ -24,6 +24,7 @@ This file is generated. Do not edit by hand. - `docs/doc_helpers.bzl` - `go/extensions.bzl` - `go/private/BUILD.bazel` +- `go/private/actions/BUILD.bazel` - `go/private/actions/archive.bzl` - `go/private/actions/compilepkg.bzl` - `go/private/actions/link.bzl` diff --git a/third_party/rgo/v0_60_0/base/go/private/actions/BUILD.bazel b/third_party/rgo/v0_60_0/base/go/private/actions/BUILD.bazel index 3bd3d155..439d4f21 100644 --- a/third_party/rgo/v0_60_0/base/go/private/actions/BUILD.bazel +++ b/third_party/rgo/v0_60_0/base/go/private/actions/BUILD.bazel @@ -43,6 +43,7 @@ bzl_library( deps = [ ":utils", "//go/private:mode", + "//go/private/orchestrion:pin_files", "@bazel_skylib//lib:shell", ], ) @@ -54,6 +55,7 @@ bzl_library( deps = [ "//go/private:common", "//go/private:mode", + "//go/private/orchestrion:pin_files", "//go/private:rpath", "@bazel_skylib//lib:collections", ], @@ -66,6 +68,7 @@ bzl_library( deps = [ ":utils", "//go/private:mode", + "//go/private/orchestrion:pin_files", "//go/private:providers", "//go/private:sdk", ], diff --git a/third_party/rgo/v0_60_0/base/go/private/orchestrion/BUILD b/third_party/rgo/v0_60_0/base/go/private/orchestrion/BUILD index 488d4adf..dad028ae 100644 --- a/third_party/rgo/v0_60_0/base/go/private/orchestrion/BUILD +++ b/third_party/rgo/v0_60_0/base/go/private/orchestrion/BUILD @@ -1,3 +1,4 @@ +load("@bazel_skylib//:bzl_library.bzl", "bzl_library") load("@bazel_skylib//rules:common_settings.bzl", "bool_flag", "string_flag") filegroup( @@ -13,6 +14,12 @@ filegroup( visibility = ["//visibility:public"], ) +bzl_library( + name = "pin_files", + srcs = ["pin_files.bzl"], + visibility = ["//go:__subpackages__"], +) + bool_flag( name = "enabled", build_setting_default = False, diff --git a/third_party/rules_go_orchestrion/patches/v0_60_0/base/0001-full-delta.patch b/third_party/rules_go_orchestrion/patches/v0_60_0/base/0001-full-delta.patch index 73645e7d..bb288f1b 100644 --- a/third_party/rules_go_orchestrion/patches/v0_60_0/base/0001-full-delta.patch +++ b/third_party/rules_go_orchestrion/patches/v0_60_0/base/0001-full-delta.patch @@ -1108,6 +1108,34 @@ index e18aa94..bdbf308 100644 "//go/private/rules:all_files", "//go/private/skylib/lib:all_files", "//go/private/tools:all_files", +diff --git a/go/private/actions/BUILD.bazel b/go/private/actions/BUILD.bazel +index 3bd3d15..439d4f2 100644 +--- a/go/private/actions/BUILD.bazel ++++ b/go/private/actions/BUILD.bazel +@@ -43,6 +43,7 @@ bzl_library( + deps = [ + ":utils", + "//go/private:mode", ++ "//go/private/orchestrion:pin_files", + "@bazel_skylib//lib:shell", + ], + ) +@@ -54,6 +55,7 @@ bzl_library( + deps = [ + "//go/private:common", + "//go/private:mode", ++ "//go/private/orchestrion:pin_files", + "//go/private:rpath", + "@bazel_skylib//lib:collections", + ], +@@ -66,6 +68,7 @@ bzl_library( + deps = [ + ":utils", + "//go/private:mode", ++ "//go/private/orchestrion:pin_files", + "//go/private:providers", + "//go/private:sdk", + ], diff --git a/go/private/actions/archive.bzl b/go/private/actions/archive.bzl index 8b17c19..544d389 100644 --- a/go/private/actions/archive.bzl @@ -1909,10 +1937,11 @@ index 64758f8..a6d95ca 100644 It is a common dependency of all Go targets.""", diff --git a/go/private/orchestrion/BUILD b/go/private/orchestrion/BUILD new file mode 100644 -index 0000000..488d4ad +index 0000000..dad028a --- /dev/null +++ b/go/private/orchestrion/BUILD -@@ -0,0 +1,65 @@ +@@ -0,0 +1,72 @@ ++load("@bazel_skylib//:bzl_library.bzl", "bzl_library") +load("@bazel_skylib//rules:common_settings.bzl", "bool_flag", "string_flag") + +filegroup( @@ -1928,6 +1957,12 @@ index 0000000..488d4ad + visibility = ["//visibility:public"], +) + ++bzl_library( ++ name = "pin_files", ++ srcs = ["pin_files.bzl"], ++ visibility = ["//go:__subpackages__"], ++) ++ +bool_flag( + name = "enabled", + build_setting_default = False, diff --git a/third_party/rules_go_orchestrion/profiles/workspace_runtime.json b/third_party/rules_go_orchestrion/profiles/workspace_runtime.json index dfa3531c..d7daf822 100644 --- a/third_party/rules_go_orchestrion/profiles/workspace_runtime.json +++ b/third_party/rules_go_orchestrion/profiles/workspace_runtime.json @@ -7,6 +7,7 @@ "/BUILD.bazel", "go/orchestrion_workspace.bzl", "go/private/BUILD.bazel", + "go/private/actions/BUILD.bazel", "go/private/actions/archive.bzl", "go/private/actions/compilepkg.bzl", "go/private/actions/link.bzl", From 14d49642e68705c0961f05f77082e7a12fb07c9d Mon Sep 17 00:00:00 2001 From: Tony Redondo Date: Tue, 14 Jul 2026 13:48:03 +0200 Subject: [PATCH 02/23] bootstrap: add reusable opt-in consumer setup --- .github/workflows/ci.yml | 63 ++++ CONTRIBUTING.md | 13 + MODULE.bazel | 8 + MODULE.bazel.lock | 13 +- README.md | 21 +- docs/Configuration_Reference.md | 7 + docs/Installation_Reference.md | 15 +- docs/Language_Onboarding.md | 41 ++- docs/Troubleshooting.md | 17 + docs/Uploader_Reference.md | 13 + docs/internal_monorepo_go_rollout_guide.md | 11 + docs/rules_go_variant_maintenance_guide.md | 17 + examples/README.md | 17 +- examples/multi_service/.bazelrc | 5 +- examples/multi_service/MODULE.bazel | 1 + examples/single_service/.bazelrc | 5 +- examples/single_service/MODULE.bazel | 1 + modules/dotnet/MODULE.bazel.lock | 4 +- modules/go/BUILD.bazel | 2 + modules/go/MODULE.bazel.lock | 2 +- modules/go/tests/BUILD.bazel | 55 +++ modules/go/tests/test_extension.bzl | 57 ++++ modules/go/tests/test_macro.bzl | 5 +- modules/go/tests/test_workspace_helpers.bzl | 134 ++++++++ modules/go/tools/dd_topt_go_bootstrap/main.go | 150 ++++++--- .../tools/dd_topt_go_bootstrap/main_test.go | 80 ++++- modules/go/topt_go_extension.bzl | 149 +++++++-- modules/go/topt_go_orchestrion.bzl | 6 +- modules/go/topt_go_orchestrion_repository.bzl | 43 +++ modules/go/topt_go_workspace.bzl | 139 ++++++++ modules/java/MODULE.bazel.lock | 4 +- modules/nodejs/MODULE.bazel.lock | 4 +- modules/python/MODULE.bazel | 6 + modules/python/MODULE.bazel.lock | 11 +- modules/python/tests/BUILD.bazel | 36 ++ modules/python/tests/test_macro.bzl | 34 +- .../python/tests/test_payloads_selector.bzl | 27 ++ modules/python/tests/test_selection_utils.bzl | 81 +++++ .../python/tools/dd_topt_py_bootstrap/main.py | 3 + .../tools/dd_topt_py_bootstrap/main_test.py | 6 + modules/python/topt_py_test.bzl | 86 +++-- modules/ruby/MODULE.bazel.lock | 4 +- .../v0_60_0/base/go/private/orchestrion/BUILD | 63 +++- .../v0_61_1/base/go/private/orchestrion/BUILD | 63 +++- .../v0_60_0/base/0001-full-delta.patch | 67 +++- .../v0_61_1/base/0001-full-delta.patch | 67 +++- .../references/bzlmod-onboarding.md | 11 + .../references/workspace-onboarding.md | 32 +- .../references/bzlmod-onboarding.md | 9 + .../references/consumer-runner.md | 6 +- .../references/workspace-onboarding.md | 9 + .../SKILL.md | 15 + tools/core/test_optimization_multi_sync.bzl | 113 +++++-- tools/core/test_optimization_sync.bzl | 313 ++++++++++++++++-- tools/core/test_optimization_sync_env.bzl | 12 +- tools/tests/core/BUILD.bazel | 72 +++- tools/tests/core/test_multi_sync_utils.bzl | 47 +++ tools/tests/core/test_sync_disabled_utils.bzl | 163 +++++++++ tools/tests/example_stub_repo.bzl | 20 +- .../integration/go_integration_mock_server.sh | 123 +++++++ .../integration/run_bzlmod_go_integration.sh | 279 ++++++++++++++-- .../integration/run_mock_server_tests.ps1 | 151 +++++++++ .../integration/run_mock_server_tests.sh | 85 ++++- .../run_workspace_go_integration.sh | 279 ++++++++++++++-- 64 files changed, 3086 insertions(+), 309 deletions(-) create mode 100644 modules/go/tests/test_extension.bzl create mode 100644 modules/go/tests/test_workspace_helpers.bzl create mode 100644 modules/go/topt_go_orchestrion_repository.bzl create mode 100644 modules/go/topt_go_workspace.bzl create mode 100644 tools/tests/core/test_sync_disabled_utils.bzl create mode 100644 tools/tests/integration/go_integration_mock_server.sh diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 0ab38611..7dfb2ecd 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -336,6 +336,69 @@ jobs: done done < <(python3 tools/dev/materialize_rules_go_fork.py list-upstreams) + windows-go-bootstrap-smoke: + needs: changes + if: ${{ needs.changes.outputs.run_full_ci == 'true' }} + timeout-minutes: 45 + runs-on: windows-latest + steps: + - name: Checkout + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + + - name: Set up Python + uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5 + with: + python-version: ${{ env.PYTHON_VERSION }} + + - name: Set up Go + uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0 + with: + go-version: ${{ env.GO_VERSION }} + + - name: Install Bazelisk + shell: bash + run: | + GO111MODULE=on go install github.com/bazelbuild/bazelisk@v1.28.1 + echo "$(go env GOPATH)/bin" >> "$GITHUB_PATH" + + - name: Validate disabled bootstrap for WORKSPACE and Bzlmod + shell: bash + run: | + while IFS= read -r upstream; do + BAZEL_VERSION=8.4.1 \ + RULES_GO_UPSTREAM="${upstream}" \ + RULES_GO_VARIANT="base" \ + ORCHESTRION_MODE=test_optimization \ + WINDOWS_DISABLED_SMOKE_ONLY=1 \ + ./tools/tests/integration/run_workspace_go_integration.sh + + BAZEL_VERSION=8.4.1 \ + RULES_GO_UPSTREAM="${upstream}" \ + RULES_GO_VARIANT="base" \ + ORCHESTRION_MODE=test_optimization \ + WINDOWS_DISABLED_SMOKE_ONLY=1 \ + ./tools/tests/integration/run_bzlmod_go_integration.sh + done < <(python3 tools/dev/materialize_rules_go_fork.py list-upstreams) + + - name: Validate enabled Orchestrion aliases and payloads for WORKSPACE and Bzlmod + shell: bash + run: | + while IFS= read -r upstream; do + BAZEL_VERSION=8.4.1 \ + RULES_GO_UPSTREAM="${upstream}" \ + RULES_GO_VARIANT="base" \ + ORCHESTRION_MODE=test_optimization \ + WINDOWS_ENABLED_SMOKE_ONLY=1 \ + ./tools/tests/integration/run_workspace_go_integration.sh + + BAZEL_VERSION=8.4.1 \ + RULES_GO_UPSTREAM="${upstream}" \ + RULES_GO_VARIANT="base" \ + ORCHESTRION_MODE=test_optimization \ + WINDOWS_ENABLED_SMOKE_ONLY=1 \ + ./tools/tests/integration/run_bzlmod_go_integration.sh + done < <(python3 tools/dev/materialize_rules_go_fork.py list-upstreams) + coverage-tools: needs: changes if: ${{ needs.changes.outputs.run_full_ci == 'true' }} diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index abb7db0b..5fd4552a 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -72,6 +72,15 @@ This product includes software developed at Datadog - Mixed-runtime uploader changes are not done until both harnesses still pass: they cover single-context, explicit override, multi-context repo selection, and no-match fallback behavior. +- Test Optimization bootstrap config: + - Keep `--config=test-optimization` as the only user-facing switch. The + config must set both `common:test-optimization --repo_env=DD_TEST_OPTIMIZATION_ENABLED=1` + and the existing `rules_go` Orchestrion `enabled=true` build setting. + - Omitting the config is the documented opt-out: metadata repositories use + their disabled stubs when `enabled_by_env = True`, and the stable + Orchestrion aliases select local empty targets. + - Do not add a consumer-local Test Optimization bool flag or a second + Orchestrion repository chooser. - Go consumer integration harnesses: - Bzlmod default smoke: `tools/tests/integration/run_bzlmod_go_integration.sh` @@ -85,6 +94,10 @@ This product includes software developed at Datadog `USE_BAZEL_VERSION=8.4.1 RULES_GO_UPSTREAM=v0_61_1 RULES_GO_VARIANT=base tools/tests/integration/run_workspace_go_integration.sh` - Bzlmod base, rules_go v0_61_1: `USE_BAZEL_VERSION=8.4.1 RULES_GO_UPSTREAM=v0_61_1 RULES_GO_VARIANT=base tools/tests/integration/run_bzlmod_go_integration.sh` + - Disabled alias gate for a fresh output root: + `WINDOWS_DISABLED_SMOKE_ONLY=1 RULES_GO_UPSTREAM=v0_60_0 RULES_GO_VARIANT=base tools/tests/integration/run_workspace_go_integration.sh` + - Enabled alias and payload gate with valid Orchestrion pins: + `WINDOWS_ENABLED_SMOKE_ONLY=1 RULES_GO_UPSTREAM=v0_60_0 RULES_GO_VARIANT=base tools/tests/integration/run_workspace_go_integration.sh` - Each script now validates: - normal mode - hermetic mode with the inline CI sandbox/network-blocking flags diff --git a/MODULE.bazel b/MODULE.bazel index 4d486e2f..690e798d 100644 --- a/MODULE.bazel +++ b/MODULE.bazel @@ -191,6 +191,14 @@ example_stub_repo = use_extension( ) example_stub_repo.example_stub_repo( name = "test_optimization_data", + labels = [ + "apps_ruby_example", + "company_product_example", + "example_nodejs_project", + "example_python_modules_python_tests", + "example_python_pkg", + "example_python_tests", + ], service_keys = [ "go_service", "go_service_a", diff --git a/MODULE.bazel.lock b/MODULE.bazel.lock index 57a19a53..ce7cfb37 100644 --- a/MODULE.bazel.lock +++ b/MODULE.bazel.lock @@ -341,8 +341,8 @@ }, "//tools/tests:example_stub_repo.bzl%example_stub_repo_extension": { "general": { - "bzlTransitiveDigest": "11ZYe98lW2gzeDxeH56YfpXqfu+Fz/W99x+6gbqhHpo=", - "usagesDigest": "ZvH9tnCNkZsHBe4vntqNT2rwFLibrjPI/w0NHRR5Lwg=", + "bzlTransitiveDigest": "dBh3mysStfbmE9OYr/8bdnByQgv+t2ybqpgSa1q2MpQ=", + "usagesDigest": "jqtG1XLwIoyhYZrl5w5guIuSZeXpuCApEcAz3bhsuio=", "recordedFileInputs": {}, "recordedDirentsInputs": {}, "envVariables": {}, @@ -353,7 +353,14 @@ "go_module_included": false, "go_module_path": "example.com/stub", "go_sanitized_module_path": "example_com_stub", - "labels": [], + "labels": [ + "apps_ruby_example", + "company_product_example", + "example_nodejs_project", + "example_python_modules_python_tests", + "example_python_pkg", + "example_python_tests" + ], "out_dir": ".testoptimization", "repo_alias": "test_optimization_data", "service_name": "stub-service", diff --git a/README.md b/README.md index 89c10c2e..a52cfc2c 100644 --- a/README.md +++ b/README.md @@ -117,6 +117,15 @@ or merge it locally instead of vendoring a second complete `rules_go` tree. Use this checklist before your first CI rollout: +For opt-in onboarding in every language, the named `test-optimization` config +must include +`common:test-optimization --repo_env=DD_TEST_OPTIMIZATION_ENABLED=1`, and the +sync declaration must set `enabled_by_env = True`. Go additionally needs +`build:test-optimization --@rules_go//go/private/orchestrion:enabled=true` +for Bzlmod, or the same flag with `@io_bazel_rules_go` for WORKSPACE. +Removing `--config=test-optimization` disables metadata resolution and, for Go, +the real Orchestrion aliases; no second Test Optimization bool flag is needed. + 1. Keep the generated repo name as `test_optimization_data` (or consistently replace it in labels/commands if you choose another name). 2. Forward sync metadata environment variable names in `.bazelrc` under a named config, then use that config for test, doctor, and upload commands: @@ -438,6 +447,11 @@ Go setup. Manual Go callsites should set ### Bzlmod + Python companion (`dd_topt_py_test`) +Configure the Python sync extension with `enabled_by_env = True` and put +`common:test-optimization --repo_env=DD_TEST_OPTIMIZATION_ENABLED=1` in the +named config. Python does not declare `rules_go` and must not copy the Go-only +Orchestrion build flag. + ```bzl bazel_dep(name = "datadog-rules-test-optimization-python", version = "1.2.0") git_override( @@ -497,8 +511,11 @@ dd_topt_py_test( In `consumer_runner` mode, pass a repository-owned `py_test_rule` wrapper or an explicit `main` that executes pytest with the ddtrace plugin enabled. The base `rules_python` `py_test` without `main` is rejected because it does not prove -pytest is actually running. Prefer `module_identifier` for payload selection in -this mode so the Datadog macro does not need to synthesize Python `imports`. +pytest is actually running. When the runtime module path and Bazel package path +identify the test, omit `module_identifier` and use the derived fallback. Pass +an explicit `module_identifier` only for a documented repository-specific +exception; the macro does not need to synthesize Python `imports` for the +normal path. Replace `@python_deps` with the repository name generated by your `rules_python` `pip_parse` / `pip.parse` setup. diff --git a/docs/Configuration_Reference.md b/docs/Configuration_Reference.md index 7e6fae58..1ae19b51 100644 --- a/docs/Configuration_Reference.md +++ b/docs/Configuration_Reference.md @@ -364,6 +364,13 @@ invocation. They do not include `FETCH_SALT`, `DD_GIT_*` test env, `DD_API_KEY` test env, upload endpoint test env, or `DD_CIVISIBILITY_AGENTLESS_ENABLED`. +For Go onboarding, the generated block also contains +`common: --repo_env=DD_TEST_OPTIMIZATION_ENABLED=1` and the existing +`rules_go` analysis setting +`build: --@//go/private/orchestrion:enabled=true`. +`--config=` is the single user-facing switch: removing it disables +both metadata resolution and the real Orchestrion aliases. + ## How data is fetched The sync rule executes HTTP requests with timeouts/retries to: diff --git a/docs/Installation_Reference.md b/docs/Installation_Reference.md index 73cdbe83..6c8c26c2 100644 --- a/docs/Installation_Reference.md +++ b/docs/Installation_Reference.md @@ -387,6 +387,8 @@ Use `--write-bazelrc` to insert or replace the managed The generated config is named `test-optimization` by default: ```text +common:test-optimization --repo_env=DD_TEST_OPTIMIZATION_ENABLED=1 +build:test-optimization --@rules_go//go/private/orchestrion:enabled=true common:test-optimization --repo_env=DD_API_KEY common:test-optimization --repo_env=DD_SITE common:test-optimization --repo_env=DD_GIT_REPOSITORY_URL @@ -540,8 +542,10 @@ Use the default `runner_mode = "managed_pytest"` when the Datadog macro should own pytest execution. Use `runner_mode = "consumer_runner"` when a repository already has a Python test wrapper and must keep control of `main`, `imports`, and internal test policy. In `consumer_runner` mode, pass a custom -`py_test_rule` or an explicit `main` that runs pytest with ddtrace enabled, and -prefer `module_identifier` for payload selection. +`py_test_rule` or an explicit `main` that runs pytest with ddtrace enabled. When +the runtime module path and Bazel package path identify the test, omit +`module_identifier` and use the derived fallback. Keep an explicit +`module_identifier` only for a documented repository-specific exception. Python consumers can generate copy/paste onboarding snippets from the companion without running tests or changing lockfiles: @@ -855,6 +859,8 @@ dd_payload_uploader( ```text # Repository rule (module/repo phase) — affects refetch +common:test-optimization --repo_env=DD_TEST_OPTIMIZATION_ENABLED=1 +build:test-optimization --@io_bazel_rules_go//go/private/orchestrion:enabled=true common:test-optimization --repo_env=DD_API_KEY common:test-optimization --repo_env=DD_SITE common:test-optimization --repo_env=DD_TEST_OPTIMIZATION_AGENTLESS_URL # Optional sync/uploader agentless URL override @@ -1051,9 +1057,14 @@ test_optimization_sync( service = "py-service", runtime_name = "python", runtime_version = "3.12", + enabled_by_env = True, ) ``` +Pair this opt-in repository rule with +`common:test-optimization --repo_env=DD_TEST_OPTIMIZATION_ENABLED=1`. Python +does not use the Go-only `@rules_go//go/private/orchestrion:enabled` flag. + Then in package BUILD files, use either managed pytest mode: ```bzl diff --git a/docs/Language_Onboarding.md b/docs/Language_Onboarding.md index c680c168..501203b1 100644 --- a/docs/Language_Onboarding.md +++ b/docs/Language_Onboarding.md @@ -48,12 +48,13 @@ Shared runtime contract for every language: - `DD_TEST_OPTIMIZATION_CONTEXT_JSON` remains a legacy explicit override, not the recommended mixed-runtime wiring path -Shared `.bazelrc` forwarding. Prefer the generated block from -`dd_topt_go_bootstrap --print-bazelrc-snippet` or -`dd_topt_go_bootstrap --write-bazelrc` for Go workspaces: +Shared `.bazelrc` forwarding for every runtime. The named config is the only +user-facing enable switch; omitting it leaves metadata repositories on their +disabled no-fetch stubs: ```text common:test-optimization --repo_env=DD_API_KEY +common:test-optimization --repo_env=DD_TEST_OPTIMIZATION_ENABLED=1 common:test-optimization --repo_env=DD_SITE common:test-optimization --repo_env=DD_GIT_REPOSITORY_URL common:test-optimization --repo_env=DD_GIT_BRANCH @@ -65,6 +66,18 @@ test:test-optimization --remote_download_regex=.*test[.]outputs.* test:test-optimization --zip_undeclared_test_outputs ``` +Go workspaces also add the existing `rules_go` analysis-time setting. Prefer +the generated block from `dd_topt_go_bootstrap --print-bazelrc-snippet` or +`dd_topt_go_bootstrap --write-bazelrc`, which substitutes the consumer's actual +apparent repository name: + +```text +build:test-optimization --@rules_go//go/private/orchestrion:enabled=true +# Use @io_bazel_rules_go instead of @rules_go in WORKSPACE mode. +``` + +Do not add that Go-only line to Python-only or other non-Go workspaces. + Pass `DD_GIT_*` only through `--repo_env`. Never forward it as test environment data because that makes Git metadata part of the test action cache key. For Go/Orchestrion, do not put `DD_TEST_OPTIMIZATION_AGENT_URL` or @@ -269,24 +282,24 @@ datadog_go_test_optimization_workspace_repositories( rto_archive_prefix = "rules_test_optimization-", ) -load("@//go:orchestrion_workspace.bzl", "go_orchestrion_tool_repo") +load("@datadog-rules-test-optimization-go//:topt_go_orchestrion_repository.bzl", "dd_topt_go_orchestrion_tool_repo") -go_orchestrion_tool_repo( +dd_topt_go_orchestrion_tool_repo( version = "v1.9.0", dd_trace_go_version = "v2.9.0", ) load( - "@datadog-rules-test-optimization//tools/core:test_optimization_sync.bzl", - "test_optimization_sync", + "@datadog-rules-test-optimization-go//:topt_go_workspace.bzl", + "dd_topt_go_workspace_sync_repositories", ) -test_optimization_sync( +dd_topt_go_workspace_sync_repositories( name = "test_optimization_data", service = "", - runtime_name = "go", runtime_version = "", - runtime_module_path = "", + module_path = "", + enabled_by_env = True, require_git_metadata = True, ) ``` @@ -520,6 +533,7 @@ topt.test_optimization_sync( service = "py-service", runtime_name = "python", runtime_version = "3.12", + enabled_by_env = True, ) use_repo(topt, "test_optimization_data") @@ -604,8 +618,10 @@ dd_topt_py_test( `consumer_runner` intentionally rejects the base `rules_python` `py_test` without an explicit `main`; that shape can execute a Python file directly and -does not prove pytest or ddtrace ran. Prefer `module_identifier` for selection -in this mode so the integration does not depend on Python import-path mutation. +does not prove pytest or ddtrace ran. When the runtime module path and Bazel +package path identify the test, omit `module_identifier` and use the derived +fallback. Keep an explicit `module_identifier` only for a documented +repository-specific exception. ### WORKSPACE single-service @@ -696,6 +712,7 @@ test_optimization_sync( service = "py-service", runtime_name = "python", runtime_version = "3.12", + enabled_by_env = True, ) ``` diff --git a/docs/Troubleshooting.md b/docs/Troubleshooting.md index d5af5ded..3e6dc233 100644 --- a/docs/Troubleshooting.md +++ b/docs/Troubleshooting.md @@ -15,6 +15,23 @@ Examples below assume the generated repository is named If Bazel reports that sync requires WORKSPACE support, add `--enable_workspace` to sync commands in this document. +## Test Optimization config and disabled mode + +`--config=test-optimization` is the single user-facing enablement switch. Its +`.bazelrc` entries should set both the metadata repository environment and the +existing `rules_go` Orchestrion flag: + +```bazelrc +common:test-optimization --repo_env=DD_TEST_OPTIMIZATION_ENABLED=1 +build:test-optimization --@rules_go//go/private/orchestrion:enabled=true +``` + +When the config is omitted, repositories configured with +`enabled_by_env = True` generate the documented no-fetch stubs and the stable +Orchestrion aliases select local empty targets. Do not add a second +consumer-local Test Optimization bool flag. For WORKSPACE, replace +`@rules_go` with the apparent repository name used by that workspace. + ## Quick triage map | Symptom | First checks | Likely section | diff --git a/docs/Uploader_Reference.md b/docs/Uploader_Reference.md index 66fbac35..47323f2f 100644 --- a/docs/Uploader_Reference.md +++ b/docs/Uploader_Reference.md @@ -35,6 +35,19 @@ The `test-optimization` config should contain the recommended Bazel test flags: `--remote_download_minimal`, `--remote_download_regex=.*test[.]outputs.*`, and `--zip_undeclared_test_outputs`. +For Go consumers using the reusable bootstrap, keep the phase-correct enablement +in the same config: + +```bazelrc +common:test-optimization --repo_env=DD_TEST_OPTIMIZATION_ENABLED=1 +build:test-optimization --@rules_go//go/private/orchestrion:enabled=true +``` + +This is one user-facing switch. Removing `--config=test-optimization` disables +both metadata resolution and the Orchestrion analysis aliases; it does not +require a second bool flag. In WORKSPACE repositories, use the apparent +`rules_go` repository name configured by that workspace. + ```bash # Vendor the full tools/test_optimization/ helper directory, or set # DD_TEST_OPTIMIZATION_SUPPORT_BUNDLE_COLLECTOR to create_support_bundle.py. diff --git a/docs/internal_monorepo_go_rollout_guide.md b/docs/internal_monorepo_go_rollout_guide.md index d1c858c1..6c1bc3d9 100644 --- a/docs/internal_monorepo_go_rollout_guide.md +++ b/docs/internal_monorepo_go_rollout_guide.md @@ -78,6 +78,17 @@ SHA256, and archive prefix generated from the same published commit. `--artifact-staging-dir=`. - Pass `DD_GIT_*` only through `--repo_env`, never through `--test_env`. - Pass uploader credentials at `bazel run` time, not into test actions. +- Keep one user-facing `test-optimization` config with both phase-correct + switches: + + ```bazelrc + common:test-optimization --repo_env=DD_TEST_OPTIMIZATION_ENABLED=1 + build:test-optimization --@io_bazel_rules_go//go/private/orchestrion:enabled=true + ``` + + Removing `--config=test-optimization` disables both metadata repositories + and Orchestrion aliases. `enabled_by_env = True` controls metadata only; it + does not dynamically control Orchestrion repository declaration. ## Bootstrap Flow diff --git a/docs/rules_go_variant_maintenance_guide.md b/docs/rules_go_variant_maintenance_guide.md index 9affcdb1..0bbc75be 100644 --- a/docs/rules_go_variant_maintenance_guide.md +++ b/docs/rules_go_variant_maintenance_guide.md @@ -263,3 +263,20 @@ they consume a complete base tree. Repositories that already own their `rules_go` patch stack should instead use a generated public consumer patch profile as a local rebase or merge input, then verify the regenerated private patch in their private patch order. + +## Test Optimization Alias Contract + +The public base trees keep the existing +`//go/private/orchestrion:enabled` setting as the analysis-time control. Their +stable Orchestrion aliases select package-local empty targets when the setting +is false and the real `rules_go_orchestrion_tool` files when it is true. +Consumers should expose one config that sets both effects: + +```bazelrc +common:test-optimization --repo_env=DD_TEST_OPTIMIZATION_ENABLED=1 +build:test-optimization --@rules_go//go/private/orchestrion:enabled=true +``` + +The metadata environment is read only by repositories explicitly configured +with `enabled_by_env = True`. Removing the config is the opt-out and must not +require a consumer-owned duplicate bool flag or stub repository. diff --git a/examples/README.md b/examples/README.md index 56b5fbdd..497f673a 100644 --- a/examples/README.md +++ b/examples/README.md @@ -39,6 +39,17 @@ repository, use `./bazelw` for local development convenience. - Provide sync credentials via environment and forward them to repository rules: - shell/CI secret: `DD_API_KEY` - `.bazelrc`: `common --repo_env=DD_API_KEY` (and optionally `common --repo_env=DD_SITE`) +- For Go Test Optimization, make the documented config the only user-facing + switch: + + ```bazelrc + common:test-optimization --repo_env=DD_TEST_OPTIMIZATION_ENABLED=1 + build:test-optimization --@rules_go//go/private/orchestrion:enabled=true + ``` + + Removing `--config=test-optimization` disables both metadata resolution and + Orchestrion analysis. In WORKSPACE mode, use the apparent `rules_go` repo + name configured by the workspace. ## Single-service (classic) @@ -214,8 +225,10 @@ runner. Repositories with an internal Python test wrapper should use `runner_mode = "consumer_runner"` and pass that wrapper through `py_test_rule`. In that mode the Datadog macro does not inject `run_pytest.py`, does not set -`main`, and does not synthesize `imports`. Prefer `module_identifier` for -payload selection so onboarding does not depend on import-path mutation. +`main`, and does not synthesize `imports`. When the runtime module path and +Bazel package path identify the test, omit `module_identifier` and use the +derived fallback. Keep an explicit `module_identifier` only for a documented +repository-specific exception. BUILD.bazel (Java companion): diff --git a/examples/multi_service/.bazelrc b/examples/multi_service/.bazelrc index 4a6123d2..ef0a75d7 100644 --- a/examples/multi_service/.bazelrc +++ b/examples/multi_service/.bazelrc @@ -1,6 +1,10 @@ # Required on Windows for dd_topt_java_test: -javaagent paths only resolve under symlinked runfiles. build --enable_runfiles +# One user-facing switch for Go metadata bootstrap and Orchestrion analysis. +common:test-optimization --repo_env=DD_TEST_OPTIMIZATION_ENABLED=1 +build:test-optimization --@rules_go//go/private/orchestrion:enabled=true + # Hermetic config (sandbox, stable env, network blocking) build:hermetic --incompatible_strict_action_env build:hermetic --spawn_strategy=sandboxed @@ -14,4 +18,3 @@ test:hermetic --test_env=LC_ALL=C # Uncomment and provide values via shell/CI secret store: # common --repo_env=DD_API_KEY # common --repo_env=DD_SITE - diff --git a/examples/multi_service/MODULE.bazel b/examples/multi_service/MODULE.bazel index 25c74c66..f9b73893 100644 --- a/examples/multi_service/MODULE.bazel +++ b/examples/multi_service/MODULE.bazel @@ -36,6 +36,7 @@ go_topt = use_extension( go_topt.test_optimization_go( name = "test_optimization_data", debug = True, + enabled_by_env = True, runtime_version = "1.25.0", services = [ "go-service-a", diff --git a/examples/single_service/.bazelrc b/examples/single_service/.bazelrc index 4a6123d2..ef0a75d7 100644 --- a/examples/single_service/.bazelrc +++ b/examples/single_service/.bazelrc @@ -1,6 +1,10 @@ # Required on Windows for dd_topt_java_test: -javaagent paths only resolve under symlinked runfiles. build --enable_runfiles +# One user-facing switch for Go metadata bootstrap and Orchestrion analysis. +common:test-optimization --repo_env=DD_TEST_OPTIMIZATION_ENABLED=1 +build:test-optimization --@rules_go//go/private/orchestrion:enabled=true + # Hermetic config (sandbox, stable env, network blocking) build:hermetic --incompatible_strict_action_env build:hermetic --spawn_strategy=sandboxed @@ -14,4 +18,3 @@ test:hermetic --test_env=LC_ALL=C # Uncomment and provide values via shell/CI secret store: # common --repo_env=DD_API_KEY # common --repo_env=DD_SITE - diff --git a/examples/single_service/MODULE.bazel b/examples/single_service/MODULE.bazel index abc71021..48a4abac 100644 --- a/examples/single_service/MODULE.bazel +++ b/examples/single_service/MODULE.bazel @@ -109,6 +109,7 @@ go_topt = use_extension( go_topt.test_optimization_go( name = "test_optimization_data", debug = True, + enabled_by_env = True, runtime_version = "1.25.0", # Logical service identifier for Datadog metadata (not language-bound). # A single service can include tests from multiple runtimes. diff --git a/modules/dotnet/MODULE.bazel.lock b/modules/dotnet/MODULE.bazel.lock index 2dd9dae1..34afb0ca 100644 --- a/modules/dotnet/MODULE.bazel.lock +++ b/modules/dotnet/MODULE.bazel.lock @@ -140,8 +140,8 @@ "moduleExtensions": { "//tests:example_stub_repo.bzl%example_stub_repo_extension": { "general": { - "bzlTransitiveDigest": "zC3aEdxLSUhPV6iwgCNk7hQr+1eO+rAQ5znCfGieX3g=", - "usagesDigest": "8+UVmqdhqtmEmW3r9vMffABIBlZnL2Mwd3je/6nAzu8=", + "bzlTransitiveDigest": "8Z0dqG8n5A2E4sAbWxvij+vncAodaOVqDuvzF/sm6wE=", + "usagesDigest": "wLZKZW3et9AZB9PdsRyNX8IC9uzvvBMQbjMnvx8yB2s=", "recordedFileInputs": {}, "recordedDirentsInputs": {}, "envVariables": {}, diff --git a/modules/go/BUILD.bazel b/modules/go/BUILD.bazel index 9e2e73b8..d736d37e 100644 --- a/modules/go/BUILD.bazel +++ b/modules/go/BUILD.bazel @@ -11,6 +11,8 @@ exports_files( "topt_go_test.bzl", "topt_go_infer.bzl", "topt_go_extension.bzl", + "topt_go_orchestrion_repository.bzl", + "topt_go_workspace.bzl", ], visibility = ["//visibility:public"], ) diff --git a/modules/go/MODULE.bazel.lock b/modules/go/MODULE.bazel.lock index 1d6ef91c..ab00f98b 100644 --- a/modules/go/MODULE.bazel.lock +++ b/modules/go/MODULE.bazel.lock @@ -148,7 +148,7 @@ "moduleExtensions": { "//tests:example_stub_repo.bzl%example_stub_repo_extension": { "general": { - "bzlTransitiveDigest": "pLQ+JPOKAu2+EI916bm9tEvq/JkYkgWy+BM9tmAwBUY=", + "bzlTransitiveDigest": "leeTyJJoO9VOzShfGyjGcOX3XVB6MjXFQabvUCpnfTg=", "usagesDigest": "yco8+ejjp1y3GIF8UGLO928CMMuVMZAHdJKNqhZnoJo=", "recordedFileInputs": {}, "recordedDirentsInputs": {}, diff --git a/modules/go/tests/BUILD.bazel b/modules/go/tests/BUILD.bazel index 3076e960..44103eb7 100644 --- a/modules/go/tests/BUILD.bazel +++ b/modules/go/tests/BUILD.bazel @@ -4,6 +4,11 @@ # This product includes software developed at Datadog # (https://www.datadoghq.com/) Copyright 2025-Present Datadog, Inc. +load( + ":test_extension.bzl", + "go_multi_specs_propagate_non_default_enablement_and_flaky_tests_test", + "go_single_spec_propagates_non_default_enablement_and_flaky_tests_test", +) load( ":test_macro.bzl", "go_macro_ci_visibility_opt_out_target", @@ -114,6 +119,12 @@ load( "select_module_group_name_test", "service_mapping_entries_filters_non_service_test", ) +load( + ":test_workspace_helpers.bzl", + "go_workspace_multi_specs_test", + "go_workspace_single_specs_test", + "orchestrion_call_spec_test", +) # Go-specific Starlark tests for orchestration selection and macro wiring. service_mapping_entries_filters_non_service_test( @@ -122,6 +133,45 @@ service_mapping_entries_filters_non_service_test( timeout = "short", ) +go_single_spec_propagates_non_default_enablement_and_flaky_tests_test( + name = "go_single_spec_propagates_non_default_enablement_and_flaky_tests_test", + size = "small", + timeout = "short", +) + +go_multi_specs_propagate_non_default_enablement_and_flaky_tests_test( + name = "go_multi_specs_propagate_non_default_enablement_and_flaky_tests_test", + size = "small", + timeout = "short", +) + +go_workspace_single_specs_test( + name = "go_workspace_single_specs_test", + size = "small", + timeout = "short", +) + +go_workspace_multi_specs_test( + name = "go_workspace_multi_specs_test", + size = "small", + timeout = "short", +) + +orchestrion_call_spec_test( + name = "orchestrion_call_spec_test", + size = "small", + timeout = "short", +) + +test_suite( + name = "workspace_helpers_tests", + tests = [ + ":go_workspace_multi_specs_test", + ":go_workspace_single_specs_test", + ":orchestrion_call_spec_test", + ], +) + resolve_topt_service_key_prefers_exact_then_sanitized_test( name = "resolve_topt_service_key_prefers_exact_then_sanitized_test", size = "small", @@ -673,7 +723,11 @@ test_suite( ":go_macro_test_optimization_linker_opt_out_wiring_test", ":go_macro_test_optimization_mode_wiring_test", ":go_macro_test_optimization_public_wrapper_mode_test", + ":go_multi_specs_propagate_non_default_enablement_and_flaky_tests_test", + ":go_single_spec_propagates_non_default_enablement_and_flaky_tests_test", ":go_stub_includes_manifest_in_files_test", + ":go_workspace_multi_specs_test", + ":go_workspace_single_specs_test", ":has_go_mod_pin_test", ":has_package_local_go_mod_test", ":normalize_user_data_handles_none_test", @@ -681,6 +735,7 @@ test_suite( ":orch_transition_forwards_mode_test", ":orch_wrapper_materialized_actual_non_windows_test", ":orch_wrapper_materialized_actual_windows_test", + ":orchestrion_call_spec_test", ":orchestrion_metadata_enabled_test", ":resolve_topt_service_key_missing_failure_test", ":resolve_topt_service_key_prefers_exact_then_sanitized_test", diff --git a/modules/go/tests/test_extension.bzl b/modules/go/tests/test_extension.bzl new file mode 100644 index 00000000..f80eee5f --- /dev/null +++ b/modules/go/tests/test_extension.bzl @@ -0,0 +1,57 @@ +# Unless explicitly stated otherwise all files in this repository are licensed under +# the Apache 2.0 License. +# +# This product includes software developed at Datadog +# (https://www.datadoghq.com/) Copyright 2025-Present Datadog, Inc. + +"""Unit tests for the Go Bzlmod extension sync-spec builders.""" + +load("@bazel_skylib//lib:unittest.bzl", "asserts", "unittest") +load( + "@datadog-rules-test-optimization-go//:topt_go_extension.bzl", + "build_go_multi_repo_specs_for_tests", + "build_go_single_repo_spec_for_tests", +) + +def _go_single_spec_propagates_non_default_enablement_and_flaky_tests_test(ctx): + env = unittest.begin(ctx) + spec = build_go_single_repo_spec_for_tests( + name = "test_optimization_data_go", + service = "go-service", + module_path = "example.com/repo", + runtime_version = "1.25.9", + enabled = False, + enabled_by_env = True, + flaky_tests = False, + ) + asserts.equals(env, False, spec["enabled"]) + asserts.equals(env, True, spec["enabled_by_env"]) + asserts.equals(env, False, spec["flaky_tests"]) + asserts.equals(env, "go", spec["runtime_name"]) + return unittest.end(env) + +def _go_multi_specs_propagate_non_default_enablement_and_flaky_tests_test(ctx): + env = unittest.begin(ctx) + specs = build_go_multi_repo_specs_for_tests( + name = "test_optimization_data_go", + services = ["go-service-a"], + module_path = "example.com/repo", + runtime_version = "1.25.9", + enabled = False, + enabled_by_env = True, + flaky_tests = False, + ) + asserts.equals(env, 1, len(specs)) + asserts.equals(env, False, specs[0]["enabled"]) + asserts.equals(env, True, specs[0]["enabled_by_env"]) + asserts.equals(env, False, specs[0]["flaky_tests"]) + asserts.equals(env, "go", specs[0]["runtime_name"]) + return unittest.end(env) + +go_single_spec_propagates_non_default_enablement_and_flaky_tests_test = unittest.make( + _go_single_spec_propagates_non_default_enablement_and_flaky_tests_test, +) + +go_multi_specs_propagate_non_default_enablement_and_flaky_tests_test = unittest.make( + _go_multi_specs_propagate_non_default_enablement_and_flaky_tests_test, +) diff --git a/modules/go/tests/test_macro.bzl b/modules/go/tests/test_macro.bzl index 7d90955f..e40b4e66 100644 --- a/modules/go/tests/test_macro.bzl +++ b/modules/go/tests/test_macro.bzl @@ -887,11 +887,12 @@ def _validate_orchestrion_mode_test_impl(ctx): return unittest.end(env) def _orch_transition_forwards_mode_test_impl(ctx): - """Assert the wrapper transition enables Orchestrion and forwards the mode.""" + """Assert the wrapper transition forwards only the Orchestrion mode.""" env = unittest.begin(ctx) result = orch_transition_impl_for_tests(None, struct(orchestrion_mode = "test_optimization")) - asserts.equals(env, True, result["@rules_go//go/private/orchestrion:enabled"]) + asserts.equals(env, 1, len(result)) asserts.equals(env, "test_optimization", result["@rules_go//go/private/orchestrion:mode"]) + asserts.false(env, "@rules_go//go/private/orchestrion:enabled" in result) return unittest.end(env) def _orch_wrapper_materialized_actual_non_windows_test_impl(ctx): diff --git a/modules/go/tests/test_workspace_helpers.bzl b/modules/go/tests/test_workspace_helpers.bzl new file mode 100644 index 00000000..572a53f4 --- /dev/null +++ b/modules/go/tests/test_workspace_helpers.bzl @@ -0,0 +1,134 @@ +# Unless explicitly stated otherwise all files in this repository are licensed under +# the Apache 2.0 License. +# +# This product includes software developed at Datadog +# (https://www.datadoghq.com/) Copyright 2025-Present Datadog, Inc. + +"""Unit tests for the public WORKSPACE bootstrap helpers.""" + +load("@bazel_skylib//lib:unittest.bzl", "asserts", "unittest") +load( + "@datadog-rules-test-optimization-go//:topt_go_orchestrion_repository.bzl", + "build_orchestrion_repo_call_for_tests", +) +load( + "@datadog-rules-test-optimization-go//:topt_go_workspace.bzl", + "build_go_workspace_sync_specs_for_tests", +) + +def _assert_sync_spec(env, spec, name, service): + asserts.equals(env, name, spec["name"]) + asserts.equals(env, name, spec["repo_name"]) + asserts.equals(env, service, spec["service"]) + asserts.equals(env, "go", spec["runtime_name"]) + asserts.equals(env, "1.25.9", spec["runtime_version"]) + asserts.equals(env, "arm64", spec["runtime_arch"]) + asserts.equals(env, "example.com/workspace", spec["runtime_module_path"]) + asserts.equals(env, "custom_topt", spec["out_dir"]) + asserts.equals(env, False, spec["enabled"]) + asserts.equals(env, True, spec["enabled_by_env"]) + asserts.equals(env, 11, spec["http_connect_timeout_seconds"]) + asserts.equals(env, 22, spec["http_max_time_seconds"]) + asserts.equals(env, 3, spec["http_retry_attempts"]) + asserts.equals(env, 4, spec["http_retry_delay_seconds"]) + asserts.equals(env, 5, spec["http_execute_timeout_buffer_seconds"]) + asserts.equals(env, False, spec["known_tests"]) + asserts.equals(env, False, spec["test_management"]) + asserts.equals(env, False, spec["flaky_tests"]) + asserts.equals(env, True, spec["require_git_metadata"]) + asserts.equals(env, True, spec["debug"]) + +def _go_workspace_single_specs_test(ctx): + env = unittest.begin(ctx) + result = build_go_workspace_sync_specs_for_tests( + name = "test_optimization_data_go", + service = "go-service", + runtime_version = "1.25.9", + module_path = "example.com/workspace", + enabled = False, + enabled_by_env = True, + runtime_arch = "arm64", + out_dir = "custom_topt", + http_connect_timeout_seconds = 11, + http_max_time_seconds = 22, + http_retry_attempts = 3, + http_retry_delay_seconds = 4, + http_execute_timeout_buffer_seconds = 5, + known_tests = False, + test_management = False, + flaky_tests = False, + require_git_metadata = True, + debug = True, + ) + asserts.equals(env, 1, len(result["sync_specs"])) + _assert_sync_spec(env, result["sync_specs"][0], "test_optimization_data_go", "go-service") + asserts.equals(env, None, result["aggregate_spec"]) + return unittest.end(env) + +def _go_workspace_multi_specs_test(ctx): + env = unittest.begin(ctx) + result = build_go_workspace_sync_specs_for_tests( + name = "test_optimization_data_go", + services = ["go-service-a", "go-service-a", "go-service-b"], + runtime_version = "1.25.9", + module_path = "example.com/workspace", + enabled = False, + enabled_by_env = True, + runtime_arch = "arm64", + out_dir = "custom_topt", + http_connect_timeout_seconds = 11, + http_max_time_seconds = 22, + http_retry_attempts = 3, + http_retry_delay_seconds = 4, + http_execute_timeout_buffer_seconds = 5, + known_tests = False, + test_management = False, + flaky_tests = False, + require_git_metadata = True, + debug = True, + ) + asserts.equals(env, ["go_service_a", "go_service_a_2", "go_service_b"], result["aggregate_spec"]["service_keys"]) + asserts.equals( + env, + [ + "test_optimization_data_go_go_service_a", + "test_optimization_data_go_go_service_a_2", + "test_optimization_data_go_go_service_b", + ], + result["aggregate_spec"]["repo_names"], + ) + for i in range(3): + _assert_sync_spec( + env, + result["sync_specs"][i], + result["aggregate_spec"]["repo_names"][i], + ["go-service-a", "go-service-a", "go-service-b"][i], + ) + return unittest.end(env) + +def _orchestrion_call_spec_test(ctx): + env = unittest.begin(ctx) + call = build_orchestrion_repo_call_for_tests( + dd_trace_go_version = "v2.9.0", + dd_trace_go_versions = {"example.com/service": "v2.8.0"}, + version = "v1.9.0", + log_timing = True, + ) + asserts.equals( + env, + { + "name": "rules_go_orchestrion_tool", + "dd_trace_go_version": "v2.9.0", + "dd_trace_go_versions": {"example.com/service": "v2.8.0"}, + "version": "v1.9.0", + "log_timing": True, + }, + call, + ) + asserts.false(env, "enabled" in call) + asserts.false(env, "enabled_by_env" in call) + return unittest.end(env) + +go_workspace_single_specs_test = unittest.make(_go_workspace_single_specs_test) +go_workspace_multi_specs_test = unittest.make(_go_workspace_multi_specs_test) +orchestrion_call_spec_test = unittest.make(_orchestrion_call_spec_test) diff --git a/modules/go/tools/dd_topt_go_bootstrap/main.go b/modules/go/tools/dd_topt_go_bootstrap/main.go index 356ffe15..adcda250 100644 --- a/modules/go/tools/dd_topt_go_bootstrap/main.go +++ b/modules/go/tools/dd_topt_go_bootstrap/main.go @@ -1363,6 +1363,17 @@ func bazelrcSnippet(cfg config) (string, error) { for _, key := range bazelrcRepoEnvKeys { fmt.Fprintf(&buf, "common:%s --repo_env=%s\n", cfg.bazelrcConfig, key) } + fmt.Fprintf(&buf, "common:%s --repo_env=DD_TEST_OPTIMIZATION_ENABLED=1\n", cfg.bazelrcConfig) + rulesGoRepoName := cfg.rulesGoRepoName + if !cfg.workspaceMode { + // Bzlmod exposes rules_go under its module name. The WORKSPACE + // repository name is configurable because repository rules may + // remap it (the default there remains io_bazel_rules_go). + rulesGoRepoName = "rules_go" + } else if rulesGoRepoName == "" { + rulesGoRepoName = defaultRulesGoRepoName + } + fmt.Fprintf(&buf, "build:%s --@%s//go/private/orchestrion:enabled=true\n", cfg.bazelrcConfig, rulesGoRepoName) fmt.Fprintf(&buf, "test:%s --remote_download_minimal\n", cfg.bazelrcConfig) fmt.Fprintf(&buf, "test:%s --remote_download_regex=.*test[.]outputs.*\n", cfg.bazelrcConfig) fmt.Fprintf(&buf, "test:%s --zip_undeclared_test_outputs\n", cfg.bazelrcConfig) @@ -1371,6 +1382,17 @@ func bazelrcSnippet(cfg config) (string, error) { return buf.String(), nil } +func apparentRulesGoRepoName(cfg config) string { + rulesGoRepoName := cfg.rulesGoRepoName + if !cfg.workspaceMode { + return "rules_go" + } + if rulesGoRepoName == "" { + return defaultRulesGoRepoName + } + return rulesGoRepoName +} + // writeBazelrcBlock inserts or replaces the managed .bazelrc block. func writeBazelrcBlock(cfg config) error { snippet, err := bazelrcSnippet(cfg) @@ -1420,6 +1442,7 @@ func validationScript(cfg config) (string, error) { fmt.Fprintf(&buf, "SYNC_REPO=%s\n", shellQuote(cfg.syncRepoName)) fmt.Fprintf(&buf, "DOCTOR_TARGET=%s\n", shellQuote(cfg.validationDoctorTarget)) fmt.Fprintf(&buf, "UPLOAD_TARGET=%s\n", shellQuote(cfg.validationUploadTarget)) + fmt.Fprintf(&buf, "RULES_GO_ENABLED_LABEL=%s\n", shellQuote("@"+apparentRulesGoRepoName(cfg)+"//go/private/orchestrion:enabled")) buf.WriteString("WORKSPACE_DIR=\"$(pwd -P)\"\n") buf.WriteString("BEP_TMP_ROOT=\"\"\n") buf.WriteString("BEP_JSON_DIR=\"\"\n") @@ -1539,6 +1562,32 @@ run_step() { "$@" } +validate_disabled_bootstrap() { + local alias_files + log "validate ordinary no-config bootstrap" + if ! env -u DD_API_KEY -u DD_SITE -u DD_TEST_OPTIMIZATION_ENABLED \ + "${BAZEL}" query "@${SYNC_REPO}//:test_optimization_files"; then + warn "ordinary no-config metadata bootstrap failed" + return 1 + fi + alias_files="$( + env -u DD_API_KEY -u DD_SITE -u DD_TEST_OPTIMIZATION_ENABLED \ + "${BAZEL}" cquery \ + "${RULES_GO_ENABLED_LABEL%:enabled}:tool_binary" --output=files + )" || return $? + if [[ -n "${alias_files}" ]]; then + warn "ordinary no-config Orchestrion alias unexpectedly exposed files: ${alias_files}" + return 1 + fi + + log "validate explicit disabled precedence" + env -u DD_API_KEY -u DD_SITE -u DD_TEST_OPTIMIZATION_ENABLED \ + "${BAZEL}" query "--config=${BAZEL_CONFIG}" \ + --repo_env=DD_TEST_OPTIMIZATION_ENABLED=0 \ + "--${RULES_GO_ENABLED_LABEL}=false" \ + "@${SYNC_REPO}//:test_optimization_files" +} + upload=0 while (($#)); do case "$1" in @@ -1564,6 +1613,10 @@ done trap cleanup EXIT check_disk +if ! validate_disabled_bootstrap; then + warn "disabled bootstrap validation failed; skipping enabled validation" + exit 1 +fi run_step "sync ${SYNC_REPO}" "${BAZEL}" sync "${SYNC_FLAGS[@]}" "--repo_env=FETCH_SALT=$(date +%s)" "--only=${SYNC_REPO}" sync_status=$? if (( sync_status != 0 )); then @@ -1882,15 +1935,15 @@ datadog_go_test_optimization_workspace_repositories( buf.WriteString(")\n\n") buf.WriteString(fmt.Sprintf(`load("@%s//go:deps.bzl", "go_register_toolchains", "go_rules_dependencies") -load("@%s//go:orchestrion_workspace.bzl", "go_orchestrion_tool_repo") +load("@datadog-rules-test-optimization-go//:topt_go_orchestrion_repository.bzl", "dd_topt_go_orchestrion_tool_repo") go_rules_dependencies() go_register_toolchains(version = "") -go_orchestrion_tool_repo( +dd_topt_go_orchestrion_tool_repo( version = "%s", %s ) -`, cfg.rulesGoRepoName, cfg.rulesGoRepoName, cfg.orchestrionVersion, workspaceSnippetTracerConfig(cfg))) +`, cfg.rulesGoRepoName, cfg.orchestrionVersion, workspaceSnippetTracerConfig(cfg))) if cfg.workspaceMode || strings.TrimSpace(cfg.service) != "" || strings.TrimSpace(cfg.runtimeVersion) != "" { buf.WriteString("\n") buf.WriteString(workspaceSyncSnippet(cfg)) @@ -1901,16 +1954,20 @@ go_orchestrion_tool_repo( // workspaceSyncSnippet renders the repository-rule call that fetches Test // Optimization metadata during WORKSPACE repository resolution. func workspaceSyncSnippet(cfg config) string { - return fmt.Sprintf(`load("@datadog-rules-test-optimization//tools/core:test_optimization_sync.bzl", "test_optimization_sync") + modulePathLine := "" + if cfg.goModulePath != "" { + modulePathLine = fmt.Sprintf(" module_path = %q,\n", cfg.goModulePath) + } + return fmt.Sprintf(`load("@datadog-rules-test-optimization-go//:topt_go_workspace.bzl", "dd_topt_go_workspace_sync_repositories") -test_optimization_sync( +dd_topt_go_workspace_sync_repositories( name = "%s", service = "%s", - runtime_name = "go", runtime_version = "%s", +%s enabled_by_env = True, require_git_metadata = True, ) -`, cfg.syncRepoName, cfg.service, cfg.runtimeVersion) +`, cfg.syncRepoName, cfg.service, cfg.runtimeVersion, modulePathLine) } func workspaceSnippetTracerConfig(cfg config) string { @@ -2276,34 +2333,29 @@ func ensureGuidedRootBuild(cfg config) error { } } - text, err = ensureLoadStatement(text, `load("@datadog-rules-test-optimization//tools/core:test_optimization_doctor.bzl", "dd_test_optimization_doctor")`) - if err != nil { - return err - } - text, err = ensureLoadStatement(text, `load("@datadog-rules-test-optimization//tools/core:test_optimization_uploader.bzl", "dd_payload_uploader")`) + text, err = ensureLoadStatement(text, `load("@datadog-rules-test-optimization//tools/core:test_optimization_targets.bzl", "dd_test_optimization_targets")`) if err != nil { return err } doctorBlock := fmt.Sprintf(`%s -dd_test_optimization_doctor( - name = "%s", - data = ["@%s//:test_optimization_context"], +dd_test_optimization_targets( + name = "test_optimization", + sync_repo_name = "%s", + doctor_name = "%s", + uploader_name = "%s", %s) %s -`, doctorBlockStart, cfg.doctorTargetName, cfg.syncRepoName, renderExpectedTargetsAttr(cfg.expectedTargets), doctorBlockEnd) +`, doctorBlockStart, cfg.syncRepoName, cfg.doctorTargetName, cfg.uploaderTargetName, renderExpectedTargetsAttr(cfg.expectedTargets), doctorBlockEnd) text, err = replaceManagedSection(text, doctorBlockStart, doctorBlockEnd, doctorBlock) if err != nil { return err } uploaderBlock := fmt.Sprintf(`%s -dd_payload_uploader( - name = "%s", - data = ["@%s//:test_optimization_context"], -) +# The doctor/uploader pair is generated by dd_test_optimization_targets above. %s -`, uploaderBlockStart, cfg.uploaderTargetName, cfg.syncRepoName, uploaderBlockEnd) +`, uploaderBlockStart, uploaderBlockEnd) text, err = replaceManagedSection(text, uploaderBlockStart, uploaderBlockEnd, uploaderBlock) if err != nil { return err @@ -3066,10 +3118,22 @@ func bootstrapSyncCommands(cfg config) [][]string { switch mode { case "targeted": - commands = append(commands, - append([]string{"list", "-mod=mod", "-tags=tools"}, orchestrionToolPackages...), - append([]string{"list", "-mod=readonly", "-tags=tools"}, orchestrionToolPackages...), - ) + // Seed every selected module version explicitly before loading package + // roots. On a cold cache, go list may otherwise query @latest even + // though go.mod already contains the intended requirements. + commands = append(commands, []string{"mod", "download", "github.com/DataDog/orchestrion@" + cfg.orchestrionVersion}) + for _, modulePath := range ddTraceGoModules { + commands = append(commands, []string{"mod", "download", modulePath + "@" + versions[modulePath]}) + } + // Resolve package roots one at a time. A single go list spanning several + // module roots can ignore freshly edited requirements on a cold module + // cache and fall back to @latest for some roots. + for _, packagePath := range orchestrionToolPackages { + commands = append(commands, []string{"list", "-mod=mod", "-tags=tools", packagePath}) + } + for _, packagePath := range orchestrionToolPackages { + commands = append(commands, []string{"list", "-mod=readonly", "-tags=tools", packagePath}) + } case "tidy": commands = append(commands, []string{"get", "github.com/DataDog/dd-trace-go/v2/orchestrion@" + versions["github.com/DataDog/dd-trace-go/v2"]}, @@ -3350,17 +3414,22 @@ func normalizedGoEnv(env []string) []string { func setEnvValue(env []string, key, value string) []string { prefix := key + "=" + normalized := make([]string, 0, len(env)+1) replaced := false - for idx, entry := range env { + for _, entry := range env { if strings.HasPrefix(entry, prefix) { - env[idx] = prefix + value - replaced = true + if !replaced { + normalized = append(normalized, prefix+value) + replaced = true + } + continue } + normalized = append(normalized, entry) } if !replaced { - env = append(env, prefix+value) + normalized = append(normalized, prefix+value) } - return env + return normalized } func envValue(env []string, key string) string { @@ -3379,15 +3448,18 @@ func orchestrionBootstrapEnv() []string { goModCache := filepath.Join(cacheRoot, "pkg", "mod") goBuildCache := filepath.Join(cacheRoot, "cache") - env = append(env, - "GO111MODULE=on", - "GOWORK=off", - "GOPATH="+cacheRoot, - "GOMODCACHE="+goModCache, - "GOCACHE="+goBuildCache, - "GOPROXY=https://proxy.golang.org,direct", - "GOSUMDB=sum.golang.org", - ) + env = setEnvValue(env, "GO111MODULE", "on") + env = setEnvValue(env, "GOWORK", "off") + env = setEnvValue(env, "GOPATH", cacheRoot) + env = setEnvValue(env, "GOMODCACHE", goModCache) + env = setEnvValue(env, "GOCACHE", goBuildCache) + env = setEnvValue(env, "GOPROXY", "https://proxy.golang.org,direct") + env = setEnvValue(env, "GOSUMDB", "sum.golang.org") + // The Orchestrion and dd-trace-go pins are public modules. Do not let a + // developer's broad GOPRIVATE/GONOSUMDB settings bypass the public proxy + // and leave the isolated module cache with incomplete package metadata. + env = setEnvValue(env, "GOPRIVATE", "") + env = setEnvValue(env, "GONOSUMDB", "") return normalizedGoEnv(env) } diff --git a/modules/go/tools/dd_topt_go_bootstrap/main_test.go b/modules/go/tools/dd_topt_go_bootstrap/main_test.go index 0e1da47a..56522f71 100644 --- a/modules/go/tools/dd_topt_go_bootstrap/main_test.go +++ b/modules/go/tools/dd_topt_go_bootstrap/main_test.go @@ -236,11 +236,14 @@ func TestWorkspaceModeSnippetIncludesSyncAndBaseVariant(t *testing.T) { for _, want := range []string{ `rules_go_variant = "base"`, `rules_go_repo_name = "io_bazel_rules_go"`, - `load("@datadog-rules-test-optimization//tools/core:test_optimization_sync.bzl", "test_optimization_sync")`, + `load("@datadog-rules-test-optimization-go//:topt_go_workspace.bzl", "dd_topt_go_workspace_sync_repositories")`, + `load("@datadog-rules-test-optimization-go//:topt_go_orchestrion_repository.bzl", "dd_topt_go_orchestrion_tool_repo")`, + `dd_topt_go_workspace_sync_repositories(`, + `dd_topt_go_orchestrion_tool_repo(`, `name = "test_optimization_data_worker"`, `service = "worker"`, - `runtime_name = "go"`, `runtime_version = "1.25.9"`, + `enabled_by_env = True`, `require_git_metadata = True`, } { if !strings.Contains(got, want) { @@ -412,6 +415,8 @@ func TestBazelrcSnippetUsesRepoEnvOnlyForSyncMetadata(t *testing.T) { `common:test-optimization --repo_env=DD_TEST_OPTIMIZATION_AGENTLESS_URL`, `common:test-optimization --repo_env=DD_GIT_REPOSITORY_URL`, `common:test-optimization --repo_env=DD_PR_NUMBER`, + `common:test-optimization --repo_env=DD_TEST_OPTIMIZATION_ENABLED=1`, + `build:test-optimization --@rules_go//go/private/orchestrion:enabled=true`, `test:test-optimization --remote_download_minimal`, `test:test-optimization --remote_download_regex=.*test[.]outputs.*`, `test:test-optimization --zip_undeclared_test_outputs`, @@ -654,6 +659,7 @@ func TestValidationScriptUsesConfiguredFlowAndUploadOptIn(t *testing.T) { `SYNC_REPO='test_optimization_data_worker'`, `DOCTOR_TARGET='//:dd_test_optimization_doctor'`, `UPLOAD_TARGET='//:dd_upload_payloads'`, + `RULES_GO_ENABLED_LABEL='@rules_go//go/private/orchestrion:enabled'`, `WORKSPACE_DIR="$(pwd -P)"`, `BEP_TMP_ROOT=""`, `BEP_JSON_DIR=""`, @@ -682,6 +688,12 @@ func TestValidationScriptUsesConfiguredFlowAndUploadOptIn(t *testing.T) { `--report-json`, `mktemp -d "${tmp_parent%/}/dd-go-topt.XXXXXX"`, `sync -> controls -> instrumented tests -> doctor -> dry-run uploader -> optional upload`, + `validate ordinary no-config bootstrap`, + `validate explicit disabled precedence`, + `query "@${SYNC_REPO}//:test_optimization_files"`, + `"${BAZEL}" cquery \ + "${RULES_GO_ENABLED_LABEL%:enabled}:tool_binary"`, + `--repo_env=DD_TEST_OPTIMIZATION_ENABLED=0`, `upload skipped; rerun with --upload`, `${BAZEL}" shutdown`, } { @@ -799,6 +811,9 @@ func TestValidationScriptRunsWithNoControlTargets(t *testing.T) { } logText := string(logBytes) for _, want := range []string{ + "query @test_optimization_data//:test_optimization_files", + "cquery @rules_go//go/private/orchestrion:tool_binary --output=files", + "query --config=test-optimization --repo_env=DD_TEST_OPTIMIZATION_ENABLED=0 --@rules_go//go/private/orchestrion:enabled=false @test_optimization_data//:test_optimization_files", "sync --config=test-optimization --repo_env=FETCH_SALT=", "test --config=test-optimization --build_event_json_file=", "//pkg:go_default_test", @@ -1608,6 +1623,31 @@ func TestBootstrapSyncCommandsTargetedModeAvoidsGoModTidy(t *testing.T) { if !strings.Contains(joined, "list -mod=readonly -tags=tools github.com/DataDog/orchestrion") { t.Fatalf("targeted bootstrap sync must verify readonly module completeness:\n%s", joined) } + for _, mode := range []string{"-mod=mod", "-mod=readonly"} { + for _, packagePath := range orchestrionToolPackages { + want := "list " + mode + " -tags=tools " + packagePath + if !strings.Contains(joined, want) { + t.Fatalf("targeted bootstrap sync missing sequential package resolution %q:\n%s", want, joined) + } + } + } + expectedDownloads := []string{ + "github.com/DataDog/orchestrion@v1.9.0", + "github.com/DataDog/dd-trace-go/v2@v2.9.0", + "github.com/DataDog/dd-trace-go/contrib/net/http/v2@v2.9.0", + "github.com/DataDog/dd-trace-go/contrib/log/slog/v2@v2.9.0", + } + for _, moduleVersion := range expectedDownloads { + want := "mod download " + moduleVersion + if !strings.Contains(joined, want) { + t.Fatalf("targeted bootstrap sync missing exact module download %q:\n%s", want, joined) + } + } + for _, command := range got { + if len(command) > 5 && command[0] == "list" { + t.Fatalf("targeted bootstrap sync must resolve one package root per go list command: %#v", command) + } + } } func TestBootstrapSyncCommandsDefaultsToTargetedMode(t *testing.T) { @@ -2145,6 +2185,42 @@ func TestNormalizedGoEnvForcesGoWorkOff(t *testing.T) { } } +func TestOrchestrionBootstrapEnvUsesPublicModuleResolution(t *testing.T) { + got := orchestrionBootstrapEnv() + cacheRoot := filepath.Join(os.TempDir(), sharedOrchestrionCacheDirName) + for key, want := range map[string]string{ + "GOPATH": cacheRoot, + "GOMODCACHE": filepath.Join(cacheRoot, "pkg", "mod"), + "GOCACHE": filepath.Join(cacheRoot, "cache"), + } { + if value := envValue(got, key); value != want { + t.Fatalf("%s=%q, want %q", key, value, want) + } + prefix := key + "=" + count := 0 + for _, entry := range got { + if strings.HasPrefix(entry, prefix) { + count++ + } + } + if count != 1 { + t.Fatalf("%s appears %d times in bootstrap environment, want exactly once", key, count) + } + } + if envValue(got, "GOPROXY") != "https://proxy.golang.org,direct" { + t.Fatalf("GOPROXY=%q, want public proxy", envValue(got, "GOPROXY")) + } + if envValue(got, "GOSUMDB") != "sum.golang.org" { + t.Fatalf("GOSUMDB=%q, want public checksum database", envValue(got, "GOSUMDB")) + } + if envValue(got, "GOPRIVATE") != "" { + t.Fatalf("GOPRIVATE=%q, want empty for public bootstrap modules", envValue(got, "GOPRIVATE")) + } + if envValue(got, "GONOSUMDB") != "" { + t.Fatalf("GONOSUMDB=%q, want empty for public bootstrap modules", envValue(got, "GONOSUMDB")) + } +} + func TestNormalizeDDTraceGoVersionMutatesConfigBeforePersistence(t *testing.T) { if runtime.GOOS == "windows" { t.Skip("shell-script based helper test is Unix-only") diff --git a/modules/go/topt_go_extension.bzl b/modules/go/topt_go_extension.bzl index 37056518..7925334b 100644 --- a/modules/go/topt_go_extension.bzl +++ b/modules/go/topt_go_extension.bzl @@ -44,16 +44,106 @@ def _record_repo_owner_or_fail(seen_repo_owners, repo_name, owner, tag_name): ) seen_repo_owners[repo_name] = owner +def _build_go_single_repo_spec( + name, + service, + module_path = "", + runtime_version = "", + runtime_arch = "", + out_dir = "", + http_connect_timeout_seconds = HTTP_POLICY_ATTR_UNSET, + http_max_time_seconds = HTTP_POLICY_ATTR_UNSET, + http_retry_attempts = HTTP_POLICY_ATTR_UNSET, + http_retry_delay_seconds = HTTP_POLICY_ATTR_UNSET, + http_execute_timeout_buffer_seconds = HTTP_POLICY_ATTR_UNSET, + known_tests = True, + test_management = True, + flaky_tests = True, + enabled = True, + enabled_by_env = False, + require_git_metadata = False, + debug = False): + """Build the complete sync spec for one Go service repository.""" + return { + "name": name, + "repo_name": name, + "out_dir": out_dir, + "service": service, + "runtime_name": "go", + "runtime_version": runtime_version, + "runtime_arch": runtime_arch, + "runtime_module_path": module_path, + "http_connect_timeout_seconds": http_connect_timeout_seconds, + "http_max_time_seconds": http_max_time_seconds, + "http_retry_attempts": http_retry_attempts, + "http_retry_delay_seconds": http_retry_delay_seconds, + "http_execute_timeout_buffer_seconds": http_execute_timeout_buffer_seconds, + "known_tests": known_tests, + "test_management": test_management, + "flaky_tests": flaky_tests, + "enabled": enabled, + "enabled_by_env": enabled_by_env, + "require_git_metadata": require_git_metadata, + "debug": debug, + } + +def _build_go_multi_repo_specs( + name, + services, + module_path = "", + runtime_version = "", + runtime_arch = "", + out_dir = "", + http_connect_timeout_seconds = HTTP_POLICY_ATTR_UNSET, + http_max_time_seconds = HTTP_POLICY_ATTR_UNSET, + http_retry_attempts = HTTP_POLICY_ATTR_UNSET, + http_retry_delay_seconds = HTTP_POLICY_ATTR_UNSET, + http_execute_timeout_buffer_seconds = HTTP_POLICY_ATTR_UNSET, + known_tests = True, + test_management = True, + flaky_tests = True, + enabled = True, + enabled_by_env = False, + require_git_metadata = False, + debug = False): + """Build the per-service sync specs for a multi-service Go tag.""" + service_keys = _compute_service_keys(services) + repo_names = _compute_repo_names(name, service_keys) + specs = [] + for i in range(len(services)): + specs.append(_build_go_single_repo_spec( + name = repo_names[i], + service = services[i], + module_path = module_path, + runtime_version = runtime_version, + runtime_arch = runtime_arch, + out_dir = out_dir, + http_connect_timeout_seconds = http_connect_timeout_seconds, + http_max_time_seconds = http_max_time_seconds, + http_retry_attempts = http_retry_attempts, + http_retry_delay_seconds = http_retry_delay_seconds, + http_execute_timeout_buffer_seconds = http_execute_timeout_buffer_seconds, + known_tests = known_tests, + test_management = test_management, + flaky_tests = flaky_tests, + enabled = enabled, + enabled_by_env = enabled_by_env, + require_git_metadata = require_git_metadata, + debug = debug, + )) + return specs + +build_go_single_repo_spec_for_tests = _build_go_single_repo_spec +build_go_multi_repo_specs_for_tests = _build_go_multi_repo_specs + def _materialize_single_service_repo(call): - test_optimization_sync( + test_optimization_sync(**_build_go_single_repo_spec( name = call.name, - repo_name = call.name, - out_dir = call.out_dir, service = call.service, - runtime_name = "go", + module_path = call.module_path, runtime_version = call.runtime_version, runtime_arch = call.runtime_arch, - runtime_module_path = call.module_path, + out_dir = call.out_dir, http_connect_timeout_seconds = call.http_connect_timeout_seconds, http_max_time_seconds = call.http_max_time_seconds, http_retry_attempts = call.http_retry_attempts, @@ -61,34 +151,38 @@ def _materialize_single_service_repo(call): http_execute_timeout_buffer_seconds = call.http_execute_timeout_buffer_seconds, known_tests = call.known_tests, test_management = call.test_management, + flaky_tests = call.flaky_tests, + enabled = call.enabled, + enabled_by_env = call.enabled_by_env, require_git_metadata = call.require_git_metadata, debug = call.debug, - ) + )) def _materialize_multi_service_repos(call): service_keys = _compute_service_keys(call.services) repo_names = _compute_repo_names(call.name, service_keys) - for i in range(len(call.services)): - test_optimization_sync( - name = repo_names[i], - repo_name = repo_names[i], - out_dir = call.out_dir, - service = call.services[i], - runtime_name = "go", - runtime_version = call.runtime_version, - runtime_arch = call.runtime_arch, - runtime_module_path = call.module_path, - http_connect_timeout_seconds = call.http_connect_timeout_seconds, - http_max_time_seconds = call.http_max_time_seconds, - http_retry_attempts = call.http_retry_attempts, - http_retry_delay_seconds = call.http_retry_delay_seconds, - http_execute_timeout_buffer_seconds = call.http_execute_timeout_buffer_seconds, - known_tests = call.known_tests, - test_management = call.test_management, - require_git_metadata = call.require_git_metadata, - debug = call.debug, - ) + for spec in _build_go_multi_repo_specs( + name = call.name, + services = call.services, + module_path = call.module_path, + runtime_version = call.runtime_version, + runtime_arch = call.runtime_arch, + out_dir = call.out_dir, + http_connect_timeout_seconds = call.http_connect_timeout_seconds, + http_max_time_seconds = call.http_max_time_seconds, + http_retry_attempts = call.http_retry_attempts, + http_retry_delay_seconds = call.http_retry_delay_seconds, + http_execute_timeout_buffer_seconds = call.http_execute_timeout_buffer_seconds, + known_tests = call.known_tests, + test_management = call.test_management, + flaky_tests = call.flaky_tests, + enabled = call.enabled, + enabled_by_env = call.enabled_by_env, + require_git_metadata = call.require_git_metadata, + debug = call.debug, + ): + test_optimization_sync(**spec) test_optimization_multi_aggregate( name = call.name, @@ -137,6 +231,9 @@ test_optimization_go_extension = module_extension( "http_execute_timeout_buffer_seconds": attr.int(default = HTTP_POLICY_ATTR_UNSET), "known_tests": attr.bool(default = True), "test_management": attr.bool(default = True), + "flaky_tests": attr.bool(default = True), + "enabled": attr.bool(default = True), + "enabled_by_env": attr.bool(default = False), "require_git_metadata": attr.bool(default = False), "debug": attr.bool(default = False), }), diff --git a/modules/go/topt_go_orchestrion.bzl b/modules/go/topt_go_orchestrion.bzl index bf33ce3e..0337e281 100644 --- a/modules/go/topt_go_orchestrion.bzl +++ b/modules/go/topt_go_orchestrion.bzl @@ -12,7 +12,6 @@ _ORCHESTRION_MODE_TEST_OPTIMIZATION = "test_optimization" def _orch_transition_impl(_settings, _attr): return { - "@rules_go//go/private/orchestrion:enabled": True, "@rules_go//go/private/orchestrion:mode": _attr.orchestrion_mode, } @@ -21,10 +20,7 @@ orch_transition_impl_for_tests = _orch_transition_impl orch_transition = transition( implementation = _orch_transition_impl, inputs = [], - outputs = [ - "@rules_go//go/private/orchestrion:enabled", - "@rules_go//go/private/orchestrion:mode", - ], + outputs = ["@rules_go//go/private/orchestrion:mode"], ) def _first_target(dep): diff --git a/modules/go/topt_go_orchestrion_repository.bzl b/modules/go/topt_go_orchestrion_repository.bzl new file mode 100644 index 00000000..d26941bb --- /dev/null +++ b/modules/go/topt_go_orchestrion_repository.bzl @@ -0,0 +1,43 @@ +# Unless explicitly stated otherwise all files in this repository are licensed under +# the Apache 2.0 License. +# +# This product includes software developed at Datadog +# (https://www.datadoghq.com/) Copyright 2025-Present Datadog, Inc. + +"""Thin WORKSPACE wrapper around rules_go's public Orchestrion repository API.""" + +load( + "@rules_go//go:orchestrion_workspace.bzl", + "go_orchestrion_tool_repo", +) + +_DEFAULT_TOOL_REPO_NAME = "rules_go_orchestrion_tool" + +def _build_orchestrion_repo_call( + dd_trace_go_version = "", + dd_trace_go_versions = {}, + version = "", + log_timing = False): + """Build the fixed-name public rules_go repository call.""" + return { + "name": _DEFAULT_TOOL_REPO_NAME, + "dd_trace_go_version": dd_trace_go_version, + "dd_trace_go_versions": dd_trace_go_versions, + "version": version, + "log_timing": log_timing, + } + +def dd_topt_go_orchestrion_tool_repo( + dd_trace_go_version = "", + dd_trace_go_versions = {}, + version = "", + log_timing = False): + """Declare the real Orchestrion repository through rules_go's public API.""" + go_orchestrion_tool_repo(**_build_orchestrion_repo_call( + dd_trace_go_version = dd_trace_go_version, + dd_trace_go_versions = dd_trace_go_versions, + version = version, + log_timing = log_timing, + )) + +build_orchestrion_repo_call_for_tests = _build_orchestrion_repo_call diff --git a/modules/go/topt_go_workspace.bzl b/modules/go/topt_go_workspace.bzl new file mode 100644 index 00000000..c7dc0dbd --- /dev/null +++ b/modules/go/topt_go_workspace.bzl @@ -0,0 +1,139 @@ +# Unless explicitly stated otherwise all files in this repository are licensed under +# the Apache 2.0 License. +# +# This product includes software developed at Datadog +# (https://www.datadoghq.com/) Copyright 2025-Present Datadog, Inc. + +"""WORKSPACE metadata bootstrap for Go consumers.""" + +load( + "@datadog-rules-test-optimization//tools/core:common_utils.bzl", + "dedup_keys", + "sanitize_label_fragment", +) +load( + "@datadog-rules-test-optimization//tools/core:test_optimization_multi_sync.bzl", + "test_optimization_multi_aggregate", +) +load( + "@datadog-rules-test-optimization//tools/core:test_optimization_sync.bzl", + "test_optimization_sync", +) + +def _service_keys(services): + return dedup_keys([sanitize_label_fragment(service) for service in services]) + +def _build_go_workspace_sync_specs( + name, + runtime_version, + module_path, + service = None, + services = [], + enabled = True, + enabled_by_env = False, + runtime_arch = "", + out_dir = "", + http_connect_timeout_seconds = -1, + http_max_time_seconds = -1, + http_retry_attempts = -1, + http_retry_delay_seconds = -1, + http_execute_timeout_buffer_seconds = -1, + known_tests = True, + test_management = True, + flaky_tests = True, + require_git_metadata = False, + debug = False): + """Build metadata sync calls and the optional aggregate call.""" + if service and services: + fail("set either service or services, not both") + if not service and not services: + fail("one of service or services is required") + + service_values = [service] if service else services + keys = [] if service else _service_keys(services) + repo_names = [name] if service else ["%s_%s" % (name, key) for key in keys] + sync_specs = [] + for i in range(len(service_values)): + sync_specs.append({ + "name": repo_names[i], + "repo_name": repo_names[i], + "service": service_values[i], + "runtime_name": "go", + "runtime_version": runtime_version, + "runtime_arch": runtime_arch, + "runtime_module_path": module_path, + "out_dir": out_dir, + "enabled": enabled, + "enabled_by_env": enabled_by_env, + "http_connect_timeout_seconds": http_connect_timeout_seconds, + "http_max_time_seconds": http_max_time_seconds, + "http_retry_attempts": http_retry_attempts, + "http_retry_delay_seconds": http_retry_delay_seconds, + "http_execute_timeout_buffer_seconds": http_execute_timeout_buffer_seconds, + "known_tests": known_tests, + "test_management": test_management, + "flaky_tests": flaky_tests, + "require_git_metadata": require_git_metadata, + "debug": debug, + }) + aggregate_spec = None + if not service: + aggregate_spec = { + "name": name, + "service_keys": keys, + "repo_names": repo_names, + "debug": debug, + } + return { + "sync_specs": sync_specs, + "aggregate_spec": aggregate_spec, + } + +build_go_workspace_sync_specs_for_tests = _build_go_workspace_sync_specs + +def dd_topt_go_workspace_sync_repositories( + name, + runtime_version, + module_path = "", + service = None, + services = [], + enabled = True, + enabled_by_env = False, + runtime_arch = "", + out_dir = "", + http_connect_timeout_seconds = -1, + http_max_time_seconds = -1, + http_retry_attempts = -1, + http_retry_delay_seconds = -1, + http_execute_timeout_buffer_seconds = -1, + known_tests = True, + test_management = True, + flaky_tests = True, + require_git_metadata = False, + debug = False): + specs = _build_go_workspace_sync_specs( + name = name, + runtime_version = runtime_version, + module_path = module_path, + service = service, + services = services, + enabled = enabled, + enabled_by_env = enabled_by_env, + runtime_arch = runtime_arch, + out_dir = out_dir, + http_connect_timeout_seconds = http_connect_timeout_seconds, + http_max_time_seconds = http_max_time_seconds, + http_retry_attempts = http_retry_attempts, + http_retry_delay_seconds = http_retry_delay_seconds, + http_execute_timeout_buffer_seconds = http_execute_timeout_buffer_seconds, + known_tests = known_tests, + test_management = test_management, + flaky_tests = flaky_tests, + require_git_metadata = require_git_metadata, + debug = debug, + ) + for sync_spec in specs["sync_specs"]: + test_optimization_sync(**sync_spec) + aggregate_spec = specs["aggregate_spec"] + if aggregate_spec: + test_optimization_multi_aggregate(**aggregate_spec) diff --git a/modules/java/MODULE.bazel.lock b/modules/java/MODULE.bazel.lock index c602cdda..6ed3a69d 100644 --- a/modules/java/MODULE.bazel.lock +++ b/modules/java/MODULE.bazel.lock @@ -140,8 +140,8 @@ "moduleExtensions": { "//tests:example_stub_repo.bzl%example_stub_repo_extension": { "general": { - "bzlTransitiveDigest": "zC3aEdxLSUhPV6iwgCNk7hQr+1eO+rAQ5znCfGieX3g=", - "usagesDigest": "WUBevKMAOfvH9naco2wxzvnDbEMGelUuwm0wvsegGYc=", + "bzlTransitiveDigest": "8Z0dqG8n5A2E4sAbWxvij+vncAodaOVqDuvzF/sm6wE=", + "usagesDigest": "6NwGJ7RyhJOwtM7+EPNpuNyawB974PzmTqQARABaogw=", "recordedFileInputs": {}, "recordedDirentsInputs": {}, "envVariables": {}, diff --git a/modules/nodejs/MODULE.bazel.lock b/modules/nodejs/MODULE.bazel.lock index ce7dabff..fb767431 100644 --- a/modules/nodejs/MODULE.bazel.lock +++ b/modules/nodejs/MODULE.bazel.lock @@ -140,8 +140,8 @@ "moduleExtensions": { "//tests:example_stub_repo.bzl%example_stub_repo_extension": { "general": { - "bzlTransitiveDigest": "zC3aEdxLSUhPV6iwgCNk7hQr+1eO+rAQ5znCfGieX3g=", - "usagesDigest": "irePOqLrnrDswqq6uNfKWfqwmNWA2wVLir9YmhV+HBw=", + "bzlTransitiveDigest": "8Z0dqG8n5A2E4sAbWxvij+vncAodaOVqDuvzF/sm6wE=", + "usagesDigest": "lqQSWgxAEY9YXf27tmAuYKvioDk6WYdJzSpQkWFpeTE=", "recordedFileInputs": {}, "recordedDirentsInputs": {}, "envVariables": {}, diff --git a/modules/python/MODULE.bazel b/modules/python/MODULE.bazel index e3740a5a..297c1ef7 100644 --- a/modules/python/MODULE.bazel +++ b/modules/python/MODULE.bazel @@ -22,6 +22,12 @@ example_stub_repo = use_extension( ) example_stub_repo.example_stub_repo( name = "test_optimization_data", + labels = [ + "example_python_modules_python_tests", + "example_python_pkg", + "example_python_stub_tests", + "example_python_tests", + ], service_keys = [ "py_service", "ruby_service", diff --git a/modules/python/MODULE.bazel.lock b/modules/python/MODULE.bazel.lock index 3b4da633..25f8ce6d 100644 --- a/modules/python/MODULE.bazel.lock +++ b/modules/python/MODULE.bazel.lock @@ -148,8 +148,8 @@ "moduleExtensions": { "//tests:example_stub_repo.bzl%example_stub_repo_extension": { "general": { - "bzlTransitiveDigest": "zC3aEdxLSUhPV6iwgCNk7hQr+1eO+rAQ5znCfGieX3g=", - "usagesDigest": "Njje2Mz52TKjSVkVkgxLXaZa/9f/0VBc5vCkeOpAhDE=", + "bzlTransitiveDigest": "8Z0dqG8n5A2E4sAbWxvij+vncAodaOVqDuvzF/sm6wE=", + "usagesDigest": "UDiAhPtQ9KsYnDQ8zl34syCpr+iVmZjdd5stUk67Umk=", "recordedFileInputs": {}, "recordedDirentsInputs": {}, "envVariables": {}, @@ -160,7 +160,12 @@ "go_module_included": false, "go_module_path": "example.com/stub", "go_sanitized_module_path": "example_com_stub", - "labels": [], + "labels": [ + "example_python_modules_python_tests", + "example_python_pkg", + "example_python_stub_tests", + "example_python_tests" + ], "out_dir": ".testoptimization", "repo_alias": "test_optimization_data", "service_name": "stub-service", diff --git a/modules/python/tests/BUILD.bazel b/modules/python/tests/BUILD.bazel index 54b30b41..4b4a2e14 100644 --- a/modules/python/tests/BUILD.bazel +++ b/modules/python/tests/BUILD.bazel @@ -36,6 +36,7 @@ load( "py_macro_env_none_wiring_test", "py_macro_explicit_service_target", "py_macro_explicit_service_wiring_test", + "py_macro_fallback_payloads_test", "py_macro_invalid_runner_mode_failure_test", "py_macro_invalid_runner_mode_target_rule", "py_macro_managed_pytest_kwargs_target", @@ -81,6 +82,8 @@ load( "selector_override_target", "selector_override_test", "selector_payload_fixture_targets", + "selector_prefixed_fallback_target", + "selector_prefixed_fallback_test", ) load( ":test_selection_utils.bzl", @@ -93,11 +96,13 @@ load( "build_module_labels_unsanitized_entry_failure_test", "build_module_labels_unsanitized_entry_target_rule", "build_module_labels_valid_test", + "build_python_fallback_identifier_test", "normalize_python_identifier_edge_cases_test", "normalize_user_data_handles_none_test", "normalize_user_data_invalid_type_failure_test", "normalize_user_data_invalid_type_target_rule", "py_stub_includes_manifest_in_files_test", + "resolve_python_selector_inputs_test", "resolve_topt_service_key_prefers_exact_then_sanitized_test", "select_module_group_name_test", "service_mapping_entries_filters_non_service_test", @@ -149,6 +154,18 @@ build_module_labels_valid_test( timeout = "short", ) +build_python_fallback_identifier_test( + name = "build_python_fallback_identifier_test", + size = "small", + timeout = "short", +) + +resolve_python_selector_inputs_test( + name = "resolve_python_selector_inputs_test", + size = "small", + timeout = "short", +) + py_stub_includes_manifest_in_files_test( name = "py_stub_includes_manifest_in_files_test", size = "small", @@ -227,6 +244,16 @@ selector_no_match_fallback_test( target_under_test = ":selector_no_match_fallback_target", ) +selector_prefixed_fallback_target( + name = "selector_prefixed_fallback_target", + tags = ["manual"], +) + +selector_prefixed_fallback_test( + name = "selector_prefixed_fallback_test", + target_under_test = ":selector_prefixed_fallback_target", +) + selector_include_disabled_target( name = "selector_include_disabled_target", tags = ["manual"], @@ -439,6 +466,11 @@ py_macro_public_wrapper_test( target_under_test = ":py_macro_single_service_target", ) +py_macro_fallback_payloads_test( + name = "py_macro_fallback_payloads_test", + target_under_test = ":py_macro_env_none_target_topt_payloads", +) + py_macro_multi_service_target( name = "py_macro_multi_service_target", tags = ["manual"], @@ -567,6 +599,7 @@ test_suite( ":build_module_labels_invalid_shape_failure_test", ":build_module_labels_unsanitized_entry_failure_test", ":build_module_labels_valid_test", + ":build_python_fallback_identifier_test", ":normalize_python_identifier_edge_cases_test", ":normalize_user_data_handles_none_test", ":normalize_user_data_invalid_type_failure_test", @@ -585,12 +618,14 @@ test_suite( ":py_macro_default_rule_detection_test", ":py_macro_env_none_wiring_test", ":py_macro_explicit_service_wiring_test", + ":py_macro_fallback_payloads_test", ":py_macro_invalid_runner_mode_failure_test", ":py_macro_managed_pytest_kwargs_test", ":py_macro_multi_service_wiring_test", ":py_macro_select_inputs_wiring_test", ":py_macro_single_service_wiring_test", ":py_stub_includes_manifest_in_files_test", + ":resolve_python_selector_inputs_test", ":resolve_topt_service_key_missing_failure_test", ":resolve_topt_service_key_prefers_exact_then_sanitized_test", ":resolve_topt_service_key_unknown_failure_test", @@ -608,6 +643,7 @@ test_suite( ":selector_omits_flaky_tests_test", ":selector_override_miss_failure_test", ":selector_override_test", + ":selector_prefixed_fallback_test", ":service_mapping_entries_filters_non_service_test", ], ) diff --git a/modules/python/tests/test_macro.bzl b/modules/python/tests/test_macro.bzl index db065961..ef339675 100644 --- a/modules/python/tests/test_macro.bzl +++ b/modules/python/tests/test_macro.bzl @@ -257,7 +257,11 @@ def _single_service_topt_data(): "repo_name": "test_optimization_data", "service_name": "py-service", "manifest_path": ".testoptimization/manifest.txt", - "labels": [], + "labels": [ + "example_python_modules_python_tests", + "example_python_pkg", + "example_python_tests", + ], "set": {}, "runtimes": { "go": { @@ -867,6 +871,31 @@ def _py_macro_public_wrapper_test_impl(ctx): asserts.equals(env, "1", run_env.get("CUSTOM_ENV")) return analysistest.end(env) +def _py_macro_fallback_payloads_test_impl(ctx): + env = analysistest.begin(ctx) + target = analysistest.target_under_test(env) + paths = [file.short_path for file in target[DefaultInfo].files.to_list()] + + modular_known_tests = False + full_bundle_known_tests = False + for path in paths: + if "/module_example_python_" in path and path.endswith("/known_tests.json"): + modular_known_tests = True + if path.endswith("/.testoptimization/cache/http/known_tests.json"): + full_bundle_known_tests = True + + asserts.true( + env, + modular_known_tests, + msg = "macro-generated selector must expose the package-derived Python module payload: %s" % paths, + ) + asserts.false( + env, + full_bundle_known_tests, + msg = "macro-generated selector must not fall back to the full known-tests bundle: %s" % paths, + ) + return analysistest.end(env) + def _resolve_topt_service_key_missing_target_impl(_ctx): resolve_topt_service_key_for_tests( { @@ -951,6 +980,9 @@ py_macro_explicit_service_wiring_test = analysistest.make( py_macro_public_wrapper_test = analysistest.make( _py_macro_public_wrapper_test_impl, ) +py_macro_fallback_payloads_test = analysistest.make( + _py_macro_fallback_payloads_test_impl, +) resolve_topt_service_key_missing_failure_test = analysistest.make( _resolve_topt_service_key_missing_failure_test_impl, expect_failure = True, diff --git a/modules/python/tests/test_payloads_selector.bzl b/modules/python/tests/test_payloads_selector.bzl index 570981c6..ea6ec296 100644 --- a/modules/python/tests/test_payloads_selector.bzl +++ b/modules/python/tests/test_payloads_selector.bzl @@ -89,6 +89,10 @@ def selector_payload_fixture_targets(): name = "module_example_python_fallback_pkg", marker = "module:fallback", ) + _payload_marker( + name = "module_domains_ffe_apps_apis_query_validator_internal_validator_tests", + marker = "module:dd-source-prefixed-fallback", + ) _payload_marker( name = "module_custom_override", marker = "module:override", @@ -188,6 +192,20 @@ def selector_no_match_fallback_target(name, tags = None): tags = tags, ) +def selector_prefixed_fallback_target(name, tags = None): + """Select a dd-source-style module path without falling back to full files.""" + topt_py_payloads_selector( + name = name, + imports = [], + deps = [], + attribute_candidates = [], + fallback_identifier = "domains.ffe.apps.apis.query_validator.internal.validator.tests", + full_files = ":full_payload", + module_groups = [":module_domains_ffe_apps_apis_query_validator_internal_validator_tests"], + include_per_module = True, + tags = tags, + ) + def selector_include_disabled_target(name, tags = None): topt_py_payloads_selector( name = name, @@ -328,6 +346,12 @@ def _selector_no_match_fallback_test_impl(ctx): _assert_selected(env, target, "full_payload") return analysistest.end(env) +def _selector_prefixed_fallback_test_impl(ctx): + env = analysistest.begin(ctx) + target = analysistest.target_under_test(env) + _assert_selected(env, target, "module_domains_ffe_apps_apis_query_validator_internal_validator_tests") + return analysistest.end(env) + def _selector_include_disabled_test_impl(ctx): env = analysistest.begin(ctx) target = analysistest.target_under_test(env) @@ -382,6 +406,9 @@ selector_fallback_test = analysistest.make( selector_no_match_fallback_test = analysistest.make( _selector_no_match_fallback_test_impl, ) +selector_prefixed_fallback_test = analysistest.make( + _selector_prefixed_fallback_test_impl, +) selector_include_disabled_test = analysistest.make( _selector_include_disabled_test_impl, ) diff --git a/modules/python/tests/test_selection_utils.bzl b/modules/python/tests/test_selection_utils.bzl index 0bce5e3d..ecd27cd7 100644 --- a/modules/python/tests/test_selection_utils.bzl +++ b/modules/python/tests/test_selection_utils.bzl @@ -15,7 +15,9 @@ load( load( "@datadog-rules-test-optimization-python//:topt_py_test.bzl", "build_module_labels_for_tests", + "build_python_fallback_identifier_for_tests", "normalize_user_data_for_tests", + "resolve_python_selector_inputs_for_tests", "resolve_topt_service_key_for_tests", "service_mapping_entries_for_tests", ) @@ -83,6 +85,83 @@ def _normalize_python_identifier_edge_cases_test(ctx): asserts.equals(env, "", normalize_python_identifier_for_tests(None)) return unittest.end(env) +def _build_python_fallback_identifier_test(ctx): + env = unittest.begin(ctx) + asserts.equals( + env, + "example.project.app", + build_python_fallback_identifier_for_tests("app", {"module_path": "example.project"}), + ) + asserts.equals( + env, + "domains.ffe.apps.apis.query_validator.internal.validator.tests", + build_python_fallback_identifier_for_tests( + "domains/ffe/apps/apis/query_validator/internal/validator/tests", + {"module_path": "domains.ffe.apps.apis.query_validator"}, + ), + ) + asserts.equals( + env, + "domains.ffe.apps.apis.query_validator", + build_python_fallback_identifier_for_tests( + "domains/ffe/apps/apis/query_validator", + {"module_path": "domains.ffe.apps.apis.query_validator"}, + ), + ) + asserts.equals( + env, + "app", + build_python_fallback_identifier_for_tests("app", {}), + ) + asserts.equals( + env, + "example.project.app.tests", + build_python_fallback_identifier_for_tests("example//project\\app//tests", {"module_path": "example.project"}), + ) + return unittest.end(env) + +def _resolve_python_selector_inputs_test(ctx): + env = unittest.begin(ctx) + derived = resolve_python_selector_inputs_for_tests( + module_identifier = None, + imports_candidates = [], + deps_labels = [], + importpath_candidate = None, + module_path_candidate = None, + fallback_identifier = "example.project.tests", + module_groups = ["@repo//:module_example_project_tests"], + module_included = False, + ) + asserts.equals(env, "", derived["explicit_identifier"]) + asserts.equals(env, "example.project.tests", derived["fallback_identifier"]) + asserts.equals(env, True, derived["include_per_module"]) + + explicit = resolve_python_selector_inputs_for_tests( + module_identifier = "explicit.module", + imports_candidates = [], + deps_labels = [], + importpath_candidate = None, + module_path_candidate = None, + fallback_identifier = "fallback.module", + module_groups = ["@repo//:module_explicit_module"], + module_included = False, + ) + asserts.equals(env, "explicit.module", explicit["explicit_identifier"]) + asserts.equals(env, True, explicit["include_per_module"]) + + no_groups = resolve_python_selector_inputs_for_tests( + module_identifier = None, + imports_candidates = [], + deps_labels = [], + importpath_candidate = None, + module_path_candidate = None, + fallback_identifier = "example.project.tests", + module_groups = [], + module_included = False, + ) + asserts.equals(env, False, no_groups["include_per_module"]) + return unittest.end(env) + def _normalize_user_data_handles_none_test(ctx): env = unittest.begin(ctx) asserts.equals(env, [], normalize_user_data_for_tests(None)) @@ -202,6 +281,8 @@ service_mapping_entries_filters_non_service_test = unittest.make(_service_mappin resolve_topt_service_key_prefers_exact_then_sanitized_test = unittest.make(_resolve_topt_service_key_prefers_exact_then_sanitized_test) select_module_group_name_test = unittest.make(_select_module_group_name_test) normalize_python_identifier_edge_cases_test = unittest.make(_normalize_python_identifier_edge_cases_test) +build_python_fallback_identifier_test = unittest.make(_build_python_fallback_identifier_test) +resolve_python_selector_inputs_test = unittest.make(_resolve_python_selector_inputs_test) normalize_user_data_handles_none_test = unittest.make(_normalize_user_data_handles_none_test) build_module_labels_valid_test = unittest.make(_build_module_labels_valid_test) py_stub_includes_manifest_in_files_test = unittest.make(_py_stub_includes_manifest_in_files_test) diff --git a/modules/python/tools/dd_topt_py_bootstrap/main.py b/modules/python/tools/dd_topt_py_bootstrap/main.py index 6baf58d0..2adb0b2b 100644 --- a/modules/python/tools/dd_topt_py_bootstrap/main.py +++ b/modules/python/tools/dd_topt_py_bootstrap/main.py @@ -164,6 +164,7 @@ def render_bazelrc_snippet(args: argparse.Namespace) -> str: lines = [ "# Datadog metadata is resolved during repository/module analysis.", "# These values are repo_env, not test_env, so tests do not receive secrets.", + f"common:{args.bazelrc_config} --repo_env=DD_TEST_OPTIMIZATION_ENABLED=1", ] lines.extend(f"common:{args.bazelrc_config} --repo_env={key}" for key in SYNC_REPO_ENV_KEYS) lines.append(f"test:{args.bazelrc_config} --remote_download_minimal") @@ -249,6 +250,7 @@ def render_workspace_snippet(args: argparse.Namespace) -> str: ' runtime_name = "python",', f" runtime_version = {_quote(args.runtime_version)},", f" runtime_module_path = {_quote(args.runtime_module_path)},", + " enabled_by_env = True,", ")", ] ) @@ -310,6 +312,7 @@ def render_bzlmod_snippet(args: argparse.Namespace) -> str: ' runtime_name = "python",', f" runtime_version = {_quote(args.runtime_version)},", f" runtime_module_path = {_quote(args.runtime_module_path)},", + " enabled_by_env = True,", ")", f"use_repo(test_optimization_sync, {_quote(args.sync_repo_name)})", ] diff --git a/modules/python/tools/dd_topt_py_bootstrap/main_test.py b/modules/python/tools/dd_topt_py_bootstrap/main_test.py index 43d5ec73..2b0b24b1 100644 --- a/modules/python/tools/dd_topt_py_bootstrap/main_test.py +++ b/modules/python/tools/dd_topt_py_bootstrap/main_test.py @@ -45,6 +45,8 @@ def test_workspace_snippet_contains_helper(self) -> None: snippet = main.render_workspace_snippet(args) self.assertIn("datadog_python_test_optimization_workspace_repositories", snippet) self.assertIn("test_optimization_sync", snippet) + self.assertIn("enabled_by_env = True", snippet) + self.assertNotIn("rules_go", snippet) self.assertNotRegex(snippet, r"(?m)^ (load|# Declare|datadog_python_test_optimization_workspace_repositories|test_optimization_sync)") def test_bzlmod_snippet_contains_bazel_dep(self) -> None: @@ -54,6 +56,8 @@ def test_bzlmod_snippet_contains_bazel_dep(self) -> None: snippet = main.render_bzlmod_snippet(args) self.assertIn('bazel_dep(name = "datadog-rules-test-optimization"', snippet) self.assertIn('bazel_dep(name = "datadog-rules-test-optimization-python"', snippet) + self.assertIn("enabled_by_env = True", snippet) + self.assertNotIn("rules_go", snippet) self.assertNotRegex(snippet, r"(?m)^ (bazel_dep|archive_override|git_override|test_optimization_sync|use_repo)") def test_bzlmod_archive_snippet_emits_sha256_pin(self) -> None: @@ -86,7 +90,9 @@ def test_bazelrc_has_no_forbidden_test_env_or_fetch_salt(self) -> None: """Generated .bazelrc keeps secrets and git metadata out of test_env.""" args = _args() snippet = main.render_bazelrc_snippet(args) + self.assertIn("common:test-optimization --repo_env=DD_TEST_OPTIMIZATION_ENABLED=1", snippet) self.assertIn("common:test-optimization --repo_env=DD_API_KEY", snippet) + self.assertNotIn("orchestrion:enabled", snippet) self.assertIn("test:test-optimization --remote_download_minimal", snippet) self.assertIn("test:test-optimization --remote_download_regex=.*test[.]outputs.*", snippet) self.assertIn("test:test-optimization --zip_undeclared_test_outputs", snippet) diff --git a/modules/python/topt_py_test.bzl b/modules/python/topt_py_test.bzl index fef4d0ff..af90f31c 100644 --- a/modules/python/topt_py_test.bzl +++ b/modules/python/topt_py_test.bzl @@ -69,12 +69,22 @@ def _build_module_labels(sync_repo_name, labels): build_module_labels_for_tests = _build_module_labels +def _normalize_python_fallback_part(value): + """Normalize a workspace package or runtime module path to dotted form.""" + dotted = (value or "").replace("\\", ".").replace("/", ".") + return ".".join([part for part in dotted.split(".") if part]) + def _build_python_fallback_identifier(package_path, runtime_info): - pkg_dotted = (package_path or "").replace("/", ".") - module_path = ((runtime_info or {}).get("module_path") or "") - if module_path: - return (module_path + "." + pkg_dotted) if pkg_dotted else module_path - return pkg_dotted + """Build a generic prefix-aware Python module fallback identifier.""" + package_dotted = _normalize_python_fallback_part(package_path) + module_path = _normalize_python_fallback_part((runtime_info or {}).get("module_path")) + if not module_path: + return package_dotted + if not package_dotted: + return module_path + if package_dotted == module_path or package_dotted.startswith(module_path + "."): + return package_dotted + return module_path + "." + package_dotted def _has_non_empty_value(value): """Return True when a macro input is present and materially non-empty.""" @@ -86,6 +96,40 @@ def _has_non_empty_value(value): return len(value) > 0 return True +def _resolve_python_selector_inputs( + module_identifier, + imports_candidates, + deps_labels, + importpath_candidate, + module_path_candidate, + fallback_identifier, + module_groups, + module_included): + """Resolve selector inputs once so production and tests share the contract.""" + module_groups = module_groups or [] + uses_explicit_inference = ( + _has_non_empty_value(module_identifier) or + _has_non_empty_value(imports_candidates) or + _has_non_empty_value(deps_labels) or + _has_non_empty_value(importpath_candidate) or + _has_non_empty_value(module_path_candidate) + ) + uses_derived_fallback = _has_non_empty_value(fallback_identifier) and len(module_groups) > 0 + if uses_explicit_inference or uses_derived_fallback: + include_per_module = True + elif module_included != None: + include_per_module = bool(module_included) + else: + include_per_module = len(module_groups) > 0 + return { + "explicit_identifier": module_identifier or "", + "fallback_identifier": fallback_identifier or "", + "include_per_module": include_per_module, + } + +build_python_fallback_identifier_for_tests = _build_python_fallback_identifier +resolve_python_selector_inputs_for_tests = _resolve_python_selector_inputs + def _is_default_py_test_rule(py_test_rule): """Return True when a py_test_rule value is the rules_python base py_test macro.""" return py_test_rule == _default_py_test @@ -195,25 +239,19 @@ def dd_topt_py_test( if type(module_path_candidate) == type("") and module_path_candidate: attribute_candidates.append(module_path_candidate) - uses_inference = ( - _has_non_empty_value(module_identifier) or - _has_non_empty_value(imports_candidates) or - _has_non_empty_value(deps_labels) or - _has_non_empty_value(importpath_candidate) or - _has_non_empty_value(module_path_candidate) - ) - if uses_inference: - include_per_module_files = True - else: - module_included = _python.get("module_included") if _is_dict(_python) else None - if module_included != None: - include_per_module_files = bool(module_included) - else: - include_per_module_files = bool(_svc.get("labels")) - files_label = "@%s//:test_optimization_files" % sync_repo_name module_labels = _build_module_labels(sync_repo_name, _svc.get("labels")) fallback_identifier = _build_python_fallback_identifier(native.package_name(), _python) + selector_inputs = _resolve_python_selector_inputs( + module_identifier = module_identifier, + imports_candidates = imports_candidates, + deps_labels = deps_labels, + importpath_candidate = importpath_candidate, + module_path_candidate = module_path_candidate, + fallback_identifier = fallback_identifier, + module_groups = module_labels, + module_included = _python.get("module_included") if _is_dict(_python) else None, + ) selector_name = name + "_topt_payloads" metadata_name = name + "_topt_bazel_metadata" @@ -222,11 +260,11 @@ def dd_topt_py_test( deps = deps_labels, imports = imports_candidates, attribute_candidates = attribute_candidates, - explicit_identifier = module_identifier or "", - fallback_identifier = fallback_identifier, + explicit_identifier = selector_inputs["explicit_identifier"], + fallback_identifier = selector_inputs["fallback_identifier"], full_files = files_label, module_groups = module_labels, - include_per_module = include_per_module_files, + include_per_module = selector_inputs["include_per_module"], module_label_override = module_label_override, importpath = importpath_candidate if importpath_candidate != None else "", module_path = module_path_candidate if module_path_candidate != None else "", diff --git a/modules/ruby/MODULE.bazel.lock b/modules/ruby/MODULE.bazel.lock index 7299adf2..b9ec6196 100644 --- a/modules/ruby/MODULE.bazel.lock +++ b/modules/ruby/MODULE.bazel.lock @@ -140,8 +140,8 @@ "moduleExtensions": { "//tests:example_stub_repo.bzl%example_stub_repo_extension": { "general": { - "bzlTransitiveDigest": "zC3aEdxLSUhPV6iwgCNk7hQr+1eO+rAQ5znCfGieX3g=", - "usagesDigest": "gJmZLd6nd/xMP9rApCY4mVIpVZPOzoIiqCGWDFbM4VA=", + "bzlTransitiveDigest": "8Z0dqG8n5A2E4sAbWxvij+vncAodaOVqDuvzF/sm6wE=", + "usagesDigest": "D5EtmFTCTKSRH5kAymUIV/dmXm5DQEi4R3poqsT00uw=", "recordedFileInputs": {}, "recordedDirentsInputs": {}, "envVariables": {}, diff --git a/third_party/rgo/v0_60_0/base/go/private/orchestrion/BUILD b/third_party/rgo/v0_60_0/base/go/private/orchestrion/BUILD index dad028ae..31a01c33 100644 --- a/third_party/rgo/v0_60_0/base/go/private/orchestrion/BUILD +++ b/third_party/rgo/v0_60_0/base/go/private/orchestrion/BUILD @@ -36,37 +36,80 @@ string_flag( visibility = ["//visibility:public"], ) -# Proxy target for the orchestrion tool binary. -# This always points to the rules_go_orchestrion_tool repo. -# The repo provides an empty filegroup by default, or the actual orchestrion -# binary when configured via module extension. -# go_context_data checks the :enabled flag to determine whether to use this. +config_setting( + name = "enabled_config", + flag_values = {":enabled": "true"}, +) + +filegroup( + name = "disabled_tool_binary", + srcs = [], +) + +filegroup( + name = "disabled_dd_trace_go_version_file", + srcs = [], +) + +filegroup( + name = "disabled_dd_trace_go_module_proxy_files", + srcs = [], +) + +filegroup( + name = "disabled_dd_trace_go_module_proxy_root_marker", + srcs = [], +) + +filegroup( + name = "disabled_orchestrion_tool_version_file", + srcs = [], +) + +# Stable Orchestrion aliases select package-local empty targets by default and +# only reference the real tool repository when the public :enabled flag is set. +# go_context_data also checks :enabled before consuming these files. alias( name = "tool_binary", - actual = "@rules_go_orchestrion_tool//:orchestrion", + actual = select({ + ":enabled_config": "@rules_go_orchestrion_tool//:orchestrion", + "//conditions:default": ":disabled_tool_binary", + }), visibility = ["//visibility:public"], ) alias( name = "dd_trace_go_version_file", - actual = "@rules_go_orchestrion_tool//:dd_trace_go_version_file", + actual = select({ + ":enabled_config": "@rules_go_orchestrion_tool//:dd_trace_go_version_file", + "//conditions:default": ":disabled_dd_trace_go_version_file", + }), visibility = ["//visibility:public"], ) alias( name = "dd_trace_go_module_proxy_files", - actual = "@rules_go_orchestrion_tool//:dd_trace_go_module_proxy_files", + actual = select({ + ":enabled_config": "@rules_go_orchestrion_tool//:dd_trace_go_module_proxy_files", + "//conditions:default": ":disabled_dd_trace_go_module_proxy_files", + }), visibility = ["//visibility:public"], ) alias( name = "dd_trace_go_module_proxy_root_marker", - actual = "@rules_go_orchestrion_tool//:dd_trace_go_module_proxy_root_marker", + actual = select({ + ":enabled_config": "@rules_go_orchestrion_tool//:dd_trace_go_module_proxy_root_marker", + "//conditions:default": ":disabled_dd_trace_go_module_proxy_root_marker", + }), visibility = ["//visibility:public"], ) alias( name = "orchestrion_tool_version_file", - actual = "@rules_go_orchestrion_tool//:orchestrion_tool_version_file", + actual = select({ + ":enabled_config": "@rules_go_orchestrion_tool//:orchestrion_tool_version_file", + "//conditions:default": ":disabled_orchestrion_tool_version_file", + }), visibility = ["//visibility:public"], ) diff --git a/third_party/rgo/v0_61_1/base/go/private/orchestrion/BUILD b/third_party/rgo/v0_61_1/base/go/private/orchestrion/BUILD index 488d4adf..177eeaf2 100644 --- a/third_party/rgo/v0_61_1/base/go/private/orchestrion/BUILD +++ b/third_party/rgo/v0_61_1/base/go/private/orchestrion/BUILD @@ -29,37 +29,80 @@ string_flag( visibility = ["//visibility:public"], ) -# Proxy target for the orchestrion tool binary. -# This always points to the rules_go_orchestrion_tool repo. -# The repo provides an empty filegroup by default, or the actual orchestrion -# binary when configured via module extension. -# go_context_data checks the :enabled flag to determine whether to use this. +config_setting( + name = "enabled_config", + flag_values = {":enabled": "true"}, +) + +filegroup( + name = "disabled_tool_binary", + srcs = [], +) + +filegroup( + name = "disabled_dd_trace_go_version_file", + srcs = [], +) + +filegroup( + name = "disabled_dd_trace_go_module_proxy_files", + srcs = [], +) + +filegroup( + name = "disabled_dd_trace_go_module_proxy_root_marker", + srcs = [], +) + +filegroup( + name = "disabled_orchestrion_tool_version_file", + srcs = [], +) + +# Stable Orchestrion aliases select package-local empty targets by default and +# only reference the real tool repository when the public :enabled flag is set. +# go_context_data also checks :enabled before consuming these files. alias( name = "tool_binary", - actual = "@rules_go_orchestrion_tool//:orchestrion", + actual = select({ + ":enabled_config": "@rules_go_orchestrion_tool//:orchestrion", + "//conditions:default": ":disabled_tool_binary", + }), visibility = ["//visibility:public"], ) alias( name = "dd_trace_go_version_file", - actual = "@rules_go_orchestrion_tool//:dd_trace_go_version_file", + actual = select({ + ":enabled_config": "@rules_go_orchestrion_tool//:dd_trace_go_version_file", + "//conditions:default": ":disabled_dd_trace_go_version_file", + }), visibility = ["//visibility:public"], ) alias( name = "dd_trace_go_module_proxy_files", - actual = "@rules_go_orchestrion_tool//:dd_trace_go_module_proxy_files", + actual = select({ + ":enabled_config": "@rules_go_orchestrion_tool//:dd_trace_go_module_proxy_files", + "//conditions:default": ":disabled_dd_trace_go_module_proxy_files", + }), visibility = ["//visibility:public"], ) alias( name = "dd_trace_go_module_proxy_root_marker", - actual = "@rules_go_orchestrion_tool//:dd_trace_go_module_proxy_root_marker", + actual = select({ + ":enabled_config": "@rules_go_orchestrion_tool//:dd_trace_go_module_proxy_root_marker", + "//conditions:default": ":disabled_dd_trace_go_module_proxy_root_marker", + }), visibility = ["//visibility:public"], ) alias( name = "orchestrion_tool_version_file", - actual = "@rules_go_orchestrion_tool//:orchestrion_tool_version_file", + actual = select({ + ":enabled_config": "@rules_go_orchestrion_tool//:orchestrion_tool_version_file", + "//conditions:default": ":disabled_orchestrion_tool_version_file", + }), visibility = ["//visibility:public"], ) diff --git a/third_party/rules_go_orchestrion/patches/v0_60_0/base/0001-full-delta.patch b/third_party/rules_go_orchestrion/patches/v0_60_0/base/0001-full-delta.patch index bb288f1b..1628affe 100644 --- a/third_party/rules_go_orchestrion/patches/v0_60_0/base/0001-full-delta.patch +++ b/third_party/rules_go_orchestrion/patches/v0_60_0/base/0001-full-delta.patch @@ -1937,10 +1937,10 @@ index 64758f8..a6d95ca 100644 It is a common dependency of all Go targets.""", diff --git a/go/private/orchestrion/BUILD b/go/private/orchestrion/BUILD new file mode 100644 -index 0000000..dad028a +index 0000000..31a01c3 --- /dev/null +++ b/go/private/orchestrion/BUILD -@@ -0,0 +1,72 @@ +@@ -0,0 +1,115 @@ +load("@bazel_skylib//:bzl_library.bzl", "bzl_library") +load("@bazel_skylib//rules:common_settings.bzl", "bool_flag", "string_flag") + @@ -1979,38 +1979,81 @@ index 0000000..dad028a + visibility = ["//visibility:public"], +) + -+# Proxy target for the orchestrion tool binary. -+# This always points to the rules_go_orchestrion_tool repo. -+# The repo provides an empty filegroup by default, or the actual orchestrion -+# binary when configured via module extension. -+# go_context_data checks the :enabled flag to determine whether to use this. ++config_setting( ++ name = "enabled_config", ++ flag_values = {":enabled": "true"}, ++) ++ ++filegroup( ++ name = "disabled_tool_binary", ++ srcs = [], ++) ++ ++filegroup( ++ name = "disabled_dd_trace_go_version_file", ++ srcs = [], ++) ++ ++filegroup( ++ name = "disabled_dd_trace_go_module_proxy_files", ++ srcs = [], ++) ++ ++filegroup( ++ name = "disabled_dd_trace_go_module_proxy_root_marker", ++ srcs = [], ++) ++ ++filegroup( ++ name = "disabled_orchestrion_tool_version_file", ++ srcs = [], ++) ++ ++# Stable Orchestrion aliases select package-local empty targets by default and ++# only reference the real tool repository when the public :enabled flag is set. ++# go_context_data also checks :enabled before consuming these files. +alias( + name = "tool_binary", -+ actual = "@rules_go_orchestrion_tool//:orchestrion", ++ actual = select({ ++ ":enabled_config": "@rules_go_orchestrion_tool//:orchestrion", ++ "//conditions:default": ":disabled_tool_binary", ++ }), + visibility = ["//visibility:public"], +) + +alias( + name = "dd_trace_go_version_file", -+ actual = "@rules_go_orchestrion_tool//:dd_trace_go_version_file", ++ actual = select({ ++ ":enabled_config": "@rules_go_orchestrion_tool//:dd_trace_go_version_file", ++ "//conditions:default": ":disabled_dd_trace_go_version_file", ++ }), + visibility = ["//visibility:public"], +) + +alias( + name = "dd_trace_go_module_proxy_files", -+ actual = "@rules_go_orchestrion_tool//:dd_trace_go_module_proxy_files", ++ actual = select({ ++ ":enabled_config": "@rules_go_orchestrion_tool//:dd_trace_go_module_proxy_files", ++ "//conditions:default": ":disabled_dd_trace_go_module_proxy_files", ++ }), + visibility = ["//visibility:public"], +) + +alias( + name = "dd_trace_go_module_proxy_root_marker", -+ actual = "@rules_go_orchestrion_tool//:dd_trace_go_module_proxy_root_marker", ++ actual = select({ ++ ":enabled_config": "@rules_go_orchestrion_tool//:dd_trace_go_module_proxy_root_marker", ++ "//conditions:default": ":disabled_dd_trace_go_module_proxy_root_marker", ++ }), + visibility = ["//visibility:public"], +) + +alias( + name = "orchestrion_tool_version_file", -+ actual = "@rules_go_orchestrion_tool//:orchestrion_tool_version_file", ++ actual = select({ ++ ":enabled_config": "@rules_go_orchestrion_tool//:orchestrion_tool_version_file", ++ "//conditions:default": ":disabled_orchestrion_tool_version_file", ++ }), + visibility = ["//visibility:public"], +) diff --git a/go/private/orchestrion/extensions.bzl b/go/private/orchestrion/extensions.bzl diff --git a/third_party/rules_go_orchestrion/patches/v0_61_1/base/0001-full-delta.patch b/third_party/rules_go_orchestrion/patches/v0_61_1/base/0001-full-delta.patch index 8fced3bc..7a4e1047 100644 --- a/third_party/rules_go_orchestrion/patches/v0_61_1/base/0001-full-delta.patch +++ b/third_party/rules_go_orchestrion/patches/v0_61_1/base/0001-full-delta.patch @@ -1937,10 +1937,10 @@ index 049b8ec..97b5629 100644 It is a common dependency of all Go targets.""", diff --git a/go/private/orchestrion/BUILD b/go/private/orchestrion/BUILD new file mode 100644 -index 0000000..488d4ad +index 0000000..177eeaf --- /dev/null +++ b/go/private/orchestrion/BUILD -@@ -0,0 +1,65 @@ +@@ -0,0 +1,108 @@ +load("@bazel_skylib//rules:common_settings.bzl", "bool_flag", "string_flag") + +filegroup( @@ -1972,38 +1972,81 @@ index 0000000..488d4ad + visibility = ["//visibility:public"], +) + -+# Proxy target for the orchestrion tool binary. -+# This always points to the rules_go_orchestrion_tool repo. -+# The repo provides an empty filegroup by default, or the actual orchestrion -+# binary when configured via module extension. -+# go_context_data checks the :enabled flag to determine whether to use this. ++config_setting( ++ name = "enabled_config", ++ flag_values = {":enabled": "true"}, ++) ++ ++filegroup( ++ name = "disabled_tool_binary", ++ srcs = [], ++) ++ ++filegroup( ++ name = "disabled_dd_trace_go_version_file", ++ srcs = [], ++) ++ ++filegroup( ++ name = "disabled_dd_trace_go_module_proxy_files", ++ srcs = [], ++) ++ ++filegroup( ++ name = "disabled_dd_trace_go_module_proxy_root_marker", ++ srcs = [], ++) ++ ++filegroup( ++ name = "disabled_orchestrion_tool_version_file", ++ srcs = [], ++) ++ ++# Stable Orchestrion aliases select package-local empty targets by default and ++# only reference the real tool repository when the public :enabled flag is set. ++# go_context_data also checks :enabled before consuming these files. +alias( + name = "tool_binary", -+ actual = "@rules_go_orchestrion_tool//:orchestrion", ++ actual = select({ ++ ":enabled_config": "@rules_go_orchestrion_tool//:orchestrion", ++ "//conditions:default": ":disabled_tool_binary", ++ }), + visibility = ["//visibility:public"], +) + +alias( + name = "dd_trace_go_version_file", -+ actual = "@rules_go_orchestrion_tool//:dd_trace_go_version_file", ++ actual = select({ ++ ":enabled_config": "@rules_go_orchestrion_tool//:dd_trace_go_version_file", ++ "//conditions:default": ":disabled_dd_trace_go_version_file", ++ }), + visibility = ["//visibility:public"], +) + +alias( + name = "dd_trace_go_module_proxy_files", -+ actual = "@rules_go_orchestrion_tool//:dd_trace_go_module_proxy_files", ++ actual = select({ ++ ":enabled_config": "@rules_go_orchestrion_tool//:dd_trace_go_module_proxy_files", ++ "//conditions:default": ":disabled_dd_trace_go_module_proxy_files", ++ }), + visibility = ["//visibility:public"], +) + +alias( + name = "dd_trace_go_module_proxy_root_marker", -+ actual = "@rules_go_orchestrion_tool//:dd_trace_go_module_proxy_root_marker", ++ actual = select({ ++ ":enabled_config": "@rules_go_orchestrion_tool//:dd_trace_go_module_proxy_root_marker", ++ "//conditions:default": ":disabled_dd_trace_go_module_proxy_root_marker", ++ }), + visibility = ["//visibility:public"], +) + +alias( + name = "orchestrion_tool_version_file", -+ actual = "@rules_go_orchestrion_tool//:orchestrion_tool_version_file", ++ actual = select({ ++ ":enabled_config": "@rules_go_orchestrion_tool//:orchestrion_tool_version_file", ++ "//conditions:default": ":disabled_orchestrion_tool_version_file", ++ }), + visibility = ["//visibility:public"], +) diff --git a/go/private/orchestrion/extensions.bzl b/go/private/orchestrion/extensions.bzl diff --git a/tools/agent-skills/go-test-optimization-onboarding/references/bzlmod-onboarding.md b/tools/agent-skills/go-test-optimization-onboarding/references/bzlmod-onboarding.md index d5fbe906..14b577c8 100644 --- a/tools/agent-skills/go-test-optimization-onboarding/references/bzlmod-onboarding.md +++ b/tools/agent-skills/go-test-optimization-onboarding/references/bzlmod-onboarding.md @@ -34,6 +34,17 @@ git_override( bazel_dep(name = "rules_go", version = "0.60.0") ``` +Add these lines to the named config used by test, doctor, and uploader: + +```text +common:test-optimization --repo_env=DD_TEST_OPTIMIZATION_ENABLED=1 +build:test-optimization --@rules_go//go/private/orchestrion:enabled=true +``` + +The config is the only user-facing switch. Removing +`--config=test-optimization` disables metadata fetching and selects the local +empty Orchestrion aliases; no second Test Optimization flag is required. + Use a commit that is reachable from `origin/main`. Do not publish branch-only commits in consumer snippets because squash merges can make them disappear. The `rules_go` version must match the selected Datadog-managed fork support diff --git a/tools/agent-skills/go-test-optimization-onboarding/references/workspace-onboarding.md b/tools/agent-skills/go-test-optimization-onboarding/references/workspace-onboarding.md index f9895a7b..3f71c6c2 100644 --- a/tools/agent-skills/go-test-optimization-onboarding/references/workspace-onboarding.md +++ b/tools/agent-skills/go-test-optimization-onboarding/references/workspace-onboarding.md @@ -81,6 +81,16 @@ currently `v0_60_0`, which preserves the existing Use the repository's existing `rules_go` repo name when it is already established. The Go companion maps its `@rules_go` dependency to that name. +Add these lines to the named config used by test, doctor, and uploader: + +```text +common:test-optimization --repo_env=DD_TEST_OPTIMIZATION_ENABLED=1 +build:test-optimization --@io_bazel_rules_go//go/private/orchestrion:enabled=true +``` + +`--config=test-optimization` is the only user-facing switch. Omitting it +renders disabled metadata stubs and selects the local empty Orchestrion aliases. + If the repository uses a non-default fetch model, set it explicitly: ```bzl @@ -223,31 +233,33 @@ rewriting unrelated dependencies. ## Sync And Orchestrion Setup Declare the Orchestrion tool repository and Test Optimization sync repository -near the repository's existing Go toolchain wiring: +through the reusable Go companion helpers near the repository's existing Go +toolchain wiring: ```bzl -load("@io_bazel_rules_go//go:orchestrion_workspace.bzl", "go_orchestrion_tool_repo") -load("@datadog-rules-test-optimization//tools/core:test_optimization_sync.bzl", "test_optimization_sync") +load("@datadog-rules-test-optimization-go//:topt_go_orchestrion_repository.bzl", "dd_topt_go_orchestrion_tool_repo") +load("@datadog-rules-test-optimization-go//:topt_go_workspace.bzl", "dd_topt_go_workspace_sync_repositories") -go_orchestrion_tool_repo( +dd_topt_go_orchestrion_tool_repo( dd_trace_go_version = "v2.9.0", version = "v1.9.0", ) -test_optimization_sync( +dd_topt_go_workspace_sync_repositories( name = "test_optimization_data_", debug = True, + enabled_by_env = True, require_git_metadata = True, - runtime_module_path = "", - runtime_name = "go", + module_path = "", runtime_version = "", service = "", ) ``` -Use the actual `rules_go` repository name in the `go_orchestrion_tool_repo` -load. For example, if the repository maps rules_go to `io_bazel_rules_go`, load -from `@io_bazel_rules_go//go:orchestrion_workspace.bzl`. +The helper loads the public Orchestrion repository API through the Go +companion's repository mapping, so consumers do not load it from their apparent +`rules_go` repository directly. The apparent repository name still belongs in +the `.bazelrc` analysis-time flag shown above. If the repository already defines a Go version constant for Bazel toolchains, reuse that constant for `runtime_version` instead of hardcoding another copy. diff --git a/tools/agent-skills/python-test-optimization-onboarding/references/bzlmod-onboarding.md b/tools/agent-skills/python-test-optimization-onboarding/references/bzlmod-onboarding.md index 8346c7a7..95501185 100644 --- a/tools/agent-skills/python-test-optimization-onboarding/references/bzlmod-onboarding.md +++ b/tools/agent-skills/python-test-optimization-onboarding/references/bzlmod-onboarding.md @@ -50,11 +50,20 @@ topt.test_optimization_sync( service = "", runtime_name = "python", runtime_version = "", + enabled_by_env = True, ) use_repo(topt, "test_optimization_data") ``` +Enable this sync only through the named Bazel config: + +```text +common:test-optimization --repo_env=DD_TEST_OPTIMIZATION_ENABLED=1 +``` + +Do not add a `rules_go` Orchestrion flag to a Python-only consumer. + ## Doctor And Uploader Targets Add one logical doctor/uploader pair. In monorepos, prefer a lightweight package diff --git a/tools/agent-skills/python-test-optimization-onboarding/references/consumer-runner.md b/tools/agent-skills/python-test-optimization-onboarding/references/consumer-runner.md index 1896deb2..f74d07a0 100644 --- a/tools/agent-skills/python-test-optimization-onboarding/references/consumer-runner.md +++ b/tools/agent-skills/python-test-optimization-onboarding/references/consumer-runner.md @@ -55,8 +55,10 @@ dd_topt_py_test( ) ``` -Prefer `module_identifier` in consumer-runner mode because the Datadog macro -does not need to synthesize Python imports to infer the module. +When the runtime module path and Bazel package path identify the test, omit +`module_identifier` and use the derived fallback. Keep an explicit +`module_identifier` only for a documented repository-specific exception; the +Datadog macro does not need to synthesize Python imports for the normal path. ## Validation diff --git a/tools/agent-skills/python-test-optimization-onboarding/references/workspace-onboarding.md b/tools/agent-skills/python-test-optimization-onboarding/references/workspace-onboarding.md index 9d94b61e..fad4e3f0 100644 --- a/tools/agent-skills/python-test-optimization-onboarding/references/workspace-onboarding.md +++ b/tools/agent-skills/python-test-optimization-onboarding/references/workspace-onboarding.md @@ -139,9 +139,18 @@ test_optimization_sync( service = "", runtime_name = "python", runtime_version = "3.12", + enabled_by_env = True, ) ``` +Enable this sync only through the named Bazel config: + +```text +common:test-optimization --repo_env=DD_TEST_OPTIMIZATION_ENABLED=1 +``` + +Do not add a `rules_go` Orchestrion flag to a Python-only consumer. + Add one logical doctor/uploader pair. Prefer a lightweight package in monorepos: ```bzl diff --git a/tools/agent-skills/rules-go-orchestrion-upstream-migration/SKILL.md b/tools/agent-skills/rules-go-orchestrion-upstream-migration/SKILL.md index 9b1aa375..0e267457 100644 --- a/tools/agent-skills/rules-go-orchestrion-upstream-migration/SKILL.md +++ b/tools/agent-skills/rules-go-orchestrion-upstream-migration/SKILL.md @@ -129,6 +129,21 @@ A migration is done only when all of these are true: - The final report names the target upstream, changed-path counts, validation results, and any remaining external blockers. +## Test Optimization Alias Contract + +When validating a consumer that uses Test Optimization, preserve the stable +alias contract from the vendored base tree: + +```bazelrc +common:test-optimization --repo_env=DD_TEST_OPTIMIZATION_ENABLED=1 +build:test-optimization --@rules_go//go/private/orchestrion:enabled=true +``` + +`--config=test-optimization` is the only user-facing switch. Omitting it must +leave metadata bootstrap disabled for `enabled_by_env = True` repositories and +select local empty Orchestrion aliases. Do not add a consumer-local duplicate +bool flag or collapse the real and empty repository rules. + ## Stop Conditions Stop and escalate instead of guessing when: diff --git a/tools/core/test_optimization_multi_sync.bzl b/tools/core/test_optimization_multi_sync.bzl index 535f409e..d4d5f420 100644 --- a/tools/core/test_optimization_multi_sync.bzl +++ b/tools/core/test_optimization_multi_sync.bzl @@ -59,6 +59,54 @@ def _compute_repo_names(base_name, service_keys): repo_names.append("%s_%s" % (base_name, key)) return repo_names +def _build_multi_sync_repo_specs( + name, + services, + runtime_name = "", + runtime_version = "", + runtime_arch = "", + runtime_module_path = "", + out_dir = ".testoptimization", + http_connect_timeout_seconds = -1, + http_max_time_seconds = -1, + http_retry_attempts = -1, + http_retry_delay_seconds = -1, + http_execute_timeout_buffer_seconds = -1, + known_tests = True, + test_management = True, + flaky_tests = True, + enabled = True, + enabled_by_env = False, + require_git_metadata = False, + debug = False): + """Build complete per-service sync specs for a multi-service tag.""" + service_keys = _compute_service_keys(services) + repo_names = _compute_repo_names(name, service_keys) + specs = [] + for i in range(len(services)): + specs.append({ + "repo": repo_names[i], + "service": services[i], + "out_dir": out_dir, + "runtime_name": runtime_name, + "runtime_version": runtime_version, + "runtime_arch": runtime_arch, + "runtime_module_path": runtime_module_path, + "http_connect_timeout_seconds": http_connect_timeout_seconds, + "http_max_time_seconds": http_max_time_seconds, + "http_retry_attempts": http_retry_attempts, + "http_retry_delay_seconds": http_retry_delay_seconds, + "http_execute_timeout_buffer_seconds": http_execute_timeout_buffer_seconds, + "known_tests": known_tests, + "test_management": test_management, + "flaky_tests": flaky_tests, + "enabled": enabled, + "enabled_by_env": enabled_by_env, + "require_git_metadata": require_git_metadata, + "debug": debug, + }) + return specs + def _record_multi_repo_owner_or_fail(seen_repo_owners, repo_name, owner): """Record generated repo owner and fail when names collide.""" prev_owner = seen_repo_owners.get(repo_name) @@ -159,6 +207,7 @@ def _render_aggregate_export_bzl(): # Public aliases for unit tests. compute_multi_service_keys_for_tests = _compute_service_keys compute_multi_repo_names_for_tests = _compute_repo_names +build_multi_sync_repo_specs_for_tests = _build_multi_sync_repo_specs render_multi_aggregate_bzl_for_tests = _render_aggregate_bzl render_multi_aggregate_export_bzl_for_tests = _render_aggregate_export_bzl record_multi_repo_owner_or_fail_for_tests = _record_multi_repo_owner_or_fail @@ -227,12 +276,27 @@ def _test_optimization_multi_sync_extension_impl(module_ctx): # Repo names are positional with keys/services; keep list order intact. per_repo_names = _compute_repo_names(name, keys) - service_repo_entries = [] - for i in range(len(services)): - service_repo_entries.append({ - "service": services[i], - "repo": per_repo_names[i], - }) + service_repo_entries = _build_multi_sync_repo_specs( + name = name, + services = services, + runtime_name = call.runtime_name, + runtime_version = call.runtime_version, + runtime_arch = call.runtime_arch, + runtime_module_path = call.runtime_module_path, + out_dir = call.out_dir, + http_connect_timeout_seconds = call.http_connect_timeout_seconds, + http_max_time_seconds = call.http_max_time_seconds, + http_retry_attempts = call.http_retry_attempts, + http_retry_delay_seconds = call.http_retry_delay_seconds, + http_execute_timeout_buffer_seconds = call.http_execute_timeout_buffer_seconds, + known_tests = call.known_tests, + test_management = call.test_management, + flaky_tests = call.flaky_tests, + enabled = call.enabled, + enabled_by_env = call.enabled_by_env, + require_git_metadata = call.require_git_metadata, + debug = call.debug, + ) # Validate generated repository names before instantiating any repos. for repo_name in [name] + per_repo_names: @@ -240,7 +304,6 @@ def _test_optimization_multi_sync_extension_impl(module_ctx): # Phase 2: materialize one sync repository per original service. for entry in service_repo_entries: - svc = entry["service"] repo = entry["repo"] # Preserve original service string for API requests; only keys @@ -248,24 +311,26 @@ def _test_optimization_multi_sync_extension_impl(module_ctx): test_optimization_sync( name = repo, repo_name = repo, - out_dir = call.out_dir, # optional; if set, used per-repo - service = svc, # override/DD_SERVICE for this service - runtime_name = call.runtime_name, - runtime_version = call.runtime_version, - runtime_arch = call.runtime_arch, - runtime_module_path = call.runtime_module_path, + out_dir = entry["out_dir"], # optional; if set, used per-repo + service = entry["service"], # override/DD_SERVICE for this service + runtime_name = entry["runtime_name"], + runtime_version = entry["runtime_version"], + runtime_arch = entry["runtime_arch"], + runtime_module_path = entry["runtime_module_path"], # Optional HTTP timeout/retry policy overrides. # Use -1 to keep default/env behavior. - http_connect_timeout_seconds = call.http_connect_timeout_seconds, - http_max_time_seconds = call.http_max_time_seconds, - http_retry_attempts = call.http_retry_attempts, - http_retry_delay_seconds = call.http_retry_delay_seconds, - http_execute_timeout_buffer_seconds = call.http_execute_timeout_buffer_seconds, - known_tests = call.known_tests, - test_management = call.test_management, - flaky_tests = call.flaky_tests, - require_git_metadata = call.require_git_metadata, - debug = call.debug, + http_connect_timeout_seconds = entry["http_connect_timeout_seconds"], + http_max_time_seconds = entry["http_max_time_seconds"], + http_retry_attempts = entry["http_retry_attempts"], + http_retry_delay_seconds = entry["http_retry_delay_seconds"], + http_execute_timeout_buffer_seconds = entry["http_execute_timeout_buffer_seconds"], + known_tests = entry["known_tests"], + test_management = entry["test_management"], + flaky_tests = entry["flaky_tests"], + enabled = entry["enabled"], + enabled_by_env = entry["enabled_by_env"], + require_git_metadata = entry["require_git_metadata"], + debug = entry["debug"], ) # Phase 3: publish one aggregate repo exposing stable aliases. @@ -300,6 +365,8 @@ test_optimization_multi_sync_extension = module_extension( "known_tests": attr.bool(default = True), "test_management": attr.bool(default = True), "flaky_tests": attr.bool(default = True), + "enabled": attr.bool(default = True), + "enabled_by_env": attr.bool(default = False), "require_git_metadata": attr.bool(default = False), "debug": attr.bool(default = False), }), diff --git a/tools/core/test_optimization_sync.bzl b/tools/core/test_optimization_sync.bzl index 4b293e59..a0934938 100644 --- a/tools/core/test_optimization_sync.bzl +++ b/tools/core/test_optimization_sync.bzl @@ -81,6 +81,7 @@ load( _first_env_from_environ = "first_env_from_environ", _normalize_env_data = "normalize_env_data", _normalize_ref = "normalize_ref", + _resolve_service_and_environment = "resolve_service_and_environment", _sanitize_repository_url = "sanitize_repository_url", _set_context_tag_from_env = "set_context_tag_from_env", ) @@ -112,6 +113,44 @@ HTTP_EXECUTE_TIMEOUT_SECONDS = ( # Sentinel value used by optional integer attrs where 0 can be meaningful. HTTP_POLICY_ATTR_UNSET = -1 +_TEST_OPTIMIZATION_ENABLED_VALUES = ["1", "true", "yes", "on"] + +def _is_test_optimization_enabled(enabled, enabled_by_env, env_value): + """Resolve repository bootstrap enablement from attrs and repo environment.""" + if not enabled: + return False + if not enabled_by_env: + return True + return (env_value or "").strip().lower() in _TEST_OPTIMIZATION_ENABLED_VALUES + +_TEST_OPTIMIZATION_REPOSITORY_BASE_ENVIRON = [ + "DD_API_KEY", + "DD_SITE", + "DD_TEST_OPTIMIZATION_AGENTLESS_URL", + "DD_TEST_OPTIMIZATION_HTTP_CONNECT_TIMEOUT_SECONDS", + "DD_TEST_OPTIMIZATION_HTTP_MAX_TIME_SECONDS", + "DD_TEST_OPTIMIZATION_HTTP_RETRY_ATTEMPTS", + "DD_TEST_OPTIMIZATION_HTTP_RETRY_DELAY_SECONDS", + "DD_TEST_OPTIMIZATION_HTTP_EXECUTE_TIMEOUT_BUFFER_SECONDS", + "FETCH_SALT", + "GIT_DIRTY", + "GO_MODULE_PATH", + "PYTHON_MODULE_PATH", + "JAVA_MODULE_PATH", + "NODEJS_MODULE_PATH", + "DOTNET_MODULE_PATH", + "RUBY_MODULE_PATH", + "OS", + "ComSpec", + "COMSPEC", + "PROCESSOR_ARCHITECTURE", + "PROCESSOR_ARCHITEW6432", +] + +_TEST_OPTIMIZATION_REPOSITORY_ENVIRON = _TEST_OPTIMIZATION_REPOSITORY_BASE_ENVIRON + _ALL_SYNC_ENV_KEYS + [ + "DD_TEST_OPTIMIZATION_ENABLED", +] + # ########################################################################## # Tools functions # ########################################################################## @@ -1288,6 +1327,215 @@ def _render_module_runfiles_bzl(repo_name, manifest_root): ")\n" ) +def _render_disabled_context_json(repo_name, out_dir, env_data, runtime_name = "", runtime_version = "", runtime_arch = ""): + """Render the minimal context contract without local repository probing.""" + tags = { + "service.name": env_data.get("service") or "unnamed-service", + "env": env_data.get("environment") or "CI", + "bazel.rule_name": TEST_BAZEL_RULE_NAME, + "bazel.rule_version": TEST_BAZEL_RULE_VERSION, + "topt.sync.enabled": False, + "topt.sync.repository_name": repo_name or "", + "topt.sync.out_dir": out_dir or TEST_OPT_DIR, + } + if runtime_name: + tags["runtime.name"] = runtime_name + if runtime_version: + tags["runtime.version"] = runtime_version + if runtime_arch: + tags["runtime.architecture"] = runtime_arch + git_tags = { + "repository_url": "git.repository_url", + "branch": "git.branch", + "tag": "git.tag", + "sha": "git.commit.sha", + "head_sha": "git.commit.head.sha", + "commit_message": "git.commit.message", + "head_message": "git.commit.head.message", + "commit_author_name": "git.commit.author.name", + "commit_author_email": "git.commit.author.email", + "commit_author_date": "git.commit.author.date", + "commit_committer_name": "git.commit.committer.name", + "commit_committer_email": "git.commit.committer.email", + "commit_committer_date": "git.commit.committer.date", + "head_author_name": "git.commit.head.author.name", + "head_author_email": "git.commit.head.author.email", + "head_author_date": "git.commit.head.author.date", + "head_committer_name": "git.commit.head.committer.name", + "head_committer_email": "git.commit.head.committer.email", + "head_committer_date": "git.commit.head.committer.date", + "pr_base_branch": "git.pull_request.base_branch", + "pr_base_branch_sha": "git.pull_request.base_branch_sha", + "pr_base_branch_head_sha": "git.pull_request.base_branch_head_sha", + "pr_number": "pr.number", + } + for source_key, tag_key in git_tags.items(): + if env_data.get(source_key): + tags[tag_key] = env_data.get(source_key) + return json.encode(tags) + "\n" + +def _render_disabled_telemetry_facts_json(service_name, runtime_name, environment): + """Render the single disabled-sync telemetry fact.""" + facts = _new_telemetry_facts( + service_name, + runtime_name = runtime_name or "", + env = environment or "", + ) + _append_telemetry_count(facts, "sync.disabled") + return json.encode(facts) + "\n" + +def _render_disabled_export(repo_name, service, runtime_name, runtime_module_path, out_dir): + """Render a disabled export using the same schema as a live sync repo.""" + sanitized_module_path = sanitize_label_fragment(runtime_module_path) if runtime_module_path else "" + values = { + "go_module_path": "", + "sanitized_go_module_path": "", + "python_module_path": "", + "sanitized_python_module_path": "", + "java_module_path": "", + "sanitized_java_module_path": "", + "nodejs_module_path": "", + "sanitized_nodejs_module_path": "", + "dotnet_module_path": "", + "sanitized_dotnet_module_path": "", + "ruby_module_path": "", + "sanitized_ruby_module_path": "", + } + if runtime_name == "go": + values["go_module_path"] = runtime_module_path or "" + values["sanitized_go_module_path"] = sanitized_module_path + elif runtime_name == "python": + values["python_module_path"] = runtime_module_path or "" + values["sanitized_python_module_path"] = sanitized_module_path + elif runtime_name == "java": + values["java_module_path"] = runtime_module_path or "" + values["sanitized_java_module_path"] = sanitized_module_path + elif runtime_name == "nodejs": + values["nodejs_module_path"] = runtime_module_path or "" + values["sanitized_nodejs_module_path"] = sanitized_module_path + elif runtime_name == "dotnet": + values["dotnet_module_path"] = runtime_module_path or "" + values["sanitized_dotnet_module_path"] = sanitized_module_path + elif runtime_name == "ruby": + values["ruby_module_path"] = runtime_module_path or "" + values["sanitized_ruby_module_path"] = sanitized_module_path + return _render_export_bzl( + repo_name = repo_name, + service_name = service, + labels = [], + set_literal = "{}", + manifest_file = "%s/manifest.txt" % out_dir, + go_module_path = values["go_module_path"], + sanitized_go_module_path = values["sanitized_go_module_path"], + go_module_included = False, + python_module_path = values["python_module_path"], + sanitized_python_module_path = values["sanitized_python_module_path"], + python_module_included = False, + java_module_path = values["java_module_path"], + sanitized_java_module_path = values["sanitized_java_module_path"], + java_module_included = False, + nodejs_module_path = values["nodejs_module_path"], + sanitized_nodejs_module_path = values["sanitized_nodejs_module_path"], + nodejs_module_included = False, + dotnet_module_path = values["dotnet_module_path"], + sanitized_dotnet_module_path = values["sanitized_dotnet_module_path"], + dotnet_module_included = False, + ruby_module_path = values["ruby_module_path"], + sanitized_ruby_module_path = values["sanitized_ruby_module_path"], + ruby_module_included = False, + ) + +def _render_disabled_build(settings_file, manifest_file, known_tests_file, test_management_file, flaky_tests_file, context_file, telemetry_facts_file): + """Render public disabled targets with the same file labels as live repos.""" + return ( + 'load(":module_runfiles.bzl", "topt_module_files")\n\n' + + "filegroup(\n" + + ' name = "test_optimization_files",\n' + + " srcs = %s,\n" % repr([settings_file, manifest_file, known_tests_file, test_management_file, flaky_tests_file]) + + ' visibility = ["//visibility:public"],\n' + + ")\n\n" + + "filegroup(\n" + + ' name = "test_optimization_context",\n' + + " srcs = %s,\n" % repr([context_file, telemetry_facts_file]) + + ' visibility = ["//visibility:public"],\n' + + ")\n\n" + + 'exports_files(["export.bzl", %s], visibility = ["//visibility:public"])\n' % repr(manifest_file) + ) + +def _explicit_runtime_module_path(ctx, runtime_name): + """Resolve only explicit module-path inputs for disabled exports.""" + if ctx.attr.runtime_module_path: + return ctx.attr.runtime_module_path + env_key_by_runtime = { + "go": "GO_MODULE_PATH", + "python": "PYTHON_MODULE_PATH", + "java": "JAVA_MODULE_PATH", + "nodejs": "NODEJS_MODULE_PATH", + "dotnet": "DOTNET_MODULE_PATH", + "ruby": "RUBY_MODULE_PATH", + } + return ctx.os.environ.get(env_key_by_runtime.get(runtime_name, "")) or "" + +def _write_disabled_repository(ctx, out_dir, env_data, debug): + """Write a complete no-fetch repository with stable public labels.""" + settings_file = "%s/cache/http/settings.json" % out_dir + known_tests_file = "%s/cache/http/known_tests.json" % out_dir + test_management_file = "%s/cache/http/test_management.json" % out_dir + flaky_tests_file = "%s/cache/http/flaky_tests.json" % out_dir + manifest_file = "%s/manifest.txt" % out_dir + context_file = "%s/context.json" % out_dir + telemetry_facts_file = "%s/telemetry_facts.json" % out_dir + for output_file in [settings_file, known_tests_file, test_management_file, flaky_tests_file, manifest_file, context_file, telemetry_facts_file]: + _ensure_parent_directory(ctx, output_file, debug) + + repo_name = getattr(ctx.attr, "repo_name", None) or ctx.name or "" + runtime_name = (ctx.attr.runtime_name or "").strip() + runtime_module_path = _explicit_runtime_module_path(ctx, runtime_name) + ctx.file(manifest_file, "version=1\n") + ctx.file( + settings_file, + '{"data": {"attributes": {"known_tests_enabled": false, "test_management": {"enabled": false}, "flaky_test_retries_enabled": false}}}\n', + ) + ctx.file(known_tests_file, '{"data": {"attributes": {"tests": {}}}}\n') + ctx.file(test_management_file, '{"data": {"attributes": {"modules": {}}}}\n') + ctx.file(flaky_tests_file, '{"data": []}\n') + ctx.file( + context_file, + _render_disabled_context_json( + repo_name, + out_dir, + env_data, + runtime_name = runtime_name, + runtime_version = ctx.attr.runtime_version or "", + runtime_arch = ctx.attr.runtime_arch or "", + ), + ) + ctx.file( + telemetry_facts_file, + _render_disabled_telemetry_facts_json( + env_data.get("service") or "unnamed-service", + runtime_name, + env_data.get("environment") or "CI", + ), + ) + ctx.file("module_runfiles.bzl", _render_module_runfiles_bzl(ctx.name, _dirname(manifest_file))) + ctx.file( + "export.bzl", + _render_disabled_export(repo_name, env_data.get("service"), runtime_name, runtime_module_path, out_dir), + ) + ctx.file( + "BUILD", + _render_disabled_build( + settings_file, + manifest_file, + known_tests_file, + test_management_file, + flaky_tests_file, + context_file, + telemetry_facts_file, + ), + ) + def _partition_unix_headers(headers): """Split public headers from DD-API-KEY for secure Unix curl transport.""" public_headers = {} @@ -1565,6 +1813,13 @@ detect_runtime_module_path_for_tests = _detect_runtime_module_path dirname_for_tests = _dirname normalize_out_dir_or_fail_for_tests = _normalize_out_dir_or_fail render_export_bzl_for_tests = _render_export_bzl +render_disabled_context_json_for_tests = _render_disabled_context_json +render_disabled_telemetry_facts_json_for_tests = _render_disabled_telemetry_facts_json +render_disabled_export_for_tests = _render_disabled_export +render_disabled_build_for_tests = _render_disabled_build +is_test_optimization_enabled_for_tests = _is_test_optimization_enabled +repository_environ_for_tests = _TEST_OPTIMIZATION_REPOSITORY_ENVIRON +resolve_service_and_environment_for_tests = _resolve_service_and_environment fnv1a_32_for_tests = _fnv1a_32 clone_payload_with_detached_attributes_for_tests = _clone_payload_with_detached_attributes http_connect_timeout_seconds_for_tests = HTTP_CONNECT_TIMEOUT_SECONDS @@ -2264,6 +2519,28 @@ def _impl(ctx): log_info("Starting repository rule implementation") ctx.report_progress("test_optimization_sync: starting") + sync_enabled = _is_test_optimization_enabled( + ctx.attr.enabled, + ctx.attr.enabled_by_env, + ctx.os.environ.get("DD_TEST_OPTIMIZATION_ENABLED", ""), + ) + if not sync_enabled: + out_dir = _normalize_out_dir_or_fail(ctx.attr.out_dir or TEST_OPT_DIR) + resolved = _resolve_service_and_environment(ctx.attr.service, ctx.os.environ) + disabled_env_data = { + "service": resolved["service"], + "environment": resolved["environment"], + } + + # Only explicit DD_GIT_* values are allowed in disabled mode. This + # path deliberately never calls `_collect_env` or local Git helpers. + _apply_dd_git_overrides(disabled_env_data, ctx.os.environ) + _normalize_env_data(disabled_env_data) + _write_disabled_repository(ctx, out_dir, disabled_env_data, debug) + log_info("Test Optimization disabled; wrote no-fetch repository stubs") + ctx.report_progress("test_optimization_sync: disabled") + return + # ------------------------------------------------------------------ # Phase 1: Resolve/validate required inputs and output paths. # ------------------------------------------------------------------ @@ -2819,38 +3096,12 @@ test_optimization_sync = repository_rule( "known_tests": attr.bool(default = True), "test_management": attr.bool(default = True), "flaky_tests": attr.bool(default = True), + "enabled": attr.bool(default = True), + "enabled_by_env": attr.bool(default = False), "require_git_metadata": attr.bool(default = False), "debug": attr.bool(default = False), # Toggle verbose debug logging }, - environ = [ - # Keep this list intentionally broad: every env var that can influence - # generated outputs must be declared so Bazel cache keys stay correct. - # Environment variables treated as rule inputs - "DD_API_KEY", # Required: Datadog API key for authentication - "DD_SITE", # Optional: Datadog site; ex: app.datadoghq.com, datadoghq.eu - "DD_TEST_OPTIMIZATION_AGENTLESS_URL", # Optional: override Datadog API base URL (test/dev) - "DD_TEST_OPTIMIZATION_HTTP_CONNECT_TIMEOUT_SECONDS", # Optional: override connect timeout - "DD_TEST_OPTIMIZATION_HTTP_MAX_TIME_SECONDS", # Optional: override request max time - "DD_TEST_OPTIMIZATION_HTTP_RETRY_ATTEMPTS", # Optional: override retry attempts - "DD_TEST_OPTIMIZATION_HTTP_RETRY_DELAY_SECONDS", # Optional: override retry delay - "DD_TEST_OPTIMIZATION_HTTP_EXECUTE_TIMEOUT_BUFFER_SECONDS", # Optional: override outer timeout buffer - "FETCH_SALT", # Optional: cache-busting salt to force re-fetch - "GIT_DIRTY", # Optional: working tree state; triggers refetch on change - "GO_MODULE_PATH", # Optional: explicit Go module path override for export.bzl - "PYTHON_MODULE_PATH", # Optional: explicit Python module path override for export.bzl - "JAVA_MODULE_PATH", # Optional: explicit Java module path override for export.bzl - "NODEJS_MODULE_PATH", # Optional: explicit NodeJS module path override for export.bzl - "DOTNET_MODULE_PATH", # Optional: explicit Dotnet module path override for export.bzl - "RUBY_MODULE_PATH", # Optional: explicit Ruby module path override for export.bzl - # Host OS hints used for cross-platform behavior and request configuration - "OS", # Windows OS marker (used in _is_windows and _detect_os_info) - "ComSpec", # Windows command processor path - "COMSPEC", # Alternate casing for Windows command processor - "PROCESSOR_ARCHITECTURE", # Windows arch detection - "PROCESSOR_ARCHITEW6432", # Windows WOW64 arch detection - # Shared CI/git metadata inputs are centralized in test_optimization_sync_env.bzl - # so provider extraction and repository_rule cache keys cannot drift apart. - ] + _ALL_SYNC_ENV_KEYS, + environ = _TEST_OPTIMIZATION_REPOSITORY_ENVIRON, # Repository rules run during workspace/module resolution; keep local=True # so host tooling/env detection remains predictable across environments. local = True, @@ -2926,6 +3177,8 @@ def _test_optimization_sync_extension_impl(module_ctx): known_tests = test_optimization_call.known_tests, test_management = test_optimization_call.test_management, flaky_tests = test_optimization_call.flaky_tests, + enabled = test_optimization_call.enabled, + enabled_by_env = test_optimization_call.enabled_by_env, require_git_metadata = test_optimization_call.require_git_metadata, debug = call_debug, ) @@ -2956,6 +3209,8 @@ test_optimization_sync_extension = module_extension( "known_tests": attr.bool(default = True), "test_management": attr.bool(default = True), "flaky_tests": attr.bool(default = True), + "enabled": attr.bool(default = True), + "enabled_by_env": attr.bool(default = False), "require_git_metadata": attr.bool(default = False), "debug": attr.bool(default = False), }), diff --git a/tools/core/test_optimization_sync_env.bzl b/tools/core/test_optimization_sync_env.bzl index c420db1b..f2645cfb 100644 --- a/tools/core/test_optimization_sync_env.bzl +++ b/tools/core/test_optimization_sync_env.bzl @@ -384,12 +384,20 @@ def normalize_ref(name): break return name +def resolve_service_and_environment(attr_service, environ): + """Resolve service/environment without probing the local checkout.""" + return { + "service": attr_service or environ.get("DD_SERVICE") or "unnamed-service", + "environment": environ.get("DD_ENV") or "CI", + } + def _new_env_data(attr_service, environ): + resolved = resolve_service_and_environment(attr_service, environ) return { "dd_site": environ.get("DD_SITE") or "", "dd_api_base": environ.get("DD_TEST_OPTIMIZATION_AGENTLESS_URL") or "", - "service": (attr_service or environ.get("DD_SERVICE") or "unnamed-service"), - "environment": environ.get("DD_ENV") or "CI", + "service": resolved["service"], + "environment": resolved["environment"], "repository_url": "", "branch": "", "tag": "", diff --git a/tools/tests/core/BUILD.bazel b/tools/tests/core/BUILD.bazel index b4b31fcb..179649a6 100644 --- a/tools/tests/core/BUILD.bazel +++ b/tools/tests/core/BUILD.bazel @@ -55,6 +55,8 @@ load( ) load( "//tools/tests/core:test_multi_sync_utils.bzl", + "build_multi_sync_specs_deduplicates_sanitized_service_keys_test", + "build_multi_sync_specs_propagates_non_default_enablement_test", "compute_repo_names_test", "compute_service_keys_dedups_collisions_test", "compute_service_keys_edge_cases_test", @@ -82,6 +84,16 @@ load( "python_missing_rules_python_repo_failure_test", "python_missing_rules_python_repo_target_rule", ) +load( + "//tools/tests/core:test_sync_disabled_utils.bzl", + "disabled_context_contract_test", + "disabled_export_and_build_shape_test", + "disabled_telemetry_contract_test", + "enabled_false_values_test", + "enabled_truthy_values_test", + "repository_environment_contains_bootstrap_inputs_test", + "service_environment_resolution_test", +) load( "//tools/tests/core:test_sync_utils.bzl", "apply_dd_git_overrides_test", @@ -112,8 +124,8 @@ load( "decode_json_object_non_json_target_rule", "decode_json_object_valid_test", "dirname_test", - "example_stub_custom_out_dir_test", "example_stub_context_contains_doctor_metadata_test", + "example_stub_custom_out_dir_test", "example_stub_export_manifest_path_test", "example_stub_export_string_escaping_test", "example_stub_includes_manifest_in_files_test", @@ -619,6 +631,48 @@ module_label_map_flaky_modules_test( timeout = "short", ) +enabled_truthy_values_test( + name = "enabled_truthy_values_test", + size = "small", + timeout = "short", +) + +enabled_false_values_test( + name = "enabled_false_values_test", + size = "small", + timeout = "short", +) + +repository_environment_contains_bootstrap_inputs_test( + name = "repository_environment_contains_bootstrap_inputs_test", + size = "small", + timeout = "short", +) + +service_environment_resolution_test( + name = "service_environment_resolution_test", + size = "small", + timeout = "short", +) + +disabled_context_contract_test( + name = "disabled_context_contract_test", + size = "small", + timeout = "short", +) + +disabled_telemetry_contract_test( + name = "disabled_telemetry_contract_test", + size = "small", + timeout = "short", +) + +disabled_export_and_build_shape_test( + name = "disabled_export_and_build_shape_test", + size = "small", + timeout = "short", +) + normalize_ref_test( name = "normalize_ref_test", size = "small", @@ -1033,6 +1087,18 @@ validate_service_name_too_long_failure_test( target_under_test = ":validate_service_name_too_long_target", ) +build_multi_sync_specs_propagates_non_default_enablement_test( + name = "build_multi_sync_specs_propagates_non_default_enablement_test", + size = "small", + timeout = "short", +) + +build_multi_sync_specs_deduplicates_sanitized_service_keys_test( + name = "build_multi_sync_specs_deduplicates_sanitized_service_keys_test", + size = "small", + timeout = "short", +) + compute_service_keys_dedups_collisions_test( name = "compute_service_keys_dedups_collisions_test", size = "small", @@ -1443,6 +1509,8 @@ test_suite( ":archive_archive_specs_test", ":base_git_specs_test", ":bash_curl_retry_flags_test", + ":build_multi_sync_specs_deduplicates_sanitized_service_keys_test", + ":build_multi_sync_specs_propagates_non_default_enablement_test", ":clone_payload_with_detached_attributes_test", ":clone_payload_with_nested_structure_test", ":codeowners_compile_regex_edge_cases_test", @@ -1482,10 +1550,10 @@ test_suite( ":doctor_kwargs_test", ":duplicate_names_failure_test", ":empty_name_failure_test", + ":example_stub_context_contains_doctor_metadata_test", ":example_stub_custom_out_dir_test", ":example_stub_export_manifest_path_test", ":example_stub_export_string_escaping_test", - ":example_stub_context_contains_doctor_metadata_test", ":example_stub_includes_manifest_in_files_test", ":example_stub_manifest_content_matches_sync_contract_test", ":example_stub_module_targets_test", diff --git a/tools/tests/core/test_multi_sync_utils.bzl b/tools/tests/core/test_multi_sync_utils.bzl index 349df4f0..33424a2a 100644 --- a/tools/tests/core/test_multi_sync_utils.bzl +++ b/tools/tests/core/test_multi_sync_utils.bzl @@ -8,6 +8,7 @@ load("@bazel_skylib//lib:unittest.bzl", "analysistest", "asserts", "unittest") load( "//tools/core:test_optimization_multi_sync.bzl", + "build_multi_sync_repo_specs_for_tests", "compute_multi_repo_names_for_tests", "compute_multi_service_keys_for_tests", "record_multi_repo_owner_or_fail_for_tests", @@ -15,6 +16,50 @@ load( "render_multi_aggregate_export_bzl_for_tests", ) +def _build_multi_sync_specs_propagates_non_default_enablement_test(ctx): + env = unittest.begin(ctx) + specs = build_multi_sync_repo_specs_for_tests( + name = "test_optimization_data_go", + services = ["service-a", "service-b"], + runtime_name = "go", + runtime_version = "1.25.9", + runtime_module_path = "example.com/repo", + enabled = False, + enabled_by_env = True, + flaky_tests = False, + ) + asserts.equals(env, 2, len(specs)) + asserts.equals(env, False, specs[0]["enabled"]) + asserts.equals(env, True, specs[0]["enabled_by_env"]) + asserts.equals(env, False, specs[0]["flaky_tests"]) + asserts.equals(env, "test_optimization_data_go_service_a", specs[0]["repo"]) + asserts.equals(env, "service-a", specs[0]["service"]) + asserts.equals(env, "test_optimization_data_go_service_b", specs[1]["repo"]) + return unittest.end(env) + +def _build_multi_sync_specs_deduplicates_sanitized_service_keys_test(ctx): + env = unittest.begin(ctx) + specs = build_multi_sync_repo_specs_for_tests( + name = "test_optimization_data_go", + services = ["service-a", "service.a", "service_a"], + runtime_name = "go", + runtime_version = "1.25.9", + runtime_module_path = "example.com/repo", + enabled_by_env = True, + ) + repo_names = [spec["repo"] for spec in specs] + asserts.equals( + env, + [ + "test_optimization_data_go_service_a", + "test_optimization_data_go_service_a_2", + "test_optimization_data_go_service_a_3", + ], + repo_names, + ) + asserts.equals(env, 3, len({name: True for name in repo_names})) + return unittest.end(env) + def _compute_service_keys_dedups_collisions_test(ctx): """Validate sanitized-key collision deduping for multi-service inputs.""" env = unittest.begin(ctx) @@ -119,6 +164,8 @@ def _record_multi_repo_owner_duplicate_failure_test_impl(ctx): compute_service_keys_dedups_collisions_test = unittest.make(_compute_service_keys_dedups_collisions_test) compute_service_keys_edge_cases_test = unittest.make(_compute_service_keys_edge_cases_test) compute_repo_names_test = unittest.make(_compute_repo_names_test) +build_multi_sync_specs_propagates_non_default_enablement_test = unittest.make(_build_multi_sync_specs_propagates_non_default_enablement_test) +build_multi_sync_specs_deduplicates_sanitized_service_keys_test = unittest.make(_build_multi_sync_specs_deduplicates_sanitized_service_keys_test) record_multi_repo_owner_success_test = unittest.make(_record_multi_repo_owner_success_test) render_multi_aggregate_bzl_contains_expected_targets_test = unittest.make(_render_multi_aggregate_bzl_contains_expected_targets_test) render_multi_aggregate_export_bzl_reexports_mapping_test = unittest.make(_render_multi_aggregate_export_bzl_reexports_mapping_test) diff --git a/tools/tests/core/test_sync_disabled_utils.bzl b/tools/tests/core/test_sync_disabled_utils.bzl new file mode 100644 index 00000000..3fafc855 --- /dev/null +++ b/tools/tests/core/test_sync_disabled_utils.bzl @@ -0,0 +1,163 @@ +# Unless explicitly stated otherwise all files in this repository are licensed under +# the Apache 2.0 License. +# +# This product includes software developed at Datadog +# (https://www.datadoghq.com/) Copyright 2025-Present Datadog, Inc. + +"""Unit tests for disabled Test Optimization repository rendering.""" + +load("@bazel_skylib//lib:unittest.bzl", "asserts", "unittest") +load( + "//tools/core:test_optimization_sync.bzl", + "is_test_optimization_enabled_for_tests", + "render_disabled_build_for_tests", + "render_disabled_context_json_for_tests", + "render_disabled_export_for_tests", + "render_disabled_telemetry_facts_json_for_tests", + "repository_environ_for_tests", + "resolve_service_and_environment_for_tests", +) + +def _enabled_truthy_values_test(ctx): + env = unittest.begin(ctx) + for value in ["1", "true", "True", "TRUE", "yes", "Yes", "YES", "on", "ON"]: + asserts.true( + env, + is_test_optimization_enabled_for_tests(True, True, value), + "expected truthy value %s" % value, + ) + return unittest.end(env) + +def _enabled_false_values_test(ctx): + env = unittest.begin(ctx) + for value in ["", "0", "false", "False", "no", "off", "disabled"]: + asserts.false( + env, + is_test_optimization_enabled_for_tests(True, True, value), + "expected false value %s" % value, + ) + asserts.false(env, is_test_optimization_enabled_for_tests(False, False, "1")) + asserts.true(env, is_test_optimization_enabled_for_tests(True, False, "")) + return unittest.end(env) + +def _repository_environment_contains_bootstrap_inputs_test(ctx): + env = unittest.begin(ctx) + for key in [ + "DD_API_KEY", + "DD_TEST_OPTIMIZATION_HTTP_MAX_TIME_SECONDS", + "FETCH_SALT", + "GO_MODULE_PATH", + "OS", + "DD_GIT_COMMIT_SHA", + "DD_TEST_OPTIMIZATION_ENABLED", + ]: + asserts.true(env, key in repository_environ_for_tests, "missing repository environ key %s" % key) + return unittest.end(env) + +def _service_environment_resolution_test(ctx): + env = unittest.begin(ctx) + asserts.equals( + env, + {"service": "attr-service", "environment": "attr-env"}, + resolve_service_and_environment_for_tests("attr-service", {"DD_SERVICE": "env-service", "DD_ENV": "attr-env"}), + ) + asserts.equals( + env, + {"service": "env-service", "environment": "env"}, + resolve_service_and_environment_for_tests("", {"DD_SERVICE": "env-service", "DD_ENV": "env"}), + ) + asserts.equals( + env, + {"service": "unnamed-service", "environment": "CI"}, + resolve_service_and_environment_for_tests("", {}), + ) + return unittest.end(env) + +def _disabled_context_contract_test(ctx): + env = unittest.begin(ctx) + content = render_disabled_context_json_for_tests( + "test_optimization_data", + "custom_topt", + { + "service": "service-name", + "environment": "test", + "repository_url": "https://github.com/example/repo", + "sha": "abc123", + }, + runtime_name = "go", + runtime_version = "1.25.0", + runtime_arch = "amd64", + ) + decoded = json.decode(content) + asserts.equals(env, "service-name", decoded["service.name"]) + asserts.equals(env, "test", decoded["env"]) + asserts.equals(env, False, decoded["topt.sync.enabled"]) + asserts.equals(env, "test_optimization_data", decoded["topt.sync.repository_name"]) + asserts.equals(env, "custom_topt", decoded["topt.sync.out_dir"]) + asserts.equals(env, "go", decoded["runtime.name"]) + asserts.equals(env, "https://github.com/example/repo", decoded["git.repository_url"]) + asserts.equals(env, "abc123", decoded["git.commit.sha"]) + asserts.false(env, "topt.api_key_fingerprint" in decoded) + + without_runtime = json.decode(render_disabled_context_json_for_tests( + "test_optimization_data", + "custom_topt", + {"service": "service-name", "environment": "test"}, + )) + asserts.false(env, "runtime.name" in without_runtime) + asserts.false(env, "runtime.version" in without_runtime) + asserts.false(env, "runtime.architecture" in without_runtime) + return unittest.end(env) + +def _disabled_telemetry_contract_test(ctx): + env = unittest.begin(ctx) + decoded = json.decode(render_disabled_telemetry_facts_json_for_tests("service-name", "go", "CI")) + asserts.equals(env, 1, decoded["schema_version"]) + asserts.equals(env, "service-name", decoded["service_name"]) + asserts.equals(env, "go", decoded["runtime_name"]) + asserts.equals(env, [{"name": "sync.disabled", "value": 1, "tags": []}], decoded["counts"]) + asserts.equals(env, [], decoded["distributions"]) + return unittest.end(env) + +def _disabled_export_and_build_shape_test(ctx): + env = unittest.begin(ctx) + export = render_disabled_export_for_tests( + "test_optimization_data", + "go-service", + "go", + "example.com/repo", + "custom_topt", + ) + asserts.true(env, '"repo_name": "test_optimization_data"' in export) + asserts.true(env, '"service_name": "go-service"' in export) + asserts.true(env, '"manifest_path": "custom_topt/manifest.txt"' in export) + asserts.true(env, '"module_path": "example.com/repo"' in export) + + build = render_disabled_build_for_tests( + "custom_topt/cache/http/settings.json", + "custom_topt/manifest.txt", + "custom_topt/cache/http/known_tests.json", + "custom_topt/cache/http/test_management.json", + "custom_topt/cache/http/flaky_tests.json", + "custom_topt/context.json", + "custom_topt/telemetry_facts.json", + ) + for fragment in [ + 'name = "test_optimization_files"', + 'name = "test_optimization_context"', + "custom_topt/cache/http/settings.json", + "custom_topt/cache/http/known_tests.json", + "custom_topt/cache/http/test_management.json", + "custom_topt/cache/http/flaky_tests.json", + "custom_topt/telemetry_facts.json", + ]: + asserts.true(env, fragment in build, "missing BUILD fragment %s" % fragment) + return unittest.end(env) + +enabled_truthy_values_test = unittest.make(_enabled_truthy_values_test) +enabled_false_values_test = unittest.make(_enabled_false_values_test) +repository_environment_contains_bootstrap_inputs_test = unittest.make(_repository_environment_contains_bootstrap_inputs_test) +service_environment_resolution_test = unittest.make(_service_environment_resolution_test) +disabled_context_contract_test = unittest.make(_disabled_context_contract_test) +disabled_telemetry_contract_test = unittest.make(_disabled_telemetry_contract_test) +disabled_export_and_build_shape_test = unittest.make(_disabled_export_and_build_shape_test) diff --git a/tools/tests/example_stub_repo.bzl b/tools/tests/example_stub_repo.bzl index 74609c05..f917ba7e 100644 --- a/tools/tests/example_stub_repo.bzl +++ b/tools/tests/example_stub_repo.bzl @@ -70,6 +70,9 @@ def _render_stub_module_runfiles_bzl(repo_name, manifest_root): " test_management = getattr(ctx.file, \"test_management\", None)\n" + " if test_management:\n" + " files.append(test_management)\n" + + " flaky_tests = getattr(ctx.file, \"flaky_tests\", None)\n" + + " if flaky_tests:\n" + + " files.append(flaky_tests)\n" + " return DefaultInfo(files = depset(files), runfiles = ctx.runfiles(files = files))\n" + "\n" + "topt_module_files = rule(\n" + @@ -79,6 +82,7 @@ def _render_stub_module_runfiles_bzl(repo_name, manifest_root): " \"manifest\": attr.label(allow_single_file = True, mandatory = True),\n" + " \"known_tests\": attr.label(allow_single_file = True),\n" + " \"test_management\": attr.label(allow_single_file = True),\n" + + " \"flaky_tests\": attr.label(allow_single_file = True),\n" + " },\n" + ")\n" ) @@ -151,7 +155,8 @@ def _render_stub_build( telemetry_facts, service_keys = None, module_labels = None, - manifest_root = ".testoptimization"): + manifest_root = ".testoptimization", + flaky_tests = None): """Render BUILD content for stub repo targets.""" def _append_filegroups(name_suffix, srcs): @@ -170,7 +175,9 @@ def _render_stub_build( ")\n\n", ) - files_srcs = [settings, manifest, known_tests, test_management] + if flaky_tests == None: + flaky_tests = "%s/cache/http/flaky_tests.json" % manifest_root + files_srcs = [settings, manifest, known_tests, test_management, flaky_tests] lines = ['load(":module_runfiles.bzl", "topt_module_files")\n\n'] _append_filegroups("", files_srcs) for key in list(service_keys or []): @@ -183,6 +190,7 @@ def _render_stub_build( (" manifest = %s,\n" % repr(manifest)) + (" known_tests = %s,\n" % repr(_module_known_tests_path(manifest_root, label))) + (" test_management = %s,\n" % repr(_module_test_management_path(manifest_root, label))) + + (" flaky_tests = %s,\n" % repr(flaky_tests)) + ' visibility = ["//visibility:public"],\n' + ")\n\n", ) @@ -205,13 +213,18 @@ def _example_stub_repo_impl(ctx): settings = "%s/cache/http/settings.json" % out_dir known_tests = "%s/cache/http/known_tests.json" % out_dir test_management = "%s/cache/http/test_management.json" % out_dir + flaky_tests = "%s/cache/http/flaky_tests.json" % out_dir context = "%s/context.json" % out_dir telemetry_facts = "%s/telemetry_facts.json" % out_dir ctx.file(manifest, _stub_manifest_content()) - ctx.file(settings, "{}\n") + ctx.file( + settings, + '{"data": {"attributes": {"known_tests_enabled": false, "test_management": {"enabled": false}, "flaky_test_retries_enabled": false}}}\n', + ) ctx.file(known_tests, '{"data": {"attributes": {"tests": {}}}}\n') ctx.file(test_management, '{"data": {"attributes": {"modules": {}}}}\n') + ctx.file(flaky_tests, '{"data": []}\n') ctx.file(context, _render_stub_context(ctx.attr.service_name)) ctx.file(telemetry_facts, _render_stub_telemetry_facts(ctx.attr.service_name)) @@ -256,6 +269,7 @@ def _example_stub_repo_impl(ctx): service_keys = service_keys, module_labels = module_labels, manifest_root = out_dir, + flaky_tests = flaky_tests, ) ctx.file("BUILD", build) diff --git a/tools/tests/integration/go_integration_mock_server.sh b/tools/tests/integration/go_integration_mock_server.sh new file mode 100644 index 00000000..7ca3e83e --- /dev/null +++ b/tools/tests/integration/go_integration_mock_server.sh @@ -0,0 +1,123 @@ +#!/usr/bin/env bash + +# Shared deterministic metadata server lifecycle for Go integration smokes. + +GO_INTEGRATION_MOCK_SERVER_PID="" +GO_INTEGRATION_MOCK_SERVER_LOG="" +declare -a GO_INTEGRATION_MOCK_REPO_ENVS=() + +stop_go_integration_mock_server() { + if [[ -n "$GO_INTEGRATION_MOCK_SERVER_PID" ]]; then + kill "$GO_INTEGRATION_MOCK_SERVER_PID" >/dev/null 2>&1 || true + wait "$GO_INTEGRATION_MOCK_SERVER_PID" >/dev/null 2>&1 || true + GO_INTEGRATION_MOCK_SERVER_PID="" + fi +} + +start_go_integration_mock_server() { + local tmp_root="$1" + local module_identifier="$2" + local fixtures_dir="$tmp_root/enabled-metadata-fixtures" + local port + local ready=0 + local server_out="$tmp_root/enabled-metadata-server.out" + + rm -rf "$fixtures_dir" + mkdir -p "$fixtures_dir" + cp "$REPO_ROOT/tools/tests/integration/fixtures/settings.json" "$fixtures_dir/settings.json" + "$PYTHON" - "$fixtures_dir" "$module_identifier" <<'PY' +import json +import pathlib +import sys + +fixtures_dir = pathlib.Path(sys.argv[1]) +module_identifier = sys.argv[2] +(fixtures_dir / "known_tests.json").write_text(json.dumps({ + "data": { + "id": "1", + "type": "ci_app_libraries_tests", + "attributes": {"tests": {module_identifier: {"suite": {"test": {}}}}}, + }, +}), encoding="utf-8") +(fixtures_dir / "test_management.json").write_text(json.dumps({ + "data": { + "id": "1", + "type": "ci_app_libraries_tests", + "attributes": {"modules": {module_identifier: {"tests": {}}}}, + }, +}), encoding="utf-8") +PY + + port="$("$PYTHON" - <<'PY' +import socket + +sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) +sock.bind(("127.0.0.1", 0)) +print(sock.getsockname()[1]) +sock.close() +PY +)" + GO_INTEGRATION_MOCK_SERVER_LOG="$tmp_root/enabled-metadata-requests.jsonl" + : >"$GO_INTEGRATION_MOCK_SERVER_LOG" + + "$PYTHON" -u "$REPO_ROOT/tools/tests/integration/mock_dd_server.py" \ + --fixtures "$fixtures_dir" \ + --log "$GO_INTEGRATION_MOCK_SERVER_LOG" \ + --port "$port" >"$server_out" 2>&1 & + GO_INTEGRATION_MOCK_SERVER_PID=$! + + for _ in $(seq 1 100); do + if "$PYTHON" - "$port" <<'PY' +import socket +import sys + +sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) +sock.settimeout(0.2) +try: + sock.connect(("127.0.0.1", int(sys.argv[1]))) +except OSError: + raise SystemExit(1) +finally: + sock.close() +PY + then + ready=1 + break + fi + if ! kill -0 "$GO_INTEGRATION_MOCK_SERVER_PID" >/dev/null 2>&1; then + cat "$server_out" >&2 + echo "error: enabled metadata server exited before becoming ready" >&2 + return 1 + fi + sleep 0.1 + done + + if [[ "$ready" != "1" ]]; then + cat "$server_out" >&2 + echo "error: timed out waiting for enabled metadata server" >&2 + return 1 + fi + + GO_INTEGRATION_MOCK_REPO_ENVS=( + --repo_env=DD_API_KEY=mock + --repo_env=DD_TEST_OPTIMIZATION_AGENTLESS_URL="http://127.0.0.1:$port" + ) +} + +assert_go_integration_metadata_requests() { + "$PYTHON" - "$GO_INTEGRATION_MOCK_SERVER_LOG" <<'PY' +import json +import sys + +required = { + "/api/v2/libraries/tests/services/setting", + "/api/v2/ci/libraries/tests", + "/api/v2/test/libraries/test-management/tests", +} +with open(sys.argv[1], encoding="utf-8") as request_log: + observed = {json.loads(line)["path"] for line in request_log if line.strip()} +missing = sorted(required - observed) +if missing: + raise SystemExit(f"enabled metadata smoke did not call required endpoints: {missing}; observed={sorted(observed)}") +PY +} diff --git a/tools/tests/integration/run_bzlmod_go_integration.sh b/tools/tests/integration/run_bzlmod_go_integration.sh index af02bb68..8bb8c8d2 100755 --- a/tools/tests/integration/run_bzlmod_go_integration.sh +++ b/tools/tests/integration/run_bzlmod_go_integration.sh @@ -42,6 +42,10 @@ BAZEL_OUTPUT_USER_ROOT="${BAZEL_OUTPUT_USER_ROOT:-$TMP_ROOT/bazel_output_user_ro GO_VERSION="${GO_VERSION:-1.25.0}" ORCHESTRION_VERSION="${ORCHESTRION_VERSION:-v1.9.0}" ORCHESTRION_MODE="${ORCHESTRION_MODE:-general}" +ORCHESTRION_DISABLED_SENTINEL="${ORCHESTRION_DISABLED_SENTINEL:-0}" +ORCHESTRION_DISABLED_SENTINEL_VERSION="v0.0.0-rto-disabled-fetch-sentinel" +WINDOWS_DISABLED_SMOKE_ONLY="${WINDOWS_DISABLED_SMOKE_ONLY:-0}" +WINDOWS_ENABLED_SMOKE_ONLY="${WINDOWS_ENABLED_SMOKE_ONLY:-0}" DD_TRACE_GO_VERSION="${DD_TRACE_GO_VERSION:-v2.9.0}" SERVICE_NAME="${SERVICE_NAME:-bzlmod-go-service}" MODULE_IMPORTPATH="${MODULE_IMPORTPATH:-example.com/bzlmod-go-integration}" @@ -80,7 +84,10 @@ BAZEL_EXTRA_ARGS+=( "--repo_env=DD_GIT_COMMIT_SHA=${FIXTURE_GIT_COMMIT_SHA}" ) +source "$REPO_ROOT/tools/tests/integration/go_integration_mock_server.sh" + cleanup() { + stop_go_integration_mock_server USE_BAZEL_VERSION="$BAZEL_VERSION" "$BAZEL" --output_user_root="$BAZEL_OUTPUT_USER_ROOT" shutdown >/dev/null 2>&1 || true if [[ "${KEEP_TMP:-0}" == "1" ]]; then echo "KEEP_TMP=1: workspace fixtures left at $TMP_ROOT" @@ -368,7 +375,7 @@ PY run_bep_freshness_scenario() { local ws_dir="$1" local mode="$2" - local -a bzlmod_flags=(--enable_bzlmod) + local -a bzlmod_flags=(--enable_bzlmod --config=test-optimization) local bep_dir="$ws_dir/.topt/bep-${mode}" local fresh_bep="$bep_dir/fresh.bep.json" local cached_bep="$bep_dir/cached.bep.json" @@ -512,14 +519,23 @@ create_fixture_archive() { ARCHIVE_URL="file://$ARCHIVE_PATH" } +write_fixture_bazelrc() { + local ws_dir="$1" + local rules_go_repo="$2" + + cat > "$ws_dir/.bazelrc" < "$ws_dir/BUILD.bazel" <<'EOF' -load("@datadog-rules-test-optimization//tools/core:test_optimization_doctor.bzl", "dd_test_optimization_doctor") -load("@datadog-rules-test-optimization//tools/core:test_optimization_uploader.bzl", "dd_payload_uploader") +load("@datadog-rules-test-optimization//tools/core:test_optimization_targets.bzl", "dd_test_optimization_targets") exports_files([ "go.mod", @@ -528,16 +544,11 @@ exports_files([ "orchestrion.yml", ]) -dd_test_optimization_doctor( - name = "dd_test_optimization_doctor", - data = ["@test_optimization_data//:test_optimization_context"], +dd_test_optimization_targets( + name = "test_optimization", + sync_repo_name = "test_optimization_data", expected_targets = ["//app:hello_test"], ) - -dd_payload_uploader( - name = "dd_upload_payloads", - data = ["@test_optimization_data//:test_optimization_context"], -) EOF cat > "$ws_dir/app/BUILD.bazel" < "$ws_dir/go.sum" + return + fi + if [[ "$DD_TRACE_GO_VERSION" == "v2.9.0" && "$ORCHESTRION_VERSION" == "v1.9.0" ]]; then cat > "$ws_dir/go.sum" <<'EOF' github.com/DataDog/dd-trace-go/contrib/log/slog/v2 v2.9.0 h1:o5PABRmFQQ1uJcog3PnNF9+182EODnjHB6fjGTFkOIs= @@ -875,21 +919,20 @@ EOF cat >> "$ws_dir/MODULE.bazel" < "$ws_dir/app/hello_test.go" <<'EOF' +package main + +import "testing" + +func TestDisabledBootstrap(t *testing.T) { + if greeting() != "Hello, Bzlmod!" { + t.Fatalf("unexpected greeting %q", greeting()) + } +} +EOF +} + +run_disabled_no_fetch_smoke() { + local ws_dir="$WORKSPACE_ROOT/disabled" + local resolved_repos="$TMP_ROOT/disabled-resolved-repositories.bzl" + local alias_log="$TMP_ROOT/disabled-aliases.log" + local test_log="$TMP_ROOT/disabled-test.log" + local -a disabled_flags=( + "${BAZEL_EXTRA_ARGS[@]}" + --enable_bzlmod + ) + local -a disabled_env=( + env + -u DD_API_KEY + -u DD_SITE + -u DD_TEST_OPTIMIZATION_ENABLED + ) + local aliases=( + tool_binary + dd_trace_go_version_file + dd_trace_go_module_proxy_files + dd_trace_go_module_proxy_root_marker + orchestrion_tool_version_file + ) + + rm -rf "$ws_dir" + mkdir -p "$ws_dir" + write_module_file "$ws_dir" + write_shared_fixture_sources "$ws_dir" + write_disabled_fixture_test "$ws_dir" + + : > "$alias_log" + for alias in "${aliases[@]}"; do + ( + cd "$ws_dir" + "${disabled_env[@]}" USE_BAZEL_VERSION="$BAZEL_VERSION" "$BAZEL" --output_user_root="$BAZEL_OUTPUT_USER_ROOT" cquery \ + "${disabled_flags[@]}" \ + --experimental_repository_resolved_file="$resolved_repos" \ + "@rules_go//go/private/orchestrion:$alias" --output=files \ + >"$TMP_ROOT/disabled-alias-$alias.out" + ) 2>"$TMP_ROOT/disabled-alias-$alias.err" + cat "$TMP_ROOT/disabled-alias-$alias.out" >>"$alias_log" + done + if [[ -s "$alias_log" ]]; then + echo "error: disabled Bzlmod Orchestrion aliases exposed files" >&2 + cat "$alias_log" >&2 + exit 1 + fi + + ( + cd "$ws_dir" + "${disabled_env[@]}" USE_BAZEL_VERSION="$BAZEL_VERSION" "$BAZEL" --output_user_root="$BAZEL_OUTPUT_USER_ROOT" test \ + "${disabled_flags[@]}" \ + --experimental_repository_resolved_file="$resolved_repos" \ + "$HELLO_TEST_TARGET" + ) >"$test_log" 2>&1 + + if [[ -f "$resolved_repos" ]] && rg -n 'rules_go_orchestrion_tool|v0[.]0[.]0-rto-disabled-fetch-sentinel' "$resolved_repos"; then + echo "error: disabled Bzlmod smoke resolved the Orchestrion sentinel repository" >&2 + cat "$resolved_repos" >&2 + exit 1 + fi + if rg -n 'rules_go_orchestrion_tool|v0[.]0[.]0-rto-disabled-fetch-sentinel' "$test_log"; then + echo "error: disabled Bzlmod smoke attempted the Orchestrion sentinel" >&2 + cat "$test_log" >&2 + exit 1 + fi + + local testlogs + testlogs="$(cd "$ws_dir" && "${disabled_env[@]}" USE_BAZEL_VERSION="$BAZEL_VERSION" "$BAZEL" --output_user_root="$BAZEL_OUTPUT_USER_ROOT" info "${disabled_flags[@]}" bazel-testlogs)" + if find "$testlogs" -path '*/test.outputs/payloads/tests/*.json' -print -quit | grep -q .; then + echo "error: disabled Bzlmod smoke emitted Test Optimization payloads" >&2 + exit 1 + fi +} + +run_windows_enabled_smoke() { + local ws_dir="$WORKSPACE_ROOT/windows-enabled" + local resolved_repos="$TMP_ROOT/windows-enabled-resolved-repositories.bzl" + local alias_files="$TMP_ROOT/windows-enabled-aliases.log" + local test_log="$TMP_ROOT/windows-enabled-test.log" + local -a enabled_flags=( + "${BAZEL_EXTRA_ARGS[@]}" + "${GO_INTEGRATION_MOCK_REPO_ENVS[@]}" + --enable_bzlmod + --config=test-optimization + ) + local aliases=( + tool_binary + dd_trace_go_version_file + dd_trace_go_module_proxy_files + dd_trace_go_module_proxy_root_marker + orchestrion_tool_version_file + ) + + rm -rf "$ws_dir" + mkdir -p "$ws_dir" + start_go_integration_mock_server "$TMP_ROOT" "$MODULE_IMPORTPATH" + write_module_file "$ws_dir" + write_shared_fixture_sources "$ws_dir" + write_fixture_bazelrc "$ws_dir" "rules_go" + + : > "$alias_files" + for alias in "${aliases[@]}"; do + ( + cd "$ws_dir" + USE_BAZEL_VERSION="$BAZEL_VERSION" "$BAZEL" --output_user_root="$BAZEL_OUTPUT_USER_ROOT" cquery \ + "${enabled_flags[@]}" \ + --experimental_repository_resolved_file="$resolved_repos" \ + "@rules_go//go/private/orchestrion:$alias" --output=files + ) >>"$alias_files" + done + if [[ ! -s "$alias_files" ]]; then + echo "error: enabled Bzlmod Orchestrion aliases exposed no files" >&2 + exit 1 + fi + + ( + cd "$ws_dir" + USE_BAZEL_VERSION="$BAZEL_VERSION" "$BAZEL" --output_user_root="$BAZEL_OUTPUT_USER_ROOT" test \ + "${enabled_flags[@]}" \ + --experimental_repository_resolved_file="$resolved_repos" \ + "$HELLO_TEST_TARGET" + ) >"$test_log" 2>&1 + + if ! rg -q 'rules_go_orchestrion_tool' "$alias_files"; then + echo "error: enabled Bzlmod aliases did not expose the real Orchestrion repository files" >&2 + cat "$alias_files" >&2 + exit 1 + fi + + local testlogs + testlogs="$(cd "$ws_dir" && USE_BAZEL_VERSION="$BAZEL_VERSION" "$BAZEL" --output_user_root="$BAZEL_OUTPUT_USER_ROOT" info "${enabled_flags[@]}" bazel-testlogs)" + if ! find "$testlogs" -path '*/test.outputs/payloads/tests/*.json' -print -quit | grep -q .; then + echo "error: enabled Bzlmod smoke emitted no Test Optimization payloads" >&2 + cat "$test_log" >&2 + exit 1 + fi + assert_go_integration_metadata_requests +} + mkdir -p "$WORKSPACE_ROOT" + +if [[ "$WINDOWS_DISABLED_SMOKE_ONLY" == "1" || "$ORCHESTRION_DISABLED_SENTINEL" == "1" ]]; then + ORCHESTRION_VERSION="$ORCHESTRION_DISABLED_SENTINEL_VERSION" + ORCHESTRION_MODE="test_optimization" + create_fixture_archive + run_disabled_no_fetch_smoke + exit 0 +fi + +if [[ "$WINDOWS_ENABLED_SMOKE_ONLY" == "1" ]]; then + ORCHESTRION_MODE="test_optimization" + create_fixture_archive + run_windows_enabled_smoke + exit 0 +fi + create_fixture_archive +start_go_integration_mock_server "$TMP_ROOT" "$MODULE_IMPORTPATH" +BAZEL_EXTRA_ARGS+=("${GO_INTEGRATION_MOCK_REPO_ENVS[@]}") if [[ "$INTEGRATION_SCENARIO_MODE" == "measure" ]]; then if [[ -z "$MEASURE_OUTPUT_PATH" ]]; then @@ -1081,6 +1298,7 @@ if [[ "$INTEGRATION_SCENARIO_MODE" == "measure" ]]; then exit 1 fi run_fixture + assert_go_integration_metadata_requests exit 0 fi @@ -1090,3 +1308,4 @@ if [[ "$INTEGRATION_SCENARIO_MODE" != "full" ]]; then fi run_fixture +assert_go_integration_metadata_requests diff --git a/tools/tests/integration/run_mock_server_tests.ps1 b/tools/tests/integration/run_mock_server_tests.ps1 index f6772564..4b0dd95c 100644 --- a/tools/tests/integration/run_mock_server_tests.ps1 +++ b/tools/tests/integration/run_mock_server_tests.ps1 @@ -285,6 +285,14 @@ function Get-JsonValue { return $null } +function Get-JsonCollectionCount { + param($Value) + if ($null -eq $Value) { return 0 } + if ($Value -is [System.Collections.IDictionary]) { return $Value.Keys.Count } + if ($Value -is [System.Array]) { return $Value.Length } + return 1 +} + # Collect metric names from a telemetry message-batch while accepting either # array-backed or singleton-object JSON materialization. function Get-TelemetryMetricNames { @@ -456,6 +464,7 @@ test_optimization_sync = use_extension( test_optimization_sync.test_optimization_sync( name = "test_optimization_data", + enabled_by_env = True, service = "mock-service", runtime_name = "go", runtime_version = "1.2.3", @@ -463,6 +472,7 @@ test_optimization_sync.test_optimization_sync( test_optimization_sync.test_optimization_sync( name = "test_optimization_data_nodejs", + enabled_by_env = True, service = "mock-service-nodejs", runtime_name = "nodejs", runtime_version = "1.2.3", @@ -470,6 +480,7 @@ test_optimization_sync.test_optimization_sync( test_optimization_sync.test_optimization_sync( name = "test_optimization_data_dotnet", + enabled_by_env = True, service = "mock-service-dotnet", runtime_name = "dotnet", runtime_version = "1.2.3", @@ -477,6 +488,7 @@ test_optimization_sync.test_optimization_sync( test_optimization_sync.test_optimization_sync( name = "test_optimization_data_ruby", + enabled_by_env = True, service = "mock-service-ruby", runtime_name = "ruby", runtime_version = "1.2.3", @@ -509,6 +521,7 @@ filegroup( $bazelFlags = @("--output_base=$syncMetadataFetchOutBase") $repoEnvs = @( "--repo_env=DD_API_KEY=mock", + "--repo_env=DD_TEST_OPTIMIZATION_ENABLED=1", "--repo_env=DD_TEST_OPTIMIZATION_AGENTLESS_URL=http://127.0.0.1:$port", "--repo_env=DD_ENV=ci", "--repo_env=DD_GIT_REPOSITORY_URL=https://example.com/repo.git", @@ -523,6 +536,144 @@ filegroup( "--repo_env=GITHUB_SHA=", "--repo_env=GITHUB_EVENT_PATH=" ) + + # ------------------------------------------------------------------------- + # Scenario: disabled repositories render complete local stubs and never fetch. + # ------------------------------------------------------------------------- + # Run this before the enabled sync so the request-log delta is isolated from + # the baseline API traffic below. + $disabledOutputBase = Join-Path $tempRoot "disabled_sync_out" + $disabledLogStart = @(Read-JsonLog -Path $mockLog).Count + $disabledRepoEnvs = @( + "--repo_env=DD_TEST_OPTIMIZATION_ENABLED=0", + "--repo_env=DISABLE_CI_METADATA=1" + ) + $savedApiKey = $env:DD_API_KEY + $savedSite = $env:DD_SITE + $savedEnabled = $env:DD_TEST_OPTIMIZATION_ENABLED + $hadApiKey = Test-Path Env:DD_API_KEY + $hadSite = Test-Path Env:DD_SITE + $hadEnabled = Test-Path Env:DD_TEST_OPTIMIZATION_ENABLED + Push-Location $syncWorkspace + try { + Remove-Item Env:DD_API_KEY -ErrorAction SilentlyContinue + Remove-Item Env:DD_SITE -ErrorAction SilentlyContinue + Remove-Item Env:DD_TEST_OPTIMIZATION_ENABLED -ErrorAction SilentlyContinue + $disabledCqueryOutput = @( + Invoke-BazelCommand -BazelInvoker $bazelInvoker -BazelArgs @( + "--output_base=$disabledOutputBase", + "cquery", + "@test_optimization_data//:test_optimization_files", + "--output=files" + ) + $disabledRepoEnvs + ) + $disabledCqueryExitCode = Get-NativeExitCode + if ($disabledCqueryExitCode -ne 0) { + throw "disabled sync cquery failed with exit code $disabledCqueryExitCode" + } + $disabledContextCqueryOutput = @( + Invoke-BazelCommand -BazelInvoker $bazelInvoker -BazelArgs @( + "--output_base=$disabledOutputBase", + "cquery", + "@test_optimization_data//:test_optimization_context", + "--output=files" + ) + $disabledRepoEnvs + ) + $disabledContextCqueryExitCode = Get-NativeExitCode + if ($disabledContextCqueryExitCode -ne 0) { + throw "disabled sync context cquery failed with exit code $disabledContextCqueryExitCode" + } + $disabledCqueryOutput += $disabledContextCqueryOutput + $disabledBuildOutput = @( + Invoke-BazelCommand -BazelInvoker $bazelInvoker -BazelArgs @( + "--output_base=$disabledOutputBase", + "build", + "@test_optimization_data//:test_optimization_files" + ) + $disabledRepoEnvs + ) + $disabledBuildExitCode = Get-NativeExitCode + if ($disabledBuildExitCode -ne 0) { + throw "disabled sync build failed with exit code $disabledBuildExitCode" + } + $disabledContextBuildOutput = @( + Invoke-BazelCommand -BazelInvoker $bazelInvoker -BazelArgs @( + "--output_base=$disabledOutputBase", + "build", + "@test_optimization_data//:test_optimization_context" + ) + $disabledRepoEnvs + ) + $disabledContextBuildExitCode = Get-NativeExitCode + if ($disabledContextBuildExitCode -ne 0) { + throw "disabled sync context build failed with exit code $disabledContextBuildExitCode" + } + } finally { + if ($hadApiKey) { $env:DD_API_KEY = $savedApiKey } else { Remove-Item Env:DD_API_KEY -ErrorAction SilentlyContinue } + if ($hadSite) { $env:DD_SITE = $savedSite } else { Remove-Item Env:DD_SITE -ErrorAction SilentlyContinue } + if ($hadEnabled) { $env:DD_TEST_OPTIMIZATION_ENABLED = $savedEnabled } else { Remove-Item Env:DD_TEST_OPTIMIZATION_ENABLED -ErrorAction SilentlyContinue } + Pop-Location + } + $disabledCqueryText = (@($disabledCqueryOutput) -join "`n") + foreach ($requiredFile in @("settings.json", "known_tests.json", "test_management.json", "flaky_tests.json", "manifest.txt", "context.json")) { + if (-not $disabledCqueryText.Contains($requiredFile)) { + throw "disabled sync cquery is missing $requiredFile" + } + } + $disabledSettings = $null + $disabledSettingsRelativePath = @($disabledCqueryText -split "`r?`n" | Where-Object { $_ -match 'test_optimization_data.*[\\/]\.testoptimization[\\/]cache[\\/]http[\\/]settings\.json$' } | Select-Object -First 1) + $disabledCqueryBases = @( + $disabledOutputBase, + (Join-Path $disabledOutputBase "execroot/_main"), + (Join-Path $disabledOutputBase "execroot/__main__") + ) + foreach ($relativePath in $disabledSettingsRelativePath) { + foreach ($basePath in $disabledCqueryBases) { + $candidatePath = Join-Path $basePath $relativePath.Trim() + if (Test-Path -LiteralPath $candidatePath -PathType Leaf) { + $disabledSettings = Get-Item -LiteralPath $candidatePath + break + } + } + if ($disabledSettings) { break } + } + if (-not $disabledSettings) { + throw "disabled sync did not materialize the expected stub repository" + } + $disabledRepoDir = Split-Path -Parent (Split-Path -Parent (Split-Path -Parent (Split-Path -Parent $disabledSettings.FullName))) + $disabledSettingsMap = Read-JsonMap -JsonText (Get-Content -LiteralPath $disabledSettings.FullName -Raw -Encoding UTF8) + $disabledSettingsData = Get-JsonValue -Object $disabledSettingsMap -Key "data" + $disabledSettingsAttributes = Get-JsonValue -Object $disabledSettingsData -Key "attributes" + if ((Get-JsonValue -Object $disabledSettingsAttributes -Key "known_tests_enabled") -ne $false -or + (Get-JsonValue -Object (Get-JsonValue -Object $disabledSettingsAttributes -Key "test_management") -Key "enabled") -ne $false -or + (Get-JsonValue -Object $disabledSettingsAttributes -Key "flaky_test_retries_enabled") -ne $false) { + throw "disabled settings.json does not contain the canonical false flags" + } + $knownTestsValue = Read-JsonMap -JsonText (Get-Content -LiteralPath (Join-Path $disabledRepoDir ".testoptimization/cache/http/known_tests.json") -Raw -Encoding UTF8) + $knownTestsAttributes = Get-JsonValue -Object $knownTestsValue -Key "data" + $knownTestsAttributes = Get-JsonValue -Object $knownTestsAttributes -Key "attributes" + $knownTestsMap = Get-JsonValue -Object $knownTestsAttributes -Key "tests" + if ((Get-JsonCollectionCount $knownTestsMap) -ne 0) { throw "disabled known_tests.json is not empty" } + $testManagementValue = Read-JsonMap -JsonText (Get-Content -LiteralPath (Join-Path $disabledRepoDir ".testoptimization/cache/http/test_management.json") -Raw -Encoding UTF8) + $testManagementAttributes = Get-JsonValue -Object $testManagementValue -Key "data" + $testManagementAttributes = Get-JsonValue -Object $testManagementAttributes -Key "attributes" + $testManagementMap = Get-JsonValue -Object $testManagementAttributes -Key "modules" + if ((Get-JsonCollectionCount $testManagementMap) -ne 0) { throw "disabled test_management.json is not empty" } + $flakyValue = Read-JsonMap -JsonText (Get-Content -LiteralPath (Join-Path $disabledRepoDir ".testoptimization/cache/http/flaky_tests.json") -Raw -Encoding UTF8) + $flakyData = Get-JsonValue -Object $flakyValue -Key "data" + if ((Get-JsonCollectionCount $flakyData) -ne 0) { throw "disabled flaky_tests.json is not empty" } + $disabledContextPath = Join-Path $disabledRepoDir ".testoptimization/context.json" + $disabledContext = Read-JsonMap -JsonText (Get-Content -LiteralPath $disabledContextPath -Raw -Encoding UTF8) + if ((Get-JsonValue -Object $disabledContext -Key "topt.sync.enabled") -ne $false) { + throw "disabled context.json is missing topt.sync.enabled=false" + } + $disabledTelemetryPath = Join-Path $disabledRepoDir ".testoptimization/telemetry_facts.json" + if (-not (Get-Content -LiteralPath $disabledTelemetryPath -Raw -Encoding UTF8).Contains('"sync.disabled"')) { + throw "disabled telemetry_facts.json is missing sync.disabled" + } + $disabledLogEnd = @(Read-JsonLog -Path $mockLog).Count + if ($disabledLogStart -ne $disabledLogEnd) { + throw "disabled sync unexpectedly contacted the mock metadata server" + } + Push-Location $syncWorkspace try { Invoke-BazelCommand -BazelInvoker $bazelInvoker -BazelArgs (@($bazelFlags + @("fetch", "//:all_sync_payloads") + $repoEnvs)) diff --git a/tools/tests/integration/run_mock_server_tests.sh b/tools/tests/integration/run_mock_server_tests.sh index 17016008..fb897d15 100755 --- a/tools/tests/integration/run_mock_server_tests.sh +++ b/tools/tests/integration/run_mock_server_tests.sh @@ -225,6 +225,7 @@ test_optimization_sync = use_extension( test_optimization_sync.test_optimization_sync( name = "test_optimization_data", + enabled_by_env = True, service = "mock-service", runtime_name = "go", runtime_version = "1.2.3", @@ -232,6 +233,7 @@ test_optimization_sync.test_optimization_sync( test_optimization_sync.test_optimization_sync( name = "test_optimization_data_nodejs", + enabled_by_env = True, service = "mock-service-nodejs", runtime_name = "nodejs", runtime_version = "1.2.3", @@ -239,6 +241,7 @@ test_optimization_sync.test_optimization_sync( test_optimization_sync.test_optimization_sync( name = "test_optimization_data_dotnet", + enabled_by_env = True, service = "mock-service-dotnet", runtime_name = "dotnet", runtime_version = "1.2.3", @@ -246,6 +249,7 @@ test_optimization_sync.test_optimization_sync( test_optimization_sync.test_optimization_sync( name = "test_optimization_data_ruby", + enabled_by_env = True, service = "mock-service-ruby", runtime_name = "ruby", runtime_version = "1.2.3", @@ -446,7 +450,9 @@ BAZEL_FLAGS=(--output_base="$OUT_BASE") env -u DD_API_KEY -u DD_SITE "$BAZEL" "${BAZEL_FLAGS[@]}" test //:write_payloads_test SYNC_LAZINESS_LOG="$TMP_WS/sync_laziness_requires_api_key.log" -if env -u DD_API_KEY -u DD_SITE "$BAZEL" "${BAZEL_FLAGS[@]}" build //:dd_upload_payloads_with_context >"$SYNC_LAZINESS_LOG" 2>&1; then +if env -u DD_API_KEY -u DD_SITE -u DD_TEST_OPTIMIZATION_ENABLED \ + "$BAZEL" "${BAZEL_FLAGS[@]}" build //:dd_upload_payloads_with_context \ + --repo_env=DD_TEST_OPTIMIZATION_ENABLED=1 >"$SYNC_LAZINESS_LOG" 2>&1; then echo "error: target consuming @test_optimization_data succeeded without DD_API_KEY" cat "$SYNC_LAZINESS_LOG" || true exit 1 @@ -460,6 +466,7 @@ fi # Provide deterministic repo metadata for fixtures + payload enrichment. REPO_ENVS=( --repo_env=DD_API_KEY=mock + --repo_env=DD_TEST_OPTIMIZATION_ENABLED=1 --repo_env=DD_TEST_OPTIMIZATION_AGENTLESS_URL=http://127.0.0.1:$PORT --repo_env=DD_ENV=ci --repo_env=DD_GIT_REPOSITORY_URL=https://example.com/repo.git @@ -474,6 +481,80 @@ REPO_ENVS=( --repo_env=GITHUB_EVENT_PATH= ) +# --------------------------------------------------------------------------- +# Scenario: disabled repositories render complete local stubs and never fetch. +# --------------------------------------------------------------------------- +# Keep this phase before the enabled sync so the request-log delta is isolated +# from the baseline API traffic below. +DISABLED_OUTPUT_BASE="$TMP_WS/.bazel_disabled_out" +DISABLED_CQUERY_OUT="$TMP_WS/disabled-cquery.out" +DISABLED_REPO_ENVS=( + --repo_env=DD_TEST_OPTIMIZATION_ENABLED=0 + --repo_env=DISABLE_CI_METADATA=1 +) +if [[ -f "$LOG_FILE" ]]; then + DISABLED_LOG_START="$(wc -l < "$LOG_FILE" | tr -d '[:space:]')" +else + DISABLED_LOG_START=0 +fi +env -u DD_API_KEY -u DD_SITE -u DD_TEST_OPTIMIZATION_ENABLED \ + "$BAZEL" --output_base="$DISABLED_OUTPUT_BASE" cquery \ + @test_optimization_data//:test_optimization_files --output=files \ + "${DISABLED_REPO_ENVS[@]}" >"$DISABLED_CQUERY_OUT" +env -u DD_API_KEY -u DD_SITE -u DD_TEST_OPTIMIZATION_ENABLED \ + "$BAZEL" --output_base="$DISABLED_OUTPUT_BASE" cquery \ + @test_optimization_data//:test_optimization_context --output=files \ + "${DISABLED_REPO_ENVS[@]}" >>"$DISABLED_CQUERY_OUT" +env -u DD_API_KEY -u DD_SITE -u DD_TEST_OPTIMIZATION_ENABLED \ + "$BAZEL" --output_base="$DISABLED_OUTPUT_BASE" build \ + @test_optimization_data//:test_optimization_files \ + "${DISABLED_REPO_ENVS[@]}" +env -u DD_API_KEY -u DD_SITE -u DD_TEST_OPTIMIZATION_ENABLED \ + "$BAZEL" --output_base="$DISABLED_OUTPUT_BASE" build \ + @test_optimization_data//:test_optimization_context \ + "${DISABLED_REPO_ENVS[@]}" + +for required in settings.json known_tests.json test_management.json flaky_tests.json manifest.txt context.json; do + if ! grep -q "$required" "$DISABLED_CQUERY_OUT"; then + echo "error: disabled sync cquery is missing $required" + cat "$DISABLED_CQUERY_OUT" + exit 1 + fi +done + +DISABLED_REPO_DIR="$(find "$DISABLED_OUTPUT_BASE" -type f \ + -path '*/external/*test_optimization_data/.testoptimization/cache/http/settings.json' \ + -print -quit)" +if [[ -z "$DISABLED_REPO_DIR" ]]; then + echo "error: disabled sync did not materialize the expected stub repository" + exit 1 +fi +DISABLED_REPO_DIR="${DISABLED_REPO_DIR%/.testoptimization/cache/http/settings.json}" +jq -e ' + .data.attributes.known_tests_enabled == false and + .data.attributes.test_management.enabled == false and + .data.attributes.flaky_test_retries_enabled == false +' "$DISABLED_REPO_DIR/.testoptimization/cache/http/settings.json" >/dev/null +jq -e '.data.attributes.tests == {}' \ + "$DISABLED_REPO_DIR/.testoptimization/cache/http/known_tests.json" >/dev/null +jq -e '.data.attributes.modules == {}' \ + "$DISABLED_REPO_DIR/.testoptimization/cache/http/test_management.json" >/dev/null +jq -e '.data == []' \ + "$DISABLED_REPO_DIR/.testoptimization/cache/http/flaky_tests.json" >/dev/null +jq -e '."topt.sync.enabled" == false' \ + "$DISABLED_REPO_DIR/.testoptimization/context.json" >/dev/null +jq -e 'any(.counts[]; .name == "sync.disabled" and .value == 1)' \ + "$DISABLED_REPO_DIR/.testoptimization/telemetry_facts.json" >/dev/null +if [[ -f "$LOG_FILE" ]]; then + DISABLED_LOG_END="$(wc -l < "$LOG_FILE" | tr -d '[:space:]')" +else + DISABLED_LOG_END=0 +fi +if [[ "$DISABLED_LOG_START" != "$DISABLED_LOG_END" ]]; then + echo "error: disabled sync unexpectedly contacted the mock metadata server" + exit 1 +fi + # --------------------------------------------------------------------------- # Scenario: baseline sync + uploader run (agentless) with fixture assertions. # --------------------------------------------------------------------------- @@ -2990,6 +3071,7 @@ fi if [ "${1:-}" = "mod" ] && [ "${2:-}" = "download" ]; then case "${3:-}" in + github.com/DataDog/orchestrion@${ORCH_VERSION}|\ github.com/DataDog/dd-trace-go/v2@v2.9.0|\ github.com/DataDog/dd-trace-go/contrib/net/http/v2@v2.9.0|\ github.com/DataDog/dd-trace-go/contrib/log/slog/v2@v2.9.0) @@ -3322,6 +3404,7 @@ fi if [ "${1:-}" = "mod" ] && [ "${2:-}" = "download" ]; then case "${3:-}" in + github.com/DataDog/orchestrion@${ORCH_VERSION}|\ github.com/DataDog/dd-trace-go/v2@v2.9.0|\ github.com/DataDog/dd-trace-go/contrib/net/http/v2@v2.9.0|\ github.com/DataDog/dd-trace-go/contrib/log/slog/v2@v2.9.0) diff --git a/tools/tests/integration/run_workspace_go_integration.sh b/tools/tests/integration/run_workspace_go_integration.sh index 1a5cdef5..5873860d 100755 --- a/tools/tests/integration/run_workspace_go_integration.sh +++ b/tools/tests/integration/run_workspace_go_integration.sh @@ -44,6 +44,10 @@ BAZEL_OUTPUT_USER_ROOT="${BAZEL_OUTPUT_USER_ROOT:-$TMP_ROOT/bazel_output_user_ro GO_VERSION="${GO_VERSION:-1.25.0}" ORCHESTRION_VERSION="${ORCHESTRION_VERSION:-v1.9.0}" ORCHESTRION_MODE="${ORCHESTRION_MODE:-general}" +ORCHESTRION_DISABLED_SENTINEL="${ORCHESTRION_DISABLED_SENTINEL:-0}" +ORCHESTRION_DISABLED_SENTINEL_VERSION="v0.0.0-rto-disabled-fetch-sentinel" +WINDOWS_DISABLED_SMOKE_ONLY="${WINDOWS_DISABLED_SMOKE_ONLY:-0}" +WINDOWS_ENABLED_SMOKE_ONLY="${WINDOWS_ENABLED_SMOKE_ONLY:-0}" # Keep this aligned with the bootstrap helper's published default tracer pin so # the WORKSPACE harness validates the same public Go path the docs describe. DD_TRACE_GO_VERSION="${DD_TRACE_GO_VERSION:-v2.9.0}" @@ -80,7 +84,10 @@ HERMETIC_TEST_FLAGS=( --test_env=LC_ALL=C ) +source "$REPO_ROOT/tools/tests/integration/go_integration_mock_server.sh" + cleanup() { + stop_go_integration_mock_server USE_BAZEL_VERSION="$BAZEL_VERSION" "$BAZEL" --output_user_root="$BAZEL_OUTPUT_USER_ROOT" shutdown >/dev/null 2>&1 || true if [[ "${KEEP_TMP:-0}" == "1" ]]; then echo "KEEP_TMP=1: workspace fixtures left at $TMP_ROOT" @@ -352,7 +359,7 @@ PY run_bep_freshness_scenario() { local ws_dir="$1" local mode="$2" - local -a workspace_flags=("${BAZEL_EXTRA_ARGS[@]}" --noenable_bzlmod --enable_workspace) + local -a workspace_flags=("${BAZEL_EXTRA_ARGS[@]}" --noenable_bzlmod --enable_workspace --config=test-optimization) local bep_dir="$ws_dir/.topt/bep-${mode}" local fresh_bep="$bep_dir/fresh.bep.json" local cached_bep="$bep_dir/cached.bep.json" @@ -487,14 +494,23 @@ create_fixture_archive() { ARCHIVE_URL="file://$ARCHIVE_PATH" } +write_fixture_bazelrc() { + local ws_dir="$1" + local rules_go_repo="$2" + + cat > "$ws_dir/.bazelrc" < "$ws_dir/BUILD.bazel" <<'EOF' -load("@datadog-rules-test-optimization//tools/core:test_optimization_doctor.bzl", "dd_test_optimization_doctor") -load("@datadog-rules-test-optimization//tools/core:test_optimization_uploader.bzl", "dd_payload_uploader") +load("@datadog-rules-test-optimization//tools/core:test_optimization_targets.bzl", "dd_test_optimization_targets") exports_files([ "go.mod", @@ -503,16 +519,11 @@ exports_files([ "orchestrion.yml", ]) -dd_test_optimization_doctor( - name = "dd_test_optimization_doctor", - data = ["@test_optimization_data//:test_optimization_context"], +dd_test_optimization_targets( + name = "test_optimization", + sync_repo_name = "test_optimization_data", expected_targets = ["//app:hello_test"], ) - -dd_payload_uploader( - name = "dd_upload_payloads", - data = ["@test_optimization_data//:test_optimization_context"], -) EOF cat > "$ws_dir/app/BUILD.bazel" < "$ws_dir/go.sum" + return + fi + if [[ "$DD_TRACE_GO_VERSION" == "v2.9.0" && "$ORCHESTRION_VERSION" == "v1.9.0" ]]; then cat > "$ws_dir/go.sum" <<'EOF' github.com/DataDog/dd-trace-go/contrib/log/slog/v2 v2.9.0 h1:o5PABRmFQQ1uJcog3PnNF9+182EODnjHB6fjGTFkOIs= @@ -894,8 +938,8 @@ http_archive( load("@io_bazel_rules_go//go:deps.bzl", "go_register_toolchains", "go_rules_dependencies") load("@bazel_gazelle//:deps.bzl", "gazelle_dependencies") -load("@io_bazel_rules_go//go:orchestrion_workspace.bzl", "go_orchestrion_tool_repo") -load("@datadog-rules-test-optimization//tools/tests:example_stub_repo.bzl", "example_stub_repo") +load("@datadog-rules-test-optimization-go//:topt_go_orchestrion_repository.bzl", "dd_topt_go_orchestrion_tool_repo") +load("@datadog-rules-test-optimization-go//:topt_go_workspace.bzl", "dd_topt_go_workspace_sync_repositories") go_rules_dependencies() go_register_toolchains(version = "${GO_VERSION}") @@ -904,20 +948,19 @@ EOF cat >> "$ws_dir/WORKSPACE" < "$ws_dir/app/hello_test.go" <<'EOF' +package main + +import "testing" + +func TestDisabledBootstrap(t *testing.T) { + if greeting() != "Hello, Workspace!" { + t.Fatalf("unexpected greeting %q", greeting()) + } +} +EOF +} + +run_disabled_no_fetch_smoke() { + local ws_dir="$WORKSPACE_ROOT/disabled" + local resolved_repos="$TMP_ROOT/disabled-resolved-repositories.bzl" + local alias_log="$TMP_ROOT/disabled-aliases.log" + local test_log="$TMP_ROOT/disabled-test.log" + local -a disabled_flags=( + "${BAZEL_EXTRA_ARGS[@]}" + --noenable_bzlmod + --enable_workspace + ) + local -a disabled_env=( + env + -u DD_API_KEY + -u DD_SITE + -u DD_TEST_OPTIMIZATION_ENABLED + ) + local aliases=( + tool_binary + dd_trace_go_version_file + dd_trace_go_module_proxy_files + dd_trace_go_module_proxy_root_marker + orchestrion_tool_version_file + ) + + rm -rf "$ws_dir" + mkdir -p "$ws_dir" + write_positive_workspace "$ws_dir" "archive" + write_shared_fixture_sources "$ws_dir" + write_disabled_fixture_test "$ws_dir" + + : > "$alias_log" + for alias in "${aliases[@]}"; do + ( + cd "$ws_dir" + "${disabled_env[@]}" USE_BAZEL_VERSION="$BAZEL_VERSION" "$BAZEL" --output_user_root="$BAZEL_OUTPUT_USER_ROOT" cquery \ + "${disabled_flags[@]}" \ + --experimental_repository_resolved_file="$resolved_repos" \ + "@io_bazel_rules_go//go/private/orchestrion:$alias" --output=files \ + >"$TMP_ROOT/disabled-alias-$alias.out" + ) 2>"$TMP_ROOT/disabled-alias-$alias.err" + cat "$TMP_ROOT/disabled-alias-$alias.out" >>"$alias_log" + done + if [[ -s "$alias_log" ]]; then + echo "error: disabled WORKSPACE Orchestrion aliases exposed files" >&2 + cat "$alias_log" >&2 + exit 1 + fi + + ( + cd "$ws_dir" + "${disabled_env[@]}" USE_BAZEL_VERSION="$BAZEL_VERSION" "$BAZEL" --output_user_root="$BAZEL_OUTPUT_USER_ROOT" test \ + "${disabled_flags[@]}" \ + --experimental_repository_resolved_file="$resolved_repos" \ + "$HELLO_TEST_TARGET" + ) >"$test_log" 2>&1 + + if [[ -f "$resolved_repos" ]] && rg -n 'rules_go_orchestrion_tool|v0[.]0[.]0-rto-disabled-fetch-sentinel' "$resolved_repos"; then + echo "error: disabled WORKSPACE smoke resolved the Orchestrion sentinel repository" >&2 + cat "$resolved_repos" >&2 + exit 1 + fi + if rg -n 'rules_go_orchestrion_tool|v0[.]0[.]0-rto-disabled-fetch-sentinel' "$test_log"; then + echo "error: disabled WORKSPACE smoke attempted the Orchestrion sentinel" >&2 + cat "$test_log" >&2 + exit 1 + fi + + local testlogs + testlogs="$(cd "$ws_dir" && "${disabled_env[@]}" USE_BAZEL_VERSION="$BAZEL_VERSION" "$BAZEL" --output_user_root="$BAZEL_OUTPUT_USER_ROOT" info "${disabled_flags[@]}" bazel-testlogs)" + if find "$testlogs" -path '*/test.outputs/payloads/tests/*.json' -print -quit | grep -q .; then + echo "error: disabled WORKSPACE smoke emitted Test Optimization payloads" >&2 + exit 1 + fi +} + +run_windows_enabled_smoke() { + local ws_dir="$WORKSPACE_ROOT/windows-enabled" + local resolved_repos="$TMP_ROOT/windows-enabled-resolved-repositories.bzl" + local alias_files="$TMP_ROOT/windows-enabled-aliases.log" + local test_log="$TMP_ROOT/windows-enabled-test.log" + local -a enabled_flags=( + "${BAZEL_EXTRA_ARGS[@]}" + "${GO_INTEGRATION_MOCK_REPO_ENVS[@]}" + --noenable_bzlmod + --enable_workspace + --config=test-optimization + ) + local aliases=( + tool_binary + dd_trace_go_version_file + dd_trace_go_module_proxy_files + dd_trace_go_module_proxy_root_marker + orchestrion_tool_version_file + ) + + rm -rf "$ws_dir" + mkdir -p "$ws_dir" + start_go_integration_mock_server "$TMP_ROOT" "$MODULE_IMPORTPATH" + write_positive_workspace "$ws_dir" "archive" + write_shared_fixture_sources "$ws_dir" + write_fixture_bazelrc "$ws_dir" "io_bazel_rules_go" + + : > "$alias_files" + for alias in "${aliases[@]}"; do + ( + cd "$ws_dir" + USE_BAZEL_VERSION="$BAZEL_VERSION" "$BAZEL" --output_user_root="$BAZEL_OUTPUT_USER_ROOT" cquery \ + "${enabled_flags[@]}" \ + --experimental_repository_resolved_file="$resolved_repos" \ + "@io_bazel_rules_go//go/private/orchestrion:$alias" --output=files + ) >>"$alias_files" + done + if [[ ! -s "$alias_files" ]]; then + echo "error: enabled WORKSPACE Orchestrion aliases exposed no files" >&2 + exit 1 + fi + + ( + cd "$ws_dir" + USE_BAZEL_VERSION="$BAZEL_VERSION" "$BAZEL" --output_user_root="$BAZEL_OUTPUT_USER_ROOT" test \ + "${enabled_flags[@]}" \ + --experimental_repository_resolved_file="$resolved_repos" \ + "$HELLO_TEST_TARGET" + ) >"$test_log" 2>&1 + + if ! rg -q 'rules_go_orchestrion_tool' "$alias_files"; then + echo "error: enabled WORKSPACE aliases did not expose the real Orchestrion repository files" >&2 + cat "$alias_files" >&2 + exit 1 + fi + + local testlogs + testlogs="$(cd "$ws_dir" && USE_BAZEL_VERSION="$BAZEL_VERSION" "$BAZEL" --output_user_root="$BAZEL_OUTPUT_USER_ROOT" info "${enabled_flags[@]}" bazel-testlogs)" + if ! find "$testlogs" -path '*/test.outputs/payloads/tests/*.json' -print -quit | grep -q .; then + echo "error: enabled WORKSPACE smoke emitted no Test Optimization payloads" >&2 + cat "$test_log" >&2 + exit 1 + fi + assert_go_integration_metadata_requests +} + run_expected_failure() { local scenario="$1" local expected_fragment="$2" @@ -1169,7 +1370,25 @@ run_expected_failure() { } mkdir -p "$WORKSPACE_ROOT" + +if [[ "$WINDOWS_DISABLED_SMOKE_ONLY" == "1" || "$ORCHESTRION_DISABLED_SENTINEL" == "1" ]]; then + ORCHESTRION_VERSION="$ORCHESTRION_DISABLED_SENTINEL_VERSION" + ORCHESTRION_MODE="test_optimization" + create_fixture_archive + run_disabled_no_fetch_smoke + exit 0 +fi + +if [[ "$WINDOWS_ENABLED_SMOKE_ONLY" == "1" ]]; then + ORCHESTRION_MODE="test_optimization" + create_fixture_archive + run_windows_enabled_smoke + exit 0 +fi + create_fixture_archive +start_go_integration_mock_server "$TMP_ROOT" "$MODULE_IMPORTPATH" +BAZEL_EXTRA_ARGS+=("${GO_INTEGRATION_MOCK_REPO_ENVS[@]}") if [[ "$INTEGRATION_SCENARIO_MODE" == "measure" ]]; then if [[ -z "$MEASURE_OUTPUT_PATH" ]]; then @@ -1177,6 +1396,7 @@ if [[ "$INTEGRATION_SCENARIO_MODE" == "measure" ]]; then exit 1 fi run_positive_fixture "archive" + assert_go_integration_metadata_requests exit 0 fi @@ -1190,3 +1410,4 @@ run_positive_fixture "archive" run_expected_failure "custom_name" "name must be rules_go_orchestrion_tool" run_expected_failure "missing_version" "version is required in WORKSPACE mode" run_expected_failure "conflicting_versions" "dd_trace_go_version and dd_trace_go_versions cannot both be set" +assert_go_integration_metadata_requests From 90834b8e7db3c4195670e6e131e7fccdaec50a03 Mon Sep 17 00:00:00 2001 From: Tony Redondo Date: Wed, 15 Jul 2026 20:57:11 +0200 Subject: [PATCH 03/23] bootstrap: make Test Optimization config-driven --- README.md | 25 ++++-- docs/Installation_Reference.md | 24 ++++-- docs/Language_Onboarding.md | 33 ++++++-- docs/Troubleshooting.md | 1 + docs/internal_monorepo_go_rollout_guide.md | 14 +++- examples/README.md | 2 + examples/multi_service/MODULE.bazel | 1 - examples/single_service/MODULE.bazel | 1 - modules/go/tests/BUILD.bazel | 17 +++++ modules/go/tests/test_extension.bzl | 26 ++++++- modules/go/tests/test_workspace_helpers.bzl | 23 ++++-- modules/go/tools/dd_topt_go_bootstrap/main.go | 7 +- .../tools/dd_topt_go_bootstrap/main_test.go | 14 +++- modules/go/topt_go_extension.bzl | 6 +- modules/go/topt_go_workspace.bzl | 4 +- modules/python/tests/BUILD.bazel | 26 +++++++ modules/python/tests/test_macro.bzl | 76 +++++++++++++++++++ .../python/tools/dd_topt_py_bootstrap/main.py | 6 +- .../tools/dd_topt_py_bootstrap/main_test.py | 35 ++++++++- modules/python/topt_py_test.bzl | 36 ++++++++- third_party/rgo/v0_60_0/base.CHANGED_FILES.md | 5 +- .../v0_60_0/base/go/private/repositories.bzl | 10 +++ third_party/rgo/v0_61_1/base.CHANGED_FILES.md | 5 +- .../v0_61_1/base/go/private/repositories.bzl | 10 +++ .../v0_60_0/base/0001-full-delta.patch | 28 +++++++ .../v0_61_1/base/0001-full-delta.patch | 28 +++++++ .../profiles/workspace_runtime.json | 1 + .../references/workspace-onboarding.md | 11 ++- .../references/bzlmod-onboarding.md | 1 + .../references/workspace-onboarding.md | 1 + .../references/consumer-runner.md | 1 - .../references/workspace-onboarding.md | 1 - tools/core/test_optimization_sync.bzl | 45 +++++++++-- tools/dev/materialize_rules_go_fork.py | 8 +- tools/tests/core/BUILD.bazel | 15 ++++ tools/tests/core/test_sync_disabled_utils.bzl | 67 ++++++++++++---- tools/tests/core/test_sync_utils.bzl | 1 + .../integration/run_mock_server_tests.sh | 52 +++++++++++++ .../run_workspace_go_integration.sh | 59 ++++++++++++-- .../python/test_rules_go_variant_tools.py | 7 ++ 40 files changed, 645 insertions(+), 88 deletions(-) diff --git a/README.md b/README.md index a52cfc2c..1a134b1e 100644 --- a/README.md +++ b/README.md @@ -117,14 +117,22 @@ or merge it locally instead of vendoring a second complete `rules_go` tree. Use this checklist before your first CI rollout: -For opt-in onboarding in every language, the named `test-optimization` config -must include -`common:test-optimization --repo_env=DD_TEST_OPTIMIZATION_ENABLED=1`, and the -sync declaration must set `enabled_by_env = True`. Go additionally needs +For config-gated metadata resolution in every language, the named +`test-optimization` config must include +`common:test-optimization --repo_env=DD_TEST_OPTIMIZATION_ENABLED=1`. The +public Go bootstrap helpers apply metadata gating by default; direct use of the +low-level core sync API must opt into `enabled_by_env = True`. Go additionally needs `build:test-optimization --@rules_go//go/private/orchestrion:enabled=true` for Bzlmod, or the same flag with `@io_bazel_rules_go` for WORKSPACE. -Removing `--config=test-optimization` disables metadata resolution and, for Go, -the real Orchestrion aliases; no second Test Optimization bool flag is needed. +Removing `--config=test-optimization` disables metadata resolution for every +config-gated sync. Go and Python additionally implement the complete runtime +opt-out described below. Other companions currently use this config as a +metadata-fetch gate; do not infer a test-runtime kill switch from it. + +When a config-gated Python sync is disabled, `dd_topt_py_test` keeps the +consumer's normal runner and test arguments, omits Test Optimization metadata +and payload wiring, and applies the CI Visibility runtime kill switch +automatically. No per-target disable attribute is required. 1. Keep the generated repo name as `test_optimization_data` (or consistently replace it in labels/commands if you choose another name). 2. Forward sync metadata environment variable names in `.bazelrc` under a named @@ -253,6 +261,7 @@ test_optimization_sync = use_extension( test_optimization_sync.test_optimization_sync( name = "test_optimization_data", service = "my-service", # or set DD_SERVICE via --repo_env + enabled_by_env = True, ) use_repo(test_optimization_sync, "test_optimization_data") ``` @@ -497,7 +506,6 @@ dd_topt_py_test( name = "pkg_py_test", py_test_rule = my_repo_pytest_wrapper, runner_mode = "consumer_runner", - module_identifier = "example.python.pkg", srcs = glob(["test_*.py"]), deps = [ ":pkg_lib", @@ -932,6 +940,7 @@ topt_go.test_optimization_sync( service = "go-service", runtime_name = "go", runtime_version = "1.25.0", + enabled_by_env = True, ) topt_ruby = use_extension( @@ -943,6 +952,7 @@ topt_ruby.test_optimization_sync( service = "ruby-service", runtime_name = "ruby", runtime_version = "3.3.9", + enabled_by_env = True, ) use_repo(topt_go, "test_optimization_data_go") @@ -997,6 +1007,7 @@ load("@datadog-rules-test-optimization//tools/core:test_optimization_sync.bzl", test_optimization_sync( name = "test_optimization_data", service = "my-service", # recommended; otherwise falls back to DD_SERVICE or unnamed-service + enabled_by_env = True, ) ``` diff --git a/docs/Installation_Reference.md b/docs/Installation_Reference.md index 6c8c26c2..afe572f4 100644 --- a/docs/Installation_Reference.md +++ b/docs/Installation_Reference.md @@ -201,6 +201,7 @@ test_optimization_sync = use_extension( # @test_optimization_data//:test_optimization_files test_optimization_sync.test_optimization_sync( name = "test_optimization_data", + enabled_by_env = True, ) use_repo(test_optimization_sync, "test_optimization_data") @@ -606,7 +607,10 @@ test_optimization_sync = use_extension( "@datadog-rules-test-optimization//tools/core:test_optimization_sync.bzl", "test_optimization_sync_extension", ) -test_optimization_sync.test_optimization_sync(name = "test_optimization_data") +test_optimization_sync.test_optimization_sync( + name = "test_optimization_data", + enabled_by_env = True, +) use_repo(test_optimization_sync, "test_optimization_data") ``` @@ -627,6 +631,7 @@ topt_multi.test_optimization_multi_sync( runtime_name = "python", runtime_version = "3.12", debug = True, + enabled_by_env = True, ) use_repo( @@ -780,6 +785,7 @@ load("@datadog-rules-test-optimization//tools/core:test_optimization_sync.bzl", test_optimization_sync( name = "test_optimization_data", service = "my-service", # recommended; otherwise falls back to DD_SERVICE or unnamed-service + enabled_by_env = True, # Optional: # runtime_name = "go", # runtime_version = "1.25.0", @@ -1096,7 +1102,6 @@ dd_topt_py_test( name = "pkg_py_test", py_test_rule = repo_py_test, runner_mode = "consumer_runner", - module_identifier = "example.python.pkg", srcs = glob(["test_*.py"]), deps = [ requirement("ddtrace"), @@ -1247,18 +1252,18 @@ http_archive( load("@io_bazel_rules_go//go:deps.bzl", "go_register_toolchains", "go_rules_dependencies") load("@bazel_gazelle//:deps.bzl", "gazelle_dependencies") -load("@io_bazel_rules_go//go:orchestrion_workspace.bzl", "go_orchestrion_tool_repo") +load("@datadog-rules-test-optimization-go//:topt_go_orchestrion_repository.bzl", "dd_topt_go_orchestrion_tool_repo") -go_rules_dependencies() -go_register_toolchains(version = "1.25.0") -gazelle_dependencies() - -go_orchestrion_tool_repo( +dd_topt_go_orchestrion_tool_repo( version = "", # Optional. When omitted, the helper uses the fork's current default # shared dd-trace-go version. dd_trace_go_version = "", ) + +go_rules_dependencies() +go_register_toolchains(version = "1.25.0") +gazelle_dependencies() ``` Notes for the helper: @@ -1267,6 +1272,9 @@ Notes for the helper: - `dd_trace_go_version` and `dd_trace_go_versions` are mutually exclusive. - Keep the default tool-repo name `rules_go_orchestrion_tool`; the current fork resolves that name internally. +- Declare the real tool repository before `go_rules_dependencies()`. The fork + supplies its own empty fallback for ordinary WORKSPACE consumers, so do not + load or declare a private stub repository yourself. - Do not configure `patches`, `patch_tool`, or `patch_args` for this integration; repositories that already own their `rules_go` patch stack should generate the public consumer patch profile and rebase or merge it locally. diff --git a/docs/Language_Onboarding.md b/docs/Language_Onboarding.md index 501203b1..436ff5aa 100644 --- a/docs/Language_Onboarding.md +++ b/docs/Language_Onboarding.md @@ -48,9 +48,11 @@ Shared runtime contract for every language: - `DD_TEST_OPTIMIZATION_CONTEXT_JSON` remains a legacy explicit override, not the recommended mixed-runtime wiring path -Shared `.bazelrc` forwarding for every runtime. The named config is the only -user-facing enable switch; omitting it leaves metadata repositories on their -disabled no-fetch stubs: +Shared `.bazelrc` forwarding for every runtime. The named config is the shared +metadata-fetch switch; omitting it leaves config-gated metadata repositories +on their disabled no-fetch stubs. Go and Python also use it as the complete +runtime opt-out. The Java, NodeJS, .NET, and Ruby companions do not yet promise +a test-runtime kill switch when the config is omitted: ```text common:test-optimization --repo_env=DD_API_KEY @@ -299,11 +301,18 @@ dd_topt_go_workspace_sync_repositories( service = "", runtime_version = "", module_path = "", - enabled_by_env = True, require_git_metadata = True, ) ``` +The public Go helper enables metadata gating by default. The single +`--config=test-optimization` switch controls both metadata sync and the +Orchestrion aliases; no per-target or per-repository enable attribute is needed. + +Python follows the same single-switch contract when its sync declaration uses +`enabled_by_env = True`: without the config, the wrapper preserves the normal +test runner but omits Test Optimization metadata, instrumentation, and payloads. + If the environment can fetch Git repositories reliably, use `rules_go_fetch = "git"` and omit the archive attributes. If the environment mirrors or blocks GitHub codeload archives, publish the same commit to a mirror @@ -605,7 +614,6 @@ dd_topt_py_test( name = "pkg_py_test", py_test_rule = repo_py_test, runner_mode = "consumer_runner", - module_identifier = "example.python.pkg", srcs = glob(["test_*.py"]), deps = [ ":pkg_lib", @@ -623,6 +631,12 @@ package path identify the test, omit `module_identifier` and use the derived fallback. Keep an explicit `module_identifier` only for a documented repository-specific exception. +The derived identifier selects a module-specific payload when that module is +present in the synchronized metadata. If the backend has not materialized a +matching module group yet, the selector intentionally uses the exact canonical +payload bundle instead. Adding an explicit `module_identifier` does not create +missing backend metadata, so it is not required for that fallback. + ### WORKSPACE single-service WORKSPACE Python onboarding uses the same runtime contract as Bzlmod: sync @@ -792,6 +806,7 @@ topt.test_optimization_multi_sync( services = ["py-service-a", "py-service-b"], runtime_name = "python", runtime_version = "3.12", + enabled_by_env = True, ) use_repo( @@ -874,6 +889,7 @@ topt.test_optimization_sync( service = "java-service", runtime_name = "java", runtime_version = "17", + enabled_by_env = True, ) use_repo(topt, "test_optimization_data") @@ -952,6 +968,7 @@ topt.test_optimization_multi_sync( services = ["java-service-a", "java-service-b"], runtime_name = "java", runtime_version = "17", + enabled_by_env = True, ) use_repo( @@ -1045,6 +1062,7 @@ topt.test_optimization_sync( service = "nodejs-service", runtime_name = "nodejs", runtime_version = "22.22.0", + enabled_by_env = True, ) use_repo(topt, "test_optimization_data") @@ -1118,6 +1136,7 @@ topt.test_optimization_multi_sync( services = ["nodejs-service-a", "nodejs-service-b"], runtime_name = "nodejs", runtime_version = "22.22.0", + enabled_by_env = True, ) use_repo( @@ -1227,6 +1246,7 @@ topt.test_optimization_sync( service = "dotnet-service", runtime_name = "dotnet", runtime_version = "8.0.100", + enabled_by_env = True, ) use_repo(topt, "test_optimization_data") @@ -1302,6 +1322,7 @@ topt.test_optimization_multi_sync( services = ["dotnet-service-a", "dotnet-service-b"], runtime_name = "dotnet", runtime_version = "8.0.100", + enabled_by_env = True, ) use_repo( @@ -1391,6 +1412,7 @@ topt.test_optimization_sync( service = "ruby-service", runtime_name = "ruby", runtime_version = "3.3.9", + enabled_by_env = True, ) use_repo(topt, "test_optimization_data") @@ -1466,6 +1488,7 @@ topt.test_optimization_multi_sync( services = ["ruby-service-a", "ruby-service-b"], runtime_name = "ruby", runtime_version = "3.3.9", + enabled_by_env = True, ) use_repo( diff --git a/docs/Troubleshooting.md b/docs/Troubleshooting.md index 3e6dc233..4fdaf1e0 100644 --- a/docs/Troubleshooting.md +++ b/docs/Troubleshooting.md @@ -252,6 +252,7 @@ values automatically. test_optimization_sync.test_optimization_sync( name = "test_optimization_data", debug = True, # Verbose logging + enabled_by_env = True, ) ``` diff --git a/docs/internal_monorepo_go_rollout_guide.md b/docs/internal_monorepo_go_rollout_guide.md index 6c1bc3d9..72c3dd32 100644 --- a/docs/internal_monorepo_go_rollout_guide.md +++ b/docs/internal_monorepo_go_rollout_guide.md @@ -87,8 +87,8 @@ SHA256, and archive prefix generated from the same published commit. ``` Removing `--config=test-optimization` disables both metadata repositories - and Orchestrion aliases. `enabled_by_env = True` controls metadata only; it - does not dynamically control Orchestrion repository declaration. + and Orchestrion aliases. The public Go helpers enable metadata gating by + default; it does not dynamically control Orchestrion repository declaration. ## Bootstrap Flow @@ -116,6 +116,16 @@ bazel run @datadog-rules-test-optimization-go//:dd_topt_go_bootstrap -- \ --control-target "//path/to/plain/control:go_default_test" ``` +### Updating an existing managed config + +When upgrading from the current release, rerun the same bootstrap with +`--write-bazelrc`. It replaces the content between the Datadog-managed markers +with the current single-config contract, preserves all content outside those +markers, and is idempotent. This adds both +`DD_TEST_OPTIMIZATION_ENABLED=1` and the existing `rules_go` Orchestrion flag to +the named config; consumers do not need a separate migration mode or a second +bool flag. + If the repository owns checked-in `go_repository(...)` declarations, run the repository-owned refresh command after targeted Go module sync and rerun bootstrap with `--check-go-repositories`. Bootstrap should verify those pins; it diff --git a/examples/README.md b/examples/README.md index 497f673a..cb2741a2 100644 --- a/examples/README.md +++ b/examples/README.md @@ -485,6 +485,7 @@ topt_go.test_optimization_sync( service = "go-service", runtime_name = "go", runtime_version = "1.25.0", + enabled_by_env = True, ) topt_py = use_extension( @@ -496,6 +497,7 @@ topt_py.test_optimization_sync( service = "py-service", runtime_name = "python", runtime_version = "3.12", + enabled_by_env = True, ) use_repo(topt_go, "test_optimization_data_go") diff --git a/examples/multi_service/MODULE.bazel b/examples/multi_service/MODULE.bazel index f9b73893..25c74c66 100644 --- a/examples/multi_service/MODULE.bazel +++ b/examples/multi_service/MODULE.bazel @@ -36,7 +36,6 @@ go_topt = use_extension( go_topt.test_optimization_go( name = "test_optimization_data", debug = True, - enabled_by_env = True, runtime_version = "1.25.0", services = [ "go-service-a", diff --git a/examples/single_service/MODULE.bazel b/examples/single_service/MODULE.bazel index 48a4abac..abc71021 100644 --- a/examples/single_service/MODULE.bazel +++ b/examples/single_service/MODULE.bazel @@ -109,7 +109,6 @@ go_topt = use_extension( go_topt.test_optimization_go( name = "test_optimization_data", debug = True, - enabled_by_env = True, runtime_version = "1.25.0", # Logical service identifier for Datadog metadata (not language-bound). # A single service can include tests from multiple runtimes. diff --git a/modules/go/tests/BUILD.bazel b/modules/go/tests/BUILD.bazel index 44103eb7..fabd17a7 100644 --- a/modules/go/tests/BUILD.bazel +++ b/modules/go/tests/BUILD.bazel @@ -8,6 +8,7 @@ load( ":test_extension.bzl", "go_multi_specs_propagate_non_default_enablement_and_flaky_tests_test", "go_single_spec_propagates_non_default_enablement_and_flaky_tests_test", + "go_specs_default_to_config_gated_test", ) load( ":test_macro.bzl", @@ -123,6 +124,7 @@ load( ":test_workspace_helpers.bzl", "go_workspace_multi_specs_test", "go_workspace_single_specs_test", + "go_workspace_specs_default_to_config_gated_test", "orchestrion_call_spec_test", ) @@ -145,6 +147,12 @@ go_multi_specs_propagate_non_default_enablement_and_flaky_tests_test( timeout = "short", ) +go_specs_default_to_config_gated_test( + name = "go_specs_default_to_config_gated_test", + size = "small", + timeout = "short", +) + go_workspace_single_specs_test( name = "go_workspace_single_specs_test", size = "small", @@ -157,6 +165,12 @@ go_workspace_multi_specs_test( timeout = "short", ) +go_workspace_specs_default_to_config_gated_test( + name = "go_workspace_specs_default_to_config_gated_test", + size = "small", + timeout = "short", +) + orchestrion_call_spec_test( name = "orchestrion_call_spec_test", size = "small", @@ -168,6 +182,7 @@ test_suite( tests = [ ":go_workspace_multi_specs_test", ":go_workspace_single_specs_test", + ":go_workspace_specs_default_to_config_gated_test", ":orchestrion_call_spec_test", ], ) @@ -725,9 +740,11 @@ test_suite( ":go_macro_test_optimization_public_wrapper_mode_test", ":go_multi_specs_propagate_non_default_enablement_and_flaky_tests_test", ":go_single_spec_propagates_non_default_enablement_and_flaky_tests_test", + ":go_specs_default_to_config_gated_test", ":go_stub_includes_manifest_in_files_test", ":go_workspace_multi_specs_test", ":go_workspace_single_specs_test", + ":workspace_helpers_tests", ":has_go_mod_pin_test", ":has_package_local_go_mod_test", ":normalize_user_data_handles_none_test", diff --git a/modules/go/tests/test_extension.bzl b/modules/go/tests/test_extension.bzl index f80eee5f..607fdd64 100644 --- a/modules/go/tests/test_extension.bzl +++ b/modules/go/tests/test_extension.bzl @@ -21,11 +21,11 @@ def _go_single_spec_propagates_non_default_enablement_and_flaky_tests_test(ctx): module_path = "example.com/repo", runtime_version = "1.25.9", enabled = False, - enabled_by_env = True, + enabled_by_env = False, flaky_tests = False, ) asserts.equals(env, False, spec["enabled"]) - asserts.equals(env, True, spec["enabled_by_env"]) + asserts.equals(env, False, spec["enabled_by_env"]) asserts.equals(env, False, spec["flaky_tests"]) asserts.equals(env, "go", spec["runtime_name"]) return unittest.end(env) @@ -38,16 +38,30 @@ def _go_multi_specs_propagate_non_default_enablement_and_flaky_tests_test(ctx): module_path = "example.com/repo", runtime_version = "1.25.9", enabled = False, - enabled_by_env = True, + enabled_by_env = False, flaky_tests = False, ) asserts.equals(env, 1, len(specs)) asserts.equals(env, False, specs[0]["enabled"]) - asserts.equals(env, True, specs[0]["enabled_by_env"]) + asserts.equals(env, False, specs[0]["enabled_by_env"]) asserts.equals(env, False, specs[0]["flaky_tests"]) asserts.equals(env, "go", specs[0]["runtime_name"]) return unittest.end(env) +def _go_specs_default_to_config_gated_test(ctx): + env = unittest.begin(ctx) + single = build_go_single_repo_spec_for_tests( + name = "test_optimization_data_go", + service = "go-service", + ) + multi = build_go_multi_repo_specs_for_tests( + name = "test_optimization_data_go", + services = ["go-service"], + ) + asserts.equals(env, True, single["enabled_by_env"]) + asserts.equals(env, True, multi[0]["enabled_by_env"]) + return unittest.end(env) + go_single_spec_propagates_non_default_enablement_and_flaky_tests_test = unittest.make( _go_single_spec_propagates_non_default_enablement_and_flaky_tests_test, ) @@ -55,3 +69,7 @@ go_single_spec_propagates_non_default_enablement_and_flaky_tests_test = unittest go_multi_specs_propagate_non_default_enablement_and_flaky_tests_test = unittest.make( _go_multi_specs_propagate_non_default_enablement_and_flaky_tests_test, ) + +go_specs_default_to_config_gated_test = unittest.make( + _go_specs_default_to_config_gated_test, +) diff --git a/modules/go/tests/test_workspace_helpers.bzl b/modules/go/tests/test_workspace_helpers.bzl index 572a53f4..329cb4e8 100644 --- a/modules/go/tests/test_workspace_helpers.bzl +++ b/modules/go/tests/test_workspace_helpers.bzl @@ -16,7 +16,7 @@ load( "build_go_workspace_sync_specs_for_tests", ) -def _assert_sync_spec(env, spec, name, service): +def _assert_sync_spec(env, spec, name, service, expected_enabled_by_env): asserts.equals(env, name, spec["name"]) asserts.equals(env, name, spec["repo_name"]) asserts.equals(env, service, spec["service"]) @@ -26,7 +26,7 @@ def _assert_sync_spec(env, spec, name, service): asserts.equals(env, "example.com/workspace", spec["runtime_module_path"]) asserts.equals(env, "custom_topt", spec["out_dir"]) asserts.equals(env, False, spec["enabled"]) - asserts.equals(env, True, spec["enabled_by_env"]) + asserts.equals(env, expected_enabled_by_env, spec["enabled_by_env"]) asserts.equals(env, 11, spec["http_connect_timeout_seconds"]) asserts.equals(env, 22, spec["http_max_time_seconds"]) asserts.equals(env, 3, spec["http_retry_attempts"]) @@ -46,7 +46,7 @@ def _go_workspace_single_specs_test(ctx): runtime_version = "1.25.9", module_path = "example.com/workspace", enabled = False, - enabled_by_env = True, + enabled_by_env = False, runtime_arch = "arm64", out_dir = "custom_topt", http_connect_timeout_seconds = 11, @@ -61,7 +61,7 @@ def _go_workspace_single_specs_test(ctx): debug = True, ) asserts.equals(env, 1, len(result["sync_specs"])) - _assert_sync_spec(env, result["sync_specs"][0], "test_optimization_data_go", "go-service") + _assert_sync_spec(env, result["sync_specs"][0], "test_optimization_data_go", "go-service", False) asserts.equals(env, None, result["aggregate_spec"]) return unittest.end(env) @@ -73,7 +73,7 @@ def _go_workspace_multi_specs_test(ctx): runtime_version = "1.25.9", module_path = "example.com/workspace", enabled = False, - enabled_by_env = True, + enabled_by_env = False, runtime_arch = "arm64", out_dir = "custom_topt", http_connect_timeout_seconds = 11, @@ -103,9 +103,21 @@ def _go_workspace_multi_specs_test(ctx): result["sync_specs"][i], result["aggregate_spec"]["repo_names"][i], ["go-service-a", "go-service-a", "go-service-b"][i], + False, ) return unittest.end(env) +def _go_workspace_specs_default_to_config_gated_test(ctx): + env = unittest.begin(ctx) + result = build_go_workspace_sync_specs_for_tests( + name = "test_optimization_data_go", + service = "go-service", + runtime_version = "1.25.9", + module_path = "example.com/workspace", + ) + asserts.equals(env, True, result["sync_specs"][0]["enabled_by_env"]) + return unittest.end(env) + def _orchestrion_call_spec_test(ctx): env = unittest.begin(ctx) call = build_orchestrion_repo_call_for_tests( @@ -131,4 +143,5 @@ def _orchestrion_call_spec_test(ctx): go_workspace_single_specs_test = unittest.make(_go_workspace_single_specs_test) go_workspace_multi_specs_test = unittest.make(_go_workspace_multi_specs_test) +go_workspace_specs_default_to_config_gated_test = unittest.make(_go_workspace_specs_default_to_config_gated_test) orchestrion_call_spec_test = unittest.make(_orchestrion_call_spec_test) diff --git a/modules/go/tools/dd_topt_go_bootstrap/main.go b/modules/go/tools/dd_topt_go_bootstrap/main.go index adcda250..ba606071 100644 --- a/modules/go/tools/dd_topt_go_bootstrap/main.go +++ b/modules/go/tools/dd_topt_go_bootstrap/main.go @@ -1937,12 +1937,12 @@ datadog_go_test_optimization_workspace_repositories( buf.WriteString(fmt.Sprintf(`load("@%s//go:deps.bzl", "go_register_toolchains", "go_rules_dependencies") load("@datadog-rules-test-optimization-go//:topt_go_orchestrion_repository.bzl", "dd_topt_go_orchestrion_tool_repo") -go_rules_dependencies() -go_register_toolchains(version = "") dd_topt_go_orchestrion_tool_repo( version = "%s", %s ) +go_rules_dependencies() +go_register_toolchains(version = "") `, cfg.rulesGoRepoName, cfg.orchestrionVersion, workspaceSnippetTracerConfig(cfg))) if cfg.workspaceMode || strings.TrimSpace(cfg.service) != "" || strings.TrimSpace(cfg.runtimeVersion) != "" { buf.WriteString("\n") @@ -1964,8 +1964,7 @@ dd_topt_go_workspace_sync_repositories( name = "%s", service = "%s", runtime_version = "%s", -%s enabled_by_env = True, - require_git_metadata = True, +%s require_git_metadata = True, ) `, cfg.syncRepoName, cfg.service, cfg.runtimeVersion, modulePathLine) } diff --git a/modules/go/tools/dd_topt_go_bootstrap/main_test.go b/modules/go/tools/dd_topt_go_bootstrap/main_test.go index 56522f71..9d252b1a 100644 --- a/modules/go/tools/dd_topt_go_bootstrap/main_test.go +++ b/modules/go/tools/dd_topt_go_bootstrap/main_test.go @@ -172,6 +172,11 @@ func TestWorkspaceSnippetSupportsMixedFetchModes(t *testing.T) { t.Fatalf("workspace snippet missing %q:\n%s", want, got) } } + toolRepoCall := strings.Index(got, "\ndd_topt_go_orchestrion_tool_repo(") + dependenciesCall := strings.Index(got, "\ngo_rules_dependencies()\n") + if toolRepoCall < 0 || dependenciesCall < 0 || toolRepoCall > dependenciesCall { + t.Fatalf("workspace snippet must declare the real Orchestrion repository before rules_go installs its fallback:\n%s", got) + } } func TestWorkspaceSnippetFallsBackToRulesGoCommit(t *testing.T) { @@ -243,7 +248,6 @@ func TestWorkspaceModeSnippetIncludesSyncAndBaseVariant(t *testing.T) { `name = "test_optimization_data_worker"`, `service = "worker"`, `runtime_version = "1.25.9"`, - `enabled_by_env = True`, `require_git_metadata = True`, } { if !strings.Contains(got, want) { @@ -1191,6 +1195,14 @@ test:old --test_env=DD_GIT_BRANCH=main if strings.Contains(text, "--test_env=DD_GIT_BRANCH") || strings.Count(text, bazelrcBlockStart) != 1 { t.Fatalf("expected old managed block to be replaced:\n%s", text) } + for _, want := range []string{ + `common:test-optimization --repo_env=DD_TEST_OPTIMIZATION_ENABLED=1`, + `build:test-optimization --@rules_go//go/private/orchestrion:enabled=true`, + } { + if !strings.Contains(text, want) { + t.Fatalf("migrated managed block missing %q:\n%s", want, text) + } + } } func TestWriteBazelrcBlockRejectsPathTraversal(t *testing.T) { diff --git a/modules/go/topt_go_extension.bzl b/modules/go/topt_go_extension.bzl index 7925334b..28fc3992 100644 --- a/modules/go/topt_go_extension.bzl +++ b/modules/go/topt_go_extension.bzl @@ -60,7 +60,7 @@ def _build_go_single_repo_spec( test_management = True, flaky_tests = True, enabled = True, - enabled_by_env = False, + enabled_by_env = True, require_git_metadata = False, debug = False): """Build the complete sync spec for one Go service repository.""" @@ -103,7 +103,7 @@ def _build_go_multi_repo_specs( test_management = True, flaky_tests = True, enabled = True, - enabled_by_env = False, + enabled_by_env = True, require_git_metadata = False, debug = False): """Build the per-service sync specs for a multi-service Go tag.""" @@ -233,7 +233,7 @@ test_optimization_go_extension = module_extension( "test_management": attr.bool(default = True), "flaky_tests": attr.bool(default = True), "enabled": attr.bool(default = True), - "enabled_by_env": attr.bool(default = False), + "enabled_by_env": attr.bool(default = True), "require_git_metadata": attr.bool(default = False), "debug": attr.bool(default = False), }), diff --git a/modules/go/topt_go_workspace.bzl b/modules/go/topt_go_workspace.bzl index c7dc0dbd..49813080 100644 --- a/modules/go/topt_go_workspace.bzl +++ b/modules/go/topt_go_workspace.bzl @@ -30,7 +30,7 @@ def _build_go_workspace_sync_specs( service = None, services = [], enabled = True, - enabled_by_env = False, + enabled_by_env = True, runtime_arch = "", out_dir = "", http_connect_timeout_seconds = -1, @@ -98,7 +98,7 @@ def dd_topt_go_workspace_sync_repositories( service = None, services = [], enabled = True, - enabled_by_env = False, + enabled_by_env = True, runtime_arch = "", out_dir = "", http_connect_timeout_seconds = -1, diff --git a/modules/python/tests/BUILD.bazel b/modules/python/tests/BUILD.bazel index 4b4a2e14..e710b4f9 100644 --- a/modules/python/tests/BUILD.bazel +++ b/modules/python/tests/BUILD.bazel @@ -32,6 +32,10 @@ load( "py_macro_consumer_runner_with_main_target", "py_macro_default_rule_detection_target_rule", "py_macro_default_rule_detection_test", + "py_macro_disabled_consumer_runner_target", + "py_macro_disabled_consumer_runner_test", + "py_macro_disabled_managed_pytest_target", + "py_macro_disabled_managed_pytest_test", "py_macro_env_none_target", "py_macro_env_none_wiring_test", "py_macro_explicit_service_target", @@ -399,6 +403,26 @@ py_macro_managed_pytest_kwargs_test( target_under_test = ":py_macro_managed_pytest_kwargs_target__raw_python_test", ) +py_macro_disabled_consumer_runner_target( + name = "py_macro_disabled_consumer_runner_target", + tags = ["consumer_tag"], +) + +py_macro_disabled_consumer_runner_test( + name = "py_macro_disabled_consumer_runner_test", + target_under_test = ":py_macro_disabled_consumer_runner_target", +) + +py_macro_disabled_managed_pytest_target( + name = "py_macro_disabled_managed_pytest_target", + tags = ["consumer_tag"], +) + +py_macro_disabled_managed_pytest_test( + name = "py_macro_disabled_managed_pytest_test", + target_under_test = ":py_macro_disabled_managed_pytest_target", +) + py_macro_consumer_runner_no_rule_no_main_target_rule( name = "py_macro_consumer_runner_no_rule_no_main_target", tags = ["manual"], @@ -616,6 +640,8 @@ test_suite( ":py_macro_consumer_runner_validation_helpers_test", ":py_macro_consumer_runner_wiring_test", ":py_macro_default_rule_detection_test", + ":py_macro_disabled_consumer_runner_test", + ":py_macro_disabled_managed_pytest_test", ":py_macro_env_none_wiring_test", ":py_macro_explicit_service_wiring_test", ":py_macro_fallback_payloads_test", diff --git a/modules/python/tests/test_macro.bzl b/modules/python/tests/test_macro.bzl index ef339675..35c2f596 100644 --- a/modules/python/tests/test_macro.bzl +++ b/modules/python/tests/test_macro.bzl @@ -282,6 +282,11 @@ def _single_service_topt_data(): }, } +def _disabled_single_service_topt_data(): + disabled = dict(_single_service_topt_data()) + disabled["enabled"] = False + return disabled + def _multi_service_topt_data(): selected = _single_service_topt_data() not_selected = dict(selected) @@ -483,6 +488,36 @@ def py_macro_managed_pytest_kwargs_target(name, tags = None): tags = tags, ) +def py_macro_disabled_consumer_runner_target(name, tags = None): + """Disabled sync preserves the consumer runner without instrumentation.""" + dd_topt_py_test( + name = name, + topt_data = _disabled_single_service_topt_data(), + py_test_rule = _py_test_kwargs_capture_macro, + runner_mode = "consumer_runner", + srcs = ["consumer_runner_main.py"], + data = [":test_macro.bzl"], + dd_requirements = ["pytest"], + env = select({ + "//conditions:default": { + "CUSTOM_ENV": "preserved", + "DD_CIVISIBILITY_ENABLED": "true", + }, + }), + args = ["-k", "consumer"], + tags = tags, + ) + +def py_macro_disabled_managed_pytest_target(name, tags = None): + """Disabled sync keeps the managed pytest runner but omits instrumentation.""" + dd_topt_py_test( + name = name, + topt_data = _disabled_single_service_topt_data(), + py_test_rule = _py_test_kwargs_capture_macro, + srcs = ["consumer_runner_main.py"], + tags = tags, + ) + def _py_macro_consumer_runner_no_rule_no_main_target_impl(_ctx): dd_topt_py_test( name = "should_not_be_created", @@ -673,6 +708,41 @@ def _py_macro_managed_pytest_kwargs_test_impl(ctx): ) return analysistest.end(env) +def _assert_no_test_optimization_wiring(env, captured): + asserts.equals(env, "false", captured.env.get("DD_CIVISIBILITY_ENABLED")) + asserts.equals(env, None, captured.env.get("DD_SERVICE")) + asserts.equals(env, None, captured.env.get("DD_TEST_OPTIMIZATION_MANIFEST_FILE")) + asserts.equals(env, None, captured.env.get("DD_TEST_OPTIMIZATION_PAYLOADS_IN_FILES")) + asserts.equals(env, None, captured.env.get("DD_TEST_OPTIMIZATION_BAZEL_TARGET_METADATA_BASENAME")) + for label in captured.data_labels: + asserts.false(env, "topt_payloads" in label) + asserts.false(env, ".testoptimization" in label) + +def _py_macro_disabled_consumer_runner_test_impl(ctx): + env = analysistest.begin(ctx) + captured = analysistest.target_under_test(env)[ToptPyKwargsCaptureInfo] + _assert_no_test_optimization_wiring(env, captured) + asserts.equals(env, "preserved", captured.env.get("CUSTOM_ENV")) + asserts.true(env, captured.saw_args) + asserts.false(env, captured.saw_imports) + asserts.false(env, captured.saw_main) + asserts.false(env, captured.saw_run_pytest) + asserts.equals(env, ["pytest"], captured.dd_requirements) + asserts.true(env, _has_label_suffix(captured.data_labels, ":test_macro.bzl")) + asserts.false(env, "manual" in captured.tags) + return analysistest.end(env) + +def _py_macro_disabled_managed_pytest_test_impl(ctx): + env = analysistest.begin(ctx) + captured = analysistest.target_under_test(env)[ToptPyKwargsCaptureInfo] + _assert_no_test_optimization_wiring(env, captured) + asserts.true(env, captured.saw_args) + asserts.true(env, captured.saw_imports) + asserts.true(env, captured.saw_main) + asserts.true(env, captured.saw_run_pytest) + asserts.false(env, "manual" in captured.tags) + return analysistest.end(env) + def _py_macro_consumer_runner_no_rule_no_main_failure_test_impl(ctx): env = analysistest.begin(ctx) asserts.expect_failure(env, "requires a consumer-owned Python test runner") @@ -728,6 +798,12 @@ py_macro_consumer_runner_select_env_test = analysistest.make( py_macro_managed_pytest_kwargs_test = analysistest.make( _py_macro_managed_pytest_kwargs_test_impl, ) +py_macro_disabled_consumer_runner_test = analysistest.make( + _py_macro_disabled_consumer_runner_test_impl, +) +py_macro_disabled_managed_pytest_test = analysistest.make( + _py_macro_disabled_managed_pytest_test_impl, +) py_macro_consumer_runner_no_rule_no_main_failure_test = analysistest.make( _py_macro_consumer_runner_no_rule_no_main_failure_test_impl, expect_failure = True, diff --git a/modules/python/tools/dd_topt_py_bootstrap/main.py b/modules/python/tools/dd_topt_py_bootstrap/main.py index 2adb0b2b..b2be07a9 100644 --- a/modules/python/tools/dd_topt_py_bootstrap/main.py +++ b/modules/python/tools/dd_topt_py_bootstrap/main.py @@ -349,9 +349,12 @@ def render_test_snippet(args: argparse.Namespace) -> str: if args.runner_mode == "consumer_runner": load_line = "# load(\"//path/to:python_rules.bzl\", \"your_py_test_rule\")" rule_ref = "your_py_test_rule" + module_identifier_line = "" if args.py_test_rule_load_label and args.py_test_rule_symbol: load_line = f"load({_quote(args.py_test_rule_load_label)}, {_quote(args.py_test_rule_symbol)})" rule_ref = args.py_test_rule_symbol + if args.module_identifier: + module_identifier_line = f" module_identifier = {_quote(args.module_identifier)},\n" return dedent( f""" load("@datadog-rules-test-optimization-python//:topt_py_test.bzl", "dd_topt_py_test") @@ -363,8 +366,7 @@ def render_test_snippet(args: argparse.Namespace) -> str: topt_data = topt_data, runner_mode = "consumer_runner", py_test_rule = {rule_ref}, - module_identifier = {_quote(args.module_identifier or args.runtime_module_path)}, - srcs = ["test_example.py"], + {module_identifier_line} srcs = ["test_example.py"], deps = [ requirement("ddtrace"), requirement("pytest"), diff --git a/modules/python/tools/dd_topt_py_bootstrap/main_test.py b/modules/python/tools/dd_topt_py_bootstrap/main_test.py index 2b0b24b1..7d0f6364 100644 --- a/modules/python/tools/dd_topt_py_bootstrap/main_test.py +++ b/modules/python/tools/dd_topt_py_bootstrap/main_test.py @@ -188,7 +188,7 @@ def test_managed_pytest_snippet_lists_consumer_dependencies(self) -> None: self.assertIn('requirement("pytest")', snippet) def test_consumer_runner_snippet_contains_module_identifier(self) -> None: - """Consumer runner examples preserve module selection guidance.""" + """An explicit module-selection exception is preserved in output.""" args = _args( "--runner-mode=consumer_runner", "--module-identifier=example.python.app", @@ -200,6 +200,17 @@ def test_consumer_runner_snippet_contains_module_identifier(self) -> None: self.assertIn('module_identifier = "example.python.app"', snippet) self.assertIn("py_test_rule = dd_py_test", snippet) + def test_consumer_runner_snippet_uses_derived_module_identifier_by_default(self) -> None: + """The normal onboarding path does not require per-target identifiers.""" + args = _args( + "--runner-mode=consumer_runner", + "--py-test-rule-load-label=//tools:python.bzl", + "--py-test-rule-symbol=dd_py_test", + ) + snippet = main.render_test_snippet(args) + self.assertNotIn("module_identifier", snippet) + self.assertIn('srcs = ["test_example.py"]', snippet) + def test_write_modes_are_idempotent_and_preserve_user_content(self) -> None: """Managed block writes preserve unmanaged content and replace only generated content.""" with tempfile.TemporaryDirectory() as tmp: @@ -215,6 +226,28 @@ def test_write_modes_are_idempotent_and_preserve_user_content(self) -> None: self.assertIn("# user content", second) self.assertEqual(1, second.count(main.BEGIN_MARKER)) + def test_write_bazelrc_upgrades_an_older_managed_block(self) -> None: + """Re-running onboarding upgrades managed config without touching user content.""" + with tempfile.TemporaryDirectory() as tmp: + path = Path(tmp) / ".bazelrc" + path.write_text( + "# user content\n" + f"{main.BEGIN_MARKER}\n" + "common:test-optimization --repo_env=DD_API_KEY\n" + f"{main.END_MARKER}\n", + encoding="utf-8", + ) + args = _args("--write-bazelrc", f"--bazelrc-path={path}") + main._validate_args(args) + main.write_outputs(args) + content = path.read_text(encoding="utf-8") + self.assertIn("# user content", content) + self.assertEqual(1, content.count(main.BEGIN_MARKER)) + self.assertIn( + "common:test-optimization --repo_env=DD_TEST_OPTIMIZATION_ENABLED=1", + content, + ) + def test_write_targets_creates_parent_directories(self) -> None: """Target write mode creates lightweight packages on demand.""" with tempfile.TemporaryDirectory() as tmp: diff --git a/modules/python/topt_py_test.bzl b/modules/python/topt_py_test.bzl index af90f31c..c0e79c40 100644 --- a/modules/python/topt_py_test.bzl +++ b/modules/python/topt_py_test.bzl @@ -162,6 +162,25 @@ def _validate_consumer_runner_inputs(py_test_rule_was_explicit, py_test_rule_is_ "for the built-in pytest runner.", ) +def _define_uninstrumented_py_test(name, py_test_rule, runner_mode, kwargs): + """Define the consumer test without Datadog wiring when sync is disabled.""" + test_kwargs = dict(kwargs) + test_kwargs["env"] = _merge_user_env( + test_kwargs.get("env"), + {"DD_CIVISIBILITY_ENABLED": "false"}, + macro_name = "dd_topt_py_test", + ) + if runner_mode == _RUNNER_MODE_MANAGED_PYTEST and test_kwargs.get("main") == None: + pkg_path = native.package_name() + test_kwargs["srcs"] = _append_data_dependencies(test_kwargs.get("srcs"), [_RUN_PYTEST]) + test_kwargs["main"] = _RUN_PYTEST + if "args" not in test_kwargs: + test_kwargs["args"] = [pkg_path] if pkg_path else [] + if "imports" not in test_kwargs: + test_kwargs["imports"] = [pkg_path] if pkg_path else [] + test_kwargs["name"] = name + py_test_rule(**test_kwargs) + # Public aliases for unit tests. validate_runner_mode_for_tests = _validate_runner_mode validate_consumer_runner_inputs_for_tests = _validate_consumer_runner_inputs @@ -202,6 +221,20 @@ def dd_topt_py_test( py_test_rule_is_default = _is_default_py_test_rule(py_test_rule) _svc = _select_service_entry_or_fail(topt_data, topt_service) + if runner_mode == _RUNNER_MODE_CONSUMER_RUNNER: + _validate_consumer_runner_inputs( + py_test_rule_was_explicit, + py_test_rule_is_default, + kwargs.get("main"), + ) + + # A disabled sync repository exports the same schema with enabled = False. + # Keep the consumer's test runnable, but do not create selectors, wrappers, + # metadata targets, Datadog env, or payload-producing instrumentation. + if not _svc.get("enabled", True): + _define_uninstrumented_py_test(name, py_test_rule, runner_mode, kwargs) + return + wrapper_kwargs, raw_passthrough = split_test_wrapper_kwargs(kwargs) user_data = kwargs.pop("data", None) @@ -222,9 +255,6 @@ def dd_topt_py_test( user_srcs = kwargs.pop("srcs", None) user_main = kwargs.pop("main", None) - if runner_mode == _RUNNER_MODE_CONSUMER_RUNNER: - _validate_consumer_runner_inputs(py_test_rule_was_explicit, py_test_rule_is_default, user_main) - # args is a wrapper-only attr; split_test_wrapper_kwargs already moved it to wrapper_kwargs. user_args = wrapper_kwargs.pop("args", None) diff --git a/third_party/rgo/v0_60_0/base.CHANGED_FILES.md b/third_party/rgo/v0_60_0/base.CHANGED_FILES.md index af16f01f..f8068104 100644 --- a/third_party/rgo/v0_60_0/base.CHANGED_FILES.md +++ b/third_party/rgo/v0_60_0/base.CHANGED_FILES.md @@ -12,8 +12,8 @@ This file is generated. Do not edit by hand. ## Summary -- Total changed paths: `53` -- Modified files: `29` +- Total changed paths: `54` +- Modified files: `30` - Added files: `24` - Removed files: `0` @@ -30,6 +30,7 @@ This file is generated. Do not edit by hand. - `go/private/actions/link.bzl` - `go/private/actions/stdlib.bzl` - `go/private/context.bzl` +- `go/private/repositories.bzl` - `go/private/rules/library.bzl` - `go/private/rules/stdlib.bzl` - `go/private/rules/test.bzl` diff --git a/third_party/rgo/v0_60_0/base/go/private/repositories.bzl b/third_party/rgo/v0_60_0/base/go/private/repositories.bzl index 94adead5..27895206 100644 --- a/third_party/rgo/v0_60_0/base/go/private/repositories.bzl +++ b/third_party/rgo/v0_60_0/base/go/private/repositories.bzl @@ -17,6 +17,7 @@ load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive") load("//go/private:common.bzl", "MINIMUM_BAZEL_VERSION") load("//go/private:nogo.bzl", "DEFAULT_NOGO", "go_register_nogo") +load("//go/private/orchestrion:extensions.bzl", "orchestrion_empty_repository") load("//go/private:polyfill_bazel_features.bzl", "polyfill_bazel_features") load("//go/private/skylib/lib:versions.bzl", "versions") load("//proto:gogo.bzl", "gogo_special_proto") @@ -39,6 +40,15 @@ def go_rules_dependencies(force = False): if getattr(native, "bazel_version", None): versions.check(MINIMUM_BAZEL_VERSION, bazel_version = native.bazel_version) + # Keep the stable Orchestrion repository mapping available to ordinary + # WORKSPACE consumers. Test Optimization consumers declare the real tool + # repository before calling go_rules_dependencies(), so this fallback does + # not replace or fetch it. + _maybe( + orchestrion_empty_repository, + name = "rules_go_orchestrion_tool", + ) + if force: wrapper = _always else: diff --git a/third_party/rgo/v0_61_1/base.CHANGED_FILES.md b/third_party/rgo/v0_61_1/base.CHANGED_FILES.md index cab689be..bed6af76 100644 --- a/third_party/rgo/v0_61_1/base.CHANGED_FILES.md +++ b/third_party/rgo/v0_61_1/base.CHANGED_FILES.md @@ -12,8 +12,8 @@ This file is generated. Do not edit by hand. ## Summary -- Total changed paths: `53` -- Modified files: `29` +- Total changed paths: `54` +- Modified files: `30` - Added files: `24` - Removed files: `0` @@ -30,6 +30,7 @@ This file is generated. Do not edit by hand. - `go/private/actions/link.bzl` - `go/private/actions/stdlib.bzl` - `go/private/context.bzl` +- `go/private/repositories.bzl` - `go/private/rules/library.bzl` - `go/private/rules/stdlib.bzl` - `go/private/rules/test.bzl` diff --git a/third_party/rgo/v0_61_1/base/go/private/repositories.bzl b/third_party/rgo/v0_61_1/base/go/private/repositories.bzl index 865c41fd..c57f7198 100644 --- a/third_party/rgo/v0_61_1/base/go/private/repositories.bzl +++ b/third_party/rgo/v0_61_1/base/go/private/repositories.bzl @@ -17,6 +17,7 @@ load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive") load("//go/private:common.bzl", "MINIMUM_BAZEL_VERSION") load("//go/private:nogo.bzl", "DEFAULT_NOGO", "go_register_nogo") +load("//go/private/orchestrion:extensions.bzl", "orchestrion_empty_repository") load("//go/private:polyfill_bazel_features.bzl", "polyfill_bazel_features") load("//go/private/skylib/lib:versions.bzl", "versions") load("//proto:gogo.bzl", "gogo_special_proto") @@ -39,6 +40,15 @@ def go_rules_dependencies(force = False): if getattr(native, "bazel_version", None): versions.check(MINIMUM_BAZEL_VERSION, bazel_version = native.bazel_version) + # Keep the stable Orchestrion repository mapping available to ordinary + # WORKSPACE consumers. Test Optimization consumers declare the real tool + # repository before calling go_rules_dependencies(), so this fallback does + # not replace or fetch it. + _maybe( + orchestrion_empty_repository, + name = "rules_go_orchestrion_tool", + ) + if force: wrapper = _always else: diff --git a/third_party/rules_go_orchestrion/patches/v0_60_0/base/0001-full-delta.patch b/third_party/rules_go_orchestrion/patches/v0_60_0/base/0001-full-delta.patch index 1628affe..b6277474 100644 --- a/third_party/rules_go_orchestrion/patches/v0_60_0/base/0001-full-delta.patch +++ b/third_party/rules_go_orchestrion/patches/v0_60_0/base/0001-full-delta.patch @@ -3530,6 +3530,34 @@ index 0000000..a3f4e39 + }, + doc = "Groups Orchestrion pin files without treating every runtime data file as a build input.", +) +diff --git a/go/private/repositories.bzl b/go/private/repositories.bzl +index 94adead..2789520 100644 +--- a/go/private/repositories.bzl ++++ b/go/private/repositories.bzl +@@ -17,6 +17,7 @@ + load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive") + load("//go/private:common.bzl", "MINIMUM_BAZEL_VERSION") + load("//go/private:nogo.bzl", "DEFAULT_NOGO", "go_register_nogo") ++load("//go/private/orchestrion:extensions.bzl", "orchestrion_empty_repository") + load("//go/private:polyfill_bazel_features.bzl", "polyfill_bazel_features") + load("//go/private/skylib/lib:versions.bzl", "versions") + load("//proto:gogo.bzl", "gogo_special_proto") +@@ -39,6 +40,15 @@ def go_rules_dependencies(force = False): + if getattr(native, "bazel_version", None): + versions.check(MINIMUM_BAZEL_VERSION, bazel_version = native.bazel_version) + ++ # Keep the stable Orchestrion repository mapping available to ordinary ++ # WORKSPACE consumers. Test Optimization consumers declare the real tool ++ # repository before calling go_rules_dependencies(), so this fallback does ++ # not replace or fetch it. ++ _maybe( ++ orchestrion_empty_repository, ++ name = "rules_go_orchestrion_tool", ++ ) ++ + if force: + wrapper = _always + else: diff --git a/go/private/rules/library.bzl b/go/private/rules/library.bzl index ae1ce57..e195072 100644 --- a/go/private/rules/library.bzl diff --git a/third_party/rules_go_orchestrion/patches/v0_61_1/base/0001-full-delta.patch b/third_party/rules_go_orchestrion/patches/v0_61_1/base/0001-full-delta.patch index 7a4e1047..44236734 100644 --- a/third_party/rules_go_orchestrion/patches/v0_61_1/base/0001-full-delta.patch +++ b/third_party/rules_go_orchestrion/patches/v0_61_1/base/0001-full-delta.patch @@ -3523,6 +3523,34 @@ index 0000000..a3f4e39 + }, + doc = "Groups Orchestrion pin files without treating every runtime data file as a build input.", +) +diff --git a/go/private/repositories.bzl b/go/private/repositories.bzl +index 865c41f..c57f719 100644 +--- a/go/private/repositories.bzl ++++ b/go/private/repositories.bzl +@@ -17,6 +17,7 @@ + load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive") + load("//go/private:common.bzl", "MINIMUM_BAZEL_VERSION") + load("//go/private:nogo.bzl", "DEFAULT_NOGO", "go_register_nogo") ++load("//go/private/orchestrion:extensions.bzl", "orchestrion_empty_repository") + load("//go/private:polyfill_bazel_features.bzl", "polyfill_bazel_features") + load("//go/private/skylib/lib:versions.bzl", "versions") + load("//proto:gogo.bzl", "gogo_special_proto") +@@ -39,6 +40,15 @@ def go_rules_dependencies(force = False): + if getattr(native, "bazel_version", None): + versions.check(MINIMUM_BAZEL_VERSION, bazel_version = native.bazel_version) + ++ # Keep the stable Orchestrion repository mapping available to ordinary ++ # WORKSPACE consumers. Test Optimization consumers declare the real tool ++ # repository before calling go_rules_dependencies(), so this fallback does ++ # not replace or fetch it. ++ _maybe( ++ orchestrion_empty_repository, ++ name = "rules_go_orchestrion_tool", ++ ) ++ + if force: + wrapper = _always + else: diff --git a/go/private/rules/library.bzl b/go/private/rules/library.bzl index 1b9bfeb..79c0b41 100644 --- a/go/private/rules/library.bzl diff --git a/third_party/rules_go_orchestrion/profiles/workspace_runtime.json b/third_party/rules_go_orchestrion/profiles/workspace_runtime.json index d7daf822..7c0b23c4 100644 --- a/third_party/rules_go_orchestrion/profiles/workspace_runtime.json +++ b/third_party/rules_go_orchestrion/profiles/workspace_runtime.json @@ -14,6 +14,7 @@ "go/private/actions/stdlib.bzl", "go/private/context.bzl", "go/private/orchestrion/**", + "go/private/repositories.bzl", "go/private/rules/library.bzl", "go/private/rules/stdlib.bzl", "go/private/rules/test.bzl", diff --git a/tools/agent-skills/go-test-optimization-onboarding/references/workspace-onboarding.md b/tools/agent-skills/go-test-optimization-onboarding/references/workspace-onboarding.md index 3f71c6c2..24553747 100644 --- a/tools/agent-skills/go-test-optimization-onboarding/references/workspace-onboarding.md +++ b/tools/agent-skills/go-test-optimization-onboarding/references/workspace-onboarding.md @@ -245,10 +245,12 @@ dd_topt_go_orchestrion_tool_repo( version = "v1.9.0", ) +# Call the repository's existing go_rules_dependencies() wiring after the +# real tool repository. rules_go supplies the disabled fallback itself. + dd_topt_go_workspace_sync_repositories( name = "test_optimization_data_", debug = True, - enabled_by_env = True, require_git_metadata = True, module_path = "", runtime_version = "", @@ -256,10 +258,15 @@ dd_topt_go_workspace_sync_repositories( ) ``` +The public Go helper is config-gated by default. Do not add a second enable +attribute to each repository or test target. + The helper loads the public Orchestrion repository API through the Go companion's repository mapping, so consumers do not load it from their apparent `rules_go` repository directly. The apparent repository name still belongs in -the `.bazelrc` analysis-time flag shown above. +the `.bazelrc` analysis-time flag shown above. Do not load +`orchestrion_empty_repository`; the fork creates that fallback internally for +ordinary WORKSPACE use. If the repository already defines a Go version constant for Bazel toolchains, reuse that constant for `runtime_version` instead of hardcoding another copy. diff --git a/tools/agent-skills/java-test-optimization-onboarding/references/bzlmod-onboarding.md b/tools/agent-skills/java-test-optimization-onboarding/references/bzlmod-onboarding.md index 45d26448..46f1bdad 100644 --- a/tools/agent-skills/java-test-optimization-onboarding/references/bzlmod-onboarding.md +++ b/tools/agent-skills/java-test-optimization-onboarding/references/bzlmod-onboarding.md @@ -51,6 +51,7 @@ topt.test_optimization_sync( service = "", runtime_name = "java", runtime_version = "", + enabled_by_env = True, ) use_repo(topt, "test_optimization_data") diff --git a/tools/agent-skills/java-test-optimization-onboarding/references/workspace-onboarding.md b/tools/agent-skills/java-test-optimization-onboarding/references/workspace-onboarding.md index 54e7b386..d33ddfe9 100644 --- a/tools/agent-skills/java-test-optimization-onboarding/references/workspace-onboarding.md +++ b/tools/agent-skills/java-test-optimization-onboarding/references/workspace-onboarding.md @@ -120,6 +120,7 @@ test_optimization_sync( service = "", runtime_name = "java", runtime_version = "", + enabled_by_env = True, ) ``` diff --git a/tools/agent-skills/python-test-optimization-onboarding/references/consumer-runner.md b/tools/agent-skills/python-test-optimization-onboarding/references/consumer-runner.md index f74d07a0..a896b0cd 100644 --- a/tools/agent-skills/python-test-optimization-onboarding/references/consumer-runner.md +++ b/tools/agent-skills/python-test-optimization-onboarding/references/consumer-runner.md @@ -44,7 +44,6 @@ dd_topt_py_test( name = "pkg_py_test", py_test_rule = repo_pytest_wrapper, runner_mode = "consumer_runner", - module_identifier = "example.python.pkg", srcs = glob(["test_*.py"]), deps = [ ":pkg_lib", diff --git a/tools/agent-skills/python-test-optimization-onboarding/references/workspace-onboarding.md b/tools/agent-skills/python-test-optimization-onboarding/references/workspace-onboarding.md index fad4e3f0..b2550efe 100644 --- a/tools/agent-skills/python-test-optimization-onboarding/references/workspace-onboarding.md +++ b/tools/agent-skills/python-test-optimization-onboarding/references/workspace-onboarding.md @@ -198,7 +198,6 @@ dd_topt_py_test( name = "pkg_py_test", py_test_rule = repo_py_test, runner_mode = "consumer_runner", - module_identifier = "example.python.pkg", srcs = glob(["test_*.py"]), deps = [ requirement("ddtrace"), diff --git a/tools/core/test_optimization_sync.bzl b/tools/core/test_optimization_sync.bzl index a0934938..f93e3544 100644 --- a/tools/core/test_optimization_sync.bzl +++ b/tools/core/test_optimization_sync.bzl @@ -1252,7 +1252,8 @@ def _render_export_bzl( dotnet_module_included = False, ruby_module_path = "", sanitized_ruby_module_path = "", - ruby_module_included = False): + ruby_module_included = False, + enabled = True): """Render export.bzl content consumed by macros and BUILD files.""" repo_name_lit = json.encode(repo_name or "") service_name_lit = json.encode(service_name or "") @@ -1278,6 +1279,7 @@ def _render_export_bzl( return ( "# Generated by test_optimization_sync; unified exports for test optimization info\n" + "topt_data = {\n" + + " \"enabled\": %s,\n" % ("True" if enabled else "False") + " \"repo_name\": %s,\n" % repo_name_lit + " \"service_name\": %s,\n" % service_name_lit + " \"manifest_path\": %s,\n" % manifest_file_lit + @@ -1384,6 +1386,30 @@ def _render_disabled_telemetry_facts_json(service_name, runtime_name, environmen _append_telemetry_count(facts, "sync.disabled") return json.encode(facts) + "\n" +def _render_disabled_settings_json(): + """Render the canonical disabled settings response.""" + return json.encode({ + "data": { + "attributes": { + "known_tests_enabled": False, + "test_management": {"enabled": False}, + "flaky_test_retries_enabled": False, + }, + }, + }) + "\n" + +def _render_disabled_known_tests_json(): + """Render the canonical empty known-tests response.""" + return json.encode({"data": {"attributes": {"tests": {}}}}) + "\n" + +def _render_disabled_test_management_json(): + """Render the canonical empty test-management response.""" + return json.encode({"data": {"attributes": {"modules": {}}}}) + "\n" + +def _render_disabled_flaky_tests_json(): + """Render the canonical empty flaky-tests response.""" + return json.encode({"data": []}) + "\n" + def _render_disabled_export(repo_name, service, runtime_name, runtime_module_path, out_dir): """Render a disabled export using the same schema as a live sync repo.""" sanitized_module_path = sanitize_label_fragment(runtime_module_path) if runtime_module_path else "" @@ -1443,6 +1469,7 @@ def _render_disabled_export(repo_name, service, runtime_name, runtime_module_pat ruby_module_path = values["ruby_module_path"], sanitized_ruby_module_path = values["sanitized_ruby_module_path"], ruby_module_included = False, + enabled = False, ) def _render_disabled_build(settings_file, manifest_file, known_tests_file, test_management_file, flaky_tests_file, context_file, telemetry_facts_file): @@ -1492,13 +1519,10 @@ def _write_disabled_repository(ctx, out_dir, env_data, debug): runtime_name = (ctx.attr.runtime_name or "").strip() runtime_module_path = _explicit_runtime_module_path(ctx, runtime_name) ctx.file(manifest_file, "version=1\n") - ctx.file( - settings_file, - '{"data": {"attributes": {"known_tests_enabled": false, "test_management": {"enabled": false}, "flaky_test_retries_enabled": false}}}\n', - ) - ctx.file(known_tests_file, '{"data": {"attributes": {"tests": {}}}}\n') - ctx.file(test_management_file, '{"data": {"attributes": {"modules": {}}}}\n') - ctx.file(flaky_tests_file, '{"data": []}\n') + ctx.file(settings_file, _render_disabled_settings_json()) + ctx.file(known_tests_file, _render_disabled_known_tests_json()) + ctx.file(test_management_file, _render_disabled_test_management_json()) + ctx.file(flaky_tests_file, _render_disabled_flaky_tests_json()) ctx.file( context_file, _render_disabled_context_json( @@ -1814,6 +1838,10 @@ dirname_for_tests = _dirname normalize_out_dir_or_fail_for_tests = _normalize_out_dir_or_fail render_export_bzl_for_tests = _render_export_bzl render_disabled_context_json_for_tests = _render_disabled_context_json +render_disabled_flaky_tests_json_for_tests = _render_disabled_flaky_tests_json +render_disabled_known_tests_json_for_tests = _render_disabled_known_tests_json +render_disabled_settings_json_for_tests = _render_disabled_settings_json +render_disabled_test_management_json_for_tests = _render_disabled_test_management_json render_disabled_telemetry_facts_json_for_tests = _render_disabled_telemetry_facts_json render_disabled_export_for_tests = _render_disabled_export render_disabled_build_for_tests = _render_disabled_build @@ -2944,6 +2972,7 @@ def _impl(ctx): ruby_module_path = ruby_module_path, sanitized_ruby_module_path = sanitized_ruby_module_path, ruby_module_included = ruby_module_included, + enabled = True, ) ctx.file("export.bzl", export_bzl) diff --git a/tools/dev/materialize_rules_go_fork.py b/tools/dev/materialize_rules_go_fork.py index c342a512..fb349e50 100644 --- a/tools/dev/materialize_rules_go_fork.py +++ b/tools/dev/materialize_rules_go_fork.py @@ -311,6 +311,11 @@ def check_selection(registry: ForkRegistry, selection: ForkSelection) -> int: return 0 +def render_upstream_ids(upstream_ids: list[str]) -> bytes: + """Render shell-consumable upstream ids with platform-independent LF endings.""" + return "".join("%s\n" % upstream_id for upstream_id in upstream_ids).encode("utf-8") + + def main(argv: list[str] | None = None) -> int: """Parse CLI arguments and run materializer commands.""" parser = argparse.ArgumentParser(description=__doc__) @@ -341,8 +346,7 @@ def main(argv: list[str] | None = None) -> int: print(repo_relative_path(registry.repo_root, selection.tree_path)) return 0 if args.command == "list-upstreams": - for upstream_id in registry.upstream_ids(): - print(upstream_id) + sys.stdout.buffer.write(render_upstream_ids(registry.upstream_ids())) return 0 if args.command == "check": selections = registry.selections() if args.all else [resolve_selection(registry, args)] diff --git a/tools/tests/core/BUILD.bazel b/tools/tests/core/BUILD.bazel index 179649a6..66ba93cc 100644 --- a/tools/tests/core/BUILD.bazel +++ b/tools/tests/core/BUILD.bazel @@ -86,6 +86,7 @@ load( ) load( "//tools/tests/core:test_sync_disabled_utils.bzl", + "disabled_cache_payloads_contract_test", "disabled_context_contract_test", "disabled_export_and_build_shape_test", "disabled_telemetry_contract_test", @@ -260,6 +261,12 @@ load( "removed_complete_variant_target_rule", ) +disabled_cache_payloads_contract_test( + name = "disabled_cache_payloads_contract_test", + size = "small", + timeout = "short", +) + filegroup( name = "legacy_context_wrapper", srcs = ["//tools/tests/core/testdata:context.json"], @@ -1546,10 +1553,16 @@ test_suite( ":dedup_keys_test", ":default_context_data_test", ":dirname_test", + ":disabled_cache_payloads_contract_test", + ":disabled_context_contract_test", + ":disabled_export_and_build_shape_test", + ":disabled_telemetry_contract_test", ":doctor_controlled_attr_failure_test", ":doctor_kwargs_test", ":duplicate_names_failure_test", ":empty_name_failure_test", + ":enabled_false_values_test", + ":enabled_truthy_values_test", ":example_stub_context_contains_doctor_metadata_test", ":example_stub_custom_out_dir_test", ":example_stub_export_manifest_path_test", @@ -1565,6 +1578,8 @@ test_suite( ":explicit_upstream_base_specs_test", ":export_bzl_escaping_test", ":export_bzl_manifest_path_test", + ":repository_environment_contains_bootstrap_inputs_test", + ":service_environment_resolution_test", ":first_env_ctx_test", ":first_env_from_environ_test", ":fnv1a_symbol_distinguishes_common_symbols_test", diff --git a/tools/tests/core/test_sync_disabled_utils.bzl b/tools/tests/core/test_sync_disabled_utils.bzl index 3fafc855..83e42c84 100644 --- a/tools/tests/core/test_sync_disabled_utils.bzl +++ b/tools/tests/core/test_sync_disabled_utils.bzl @@ -7,12 +7,17 @@ """Unit tests for disabled Test Optimization repository rendering.""" load("@bazel_skylib//lib:unittest.bzl", "asserts", "unittest") +load("//tools/core:common_utils.bzl", "RULES_VERSION") load( "//tools/core:test_optimization_sync.bzl", "is_test_optimization_enabled_for_tests", "render_disabled_build_for_tests", "render_disabled_context_json_for_tests", "render_disabled_export_for_tests", + "render_disabled_flaky_tests_json_for_tests", + "render_disabled_known_tests_json_for_tests", + "render_disabled_settings_json_for_tests", + "render_disabled_test_management_json_for_tests", "render_disabled_telemetry_facts_json_for_tests", "repository_environ_for_tests", "resolve_service_and_environment_for_tests", @@ -89,15 +94,20 @@ def _disabled_context_contract_test(ctx): runtime_arch = "amd64", ) decoded = json.decode(content) - asserts.equals(env, "service-name", decoded["service.name"]) - asserts.equals(env, "test", decoded["env"]) - asserts.equals(env, False, decoded["topt.sync.enabled"]) - asserts.equals(env, "test_optimization_data", decoded["topt.sync.repository_name"]) - asserts.equals(env, "custom_topt", decoded["topt.sync.out_dir"]) - asserts.equals(env, "go", decoded["runtime.name"]) - asserts.equals(env, "https://github.com/example/repo", decoded["git.repository_url"]) - asserts.equals(env, "abc123", decoded["git.commit.sha"]) - asserts.false(env, "topt.api_key_fingerprint" in decoded) + asserts.equals(env, { + "bazel.rule_name": "datadog-rules-test-optimization", + "bazel.rule_version": RULES_VERSION, + "env": "test", + "git.commit.sha": "abc123", + "git.repository_url": "https://github.com/example/repo", + "runtime.architecture": "amd64", + "runtime.name": "go", + "runtime.version": "1.25.0", + "service.name": "service-name", + "topt.sync.enabled": False, + "topt.sync.out_dir": "custom_topt", + "topt.sync.repository_name": "test_optimization_data", + }, decoded) without_runtime = json.decode(render_disabled_context_json_for_tests( "test_optimization_data", @@ -112,11 +122,38 @@ def _disabled_context_contract_test(ctx): def _disabled_telemetry_contract_test(ctx): env = unittest.begin(ctx) decoded = json.decode(render_disabled_telemetry_facts_json_for_tests("service-name", "go", "CI")) - asserts.equals(env, 1, decoded["schema_version"]) - asserts.equals(env, "service-name", decoded["service_name"]) - asserts.equals(env, "go", decoded["runtime_name"]) - asserts.equals(env, [{"name": "sync.disabled", "value": 1, "tags": []}], decoded["counts"]) - asserts.equals(env, [], decoded["distributions"]) + asserts.equals(env, { + "counts": [{"name": "sync.disabled", "value": 1, "tags": []}], + "distributions": [], + "env": "CI", + "runtime_name": "go", + "schema_version": 1, + "service_name": "service-name", + }, decoded) + return unittest.end(env) + +def _disabled_cache_payloads_contract_test(ctx): + env = unittest.begin(ctx) + asserts.equals(env, { + "data": { + "attributes": { + "flaky_test_retries_enabled": False, + "known_tests_enabled": False, + "test_management": {"enabled": False}, + }, + }, + }, json.decode(render_disabled_settings_json_for_tests())) + asserts.equals( + env, + {"data": {"attributes": {"tests": {}}}}, + json.decode(render_disabled_known_tests_json_for_tests()), + ) + asserts.equals( + env, + {"data": {"attributes": {"modules": {}}}}, + json.decode(render_disabled_test_management_json_for_tests()), + ) + asserts.equals(env, {"data": []}, json.decode(render_disabled_flaky_tests_json_for_tests())) return unittest.end(env) def _disabled_export_and_build_shape_test(ctx): @@ -130,6 +167,7 @@ def _disabled_export_and_build_shape_test(ctx): ) asserts.true(env, '"repo_name": "test_optimization_data"' in export) asserts.true(env, '"service_name": "go-service"' in export) + asserts.true(env, '"enabled": False' in export) asserts.true(env, '"manifest_path": "custom_topt/manifest.txt"' in export) asserts.true(env, '"module_path": "example.com/repo"' in export) @@ -159,5 +197,6 @@ enabled_false_values_test = unittest.make(_enabled_false_values_test) repository_environment_contains_bootstrap_inputs_test = unittest.make(_repository_environment_contains_bootstrap_inputs_test) service_environment_resolution_test = unittest.make(_service_environment_resolution_test) disabled_context_contract_test = unittest.make(_disabled_context_contract_test) +disabled_cache_payloads_contract_test = unittest.make(_disabled_cache_payloads_contract_test) disabled_telemetry_contract_test = unittest.make(_disabled_telemetry_contract_test) disabled_export_and_build_shape_test = unittest.make(_disabled_export_and_build_shape_test) diff --git a/tools/tests/core/test_sync_utils.bzl b/tools/tests/core/test_sync_utils.bzl index 8516d447..4fe4dd5f 100644 --- a/tools/tests/core/test_sync_utils.bzl +++ b/tools/tests/core/test_sync_utils.bzl @@ -1247,6 +1247,7 @@ def _export_bzl_manifest_path_test(ctx): sanitized_ruby_module_path = "apps_ruby_service", ruby_module_included = False, ) + asserts.true(env, '"enabled": True' in content) asserts.true(env, "\"service_name\": \"service-name\"" in content) asserts.true(env, "\"manifest_path\": \".testoptimization/manifest.txt\"" in content) asserts.true(env, "\"runtimes\": {" in content) diff --git a/tools/tests/integration/run_mock_server_tests.sh b/tools/tests/integration/run_mock_server_tests.sh index fb897d15..6d300432 100755 --- a/tools/tests/integration/run_mock_server_tests.sh +++ b/tools/tests/integration/run_mock_server_tests.sh @@ -276,6 +276,17 @@ sh_test( timeout = "short", ) +sh_test( + name = "disabled_metadata_runfiles_test", + srcs = ["disabled_metadata_runfiles_test.sh"], + data = [ + "@test_optimization_data//:test_optimization_context", + "@test_optimization_data//:test_optimization_files", + ], + size = "small", + timeout = "short", +) + dd_payload_uploader( name = "dd_upload_payloads", ) @@ -437,6 +448,43 @@ JSON_EOF PAYLOAD_EOF chmod +x payload_writer.sh +cat > disabled_metadata_runfiles_test.sh <<'DISABLED_RUNFILES_EOF' +#!/usr/bin/env bash +set -euo pipefail + +runfiles_root="${RUNFILES_DIR:-${TEST_SRCDIR:-}}" +if [[ -z "$runfiles_root" || ! -d "$runfiles_root" ]]; then + echo "error: Bazel runfiles directory is unavailable" >&2 + exit 1 +fi + +settings_path="$(find -L "$runfiles_root" -type f \ + -path '*test_optimization_data/.testoptimization/cache/http/settings.json' \ + -print -quit)" +if [[ -z "$settings_path" ]]; then + echo "error: disabled settings.json was not present in test runfiles" >&2 + exit 1 +fi +repo_dir="${settings_path%/.testoptimization/cache/http/settings.json}" + +jq -e '.data.attributes == { + "flaky_test_retries_enabled": false, + "known_tests_enabled": false, + "test_management": {"enabled": false} +}' "$settings_path" >/dev/null +jq -e '.data.attributes.tests == {}' \ + "$repo_dir/.testoptimization/cache/http/known_tests.json" >/dev/null +jq -e '.data.attributes.modules == {}' \ + "$repo_dir/.testoptimization/cache/http/test_management.json" >/dev/null +jq -e '.data == []' \ + "$repo_dir/.testoptimization/cache/http/flaky_tests.json" >/dev/null +jq -e '."topt.sync.enabled" == false' \ + "$repo_dir/.testoptimization/context.json" >/dev/null +jq -e 'any(.counts[]; .name == "sync.disabled" and .value == 1)' \ + "$repo_dir/.testoptimization/telemetry_facts.json" >/dev/null +DISABLED_RUNFILES_EOF +chmod +x disabled_metadata_runfiles_test.sh + BAZEL="$REPO_ROOT/bazelw" OUT_BASE="$TMP_WS/.bazel_out" BAZEL_FLAGS=(--output_base="$OUT_BASE") @@ -513,6 +561,10 @@ env -u DD_API_KEY -u DD_SITE -u DD_TEST_OPTIMIZATION_ENABLED \ "$BAZEL" --output_base="$DISABLED_OUTPUT_BASE" build \ @test_optimization_data//:test_optimization_context \ "${DISABLED_REPO_ENVS[@]}" +env -u DD_API_KEY -u DD_SITE -u DD_TEST_OPTIMIZATION_ENABLED \ + "$BAZEL" --output_base="$DISABLED_OUTPUT_BASE" test \ + //:disabled_metadata_runfiles_test \ + "${DISABLED_REPO_ENVS[@]}" for required in settings.json known_tests.json test_management.json flaky_tests.json manifest.txt context.json; do if ! grep -q "$required" "$DISABLED_CQUERY_OUT"; then diff --git a/tools/tests/integration/run_workspace_go_integration.sh b/tools/tests/integration/run_workspace_go_integration.sh index 5873860d..bbeea9ae 100755 --- a/tools/tests/integration/run_workspace_go_integration.sh +++ b/tools/tests/integration/run_workspace_go_integration.sh @@ -941,18 +941,15 @@ load("@bazel_gazelle//:deps.bzl", "gazelle_dependencies") load("@datadog-rules-test-optimization-go//:topt_go_orchestrion_repository.bzl", "dd_topt_go_orchestrion_tool_repo") load("@datadog-rules-test-optimization-go//:topt_go_workspace.bzl", "dd_topt_go_workspace_sync_repositories") -go_rules_dependencies() -go_register_toolchains(version = "${GO_VERSION}") -gazelle_dependencies() -EOF - - cat >> "$ws_dir/WORKSPACE" < "$ws_dir/WORKSPACE" < "$ws_dir/BUILD.bazel" <<'EOF' +filegroup( + name = "probe", + srcs = [], +) +EOF + + output="$( + ( + cd "$ws_dir" + USE_BAZEL_VERSION="$BAZEL_VERSION" "$BAZEL" --output_user_root="$BAZEL_OUTPUT_USER_ROOT" cquery \ + "${BAZEL_EXTRA_ARGS[@]}" \ + --noenable_bzlmod \ + --enable_workspace \ + @rules_go_orchestrion_tool//:orchestrion \ + --output=files + ) + )" + if [[ -n "$output" ]]; then + echo "error: rules_go default Orchestrion stub exposed files" >&2 + printf '%s\n' "$output" >&2 + exit 1 + fi +} + run_windows_enabled_smoke() { local ws_dir="$WORKSPACE_ROOT/windows-enabled" local resolved_repos="$TMP_ROOT/windows-enabled-resolved-repositories.bzl" @@ -1370,6 +1414,7 @@ run_expected_failure() { } mkdir -p "$WORKSPACE_ROOT" +run_rules_go_default_stub_smoke if [[ "$WINDOWS_DISABLED_SMOKE_ONLY" == "1" || "$ORCHESTRION_DISABLED_SENTINEL" == "1" ]]; then ORCHESTRION_VERSION="$ORCHESTRION_DISABLED_SENTINEL_VERSION" diff --git a/tools/tests/python/test_rules_go_variant_tools.py b/tools/tests/python/test_rules_go_variant_tools.py index 50bc91aa..9eddeb30 100644 --- a/tools/tests/python/test_rules_go_variant_tools.py +++ b/tools/tests/python/test_rules_go_variant_tools.py @@ -594,6 +594,13 @@ def test_materializer_applies_patch_series(self) -> None: (materialized / "file.txt").read_text(encoding="utf-8"), ) + def test_list_upstreams_uses_lf_on_every_platform(self) -> None: + """Bash consumers never receive a carriage return in an upstream id.""" + self.assertEqual( + b"v0_60_0\nv0_61_1\n", + self.mod.render_upstream_ids(["v0_60_0", "v0_61_1"]), + ) + def test_materializer_rejects_complete_variant(self) -> None: """The removed complete variant fails before patch application.""" selection = SimpleNamespace( From d1ed8a09f91cbdca7e3110bb1d9ad88094951bfc Mon Sep 17 00:00:00 2001 From: Tony Redondo Date: Thu, 16 Jul 2026 14:42:39 +0200 Subject: [PATCH 04/23] docs: clarify config-driven onboarding scope --- README.md | 12 ++++------ docs/Language_Onboarding.md | 24 ++++++++----------- .../references/bzlmod-onboarding.md | 1 - .../references/workspace-onboarding.md | 1 - 4 files changed, 15 insertions(+), 23 deletions(-) diff --git a/README.md b/README.md index 1a134b1e..8dcbceb1 100644 --- a/README.md +++ b/README.md @@ -117,17 +117,16 @@ or merge it locally instead of vendoring a second complete `rules_go` tree. Use this checklist before your first CI rollout: -For config-gated metadata resolution in every language, the named -`test-optimization` config must include +For config-gated Go and Python onboarding, the named `test-optimization` +config must include `common:test-optimization --repo_env=DD_TEST_OPTIMIZATION_ENABLED=1`. The public Go bootstrap helpers apply metadata gating by default; direct use of the low-level core sync API must opt into `enabled_by_env = True`. Go additionally needs `build:test-optimization --@rules_go//go/private/orchestrion:enabled=true` for Bzlmod, or the same flag with `@io_bazel_rules_go` for WORKSPACE. -Removing `--config=test-optimization` disables metadata resolution for every -config-gated sync. Go and Python additionally implement the complete runtime -opt-out described below. Other companions currently use this config as a -metadata-fetch gate; do not infer a test-runtime kill switch from it. +Removing `--config=test-optimization` provides the complete metadata and +runtime opt-out for the Go and Python integrations described below. This +release does not change the enablement contract of the other companions. When a config-gated Python sync is disabled, `dd_topt_py_test` keeps the consumer's normal runner and test arguments, omits Test Optimization metadata @@ -952,7 +951,6 @@ topt_ruby.test_optimization_sync( service = "ruby-service", runtime_name = "ruby", runtime_version = "3.3.9", - enabled_by_env = True, ) use_repo(topt_go, "test_optimization_data_go") diff --git a/docs/Language_Onboarding.md b/docs/Language_Onboarding.md index 436ff5aa..1fb8943c 100644 --- a/docs/Language_Onboarding.md +++ b/docs/Language_Onboarding.md @@ -48,15 +48,10 @@ Shared runtime contract for every language: - `DD_TEST_OPTIMIZATION_CONTEXT_JSON` remains a legacy explicit override, not the recommended mixed-runtime wiring path -Shared `.bazelrc` forwarding for every runtime. The named config is the shared -metadata-fetch switch; omitting it leaves config-gated metadata repositories -on their disabled no-fetch stubs. Go and Python also use it as the complete -runtime opt-out. The Java, NodeJS, .NET, and Ruby companions do not yet promise -a test-runtime kill switch when the config is omitted: +Shared `.bazelrc` metadata forwarding for every runtime: ```text common:test-optimization --repo_env=DD_API_KEY -common:test-optimization --repo_env=DD_TEST_OPTIMIZATION_ENABLED=1 common:test-optimization --repo_env=DD_SITE common:test-optimization --repo_env=DD_GIT_REPOSITORY_URL common:test-optimization --repo_env=DD_GIT_BRANCH @@ -68,6 +63,15 @@ test:test-optimization --remote_download_regex=.*test[.]outputs.* test:test-optimization --zip_undeclared_test_outputs ``` +Go and Python config-gated onboarding also adds the single enable switch below. +Omitting the named config then provides the complete metadata and runtime +opt-out for those two integrations. This release does not change the +enablement contract of the other companions: + +```text +common:test-optimization --repo_env=DD_TEST_OPTIMIZATION_ENABLED=1 +``` + Go workspaces also add the existing `rules_go` analysis-time setting. Prefer the generated block from `dd_topt_go_bootstrap --print-bazelrc-snippet` or `dd_topt_go_bootstrap --write-bazelrc`, which substitutes the consumer's actual @@ -889,7 +893,6 @@ topt.test_optimization_sync( service = "java-service", runtime_name = "java", runtime_version = "17", - enabled_by_env = True, ) use_repo(topt, "test_optimization_data") @@ -968,7 +971,6 @@ topt.test_optimization_multi_sync( services = ["java-service-a", "java-service-b"], runtime_name = "java", runtime_version = "17", - enabled_by_env = True, ) use_repo( @@ -1062,7 +1064,6 @@ topt.test_optimization_sync( service = "nodejs-service", runtime_name = "nodejs", runtime_version = "22.22.0", - enabled_by_env = True, ) use_repo(topt, "test_optimization_data") @@ -1136,7 +1137,6 @@ topt.test_optimization_multi_sync( services = ["nodejs-service-a", "nodejs-service-b"], runtime_name = "nodejs", runtime_version = "22.22.0", - enabled_by_env = True, ) use_repo( @@ -1246,7 +1246,6 @@ topt.test_optimization_sync( service = "dotnet-service", runtime_name = "dotnet", runtime_version = "8.0.100", - enabled_by_env = True, ) use_repo(topt, "test_optimization_data") @@ -1322,7 +1321,6 @@ topt.test_optimization_multi_sync( services = ["dotnet-service-a", "dotnet-service-b"], runtime_name = "dotnet", runtime_version = "8.0.100", - enabled_by_env = True, ) use_repo( @@ -1412,7 +1410,6 @@ topt.test_optimization_sync( service = "ruby-service", runtime_name = "ruby", runtime_version = "3.3.9", - enabled_by_env = True, ) use_repo(topt, "test_optimization_data") @@ -1488,7 +1485,6 @@ topt.test_optimization_multi_sync( services = ["ruby-service-a", "ruby-service-b"], runtime_name = "ruby", runtime_version = "3.3.9", - enabled_by_env = True, ) use_repo( diff --git a/tools/agent-skills/java-test-optimization-onboarding/references/bzlmod-onboarding.md b/tools/agent-skills/java-test-optimization-onboarding/references/bzlmod-onboarding.md index 46f1bdad..45d26448 100644 --- a/tools/agent-skills/java-test-optimization-onboarding/references/bzlmod-onboarding.md +++ b/tools/agent-skills/java-test-optimization-onboarding/references/bzlmod-onboarding.md @@ -51,7 +51,6 @@ topt.test_optimization_sync( service = "", runtime_name = "java", runtime_version = "", - enabled_by_env = True, ) use_repo(topt, "test_optimization_data") diff --git a/tools/agent-skills/java-test-optimization-onboarding/references/workspace-onboarding.md b/tools/agent-skills/java-test-optimization-onboarding/references/workspace-onboarding.md index d33ddfe9..54e7b386 100644 --- a/tools/agent-skills/java-test-optimization-onboarding/references/workspace-onboarding.md +++ b/tools/agent-skills/java-test-optimization-onboarding/references/workspace-onboarding.md @@ -120,7 +120,6 @@ test_optimization_sync( service = "", runtime_name = "java", runtime_version = "", - enabled_by_env = True, ) ``` From fdee59f091a1e465970db9763092bc2c31da1584 Mon Sep 17 00:00:00 2001 From: Tony Redondo Date: Thu, 16 Jul 2026 15:19:40 +0200 Subject: [PATCH 05/23] docs: clarify language-specific activation --- CONTRIBUTING.md | 24 +++++++--- README.md | 24 ++++++---- docs/Configuration_Reference.md | 5 ++ docs/Installation_Reference.md | 35 +++++++++++--- docs/Troubleshooting.md | 46 +++++++++++-------- docs/Uploader_Reference.md | 7 ++- docs/internal_monorepo_go_rollout_guide.md | 13 +++--- examples/README.md | 13 ++++-- .../go-test-optimization-onboarding/SKILL.md | 2 +- .../references/troubleshooting.md | 2 +- .../references/validation-checklist.md | 2 +- .../references/workspace-onboarding.md | 2 +- .../SKILL.md | 4 +- .../references/troubleshooting.md | 4 +- .../references/validation-checklist.md | 2 +- 15 files changed, 124 insertions(+), 61 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 5fd4552a..91c4e61c 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -26,6 +26,9 @@ This product includes software developed at Datadog - Canonical full-repo command: - `./bazelw test //...` + - On macOS with Bazel 8.5.1, append + `--noexperimental_split_xml_generation`. The split XML helper can terminate + with `SIGSEGV` independently of the test target. - Core module tests (repo root): - `./bazelw test //tools/...` @@ -73,13 +76,20 @@ This product includes software developed at Datadog they cover single-context, explicit override, multi-context repo selection, and no-match fallback behavior. - Test Optimization bootstrap config: - - Keep `--config=test-optimization` as the only user-facing switch. The - config must set both `common:test-optimization --repo_env=DD_TEST_OPTIMIZATION_ENABLED=1` - and the existing `rules_go` Orchestrion `enabled=true` build setting. - - Omitting the config is the documented opt-out: metadata repositories use - their disabled stubs when `enabled_by_env = True`, and the stable - Orchestrion aliases select local empty targets. - - Do not add a consumer-local Test Optimization bool flag or a second + - For config-gated Go and Python onboarding, keep + `--config=test-optimization` as the only user-facing switch. The shared + config entry is + `common:test-optimization --repo_env=DD_TEST_OPTIMIZATION_ENABLED=1`. + - Go additionally sets the existing `rules_go` Orchestrion `enabled=true` + build setting. Python-only consumers must not declare that Go-only label. + - Omitting the config is the documented complete opt-out for Go and Python: + metadata repositories use disabled stubs when `enabled_by_env = True`, Go + aliases select local empty targets, and Python keeps the consumer runner + without Test Optimization wiring. + - Other companions retain their existing enablement contract. Do not add + `enabled_by_env = True` to their sync repositories until their runtime + wrapper implements and tests the disabled export contract. + - Do not add a consumer-local Test Optimization bool flag or a second Go Orchestrion repository chooser. - Go consumer integration harnesses: - Bzlmod default smoke: diff --git a/README.md b/README.md index 8dcbceb1..2ffb0c82 100644 --- a/README.md +++ b/README.md @@ -121,7 +121,8 @@ For config-gated Go and Python onboarding, the named `test-optimization` config must include `common:test-optimization --repo_env=DD_TEST_OPTIMIZATION_ENABLED=1`. The public Go bootstrap helpers apply metadata gating by default; direct use of the -low-level core sync API must opt into `enabled_by_env = True`. Go additionally needs +low-level core sync API in a config-gated Go or Python setup must opt into +`enabled_by_env = True`. Go additionally needs `build:test-optimization --@rules_go//go/private/orchestrion:enabled=true` for Bzlmod, or the same flag with `@io_bazel_rules_go` for WORKSPACE. Removing `--config=test-optimization` provides the complete metadata and @@ -260,11 +261,15 @@ test_optimization_sync = use_extension( test_optimization_sync.test_optimization_sync( name = "test_optimization_data", service = "my-service", # or set DD_SERVICE via --repo_env - enabled_by_env = True, ) use_repo(test_optimization_sync, "test_optimization_data") ``` +The low-level core API remains always enabled by default. Set +`enabled_by_env = True` only when this repository is part of the config-gated +Go or Python onboarding described below. Other companions retain their current +enablement contract. + ```bzl # BUILD.bazel (workspace root) load("@datadog-rules-test-optimization//tools/core:test_optimization_uploader.bzl", "dd_payload_uploader") @@ -1005,10 +1010,13 @@ load("@datadog-rules-test-optimization//tools/core:test_optimization_sync.bzl", test_optimization_sync( name = "test_optimization_data", service = "my-service", # recommended; otherwise falls back to DD_SERVICE or unnamed-service - enabled_by_env = True, ) ``` +This low-level WORKSPACE example preserves the always-enabled core default. +Config-gated Go and Python consumers set `enabled_by_env = True` through their +language-specific setup; the other companions remain unchanged. + For Go in WORKSPACE mode, keep the core and Go companion as separate external repositories and load `dd_topt_go_test` from `@datadog-rules-test-optimization-go//:topt_go_test.bzl`. Prefer the public @@ -1428,14 +1436,14 @@ bep_json="$(mktemp "${TMPDIR:-/tmp}/dd-topt-bep.XXXXXX.json")" artifact_staging_dir="$(mktemp -d "${TMPDIR:-/tmp}/dd-topt-artifacts.XXXXXX")" bazel test --config=test-optimization --build_event_json_file="$bep_json" //... -bazel run //:dd_test_optimization_doctor -- \ +bazel run --config=test-optimization //:dd_test_optimization_doctor -- \ --bep-json="$bep_json" \ --freshness-source=bep \ --freshness-mode=required \ --artifact-source=bep \ --artifact-staging-dir="$artifact_staging_dir" -bazel run //:dd_upload_payloads -- \ +bazel run --config=test-optimization //:dd_upload_payloads -- \ --bep-json="$bep_json" \ --freshness-source=bep \ --freshness-mode=required \ @@ -1504,7 +1512,7 @@ selective remote download flags, enable remote BEP artifact staging. Plain HTTP/HTTPS `outputs.zip` BEP carriers do not need a downloader: ```bash -bazel run //:dd_upload_payloads -- \ +bazel run --config=test-optimization //:dd_upload_payloads -- \ --bep-json="$bep_json" \ --freshness-source=bep \ --freshness-mode=required \ @@ -1908,9 +1916,9 @@ Fast checks before diving deep: - Verify sync env forwarding (`DD_API_KEY`, `DD_SITE`, and required `DD_GIT_*`) through `--repo_env`, not `--test_env`. - Force metadata refresh only when you intentionally need fresh backend state: - - `bazel sync --only= --repo_env=FETCH_SALT="$(date +%s)"` + - `bazel sync --config=test-optimization --only= --repo_env=FETCH_SALT="$(date +%s)"` - If Bazel reports WORKSPACE-disabled sync errors, retry with: - `bazel sync --enable_workspace --only= --repo_env=FETCH_SALT="$(date +%s)"` + `bazel sync --enable_workspace --config=test-optimization --only= --repo_env=FETCH_SALT="$(date +%s)"` - Do not add `FETCH_SALT` to normal `bazel test`, doctor, or uploader commands. - Confirm payload files exist under `bazel-testlogs/*/test.outputs/`, or that diff --git a/docs/Configuration_Reference.md b/docs/Configuration_Reference.md index 1ae19b51..5f5c85e8 100644 --- a/docs/Configuration_Reference.md +++ b/docs/Configuration_Reference.md @@ -371,6 +371,11 @@ For Go onboarding, the generated block also contains `--config=` is the single user-facing switch: removing it disables both metadata resolution and the real Orchestrion aliases. +Config-gated Python onboarding uses the same +`DD_TEST_OPTIMIZATION_ENABLED=1` repository environment entry but does not use +the Go-only Orchestrion setting. The Java, NodeJS, .NET, and Ruby companions +retain their existing enablement contract in this release. + ## How data is fetched The sync rule executes HTTP requests with timeouts/retries to: diff --git a/docs/Installation_Reference.md b/docs/Installation_Reference.md index afe572f4..a4f2d772 100644 --- a/docs/Installation_Reference.md +++ b/docs/Installation_Reference.md @@ -201,12 +201,16 @@ test_optimization_sync = use_extension( # @test_optimization_data//:test_optimization_files test_optimization_sync.test_optimization_sync( name = "test_optimization_data", - enabled_by_env = True, ) use_repo(test_optimization_sync, "test_optimization_data") ``` +The low-level core sync remains always enabled by default. Config-gated Go and +Python onboarding sets `enabled_by_env = True` in the language-specific setup. +Do not add that attribute to another companion until its runtime wrapper +implements the disabled export contract. + Core module note: `datadog-rules-test-optimization` is runtime-agnostic and does not declare language-rule dependencies. Language-specific orchestration lives in companion modules: @@ -417,8 +421,9 @@ repeatable `--bep-json=` flags. This keeps parallel CI jobs and repeated local runs from overwriting or reusing stale BEP files. `FETCH_SALT` is intentionally not part of the generated default config. Use it -only in a separate force-refresh `bazel sync --only=` command when you -deliberately want fresh backend metadata. +only in a separate force-refresh +`bazel sync --config=test-optimization --only=` command when you +deliberately want fresh backend metadata from a config-gated repository. Run Go onboarding commands with this config: @@ -474,7 +479,7 @@ For a first-pass support request after tests have run, the customer can run only the doctor: ```bash -bazel run //:dd_test_optimization_doctor -- \ +bazel run --config=test-optimization //:dd_test_optimization_doctor -- \ --support-bundle .topt/reports/dd-test-optimization-support.zip ``` @@ -609,11 +614,14 @@ test_optimization_sync = use_extension( ) test_optimization_sync.test_optimization_sync( name = "test_optimization_data", - enabled_by_env = True, ) use_repo(test_optimization_sync, "test_optimization_data") ``` +This core-only example preserves the always-enabled contract. Add +`enabled_by_env = True` only when deliberately composing the config-gated Go or +Python flow. + ### Multi-service usage (Bzlmod) Fetch multiple services with one extension and select per-service data by label: @@ -785,7 +793,6 @@ load("@datadog-rules-test-optimization//tools/core:test_optimization_sync.bzl", test_optimization_sync( name = "test_optimization_data", service = "my-service", # recommended; otherwise falls back to DD_SERVICE or unnamed-service - enabled_by_env = True, # Optional: # runtime_name = "go", # runtime_version = "1.25.0", @@ -794,6 +801,10 @@ test_optimization_sync( ) ``` +This generic WORKSPACE example preserves the always-enabled core default. The +Go and Python sections add `enabled_by_env = True` where their macros can safely +consume a disabled export. + ### 3) Depend on generated files in BUILD files ```bzl @@ -863,10 +874,20 @@ dd_payload_uploader( ### 5) Forward environment variables in `.bazelrc` +The metadata forwarding entries below apply to every runtime. Config-gated Go +and Python onboarding additionally includes: + ```text -# Repository rule (module/repo phase) — affects refetch common:test-optimization --repo_env=DD_TEST_OPTIMIZATION_ENABLED=1 +# Go only; use the apparent rules_go repository name: build:test-optimization --@io_bazel_rules_go//go/private/orchestrion:enabled=true +``` + +Python-only consumers omit the Go line. Java, NodeJS, .NET, and Ruby retain +their existing enablement contract and omit both lines in this release. + +```text +# Repository rule (module/repo phase) — affects refetch common:test-optimization --repo_env=DD_API_KEY common:test-optimization --repo_env=DD_SITE common:test-optimization --repo_env=DD_TEST_OPTIMIZATION_AGENTLESS_URL # Optional sync/uploader agentless URL override diff --git a/docs/Troubleshooting.md b/docs/Troubleshooting.md index 4fdaf1e0..7ebeb37d 100644 --- a/docs/Troubleshooting.md +++ b/docs/Troubleshooting.md @@ -10,27 +10,36 @@ This product includes software developed at Datadog Examples below assume the generated repository is named `test_optimization_data`. If you used a different `name`, replace labels and -`bazel sync --only=` accordingly. +repository names accordingly. For config-gated Go and Python repositories, +include `--config=test-optimization` in sync, test, doctor, and uploader +commands. Other companions retain their current activation contract. If Bazel reports that sync requires WORKSPACE support, add `--enable_workspace` to sync commands in this document. ## Test Optimization config and disabled mode -`--config=test-optimization` is the single user-facing enablement switch. Its -`.bazelrc` entries should set both the metadata repository environment and the -existing `rules_go` Orchestrion flag: +For config-gated Go and Python onboarding, +`--config=test-optimization` is the single user-facing enablement switch. Both +languages set the metadata repository environment: ```bazelrc common:test-optimization --repo_env=DD_TEST_OPTIMIZATION_ENABLED=1 +``` + +Go additionally sets the existing `rules_go` Orchestrion flag: + +```bazelrc build:test-optimization --@rules_go//go/private/orchestrion:enabled=true ``` When the config is omitted, repositories configured with -`enabled_by_env = True` generate the documented no-fetch stubs and the stable -Orchestrion aliases select local empty targets. Do not add a second -consumer-local Test Optimization bool flag. For WORKSPACE, replace -`@rules_go` with the apparent repository name used by that workspace. +`enabled_by_env = True` generate the documented no-fetch stubs. Go aliases +select local empty targets; Python keeps the normal consumer runner without +Test Optimization metadata or payload wiring. Python-only consumers omit the +Go line. For WORKSPACE Go, replace `@rules_go` with the apparent repository name +used by that workspace. Java, NodeJS, .NET, and Ruby retain their existing +enablement contract in this release. ## Quick triage map @@ -58,7 +67,7 @@ from the same run. For the simplest customer ask after tests have already run, use the doctor directly: ```bash -bazel run //:dd_test_optimization_doctor -- \ +bazel run --config=test-optimization //:dd_test_optimization_doctor -- \ --support-bundle .topt/reports/dd-test-optimization-support.zip ``` @@ -224,10 +233,10 @@ values automatically. 2. **Force refetch only when intentional** with a cache-busting salt: ```bash - bazel sync --only= --repo_env=FETCH_SALT="$(date +%s)" + bazel sync --config=test-optimization --only= --repo_env=FETCH_SALT="$(date +%s)" ``` ```powershell - bazel sync --only= --repo_env=FETCH_SALT="$(Get-Date -UFormat %s)" + bazel sync --config=test-optimization --only= --repo_env=FETCH_SALT="$(Get-Date -UFormat %s)" ``` Do not put `FETCH_SALT` in `.bazelrc`, `bazel test`, doctor, or uploader commands. It deliberately breaks the repository-rule cache key and should be @@ -252,9 +261,10 @@ values automatically. test_optimization_sync.test_optimization_sync( name = "test_optimization_data", debug = True, # Verbose logging - enabled_by_env = True, ) ``` + Preserve the repository's existing `enabled_by_env` value; enabling debug + logging must not change its activation contract. ## Published Go pins @@ -396,10 +406,10 @@ global root `BUILD.bazel` wiring unrelated to Test Optimization. 3. **Check DD_TEST_OPTIMIZATION_PAYLOADS_IN_FILES**: The macro should set this to `"true"`. Verify your test environment: ```bash - bazel test //your:test --test_output=all 2>&1 | grep DD_TEST_OPTIMIZATION + bazel test --config=test-optimization //your:test --test_output=all 2>&1 | grep DD_TEST_OPTIMIZATION ``` ```powershell - bazel test //your:test --test_output=all *>&1 | Select-String "DD_TEST_OPTIMIZATION" + bazel test --config=test-optimization //your:test --test_output=all *>&1 | Select-String "DD_TEST_OPTIMIZATION" ``` PowerShell uses `*>&1` (not Bash `2>&1`) to merge stderr/stdout. @@ -887,8 +897,8 @@ client-side Bazel behavior. - PowerShell: set `$env:DD_API_KEY` and `$env:DD_SITE` first, then run `bazel run --config=test-optimization //:dd_upload_payloads` - Quote paths containing spaces and avoid `eval`-style wrappers. - For refetch debugging, use: - - `bazel sync --only= --repo_env=FETCH_SALT=` - - if required by workspace mode: `bazel sync --enable_workspace --only= --repo_env=FETCH_SALT=` + - `bazel sync --config=test-optimization --only= --repo_env=FETCH_SALT=` + - if required by workspace mode: `bazel sync --enable_workspace --config=test-optimization --only= --repo_env=FETCH_SALT=` ## Getting help @@ -896,10 +906,10 @@ If issues persist: 1. **Enable debug mode** and capture full output: ```bash - bazel sync --only= --repo_env=FETCH_SALT= 2>&1 | tee debug.log + bazel sync --config=test-optimization --only= --repo_env=FETCH_SALT= 2>&1 | tee debug.log ``` ```powershell - bazel sync --only= --repo_env=FETCH_SALT= *>&1 | Tee-Object -FilePath debug.log + bazel sync --config=test-optimization --only= --repo_env=FETCH_SALT= *>&1 | Tee-Object -FilePath debug.log ``` 2. **Collect diagnostic info**: diff --git a/docs/Uploader_Reference.md b/docs/Uploader_Reference.md index 47323f2f..fec6e848 100644 --- a/docs/Uploader_Reference.md +++ b/docs/Uploader_Reference.md @@ -48,6 +48,11 @@ both metadata resolution and the Orchestrion analysis aliases; it does not require a second bool flag. In WORKSPACE repositories, use the apparent `rules_go` repository name configured by that workspace. +Config-gated Python consumers use only the +`DD_TEST_OPTIMIZATION_ENABLED=1` entry and omit the Go-specific Orchestrion +line. Java, NodeJS, .NET, and Ruby retain their existing enablement contract in +this release. + ```bash # Vendor the full tools/test_optimization/ helper directory, or set # DD_TEST_OPTIMIZATION_SUPPORT_BUNDLE_COLLECTOR to create_support_bundle.py. @@ -321,7 +326,7 @@ For first-pass support, the doctor can create a doctor-only bundle without the wrapper: ```bash -bazel run //:dd_test_optimization_doctor -- \ +bazel run --config=test-optimization //:dd_test_optimization_doctor -- \ --support-bundle .topt/reports/dd-test-optimization-support.zip ``` diff --git a/docs/internal_monorepo_go_rollout_guide.md b/docs/internal_monorepo_go_rollout_guide.md index 72c3dd32..20c9e2c3 100644 --- a/docs/internal_monorepo_go_rollout_guide.md +++ b/docs/internal_monorepo_go_rollout_guide.md @@ -49,15 +49,16 @@ SHA256, and archive prefix generated from the same published commit. - Use a commit that is reachable from `origin/main`; never publish feature-branch SHAs into consumer snippets. -- Configure `go_orchestrion_tool_repo(...)` with the current supported +- Configure `dd_topt_go_orchestrion_tool_repo(...)` with the current supported Orchestrion version and the current supported `dd-trace-go` Bazel-mode - version. -- Configure `test_optimization_sync(...)` with: + version. Do not load the underlying `rules_go` repository rule directly. +- Configure `dd_topt_go_workspace_sync_repositories(...)` with: - `service` - - `runtime_name = "go"` - `runtime_version` - - `runtime_module_path` + - `module_path` - `require_git_metadata = True` + The public helper supplies `runtime_name = "go"` and config-gated metadata + sync by default. - Keep repository-specific scheduling, Docker, tags, platform constraints, and flaky policy in the repository-local wrapper layer. - Set `orchestrion_mode = "test_optimization"` in the optimized wrapper for @@ -88,7 +89,7 @@ SHA256, and archive prefix generated from the same published commit. Removing `--config=test-optimization` disables both metadata repositories and Orchestrion aliases. The public Go helpers enable metadata gating by - default; it does not dynamically control Orchestrion repository declaration. + default; they do not dynamically control Orchestrion repository declaration. ## Bootstrap Flow diff --git a/examples/README.md b/examples/README.md index cb2741a2..afd2c21f 100644 --- a/examples/README.md +++ b/examples/README.md @@ -39,17 +39,20 @@ repository, use `./bazelw` for local development convenience. - Provide sync credentials via environment and forward them to repository rules: - shell/CI secret: `DD_API_KEY` - `.bazelrc`: `common --repo_env=DD_API_KEY` (and optionally `common --repo_env=DD_SITE`) -- For Go Test Optimization, make the documented config the only user-facing - switch: +- For config-gated Go and Python Test Optimization, make the documented config + the only user-facing switch: ```bazelrc common:test-optimization --repo_env=DD_TEST_OPTIMIZATION_ENABLED=1 + # Go only: build:test-optimization --@rules_go//go/private/orchestrion:enabled=true ``` - Removing `--config=test-optimization` disables both metadata resolution and - Orchestrion analysis. In WORKSPACE mode, use the apparent `rules_go` repo - name configured by the workspace. + Removing `--config=test-optimization` disables metadata resolution and the + matching Go/Python runtime wiring. Go additionally disables Orchestrion + analysis; in WORKSPACE mode, use the apparent `rules_go` repo name configured + by the workspace. Python-only consumers omit the Go line. Other companions + retain their existing enablement contract in this release. ## Single-service (classic) diff --git a/tools/agent-skills/go-test-optimization-onboarding/SKILL.md b/tools/agent-skills/go-test-optimization-onboarding/SKILL.md index 4f2cfde1..726e79a3 100644 --- a/tools/agent-skills/go-test-optimization-onboarding/SKILL.md +++ b/tools/agent-skills/go-test-optimization-onboarding/SKILL.md @@ -47,7 +47,7 @@ Keep the RFC contract intact: `DD_TEST_OPTIMIZATION_REPORT_DIR` or wrapper `--report-dir`, and configure wrapper `--support-bundle` or `DD_TEST_OPTIMIZATION_SUPPORT_BUNDLE` for complete escalation artifacts. For first-pass customer troubleshooting after - tests have run, ask for `bazel run //:dd_test_optimization_doctor -- --support-bundle=` + tests have run, ask for `bazel run --config=test-optimization //:dd_test_optimization_doctor -- --support-bundle=` with any matching BEP/artifact flags. For bundle triage, inspect `summary.md`, `diagnostics.json`, `reports/doctor-report.json`, optional uploader reports, and diff --git a/tools/agent-skills/go-test-optimization-onboarding/references/troubleshooting.md b/tools/agent-skills/go-test-optimization-onboarding/references/troubleshooting.md index 58429a43..2579e156 100644 --- a/tools/agent-skills/go-test-optimization-onboarding/references/troubleshooting.md +++ b/tools/agent-skills/go-test-optimization-onboarding/references/troubleshooting.md @@ -146,7 +146,7 @@ intentionally lacks backend full-bundle data. ## Diagnostic Reports When logs are long or ambiguous, first ask for a doctor-only support bundle with -`bazel run //:dd_test_optimization_doctor -- --support-bundle=` plus any +`bazel run --config=test-optimization //:dd_test_optimization_doctor -- --support-bundle=` plus any matching BEP/artifact flags. Use the CI wrapper bundle when uploader dry-run or upload results matter. If a repository cannot use either bundle mode, collect `doctor-report.json`, `uploader-dry-run-report.json`, optional diff --git a/tools/agent-skills/go-test-optimization-onboarding/references/validation-checklist.md b/tools/agent-skills/go-test-optimization-onboarding/references/validation-checklist.md index f28bcdf4..a5683828 100644 --- a/tools/agent-skills/go-test-optimization-onboarding/references/validation-checklist.md +++ b/tools/agent-skills/go-test-optimization-onboarding/references/validation-checklist.md @@ -91,7 +91,7 @@ cat "$(bazel info output_base)/external/test_optimization_data_/exp ## Test, Doctor, Dry-Run, Upload For the simplest customer troubleshooting request after tests have run, use -`bazel run //:dd_test_optimization_doctor -- --support-bundle=` with any +`bazel run --config=test-optimization //:dd_test_optimization_doctor -- --support-bundle=` with any matching BEP/artifact flags. Prefer the CI wrapper when the repository can vendor the helper directory and you need uploader dry-run or upload coverage: diff --git a/tools/agent-skills/go-test-optimization-onboarding/references/workspace-onboarding.md b/tools/agent-skills/go-test-optimization-onboarding/references/workspace-onboarding.md index 24553747..201d5468 100644 --- a/tools/agent-skills/go-test-optimization-onboarding/references/workspace-onboarding.md +++ b/tools/agent-skills/go-test-optimization-onboarding/references/workspace-onboarding.md @@ -386,7 +386,7 @@ test sandbox. Do not add `FETCH_SALT` to the normal config. Use it only in a separate, explicit force-refresh command such as -`bazel sync --only= --repo_env=FETCH_SALT="$(date +%s)"` when rollout +`bazel sync --config=test-optimization --only= --repo_env=FETCH_SALT="$(date +%s)"` when rollout owners intentionally need fresh backend metadata. Never add: diff --git a/tools/agent-skills/python-test-optimization-onboarding/SKILL.md b/tools/agent-skills/python-test-optimization-onboarding/SKILL.md index 6f1d0bcd..9b54c0b1 100644 --- a/tools/agent-skills/python-test-optimization-onboarding/SKILL.md +++ b/tools/agent-skills/python-test-optimization-onboarding/SKILL.md @@ -47,7 +47,7 @@ Keep the RFC contract intact: `DD_TEST_OPTIMIZATION_REPORT_DIR` or wrapper `--report-dir`, and configure wrapper `--support-bundle` or `DD_TEST_OPTIMIZATION_SUPPORT_BUNDLE` for complete escalation artifacts. For first-pass customer troubleshooting after - tests have run, ask for `bazel run //:dd_test_optimization_doctor -- --support-bundle=` + tests have run, ask for `bazel run --config=test-optimization //:dd_test_optimization_doctor -- --support-bundle=` with any matching BEP/artifact flags. For bundle triage, inspect `summary.md`, `diagnostics.json`, `reports/doctor-report.json`, optional uploader reports, and @@ -116,7 +116,7 @@ Every successful Python onboarding should end with these pieces: support bundle for the simplest initial customer request. Keep individual reports for local inspection and manual fallback flows. - `FETCH_SALT` is used only for a separate, explicit - `bazel sync --only= --repo_env=FETCH_SALT="$(date +%s)"` refresh, never + `bazel sync --config=test-optimization --only= --repo_env=FETCH_SALT="$(date +%s)"` refresh, never as part of normal test, doctor, or uploader commands. - Real upload happens only after tests, doctor, and dry-run enrichment pass. diff --git a/tools/agent-skills/python-test-optimization-onboarding/references/troubleshooting.md b/tools/agent-skills/python-test-optimization-onboarding/references/troubleshooting.md index 78760602..4ffbdc34 100644 --- a/tools/agent-skills/python-test-optimization-onboarding/references/troubleshooting.md +++ b/tools/agent-skills/python-test-optimization-onboarding/references/troubleshooting.md @@ -82,7 +82,7 @@ and run those package-local labels before changing instrumentation. If metadata refetches repeatedly, check whether `.bazelrc` or scripts set `FETCH_SALT` by default. It should appear only in an explicit -`bazel sync --only= --repo_env=FETCH_SALT="$(date +%s)"` force-refresh +`bazel sync --config=test-optimization --only= --repo_env=FETCH_SALT="$(date +%s)"` force-refresh command. ## Remote Outputs Missing @@ -116,7 +116,7 @@ Then re-run tests before running doctor and uploader. ## Diagnostic Reports When logs are long or ambiguous, first ask for a doctor-only support bundle with -`bazel run //:dd_test_optimization_doctor -- --support-bundle=` plus any +`bazel run --config=test-optimization //:dd_test_optimization_doctor -- --support-bundle=` plus any matching BEP/artifact flags. Use the CI wrapper bundle when uploader dry-run or upload results matter. If a repository cannot use either bundle mode, collect `doctor-report.json`, `uploader-dry-run-report.json`, optional diff --git a/tools/agent-skills/python-test-optimization-onboarding/references/validation-checklist.md b/tools/agent-skills/python-test-optimization-onboarding/references/validation-checklist.md index 9e2494d2..6fead286 100644 --- a/tools/agent-skills/python-test-optimization-onboarding/references/validation-checklist.md +++ b/tools/agent-skills/python-test-optimization-onboarding/references/validation-checklist.md @@ -72,7 +72,7 @@ bazel sync --enable_workspace --config=test-optimization \ ## Test, Doctor, Dry-Run, Upload For the simplest customer troubleshooting request after tests have run, use -`bazel run //:dd_test_optimization_doctor -- --support-bundle=` with any +`bazel run --config=test-optimization //:dd_test_optimization_doctor -- --support-bundle=` with any matching BEP/artifact flags. Prefer the CI wrapper when the repository can vendor the helper directory and you need uploader dry-run or upload coverage: From 6eff36ef182c601f9a7e512f0ff62d8ea7346876 Mon Sep 17 00:00:00 2001 From: Tony Redondo Date: Thu, 16 Jul 2026 16:17:11 +0200 Subject: [PATCH 06/23] go: guard incomplete test optimization activation --- CHANGELOG.md | 15 ++++++++++ MODULE.bazel | 1 + MODULE.bazel.lock | 5 ++-- README.md | 13 ++++++-- docs/Installation_Reference.md | 1 + docs/Language_Onboarding.md | 1 + examples/README.md | 1 + examples/multi_service/MODULE.bazel | 1 + examples/single_service/MODULE.bazel | 1 + .../src/python-project/BUILD.bazel | 4 +++ .../src/python-project/main_test.py | 7 ++++- .../src/python-project/pytest_test.py | 10 +++---- modules/go/tests/BUILD.bazel | 23 ++++++++++---- modules/go/tests/test_extension.bzl | 10 +++---- modules/go/tests/test_macro.bzl | 30 ++++++++++++++++--- modules/go/tools/dd_topt_go_bootstrap/main.go | 1 + .../tools/dd_topt_go_bootstrap/main_test.go | 3 ++ modules/go/topt_go_extension.bzl | 6 ++-- modules/go/topt_go_orchestrion.bzl | 17 +++++++++++ modules/go/topt_go_test.bzl | 1 + .../references/bzlmod-onboarding.md | 2 ++ tools/tests/core/test_sync_disabled_utils.bzl | 2 +- tools/tests/core/test_sync_utils.bzl | 2 ++ tools/tests/example_stub_repo.bzl | 8 ++++- .../integration/run_bzlmod_go_integration.sh | 27 +++++++++++++++-- .../integration/run_mock_server_tests.sh | 2 ++ .../run_workspace_go_integration.sh | 27 +++++++++++++++-- 27 files changed, 187 insertions(+), 34 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7b50aa9d..39c276ea 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,21 @@ versioning. ## [Unreleased] +### Changed +- Config-gated Go Bzlmod onboarding now writes `enabled_by_env = True` + explicitly. The Go extension keeps its previous always-enabled metadata + default when that attribute is omitted so existing consumers cannot silently + enter the new disabled mode during an upgrade. +- Go consumers upgrading from `1.2.0` should rerun `dd_topt_go_bootstrap` with + `--write-bazelrc` and set `enabled_by_env = True` in manual extension wiring. + The managed `.bazelrc` update is idempotent and adds both metadata and + Orchestrion activation to the `test-optimization` config. + +### Fixed +- Go test analysis now fails with migration guidance when Test Optimization + metadata is enabled but the global Orchestrion build setting is disabled, + preventing a partial upgrade from silently dropping instrumentation. + ## [1.2.0] - 2026-06-03 ### Added diff --git a/MODULE.bazel b/MODULE.bazel index 690e798d..6b0643df 100644 --- a/MODULE.bazel +++ b/MODULE.bazel @@ -191,6 +191,7 @@ example_stub_repo = use_extension( ) example_stub_repo.example_stub_repo( name = "test_optimization_data", + enabled = False, labels = [ "apps_ruby_example", "company_product_example", diff --git a/MODULE.bazel.lock b/MODULE.bazel.lock index ce7cfb37..383e2c45 100644 --- a/MODULE.bazel.lock +++ b/MODULE.bazel.lock @@ -341,8 +341,8 @@ }, "//tools/tests:example_stub_repo.bzl%example_stub_repo_extension": { "general": { - "bzlTransitiveDigest": "dBh3mysStfbmE9OYr/8bdnByQgv+t2ybqpgSa1q2MpQ=", - "usagesDigest": "jqtG1XLwIoyhYZrl5w5guIuSZeXpuCApEcAz3bhsuio=", + "bzlTransitiveDigest": "TrTrDHJrP0Rre84rDDioXjXxdcPRZwzJZA21GdNKrkE=", + "usagesDigest": "9cmeLOZZK/XcmUYXh727qZj+Cgnz6JtINwWZZD6BKfg=", "recordedFileInputs": {}, "recordedDirentsInputs": {}, "envVariables": {}, @@ -350,6 +350,7 @@ "test_optimization_data": { "repoRuleId": "@@//tools/tests:example_stub_repo.bzl%example_stub_repo", "attributes": { + "enabled": false, "go_module_included": false, "go_module_path": "example.com/stub", "go_sanitized_module_path": "example_com_stub", diff --git a/README.md b/README.md index 2ffb0c82..a26b737b 100644 --- a/README.md +++ b/README.md @@ -120,9 +120,10 @@ Use this checklist before your first CI rollout: For config-gated Go and Python onboarding, the named `test-optimization` config must include `common:test-optimization --repo_env=DD_TEST_OPTIMIZATION_ENABLED=1`. The -public Go bootstrap helpers apply metadata gating by default; direct use of the -low-level core sync API in a config-gated Go or Python setup must opt into -`enabled_by_env = True`. Go additionally needs +public Go bootstrap helpers apply metadata gating by default. Manual Go +extension wiring and direct use of the low-level core sync API in a +config-gated Go or Python setup must opt into `enabled_by_env = True`. Go +additionally needs `build:test-optimization --@rules_go//go/private/orchestrion:enabled=true` for Bzlmod, or the same flag with `@io_bazel_rules_go` for WORKSPACE. Removing `--config=test-optimization` provides the complete metadata and @@ -915,6 +916,7 @@ go_topt.test_optimization_go( name = "test_optimization_data", services = ["go-service-a", "go-service-b"], runtime_version = "1.25.0", + enabled_by_env = True, ) use_repo( @@ -1625,11 +1627,16 @@ go_topt.test_optimization_go( service = "go-service", runtime_version = "1.25.0", module_path = "github.com/example/service", + enabled_by_env = True, ) use_repo(go_topt, "test_optimization_data") ``` +Keep `enabled_by_env = True` in new config-gated Go setups. Omitting it retains +the previous always-enabled metadata behavior only for upgrade compatibility; +the Go macro then requires the Orchestrion build setting to be enabled. + `module_path` should match the Go module path from `go.mod`. The sync rule still honors `GO_MODULE_PATH` first for CI overrides, but the explicit attr is the recommended default because it avoids repo-local `--repo_env` glue. diff --git a/docs/Installation_Reference.md b/docs/Installation_Reference.md index a4f2d772..a4f47f07 100644 --- a/docs/Installation_Reference.md +++ b/docs/Installation_Reference.md @@ -526,6 +526,7 @@ go_topt.test_optimization_go( service = "go-service", runtime_version = "1.25.0", module_path = "github.com/example/service", + enabled_by_env = True, ) ``` diff --git a/docs/Language_Onboarding.md b/docs/Language_Onboarding.md index 1fb8943c..2451fbba 100644 --- a/docs/Language_Onboarding.md +++ b/docs/Language_Onboarding.md @@ -462,6 +462,7 @@ go_topt.test_optimization_go( name = "test_optimization_data", services = ["go-service-a", "go-service-b"], runtime_version = "1.25.0", + enabled_by_env = True, ) use_repo( diff --git a/examples/README.md b/examples/README.md index afd2c21f..6337ac20 100644 --- a/examples/README.md +++ b/examples/README.md @@ -414,6 +414,7 @@ go_topt.test_optimization_go( name = "test_optimization_data", services = ["go-service-a", "go-service-b"], runtime_version = "1.25.0", + enabled_by_env = True, ) use_repo( diff --git a/examples/multi_service/MODULE.bazel b/examples/multi_service/MODULE.bazel index 25c74c66..f9b73893 100644 --- a/examples/multi_service/MODULE.bazel +++ b/examples/multi_service/MODULE.bazel @@ -36,6 +36,7 @@ go_topt = use_extension( go_topt.test_optimization_go( name = "test_optimization_data", debug = True, + enabled_by_env = True, runtime_version = "1.25.0", services = [ "go-service-a", diff --git a/examples/single_service/MODULE.bazel b/examples/single_service/MODULE.bazel index abc71021..48a4abac 100644 --- a/examples/single_service/MODULE.bazel +++ b/examples/single_service/MODULE.bazel @@ -109,6 +109,7 @@ go_topt = use_extension( go_topt.test_optimization_go( name = "test_optimization_data", debug = True, + enabled_by_env = True, runtime_version = "1.25.0", # Logical service identifier for Datadog metadata (not language-bound). # A single service can include tests from multiple runtimes. diff --git a/examples/single_service/src/python-project/BUILD.bazel b/examples/single_service/src/python-project/BUILD.bazel index 49a32aa4..e4e77a5f 100644 --- a/examples/single_service/src/python-project/BUILD.bazel +++ b/examples/single_service/src/python-project/BUILD.bazel @@ -8,6 +8,8 @@ load("@datadog-rules-test-optimization-python//:topt_py_test.bzl", "dd_topt_py_t load("@example_pip//:requirements.bzl", "requirement") load("@test_optimization_data//:export.bzl", "topt_data") +_TEST_OPTIMIZATION_EXPECTED = "1" if topt_data.get("enabled", True) else "0" + exports_files( [ "requirements.in", @@ -25,6 +27,7 @@ py_library( dd_topt_py_test( name = "hello_test", srcs = ["main_test.py"], + env = {"EXAMPLE_EXPECT_TEST_OPTIMIZATION": _TEST_OPTIMIZATION_EXPECTED}, imports = ["example/python/project"], main = "main_test.py", py_test_rule = py_test, @@ -38,6 +41,7 @@ dd_topt_py_test( dd_topt_py_test( name = "hello_pytest_test", srcs = ["pytest_test.py"], + env = {"EXAMPLE_EXPECT_TEST_OPTIMIZATION": _TEST_OPTIMIZATION_EXPECTED}, target_compatible_with = select({ "@platforms//os:windows": ["@platforms//:incompatible"], "//conditions:default": [], diff --git a/examples/single_service/src/python-project/main_test.py b/examples/single_service/src/python-project/main_test.py index 714e8160..c77dd54a 100644 --- a/examples/single_service/src/python-project/main_test.py +++ b/examples/single_service/src/python-project/main_test.py @@ -61,8 +61,13 @@ def test_greeting(self): module = _load_main_module() self.assertEqual("Hello from Python!", module.get_greeting()) - def test_manifest_metadata_files_present(self): + def test_manifest_metadata_contract(self): + expected = os.getenv("EXAMPLE_EXPECT_TEST_OPTIMIZATION", "1") == "1" manifest_rloc = os.getenv("DD_TEST_OPTIMIZATION_MANIFEST_FILE", "") + if not expected: + self.assertFalse(manifest_rloc, "disabled tests must not receive Test Optimization metadata") + return + self.assertTrue(manifest_rloc, "DD_TEST_OPTIMIZATION_MANIFEST_FILE should be set by dd_topt_py_test") manifest_path = _resolve_runfile(manifest_rloc) diff --git a/examples/single_service/src/python-project/pytest_test.py b/examples/single_service/src/python-project/pytest_test.py index d8bf67d3..f3badcfd 100644 --- a/examples/single_service/src/python-project/pytest_test.py +++ b/examples/single_service/src/python-project/pytest_test.py @@ -64,13 +64,13 @@ def test_greeting(): assert module.get_greeting() == "Hello from Python!" -def test_manifest_env_set(): +def test_manifest_env_contract(): + expected = os.getenv("EXAMPLE_EXPECT_TEST_OPTIMIZATION", "1") == "1" manifest_rloc = os.getenv("DD_TEST_OPTIMIZATION_MANIFEST_FILE", "") - assert manifest_rloc, "DD_TEST_OPTIMIZATION_MANIFEST_FILE should be set by dd_topt_py_test" - + if not expected: + assert not manifest_rloc, "disabled tests must not receive Test Optimization metadata" + return -def test_manifest_metadata_files_present(): - manifest_rloc = os.getenv("DD_TEST_OPTIMIZATION_MANIFEST_FILE", "") assert manifest_rloc, "DD_TEST_OPTIMIZATION_MANIFEST_FILE should be set by dd_topt_py_test" manifest_path = _resolve_runfile(manifest_rloc) diff --git a/modules/go/tests/BUILD.bazel b/modules/go/tests/BUILD.bazel index fabd17a7..dc3ea773 100644 --- a/modules/go/tests/BUILD.bazel +++ b/modules/go/tests/BUILD.bazel @@ -8,7 +8,7 @@ load( ":test_extension.bzl", "go_multi_specs_propagate_non_default_enablement_and_flaky_tests_test", "go_single_spec_propagates_non_default_enablement_and_flaky_tests_test", - "go_specs_default_to_config_gated_test", + "go_specs_preserve_legacy_always_enabled_default_test", ) load( ":test_macro.bzl", @@ -24,6 +24,8 @@ load( "go_macro_general_mode_linker_flags_wiring_test", "go_macro_multi_service_target", "go_macro_multi_service_wiring_test", + "go_macro_orchestrion_enablement_mismatch_failure_test", + "go_macro_orchestrion_enablement_mismatch_target", "go_macro_orchestrion_pin_files_provider_test", "go_macro_orchestrion_pin_files_target", "go_macro_orchestrion_pin_files_wiring_test", @@ -147,8 +149,8 @@ go_multi_specs_propagate_non_default_enablement_and_flaky_tests_test( timeout = "short", ) -go_specs_default_to_config_gated_test( - name = "go_specs_default_to_config_gated_test", +go_specs_preserve_legacy_always_enabled_default_test( + name = "go_specs_preserve_legacy_always_enabled_default_test", size = "small", timeout = "short", ) @@ -633,6 +635,16 @@ validate_test_optimization_pin_files_missing_go_mod_failure_test( target_under_test = ":validate_test_optimization_pin_files_missing_go_mod_target", ) +go_macro_orchestrion_enablement_mismatch_target( + name = "go_macro_orchestrion_enablement_mismatch_target", + tags = ["manual"], +) + +go_macro_orchestrion_enablement_mismatch_failure_test( + name = "go_macro_orchestrion_enablement_mismatch_failure_test", + target_under_test = ":go_macro_orchestrion_enablement_mismatch_target", +) + wrapper_output_name_target_rule( name = "wrapper_output_name_non_windows_target", executable_basename = "hello_test__raw_go_test", @@ -720,6 +732,7 @@ test_suite( ":go_macro_env_none_wiring_test", ":go_macro_explicit_service_wiring_test", ":go_macro_multi_service_wiring_test", + ":go_macro_orchestrion_enablement_mismatch_failure_test", ":go_macro_orchestrion_pin_files_provider_test", ":go_macro_orchestrion_pin_files_wiring_test", ":go_macro_public_wrapper_test", @@ -740,11 +753,10 @@ test_suite( ":go_macro_test_optimization_public_wrapper_mode_test", ":go_multi_specs_propagate_non_default_enablement_and_flaky_tests_test", ":go_single_spec_propagates_non_default_enablement_and_flaky_tests_test", - ":go_specs_default_to_config_gated_test", + ":go_specs_preserve_legacy_always_enabled_default_test", ":go_stub_includes_manifest_in_files_test", ":go_workspace_multi_specs_test", ":go_workspace_single_specs_test", - ":workspace_helpers_tests", ":has_go_mod_pin_test", ":has_package_local_go_mod_test", ":normalize_user_data_handles_none_test", @@ -774,6 +786,7 @@ test_suite( ":validate_orchestrion_mode_test", ":validate_test_optimization_pin_files_missing_go_mod_failure_test", ":windows_wrapper_uses_file_payload_mode_test", + ":workspace_helpers_tests", ":wrapper_output_name_non_windows_test", ":wrapper_output_name_windows_test", ], diff --git a/modules/go/tests/test_extension.bzl b/modules/go/tests/test_extension.bzl index 607fdd64..822a0642 100644 --- a/modules/go/tests/test_extension.bzl +++ b/modules/go/tests/test_extension.bzl @@ -48,7 +48,7 @@ def _go_multi_specs_propagate_non_default_enablement_and_flaky_tests_test(ctx): asserts.equals(env, "go", specs[0]["runtime_name"]) return unittest.end(env) -def _go_specs_default_to_config_gated_test(ctx): +def _go_specs_preserve_legacy_always_enabled_default_test(ctx): env = unittest.begin(ctx) single = build_go_single_repo_spec_for_tests( name = "test_optimization_data_go", @@ -58,8 +58,8 @@ def _go_specs_default_to_config_gated_test(ctx): name = "test_optimization_data_go", services = ["go-service"], ) - asserts.equals(env, True, single["enabled_by_env"]) - asserts.equals(env, True, multi[0]["enabled_by_env"]) + asserts.equals(env, False, single["enabled_by_env"]) + asserts.equals(env, False, multi[0]["enabled_by_env"]) return unittest.end(env) go_single_spec_propagates_non_default_enablement_and_flaky_tests_test = unittest.make( @@ -70,6 +70,6 @@ go_multi_specs_propagate_non_default_enablement_and_flaky_tests_test = unittest. _go_multi_specs_propagate_non_default_enablement_and_flaky_tests_test, ) -go_specs_default_to_config_gated_test = unittest.make( - _go_specs_default_to_config_gated_test, +go_specs_preserve_legacy_always_enabled_default_test = unittest.make( + _go_specs_preserve_legacy_always_enabled_default_test, ) diff --git a/modules/go/tests/test_macro.bzl b/modules/go/tests/test_macro.bzl index e40b4e66..782462ae 100644 --- a/modules/go/tests/test_macro.bzl +++ b/modules/go/tests/test_macro.bzl @@ -180,8 +180,9 @@ wrapper_output_name_target_rule = rule( }, ) -def _single_service_topt_data(): +def _single_service_topt_data(enabled = True): return { + "enabled": enabled, "repo_name": "test_optimization_data", "service_name": "go-service", "manifest_path": ".testoptimization/manifest.txt", @@ -211,7 +212,7 @@ def go_macro_single_service_target(name, tags = None): """Target-under-test: single-service wiring + default rundir path.""" dd_topt_go_test( name = name, - topt_data = _single_service_topt_data(), + topt_data = _single_service_topt_data(enabled = False), go_test_rule = _go_test_capture_rule, data = [":test_macro.bzl"], env = { @@ -368,7 +369,7 @@ def go_macro_test_optimization_public_wrapper_mode_target(name, tags = None): """Target under test for public wrapper Orchestrion mode forwarding.""" dd_topt_go_test( name = name, - topt_data = _single_service_topt_data(), + topt_data = _single_service_topt_data(enabled = False), go_test_rule = _go_test_transition_mode_rule, orchestrion_mode = "test_optimization", orchestrion_pin_files = [":go.mod"], @@ -379,7 +380,7 @@ def go_macro_default_general_public_wrapper_mode_target(name, tags = None): """Target under test for omitted Orchestrion mode defaulting to general.""" dd_topt_go_test( name = name, - topt_data = _single_service_topt_data(), + topt_data = _single_service_topt_data(enabled = False), go_test_rule = _go_test_transition_mode_rule, tags = tags, ) @@ -449,6 +450,15 @@ def orch_wrapper_materialized_actual_windows_target(name, tags = None): tags = tags, ) +def go_macro_orchestrion_enablement_mismatch_target(name, tags = None): + """Target under test for an incomplete config-gated Go upgrade.""" + dd_topt_go_test( + name = name, + topt_data = _single_service_topt_data(enabled = True), + go_test_rule = _go_test_capture_rule, + tags = tags, + ) + def _go_macro_single_service_wiring_test_impl(ctx): """Assert env/data/rundir contract for single-service macro usage.""" env = analysistest.begin(ctx) @@ -834,6 +844,14 @@ def _validate_test_optimization_pin_files_missing_go_mod_failure_test_impl(ctx): asserts.expect_failure(env, "requires a package-local go.mod or explicit orchestrion_pin_files") return analysistest.end(env) +def _go_macro_orchestrion_enablement_mismatch_failure_test_impl(ctx): + """Assert a partial upgrade fails instead of silently dropping instrumentation.""" + env = analysistest.begin(ctx) + asserts.expect_failure(env, "Test Optimization metadata is enabled but Orchestrion is disabled") + asserts.expect_failure(env, "--config=test-optimization") + asserts.expect_failure(env, "--write-bazelrc") + return analysistest.end(env) + def _wrapper_output_name_non_windows_test_impl(ctx): """Assert non-Windows wrapper names remain extensionless.""" env = analysistest.begin(ctx) @@ -1019,6 +1037,10 @@ validate_test_optimization_pin_files_missing_go_mod_failure_test = analysistest. _validate_test_optimization_pin_files_missing_go_mod_failure_test_impl, expect_failure = True, ) +go_macro_orchestrion_enablement_mismatch_failure_test = analysistest.make( + _go_macro_orchestrion_enablement_mismatch_failure_test_impl, + expect_failure = True, +) wrapper_output_name_non_windows_test = analysistest.make( _wrapper_output_name_non_windows_test_impl, ) diff --git a/modules/go/tools/dd_topt_go_bootstrap/main.go b/modules/go/tools/dd_topt_go_bootstrap/main.go index ba606071..0fbccb48 100644 --- a/modules/go/tools/dd_topt_go_bootstrap/main.go +++ b/modules/go/tools/dd_topt_go_bootstrap/main.go @@ -2025,6 +2025,7 @@ datadog_go_topt.test_optimization_go( name = "%s", service = "%s", runtime_version = "%s", + enabled_by_env = True, %s) use_repo(datadog_go_topt, "%s") diff --git a/modules/go/tools/dd_topt_go_bootstrap/main_test.go b/modules/go/tools/dd_topt_go_bootstrap/main_test.go index 9d252b1a..85305ef3 100644 --- a/modules/go/tools/dd_topt_go_bootstrap/main_test.go +++ b/modules/go/tools/dd_topt_go_bootstrap/main_test.go @@ -1565,6 +1565,9 @@ func TestManagedGuidedModuleBlockIncludesModulePath(t *testing.T) { if !strings.Contains(got, `module_path = "github.com/DataDog/example-service"`) { t.Fatalf("expected guided block to include explicit module_path:\n%s", got) } + if !strings.Contains(got, `enabled_by_env = True`) { + t.Fatalf("expected guided block to opt into config-gated metadata sync:\n%s", got) + } } func TestWriteStarterOrchestrionYML(t *testing.T) { diff --git a/modules/go/topt_go_extension.bzl b/modules/go/topt_go_extension.bzl index 28fc3992..7925334b 100644 --- a/modules/go/topt_go_extension.bzl +++ b/modules/go/topt_go_extension.bzl @@ -60,7 +60,7 @@ def _build_go_single_repo_spec( test_management = True, flaky_tests = True, enabled = True, - enabled_by_env = True, + enabled_by_env = False, require_git_metadata = False, debug = False): """Build the complete sync spec for one Go service repository.""" @@ -103,7 +103,7 @@ def _build_go_multi_repo_specs( test_management = True, flaky_tests = True, enabled = True, - enabled_by_env = True, + enabled_by_env = False, require_git_metadata = False, debug = False): """Build the per-service sync specs for a multi-service Go tag.""" @@ -233,7 +233,7 @@ test_optimization_go_extension = module_extension( "test_management": attr.bool(default = True), "flaky_tests": attr.bool(default = True), "enabled": attr.bool(default = True), - "enabled_by_env": attr.bool(default = True), + "enabled_by_env": attr.bool(default = False), "require_git_metadata": attr.bool(default = False), "debug": attr.bool(default = False), }), diff --git a/modules/go/topt_go_orchestrion.bzl b/modules/go/topt_go_orchestrion.bzl index 0337e281..8aa22473 100644 --- a/modules/go/topt_go_orchestrion.bzl +++ b/modules/go/topt_go_orchestrion.bzl @@ -6,6 +6,8 @@ """Internal Orchestrion wrapper rule for Go tests.""" +load("@bazel_skylib//rules:common_settings.bzl", "BuildSettingInfo") + _BAZEL_TARGET_METADATA_OUTPUT = "bazel_target_metadata.json" _ORCHESTRION_MODE_GENERAL = "general" _ORCHESTRION_MODE_TEST_OPTIMIZATION = "test_optimization" @@ -101,6 +103,13 @@ if not "%%META_BASENAME%%"=="" if not "%%UNDECLARED_DIR%%"=="" ( ) def _orch_go_test_impl(ctx): + if ctx.attr.test_optimization_enabled and not ctx.attr._orchestrion_enabled[BuildSettingInfo].value: + fail( + "orch_go_test: Test Optimization metadata is enabled but Orchestrion is disabled; " + + "run with --config=test-optimization. Consumers upgrading an existing setup should " + + "rerun dd_topt_go_bootstrap with --write-bazelrc before building tests.", + ) + dep_exe, dep_runfiles = _dep_exec_and_runfiles(ctx.attr.actual) dep_run_environment = _dep_run_environment_info(ctx.attr.actual) is_windows = ctx.target_platform_has_constraint(ctx.attr._windows_constraint[platform_common.ConstraintValueInfo]) @@ -142,9 +151,17 @@ orch_go_test = rule( ], doc = "Internal Orchestrion mode forwarded to the raw go_test target.", ), + "test_optimization_enabled": attr.bool( + default = False, + doc = "Whether the selected generated metadata repository is enabled.", + ), "_allowlist_function_transition": attr.label( default = "@bazel_tools//tools/allowlists/function_transition_allowlist", ), + "_orchestrion_enabled": attr.label( + default = "@rules_go//go/private/orchestrion:enabled", + providers = [BuildSettingInfo], + ), "_windows_constraint": attr.label(default = "@platforms//os:windows"), }, test = True, diff --git a/modules/go/topt_go_test.bzl b/modules/go/topt_go_test.bzl index 069c153f..d1afeb99 100644 --- a/modules/go/topt_go_test.bzl +++ b/modules/go/topt_go_test.bzl @@ -603,5 +603,6 @@ def dd_topt_go_test( name = name, actual = ":" + raw_name, orchestrion_mode = orchestrion_mode, + test_optimization_enabled = bool(_svc.get("enabled", True)), **wrapper_kwargs ) diff --git a/tools/agent-skills/go-test-optimization-onboarding/references/bzlmod-onboarding.md b/tools/agent-skills/go-test-optimization-onboarding/references/bzlmod-onboarding.md index 14b577c8..02ccc24b 100644 --- a/tools/agent-skills/go-test-optimization-onboarding/references/bzlmod-onboarding.md +++ b/tools/agent-skills/go-test-optimization-onboarding/references/bzlmod-onboarding.md @@ -146,6 +146,7 @@ datadog_go_topt.test_optimization_go( service = "", runtime_version = "", module_path = "", + enabled_by_env = True, require_git_metadata = True, ) @@ -296,6 +297,7 @@ datadog_go_topt.test_optimization_go( services = ["go-service-a", "go-service-b"], runtime_version = "", module_path = "", + enabled_by_env = True, require_git_metadata = True, ) diff --git a/tools/tests/core/test_sync_disabled_utils.bzl b/tools/tests/core/test_sync_disabled_utils.bzl index 83e42c84..4a574ae8 100644 --- a/tools/tests/core/test_sync_disabled_utils.bzl +++ b/tools/tests/core/test_sync_disabled_utils.bzl @@ -17,8 +17,8 @@ load( "render_disabled_flaky_tests_json_for_tests", "render_disabled_known_tests_json_for_tests", "render_disabled_settings_json_for_tests", - "render_disabled_test_management_json_for_tests", "render_disabled_telemetry_facts_json_for_tests", + "render_disabled_test_management_json_for_tests", "repository_environ_for_tests", "resolve_service_and_environment_for_tests", ) diff --git a/tools/tests/core/test_sync_utils.bzl b/tools/tests/core/test_sync_utils.bzl index 4fe4dd5f..2a115189 100644 --- a/tools/tests/core/test_sync_utils.bzl +++ b/tools/tests/core/test_sync_utils.bzl @@ -1528,7 +1528,9 @@ def _example_stub_export_manifest_path_test(ctx): go_module_path = "example.com/workspace-go-integration", go_sanitized_module_path = "example_com_workspace_go_integration", go_module_included = True, + enabled = False, ) + asserts.true(env, '"enabled": False' in content) asserts.true(env, '"repo_name": "test_optimization_data"' in content) asserts.true(env, '"manifest_path": "custom_topt/manifest.txt"' in content) asserts.true(env, '"service_name": "workspace-go-service"' in content) diff --git a/tools/tests/example_stub_repo.bzl b/tools/tests/example_stub_repo.bzl index f917ba7e..5f4a9f2e 100644 --- a/tools/tests/example_stub_repo.bzl +++ b/tools/tests/example_stub_repo.bzl @@ -95,7 +95,8 @@ def _render_stub_export( manifest_path, go_module_path, go_sanitized_module_path, - go_module_included): + go_module_included, + enabled = True): """Render export.bzl content for the stub repository.""" mapping_lines = [] for key in service_keys: @@ -103,6 +104,7 @@ def _render_stub_export( return ( "topt_data = {\n" + + " \"enabled\": %s,\n" % ("True" if enabled else "False") + " \"repo_name\": %s,\n" % _bzl_string_literal(repo_name) + " \"service_name\": %s,\n" % _bzl_string_literal(service_name) + " \"manifest_path\": %s,\n" % _bzl_string_literal(manifest_path) + @@ -255,6 +257,7 @@ def _example_stub_repo_impl(ctx): go_module_path = ctx.attr.go_module_path, go_sanitized_module_path = ctx.attr.go_sanitized_module_path, go_module_included = ctx.attr.go_module_included, + enabled = ctx.attr.enabled, ) ctx.file("export.bzl", export) ctx.file("module_runfiles.bzl", _render_stub_module_runfiles_bzl(ctx.name, out_dir)) @@ -276,6 +279,7 @@ def _example_stub_repo_impl(ctx): example_stub_repo = repository_rule( implementation = _example_stub_repo_impl, attrs = { + "enabled": attr.bool(default = True), "go_module_included": attr.bool(default = False), "go_module_path": attr.string(default = "example.com/stub"), "go_sanitized_module_path": attr.string(default = "example_com_stub"), @@ -293,6 +297,7 @@ def _example_stub_repo_extension_impl(module_ctx): for call in mod.tags.example_stub_repo: example_stub_repo( name = call.name, + enabled = call.enabled, go_module_included = call.go_module_included, go_module_path = call.go_module_path, go_sanitized_module_path = call.go_sanitized_module_path, @@ -307,6 +312,7 @@ example_stub_repo_extension = module_extension( implementation = _example_stub_repo_extension_impl, tag_classes = { "example_stub_repo": tag_class(attrs = { + "enabled": attr.bool(default = True), "go_module_included": attr.bool(default = False), "go_module_path": attr.string(default = "example.com/stub"), "go_sanitized_module_path": attr.string(default = "example_com_stub"), diff --git a/tools/tests/integration/run_bzlmod_go_integration.sh b/tools/tests/integration/run_bzlmod_go_integration.sh index 8bb8c8d2..ac028dd5 100755 --- a/tools/tests/integration/run_bzlmod_go_integration.sh +++ b/tools/tests/integration/run_bzlmod_go_integration.sh @@ -86,9 +86,30 @@ BAZEL_EXTRA_ARGS+=( source "$REPO_ROOT/tools/tests/integration/go_integration_mock_server.sh" +shutdown_bazel_workspace_servers() { + local workspace_dir + + # Bazel owns one server per generated workspace. Windows cannot remove their + # output bases until every server releases its files. + if [[ -d "$WORKSPACE_ROOT" ]]; then + for workspace_dir in "$WORKSPACE_ROOT"/*; do + [[ -d "$workspace_dir" ]] || continue + ( + cd "$workspace_dir" + USE_BAZEL_VERSION="$BAZEL_VERSION" "$BAZEL" --output_user_root="$BAZEL_OUTPUT_USER_ROOT" shutdown + ) >/dev/null 2>&1 || true + done + fi + + ( + cd "$REPO_ROOT" + USE_BAZEL_VERSION="$BAZEL_VERSION" "$BAZEL" --output_user_root="$BAZEL_OUTPUT_USER_ROOT" shutdown + ) >/dev/null 2>&1 || true +} + cleanup() { stop_go_integration_mock_server - USE_BAZEL_VERSION="$BAZEL_VERSION" "$BAZEL" --output_user_root="$BAZEL_OUTPUT_USER_ROOT" shutdown >/dev/null 2>&1 || true + shutdown_bazel_workspace_servers if [[ "${KEEP_TMP:-0}" == "1" ]]; then echo "KEEP_TMP=1: workspace fixtures left at $TMP_ROOT" return @@ -1211,6 +1232,9 @@ run_windows_enabled_smoke() { local resolved_repos="$TMP_ROOT/windows-enabled-resolved-repositories.bzl" local alias_files="$TMP_ROOT/windows-enabled-aliases.log" local test_log="$TMP_ROOT/windows-enabled-test.log" + + start_go_integration_mock_server "$TMP_ROOT" "$MODULE_IMPORTPATH" + local -a enabled_flags=( "${BAZEL_EXTRA_ARGS[@]}" "${GO_INTEGRATION_MOCK_REPO_ENVS[@]}" @@ -1227,7 +1251,6 @@ run_windows_enabled_smoke() { rm -rf "$ws_dir" mkdir -p "$ws_dir" - start_go_integration_mock_server "$TMP_ROOT" "$MODULE_IMPORTPATH" write_module_file "$ws_dir" write_shared_fixture_sources "$ws_dir" write_fixture_bazelrc "$ws_dir" "rules_go" diff --git a/tools/tests/integration/run_mock_server_tests.sh b/tools/tests/integration/run_mock_server_tests.sh index 6d300432..7deecee4 100755 --- a/tools/tests/integration/run_mock_server_tests.sh +++ b/tools/tests/integration/run_mock_server_tests.sh @@ -2846,6 +2846,7 @@ go_topt.test_optimization_go( services = ["go-service", "go_service"], out_dir = "custom_topt", runtime_version = "1.2.3", + enabled_by_env = True, ) use_repo( @@ -3025,6 +3026,7 @@ go_topt.test_optimization_go( name = "test_optimization_data", service = "go-service", runtime_version = "1.2.3", + enabled_by_env = True, ) use_repo(go_topt, "test_optimization_data") diff --git a/tools/tests/integration/run_workspace_go_integration.sh b/tools/tests/integration/run_workspace_go_integration.sh index bbeea9ae..72f21911 100755 --- a/tools/tests/integration/run_workspace_go_integration.sh +++ b/tools/tests/integration/run_workspace_go_integration.sh @@ -86,9 +86,30 @@ HERMETIC_TEST_FLAGS=( source "$REPO_ROOT/tools/tests/integration/go_integration_mock_server.sh" +shutdown_bazel_workspace_servers() { + local workspace_dir + + # Bazel owns one server per generated workspace. Windows cannot remove their + # output bases until every server releases its files. + if [[ -d "$WORKSPACE_ROOT" ]]; then + for workspace_dir in "$WORKSPACE_ROOT"/*; do + [[ -d "$workspace_dir" ]] || continue + ( + cd "$workspace_dir" + USE_BAZEL_VERSION="$BAZEL_VERSION" "$BAZEL" --output_user_root="$BAZEL_OUTPUT_USER_ROOT" shutdown + ) >/dev/null 2>&1 || true + done + fi + + ( + cd "$REPO_ROOT" + USE_BAZEL_VERSION="$BAZEL_VERSION" "$BAZEL" --output_user_root="$BAZEL_OUTPUT_USER_ROOT" shutdown + ) >/dev/null 2>&1 || true +} + cleanup() { stop_go_integration_mock_server - USE_BAZEL_VERSION="$BAZEL_VERSION" "$BAZEL" --output_user_root="$BAZEL_OUTPUT_USER_ROOT" shutdown >/dev/null 2>&1 || true + shutdown_bazel_workspace_servers if [[ "${KEEP_TMP:-0}" == "1" ]]; then echo "KEEP_TMP=1: workspace fixtures left at $TMP_ROOT" return @@ -1321,6 +1342,9 @@ run_windows_enabled_smoke() { local resolved_repos="$TMP_ROOT/windows-enabled-resolved-repositories.bzl" local alias_files="$TMP_ROOT/windows-enabled-aliases.log" local test_log="$TMP_ROOT/windows-enabled-test.log" + + start_go_integration_mock_server "$TMP_ROOT" "$MODULE_IMPORTPATH" + local -a enabled_flags=( "${BAZEL_EXTRA_ARGS[@]}" "${GO_INTEGRATION_MOCK_REPO_ENVS[@]}" @@ -1338,7 +1362,6 @@ run_windows_enabled_smoke() { rm -rf "$ws_dir" mkdir -p "$ws_dir" - start_go_integration_mock_server "$TMP_ROOT" "$MODULE_IMPORTPATH" write_positive_workspace "$ws_dir" "archive" write_shared_fixture_sources "$ws_dir" write_fixture_bazelrc "$ws_dir" "io_bazel_rules_go" From d1bf5248233d9bdb015552383840e2f8b66061fd Mon Sep 17 00:00:00 2001 From: Tony Redondo Date: Thu, 16 Jul 2026 16:35:02 +0200 Subject: [PATCH 07/23] ci: preserve Windows smoke test status --- .../integration/run_bzlmod_go_integration.sh | 34 +++++++++++++++---- .../run_workspace_go_integration.sh | 34 +++++++++++++++---- 2 files changed, 54 insertions(+), 14 deletions(-) diff --git a/tools/tests/integration/run_bzlmod_go_integration.sh b/tools/tests/integration/run_bzlmod_go_integration.sh index ac028dd5..565f2ace 100755 --- a/tools/tests/integration/run_bzlmod_go_integration.sh +++ b/tools/tests/integration/run_bzlmod_go_integration.sh @@ -108,14 +108,23 @@ shutdown_bazel_workspace_servers() { } cleanup() { + local status=$? + + set +e stop_go_integration_mock_server shutdown_bazel_workspace_servers if [[ "${KEEP_TMP:-0}" == "1" ]]; then echo "KEEP_TMP=1: workspace fixtures left at $TMP_ROOT" - return + return "$status" fi chmod -R u+w "$TMP_ROOT" 2>/dev/null || true - rm -rf "$TMP_ROOT" + if ! rm -rf "$TMP_ROOT" 2>/dev/null; then + sleep 2 + if ! rm -rf "$TMP_ROOT" 2>/dev/null; then + echo "warning: unable to remove temporary workspace $TMP_ROOT" >&2 + fi + fi + return "$status" } trap cleanup EXIT INT TERM HUP @@ -1184,14 +1193,18 @@ run_disabled_no_fetch_smoke() { : > "$alias_log" for alias in "${aliases[@]}"; do - ( + if ! ( cd "$ws_dir" "${disabled_env[@]}" USE_BAZEL_VERSION="$BAZEL_VERSION" "$BAZEL" --output_user_root="$BAZEL_OUTPUT_USER_ROOT" cquery \ "${disabled_flags[@]}" \ --experimental_repository_resolved_file="$resolved_repos" \ "@rules_go//go/private/orchestrion:$alias" --output=files \ >"$TMP_ROOT/disabled-alias-$alias.out" - ) 2>"$TMP_ROOT/disabled-alias-$alias.err" + ) 2>"$TMP_ROOT/disabled-alias-$alias.err"; then + echo "error: disabled Bzlmod alias query failed for $alias" >&2 + cat "$TMP_ROOT/disabled-alias-$alias.err" >&2 + exit 1 + fi cat "$TMP_ROOT/disabled-alias-$alias.out" >>"$alias_log" done if [[ -s "$alias_log" ]]; then @@ -1200,13 +1213,17 @@ run_disabled_no_fetch_smoke() { exit 1 fi - ( + if ! ( cd "$ws_dir" "${disabled_env[@]}" USE_BAZEL_VERSION="$BAZEL_VERSION" "$BAZEL" --output_user_root="$BAZEL_OUTPUT_USER_ROOT" test \ "${disabled_flags[@]}" \ --experimental_repository_resolved_file="$resolved_repos" \ "$HELLO_TEST_TARGET" - ) >"$test_log" 2>&1 + ) >"$test_log" 2>&1; then + echo "error: disabled Bzlmod test failed" >&2 + cat "$test_log" >&2 + exit 1 + fi if [[ -f "$resolved_repos" ]] && rg -n 'rules_go_orchestrion_tool|v0[.]0[.]0-rto-disabled-fetch-sentinel' "$resolved_repos"; then echo "error: disabled Bzlmod smoke resolved the Orchestrion sentinel repository" >&2 @@ -1220,7 +1237,10 @@ run_disabled_no_fetch_smoke() { fi local testlogs - testlogs="$(cd "$ws_dir" && "${disabled_env[@]}" USE_BAZEL_VERSION="$BAZEL_VERSION" "$BAZEL" --output_user_root="$BAZEL_OUTPUT_USER_ROOT" info "${disabled_flags[@]}" bazel-testlogs)" + if ! testlogs="$(cd "$ws_dir" && "${disabled_env[@]}" USE_BAZEL_VERSION="$BAZEL_VERSION" "$BAZEL" --output_user_root="$BAZEL_OUTPUT_USER_ROOT" info "${disabled_flags[@]}" bazel-testlogs)"; then + echo "error: unable to locate disabled Bzlmod test logs" >&2 + exit 1 + fi if find "$testlogs" -path '*/test.outputs/payloads/tests/*.json' -print -quit | grep -q .; then echo "error: disabled Bzlmod smoke emitted Test Optimization payloads" >&2 exit 1 diff --git a/tools/tests/integration/run_workspace_go_integration.sh b/tools/tests/integration/run_workspace_go_integration.sh index 72f21911..01f0208e 100755 --- a/tools/tests/integration/run_workspace_go_integration.sh +++ b/tools/tests/integration/run_workspace_go_integration.sh @@ -108,14 +108,23 @@ shutdown_bazel_workspace_servers() { } cleanup() { + local status=$? + + set +e stop_go_integration_mock_server shutdown_bazel_workspace_servers if [[ "${KEEP_TMP:-0}" == "1" ]]; then echo "KEEP_TMP=1: workspace fixtures left at $TMP_ROOT" - return + return "$status" fi chmod -R u+w "$TMP_ROOT" 2>/dev/null || true - rm -rf "$TMP_ROOT" + if ! rm -rf "$TMP_ROOT" 2>/dev/null; then + sleep 2 + if ! rm -rf "$TMP_ROOT" 2>/dev/null; then + echo "warning: unable to remove temporary workspace $TMP_ROOT" >&2 + fi + fi + return "$status" } trap cleanup EXIT INT TERM HUP @@ -1247,14 +1256,18 @@ run_disabled_no_fetch_smoke() { : > "$alias_log" for alias in "${aliases[@]}"; do - ( + if ! ( cd "$ws_dir" "${disabled_env[@]}" USE_BAZEL_VERSION="$BAZEL_VERSION" "$BAZEL" --output_user_root="$BAZEL_OUTPUT_USER_ROOT" cquery \ "${disabled_flags[@]}" \ --experimental_repository_resolved_file="$resolved_repos" \ "@io_bazel_rules_go//go/private/orchestrion:$alias" --output=files \ >"$TMP_ROOT/disabled-alias-$alias.out" - ) 2>"$TMP_ROOT/disabled-alias-$alias.err" + ) 2>"$TMP_ROOT/disabled-alias-$alias.err"; then + echo "error: disabled WORKSPACE alias query failed for $alias" >&2 + cat "$TMP_ROOT/disabled-alias-$alias.err" >&2 + exit 1 + fi cat "$TMP_ROOT/disabled-alias-$alias.out" >>"$alias_log" done if [[ -s "$alias_log" ]]; then @@ -1263,13 +1276,17 @@ run_disabled_no_fetch_smoke() { exit 1 fi - ( + if ! ( cd "$ws_dir" "${disabled_env[@]}" USE_BAZEL_VERSION="$BAZEL_VERSION" "$BAZEL" --output_user_root="$BAZEL_OUTPUT_USER_ROOT" test \ "${disabled_flags[@]}" \ --experimental_repository_resolved_file="$resolved_repos" \ "$HELLO_TEST_TARGET" - ) >"$test_log" 2>&1 + ) >"$test_log" 2>&1; then + echo "error: disabled WORKSPACE test failed" >&2 + cat "$test_log" >&2 + exit 1 + fi if [[ -f "$resolved_repos" ]] && rg -n 'rules_go_orchestrion_tool|v0[.]0[.]0-rto-disabled-fetch-sentinel' "$resolved_repos"; then echo "error: disabled WORKSPACE smoke resolved the Orchestrion sentinel repository" >&2 @@ -1283,7 +1300,10 @@ run_disabled_no_fetch_smoke() { fi local testlogs - testlogs="$(cd "$ws_dir" && "${disabled_env[@]}" USE_BAZEL_VERSION="$BAZEL_VERSION" "$BAZEL" --output_user_root="$BAZEL_OUTPUT_USER_ROOT" info "${disabled_flags[@]}" bazel-testlogs)" + if ! testlogs="$(cd "$ws_dir" && "${disabled_env[@]}" USE_BAZEL_VERSION="$BAZEL_VERSION" "$BAZEL" --output_user_root="$BAZEL_OUTPUT_USER_ROOT" info "${disabled_flags[@]}" bazel-testlogs)"; then + echo "error: unable to locate disabled WORKSPACE test logs" >&2 + exit 1 + fi if find "$testlogs" -path '*/test.outputs/payloads/tests/*.json' -print -quit | grep -q .; then echo "error: disabled WORKSPACE smoke emitted Test Optimization payloads" >&2 exit 1 From 8518b12d62898226fbe1c6b9473bcd2f305614ba Mon Sep 17 00:00:00 2001 From: Tony Redondo Date: Thu, 16 Jul 2026 17:22:18 +0200 Subject: [PATCH 08/23] ci: fix Go integration harness portability --- .../integration/run_bzlmod_go_integration.sh | 16 +++++++++++++++- tools/tests/integration/run_mock_server_tests.sh | 13 ++++++++++--- .../integration/run_workspace_go_integration.sh | 16 +++++++++++++++- 3 files changed, 40 insertions(+), 5 deletions(-) diff --git a/tools/tests/integration/run_bzlmod_go_integration.sh b/tools/tests/integration/run_bzlmod_go_integration.sh index 565f2ace..481f98ab 100755 --- a/tools/tests/integration/run_bzlmod_go_integration.sh +++ b/tools/tests/integration/run_bzlmod_go_integration.sh @@ -197,6 +197,20 @@ sha256_file() { exit 1 } +file_uri() { + local path="$1" + + if command -v cygpath >/dev/null 2>&1; then + path="$(cygpath -m "$path")" + fi + "$PYTHON" - "$path" <<'PY' +from pathlib import Path +import sys + +print(Path(sys.argv[1]).resolve().as_uri()) +PY +} + wall_time_ns() { "$PYTHON" - <<'PY' import time @@ -546,7 +560,7 @@ create_fixture_archive() { tar -czf "$ARCHIVE_PATH" "$ARCHIVE_NAME" ) ARCHIVE_SHA256="$(sha256_file "$ARCHIVE_PATH")" - ARCHIVE_URL="file://$ARCHIVE_PATH" + ARCHIVE_URL="$(file_uri "$ARCHIVE_PATH")" } write_fixture_bazelrc() { diff --git a/tools/tests/integration/run_mock_server_tests.sh b/tools/tests/integration/run_mock_server_tests.sh index 7deecee4..8f7a9604 100755 --- a/tools/tests/integration/run_mock_server_tests.sh +++ b/tools/tests/integration/run_mock_server_tests.sh @@ -2812,6 +2812,10 @@ run_retry_sync_case \ # - dual local_path_override (repo root + modules/go) MULTI_WS="$TMP_WS/ws_multi" mkdir -p "$MULTI_WS" +cat > "$MULTI_WS/.bazelrc" <<'MULTI_BAZELRC_EOF' +common:test-optimization --repo_env=DD_TEST_OPTIMIZATION_ENABLED=1 +build:test-optimization --@rules_go//go/private/orchestrion:enabled=true +MULTI_BAZELRC_EOF cat > "$MULTI_WS/MODULE.bazel" <