Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .bazelignore
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
.bazel_output
.bazel_root
bazel-bin
bazel-out
bazel-testlogs
modules/go/bazel-bin
modules/go/bazel-out
modules/go/bazel-testlogs
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
# Docs: https://datadoghq.atlassian.net/wiki/spaces/SECENG/pages/5138645099/User+guide+dd-octo-sts
issuer: https://token.actions.githubusercontent.com

subject_pattern: "repo:DataDog/rules_test_optimization_tests:.*"
subject_pattern: "repo:DataDog/rules_test_optimization:.*"
claim_pattern:
event_name: (push|schedule|workflow_dispatch|pull_request|pull_request_target)
# ref: refs/heads/main
# ref_protected: "true"
# job_workflow_ref: DataDog/rules_test_optimization_tests/\.github/workflows/.*
repository: DataDog/rules_test_optimization_tests
# job_workflow_ref: DataDog/rules_test_optimization/\.github/workflows/.*
repository: DataDog/rules_test_optimization

permissions:
contents: read
2 changes: 2 additions & 0 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
version: 2
updates:
# This repo does not maintain pip/go/npm manifests; dependency automation is
# focused on security-sensitive GitHub Action pins.
- package-ecosystem: "github-actions"
directory: "/"
schedule:
Expand Down
92 changes: 69 additions & 23 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@ on:
- main
- "feature/**"

permissions:
contents: read

jobs:
bazel-tests:
strategy:
Expand Down Expand Up @@ -43,10 +46,14 @@ jobs:

- name: Exercise single-service runtests script (dry-run)
shell: bash
# Keep CI deterministic and secret-free: this validates script wiring
# and command construction without requiring live Datadog credentials.
run: RUNTESTS_DRY_RUN=1 bash ./examples/single_service/runtests.sh

- name: Exercise multi-service runtests script (dry-run)
shell: bash
# Keep CI deterministic and secret-free: this validates script wiring
# and command construction without requiring live Datadog credentials.
run: RUNTESTS_DRY_RUN=1 bash ./examples/multi_service/runtests.sh

# Exercises the uploader end-to-end against a local mock server.
Expand All @@ -67,41 +74,80 @@ jobs:
- name: Checkout
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2

- name: Run tools coverage report
shell: bash
run: ./bazelw coverage //tools/... --combined_report=lcov

- name: Enforce minimum tools coverage signal
shell: bash
env:
TOOLS_COVERAGE_MIN: "1.0"
TOOLS_COVERAGE_MIN: "5.0"
run: |
COVERAGE_FILE="$(./bazelw info output_path)/_coverage/_coverage_report.dat"
if [[ ! -e "$COVERAGE_FILE" ]]; then
echo "error: coverage report missing at $COVERAGE_FILE"
exit 1
fi
COVERAGE_FILE="$COVERAGE_FILE" python3 - <<'PY'
TOOLS_COVERAGE_MIN="$TOOLS_COVERAGE_MIN" python3 - <<'PY'
import os
import pathlib
import runpy
import sys

path = os.environ["COVERAGE_FILE"]
min_pct = float(os.environ.get("TOOLS_COVERAGE_MIN", "1.0"))
import trace

repo_root = pathlib.Path.cwd()
min_pct = float(os.environ.get("TOOLS_COVERAGE_MIN", "5.0"))
targets = [
repo_root / "tools/core/validate_payload_schema.py",
repo_root / "tools/core/schemas/sync_agentless_schema.py",
repo_root / "tools/dev/check_module_versions.py",
]

tracer = trace.Trace(count=True, trace=False)
exit_code = 0
try:
tracer.runctx(
'runpy.run_path("tools/tests/python/test_python_tools.py", run_name="__main__")',
{"runpy": runpy},
{},
)
except SystemExit as exc:
code = exc.code
if code is None:
exit_code = 0
elif isinstance(code, int):
exit_code = code
else:
print(code)
exit_code = 1

if exit_code != 0:
sys.exit(exit_code)

counts = tracer.results().counts
lines_found = 0
lines_hit = 0
with open(path, "r", encoding="utf-8") as handle:
for line in handle:
if line.startswith("LF:"):
lines_found += int(line[3:].strip())
elif line.startswith("LH:"):
lines_hit += int(line[3:].strip())

for target in targets:
target_path = target.resolve()
source_lines = target_path.read_text(encoding="utf-8").splitlines()
executable_lines = [
idx
for idx, line in enumerate(source_lines, start=1)
if line.strip() and not line.lstrip().startswith("#")
]
hit_lines = set()
for (filename, lineno), count in counts.items():
if count <= 0:
continue
try:
if pathlib.Path(filename).resolve() == target_path:
hit_lines.add(lineno)
except OSError:
continue
found = len(executable_lines)
hit = len([ln for ln in executable_lines if ln in hit_lines])
lines_found += found
lines_hit += hit
print(f"tools coverage file: {target_path} -> {hit}/{found} lines hit")

if lines_found <= 0:
print("warning: coverage report has zero instrumented lines; skipping threshold check")
sys.exit(0)
print("error: tools coverage probe found zero executable lines")
sys.exit(1)

pct = (lines_hit / lines_found) * 100.0
print(f"tools coverage: {lines_hit}/{lines_found} lines hit ({pct:.2f}%)")
print(f"tools coverage total: {lines_hit}/{lines_found} lines hit ({pct:.2f}%)")
if pct < min_pct:
print(
f"error: tools coverage {pct:.2f}% is below minimum {min_pct:.2f}%"
Expand Down
3 changes: 3 additions & 0 deletions .github/workflows/docs-links.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@ on:
- main
- "feature/**"

permissions:
contents: read

jobs:
links:
runs-on: ubuntu-latest
Expand Down
4 changes: 3 additions & 1 deletion .github/workflows/security.yml
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,9 @@ jobs:
strategy:
fail-fast: false
matrix:
language: ["python"]
# Python scans schema/tooling scripts; Actions scans workflow risks.
# Go code in this repository is limited to tiny examples.
language: ["python", "actions"]
steps:
- name: Checkout
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
Expand Down
6 changes: 5 additions & 1 deletion CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,9 +66,13 @@
## PR Checklist

- [ ] Updated tests for changed behavior.
- [ ] For parser/tooling edits, added malformed-input coverage and verified
error diagnostics remain actionable.
- [ ] Ran split-aware validation commands relevant to changed files.
- [ ] Updated docs/snippets for any load-path, module, or API changes.
- [ ] Confirmed no stale references to removed legacy paths (for example `//tools/go:*`).
- [ ] Confirmed no stale references to removed legacy paths (for example
`//tools/go:*`, replaced by `modules/go/...` targets).
- [ ] Reviewed timeout metadata when adding new slow tests (`--test_verbose_timeout_warnings`).
- [ ] Included rationale and risk notes in PR description.

## Release Runbook (Core + Go Companion)
Expand Down
2 changes: 1 addition & 1 deletion MODULE.bazel.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,7 @@ Use the core-only path above, then wire your language test rule/macro so it:

1. Includes `@test_optimization_data//:test_optimization_files` in `data`
2. Sets `DD_TEST_OPTIMIZATION_MANIFEST_FILE` to the manifest runfile path
- At runtime, resolve the payload root as `dirname(DD_TEST_OPTIMIZATION_MANIFEST_FILE)`.
3. Sets `DD_TEST_OPTIMIZATION_PAYLOADS_IN_FILES = "true"`
4. Writes payloads under `TEST_UNDECLARED_OUTPUTS_DIR/payloads/{tests,coverage}`
5. Adds `@test_optimization_data//:test_optimization_context` to uploader `data`
Expand Down Expand Up @@ -518,5 +519,5 @@ Fast checks before diving deep:

## Tips

- Maintainers: this repository's `./bazelw` supports `FETCH_SALT_TTL` (for example: `FETCH_SALT_TTL=3600 ./bazelw build //...`).
- Maintainers: this repository's `./bazelw` supports `FETCH_SALT_TTL` (for example: `FETCH_SALT_TTL=3600 ./bazelw build //tools/... //examples/...`).
- For debugging, set `debug = True` when calling the extension to get verbose logs, including request bodies and detected OS info.
9 changes: 8 additions & 1 deletion docs/Configuration_Reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ Notes:
- Parent directories are created automatically for all output paths.
- Omitted optional attributes keep default behavior and avoid unnecessary
cache-key churn.
- For HTTP numeric overrides, `-1` means "do not pin here"; resolution falls
back to environment overrides first, then the rule default.

## Multi-sync extension attributes

Expand All @@ -43,6 +45,11 @@ Extension tag: `test_optimization_multi_sync.test_optimization_multi_sync(...)`
| `runtime_name` | string | empty | Optional runtime name propagated to each per-service sync repo |
| `runtime_version` | string | empty | Optional runtime version propagated to each per-service sync repo |
| `runtime_arch` | string | auto-detected | Optional runtime arch propagated to each per-service sync repo |
| `http_connect_timeout_seconds` | int | `10` | Optional connect-timeout override propagated to each per-service sync repo (`-1` keeps default/env behavior) |
| `http_max_time_seconds` | int | `60` | Optional per-request max-time override propagated to each per-service sync repo (`-1` keeps default/env behavior) |
| `http_retry_attempts` | int | `3` | Optional retry-attempt override propagated to each per-service sync repo (`-1` keeps default/env behavior) |
| `http_retry_delay_seconds` | int | `2` | Optional retry-delay override propagated to each per-service sync repo (`-1` keeps default/env behavior) |
| `http_execute_timeout_buffer_seconds` | int | `60` | Optional outer execute-timeout buffer override propagated to each per-service sync repo (`-1` keeps default/env behavior) |
| `known_tests` | bool | `True` | Known Tests kill-switch propagated to each per-service sync repo |
| `test_management` | bool | `True` | Test Management kill-switch propagated to each per-service sync repo |
| `debug` | bool | `False` | Enables verbose logging for generated per-service sync repos |
Expand Down Expand Up @@ -157,7 +164,7 @@ The uploader rule reads these variables at `bazel run` time:
| `DD_TRACE_AGENT_URL` | Enables EVP proxy mode |
| `DD_TEST_OPTIMIZATION_INTAKE_BASE` | Optional agentless intake base override for test/dev setups |
| `DD_TEST_OPTIMIZATION_KEEP_PAYLOADS` | Keep payload files after successful upload |
| `DD_TEST_OPTIMIZATION_FILTER_PREFIX` | Upload only prefixed payload filenames |
| `DD_TEST_OPTIMIZATION_FILTER_PREFIX` | `0` uploads all payloads; `1` restricts to `span_events_*.json` / `coverage_*.json` |
| `DD_TEST_OPTIMIZATION_DEBUG` | Enable verbose uploader logs |
| `DD_TEST_OPTIMIZATION_GZIP` | Gzip test payloads before upload |
| `DD_TEST_OPTIMIZATION_MAX_WAIT_SEC` | Override uploader max wait |
Expand Down
14 changes: 13 additions & 1 deletion docs/Installation_Reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ git_override(
bazel_dep(name = "rules_go", version = "0.59.0")
```

Use the same commit SHA for core and companion modules.
Use the same full commit SHA (40 chars) for core and companion modules.

### Option B: local development overrides

Expand Down Expand Up @@ -262,6 +262,18 @@ dd_payload_uploader(
)
```

Multi-service aggregator variant:

```bzl
dd_payload_uploader(
name = "dd_upload_payloads",
data = [
"@test_optimization_data//:test_optimization_context_go_service",
"@test_optimization_data//:test_optimization_context_ruby_service",
],
)
```

### 5) Forward environment variables in `.bazelrc`

```text
Expand Down
20 changes: 16 additions & 4 deletions docs/Maintainers.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,9 @@ This document is for contributors and maintainers of

- Root workspace resolves `@datadog-rules-test-optimization-go` through
`tools/dev/go_bootstrap.bzl` (dev-only wiring).
- `go_bootstrap.local_go_companion(path = "...")` must stay repository-relative
(no absolute paths, drive prefixes, or `..` traversal) and must point to a
real module root containing `MODULE.bazel`.
- Do not add a root `bazel_dep` edge from core to the Go companion; that creates
a dependency cycle (`core -> go -> core`).
- Schema ownership remains in core:
Expand Down Expand Up @@ -87,10 +90,10 @@ Examples:

```sh
# Refresh only on git environment variables
./bazelw build //...
./bazelw build //tools/... //examples/...

# Refresh on an hourly TTL
FETCH_SALT_TTL=3600 ./bazelw build //...
FETCH_SALT_TTL=3600 ./bazelw build //tools/... //examples/...

# Override computed Git metadata
DD_GIT_REPOSITORY_URL=https://github.com/acme/api.git \
Expand All @@ -101,11 +104,11 @@ DD_GIT_COMMIT_SHA=$(git rev-parse HEAD) \

```powershell
# Refresh only on git environment variables
.\bazelw build //...
.\bazelw build //tools/... //examples/...

# Refresh on an hourly TTL
$env:FETCH_SALT_TTL = "3600"
.\bazelw build //...
.\bazelw build //tools/... //examples/...

# Override computed Git metadata
$env:DD_GIT_REPOSITORY_URL = "https://github.com/acme/api.git"
Expand Down Expand Up @@ -147,6 +150,11 @@ Notes:
- `DD_TEST_OPTIMIZATION_INTAKE_BASE` (uploader, agentless path)
- The harness asserts CODEOWNERS enrichment/preservation and runfile manifest
fallback behavior, and prints focused diagnostics on assertion failures.
- The harness requires `jq` for snapshot/enrichment assertions.
- Snapshot fixture contract:
- `citestcov_event.json` remains a JSON object with a non-empty `events` list.
- `citestcov_coverage.json` remains a JSON object with `version` and a
non-empty `files` list containing `filename` + `segments`.
- CODEOWNERS discovery intentionally checks both `docs/CODEOWNERS` and
`.docs/CODEOWNERS`; the `.docs` path is retained as a legacy compatibility
fallback for repositories that still keep ownership files there.
Expand All @@ -173,8 +181,12 @@ Notes:
line-coverage artifacts are generated and stay above a minimum floor.
- Shell linting scope includes integration harnesses, `bazelw`, and example
`runtests.sh` scripts.
- CI runs example `runtests.sh` scripts in `RUNTESTS_DRY_RUN=1` mode to verify
wiring without requiring Datadog credentials in PR checks.
- Repository tracks both `.bazelversion` and `MODULE.bazel.lock` in git to
reduce local/CI drift.
- `.bazelversion` is intentionally duplicated at repository root and
`modules/go/` so either workspace entrypoint resolves the same Bazel line.
- Current PR baseline checks:

```sh
Expand Down
4 changes: 3 additions & 1 deletion docs/RFC.md
Original file line number Diff line number Diff line change
Expand Up @@ -223,7 +223,9 @@ Runtime Uploader
- `dd_payload_uploader` is a normal Bazel rule (not a test) that runs via `bazel run` after tests complete. It discovers all `test.outputs/` directories in `bazel-testlogs/`, waits for quiescence, then uploads and deletes payloads. It supports:
- Agentless mode (`DD_API_KEY`, `DD_SITE`) posting to `https://citestcycle-intake.<site>/api/v2/citestcycle` and `https://citestcov-intake.<site>/api/v2/citestcov`.
- EVP proxy mode (`DD_TRACE_AGENT_URL`) posting to `/evp_proxy/v2/...` with subdomain routing headers.
- A single uploader target per workspace is required (enforced via lock file to prevent concurrent uploaders).
- A single uploader target per workspace is required (enforced via a runtime
uploader lock file to prevent concurrent uploads; unrelated to
`MODULE.bazel.lock`).
- When `context.json` is present in runfiles (supplied via a data dependency on `@<repo>//:test_optimization_context`), test payloads are enriched by merging context keys.
- Since `bazel run` executes locally with full host access, no sandbox workarounds are needed. The uploader runs after all tests complete, discovering payloads that Bazel collected from `TEST_UNDECLARED_OUTPUTS_DIR`.
- Recommended invocation preserves test exit code:
Expand Down
4 changes: 4 additions & 0 deletions docs/Troubleshooting.md
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,7 @@ If Bazel reports that sync requires WORKSPACE support, add
```powershell
bazel test //your:test --test_output=all *>&1 | Select-String "DD_TEST_OPTIMIZATION"
```
PowerShell uses `*>&1` (not Bash `2>&1`) to merge stderr/stdout.

4. **For RBE users**: Add `--remote_download_outputs=all` to download test
outputs locally.
Expand Down Expand Up @@ -181,6 +182,9 @@ full bundle.

2. **Check paths use forward slashes** in Starlark/Bazel contexts (backslashes
are auto-converted).
3. **Git Bash path conversion**: when running the integration harness from
PowerShell, ensure `cygpath` is available (Git for Windows). The harness
falls back to raw paths, but mixed-path conversion improves consistency.

## Getting help

Expand Down
Loading