Skip to content

fix(e2e): resolve repo root correctly in deploy helpers#1982

Closed
0xDevNinja wants to merge 2 commits into
Tracer-Cloud:mainfrom
0xDevNinja:issue/1418-deploy-helpers-repo-root
Closed

fix(e2e): resolve repo root correctly in deploy helpers#1982
0xDevNinja wants to merge 2 commits into
Tracer-Cloud:mainfrom
0xDevNinja:issue/1418-deploy-helpers-repo-root

Conversation

@0xDevNinja

Copy link
Copy Markdown
Contributor

Fixes #1418

Describe the changes you have made in this PR -

Three E2E AWS deploy helpers were computing project_root as Path(__file__).resolve().parents[3], which lands on <repo>/tests instead of the actual repo root. Subsequent asset paths then double-stacked into <repo>/tests/tests/upstream_lambda/pipeline_code and similar nonexistent locations, breaking Lambda code bundling and Docker build-context resolution before any cloud resources were provisioned.

This PR:

  • Corrects parents[3]parents[4] in all three deploy helpers:
    • tests/e2e/upstream_lambda/infrastructure_sdk/deploy.py
    • tests/e2e/upstream_prefect_ecs_fargate/infrastructure_sdk/deploy.py
    • tests/e2e/upstream_apache_flink_ecs/infrastructure_sdk/deploy.py
  • Repoints scenario-specific assets so they resolve under tests/e2e/<scenario>/... (Lambda pipeline_code, Prefect Dockerfile + trigger lambda, Flink Dockerfile).
  • Keeps shared assets resolving from tests/shared/... unchanged.
  • Adds an explicit E2E_DIR alongside TESTS_DIR in the Prefect helper so the shared-vs-scenario split is obvious from the path table.
  • Adds tests/e2e/test_infrastructure_deploy_paths.py — a regression test that imports each deploy helper via importlib, asserts module.project_root equals the actual repository root, and verifies the bundled asset paths (Lambda code, Dockerfiles, Alloy config) exist on disk. Catches parents[N] drift locally before CI burns minutes on cloud provisioning that will fail at iterdir().

Acceptance criteria from #1418:

  • All three deploy helpers resolve the repository root correctly.
  • Shared assets still resolve from tests/shared/....
  • Scenario-specific assets resolve from their correct tests/e2e/... directories.
  • A regression test covers the path resolution so these helpers do not drift again.

Demo/Screenshot for feature changes and bug fixes -

Before (current main, parents[3]):

$ python3 -c "from pathlib import Path; p=Path('tests/e2e/upstream_lambda/infrastructure_sdk/deploy.py'); print(p.resolve().parents[3])"
/Users/.../opensre/tests

Composed pipeline_dir then resolves to /Users/.../opensre/tests/tests/upstream_lambda/pipeline_code — does not exist, iterdir() raises during bundling.

After (this PR):

$ uv run pytest tests/e2e/test_infrastructure_deploy_paths.py -v
tests/e2e/test_infrastructure_deploy_paths.py::test_deploy_helper_resolves_repo_root[upstream_lambda] PASSED
tests/e2e/test_infrastructure_deploy_paths.py::test_deploy_helper_resolves_repo_root[upstream_prefect_ecs_fargate] PASSED
tests/e2e/test_infrastructure_deploy_paths.py::test_deploy_helper_resolves_repo_root[upstream_apache_flink_ecs] PASSED
=========== 3 passed in 0.11s ===========

Full make test-cov: 6512 passed, 6 skipped in 92s on this branch.


Code Understanding and AI Usage

Did you use AI assistance (ChatGPT, Claude, Copilot, etc.) to write any part of this code?

  • No, I wrote all the code myself
  • Yes, I used AI assistance (continue below)

Explain your implementation approach:

The bug is a single-character index drift compounded by a string-concatenation mistake. Each deploy helper lives at depth 4 from the repo root (tests/e2e/<scenario>/infrastructure_sdk/deploy.py), so Path(__file__).resolve().parents[N] must use N=4 to reach the repo, not N=3. Once that is correct, the asset-path strings still need to include the e2e/ segment for scenario-specific files (Lambda zips, Docker contexts) while leaving shared paths (tests/shared/...) alone.

I considered two alternatives before settling on this one:

  1. Inline Path(__file__).resolve().parents[N] with a smaller N and rebase asset paths off the helper directory directly. Rejected because it makes shared-asset paths read ../../../shared/... from inside the helper, which is harder to grep for and inconsistent with the existing pattern.
  2. Walk upwards searching for a sentinel (pyproject.toml). Rejected because it adds runtime cost and a layer of indirection for a fix that should be a one-line index change.

The regression test loads each helper via importlib.util.spec_from_file_location rather than a normal from tests.e2e... import because the per-scenario directories do not have __init__.py files. Loading by file path keeps the test self-contained and avoids touching package layout.


Checklist before requesting a review

  • I have added proper PR title and linked to the issue
  • I have performed a self-review of my code
  • I can explain the purpose of every function, class, and logic block I added
  • I understand why my changes work and have tested them thoroughly
  • I have considered potential edge cases and how my code handles them
  • If it is a core feature, I have added thorough tests
  • My code follows the project's style guidelines and conventions

The three AWS deploy helpers under tests/e2e/.../infrastructure_sdk/
computed `Path(__file__).resolve().parents[3]`, which lands on
<repo>/tests rather than the repo root. Asset paths were then built
with a `tests/<scenario>/...` prefix that double-stacked into
<repo>/tests/tests/... and broke Lambda bundling and Docker build
context resolution before any cloud resources were provisioned.

- parents[3] -> parents[4] in all three helpers
- scenario-specific asset paths now resolve under tests/e2e/<scenario>
- shared assets continue to resolve under tests/shared/...
- prefect helper introduces an explicit E2E_DIR alongside TESTS_DIR
  so the shared-vs-scenario split is obvious at the path table

Refs Tracer-Cloud#1418
Imports each deploy helper via importlib and asserts that
`module.project_root` equals the actual repository root, and that the
key bundled asset paths (Lambda zip sources, Docker contexts, Alloy
config) exist on disk. If a future edit drifts `parents[N]` back to 3
or re-introduces a `tests/<scenario>` double-prefix, the test fails
locally instead of in the middle of a multi-minute provisioning run.

Refs Tracer-Cloud#1418
@github-actions

Copy link
Copy Markdown
Contributor

Greptile code review

This repo uses Greptile for automated review. Before merge, aim for Confidence Score: 5/5 with zero unresolved review threads — see CONTRIBUTING.md.

Run a review — add a PR comment with:

@greptile review

Give it ~5-10 minutes (sometimes longer) for results, then fix feedback and re-trigger until you reach Confidence Score: 5/5.

Optional: automate with the greploop skill.

@greptile-apps

greptile-apps Bot commented May 14, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR fixes a one-level parents[N] index drift (34) in three E2E AWS deploy helpers so project_root resolves to the actual repository root instead of the tests/ subdirectory, and corrects the scenario-specific asset paths that were double-stacking the tests/ prefix.

  • Fixes parents[3]parents[4] in all three deploy helpers (upstream_lambda, upstream_prefect_ecs_fargate, upstream_apache_flink_ecs) and adds the missing e2e/ path segment to all scenario-specific asset paths (Lambda pipeline_code, Prefect Dockerfile + trigger lambda, Flink Dockerfile); shared assets under tests/shared/ are left unchanged.
  • Adds E2E_DIR = TESTS_DIR / \"e2e\" to the Prefect helper to make the shared-vs-scenario split unambiguous.
  • Adds tests/e2e/test_infrastructure_deploy_paths.py as a regression guard that loads each helper via importlib, asserts module.project_root == REPO_ROOT, and checks that the declared asset paths exist on disk.

Confidence Score: 4/5

The deploy helper changes are straightforward and correct; the only rough edges are in the new regression test's coverage depth and its implicit dependency on the full e2e package set.

The three root-path fixes are accurate and every affected path constant was updated consistently. The regression test correctly computes REPO_ROOT and will catch a future parents[N] drift. Two test design notes: it checks asset existence against independently-hardcoded paths rather than the module's own path attributes, and exec_module pulls in all transitive imports which could surface as opaque import errors in environments lacking the full e2e dependency set.

tests/e2e/test_infrastructure_deploy_paths.py - the test's coverage model and import-coupling are worth a second look before expanding it to additional scenarios.

Important Files Changed

Filename Overview
tests/e2e/upstream_lambda/infrastructure_sdk/deploy.py parents[3] to parents[4] fix is correct; both pipeline_dir usages (lines 189 and 434) updated consistently to include the e2e/ segment
tests/e2e/upstream_prefect_ecs_fargate/infrastructure_sdk/deploy.py parents[4] fix correct; new E2E_DIR constant makes shared-vs-scenario split explicit; PREFECT_DOCKERFILE and TRIGGER_LAMBDA_CODE now route through E2E_DIR; shared paths (ALLOY_CONFIG_DIR, MOCK_API_CODE) left unchanged via TESTS_DIR
tests/e2e/upstream_apache_flink_ecs/infrastructure_sdk/deploy.py parents[4] fix correct; dockerfile_path gets the missing e2e/ segment; trigger_code_dir at line 364 already encoded e2e/ as a single-string path and is correct with the root fix
tests/e2e/test_infrastructure_deploy_paths.py Regression test correctly verifies REPO_ROOT and asset existence, but checks hardcoded expected paths independently rather than asserting the module's own path attributes; exec_module also couples the test to boto3/botocore/app package availability

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A["deploy.py (depth 4 from repo root)"] -->|"Path(__file__).resolve()"| B["Absolute file path"]
    B -->|".parents[4] (was .parents[3])"| C["project_root = repo root"]
    C --> D["TESTS_DIR = project_root / tests"]
    D --> E["Shared assets: tests/shared/external_vendor_api, tests/shared/infrastructure_code/alloy_config"]
    C --> F["E2E_DIR = TESTS_DIR / e2e (new)"]
    F --> G["Scenario-specific assets: tests/e2e/scenario/pipeline_code, tests/e2e/scenario/infrastructure_code/Dockerfile"]
Loading

Reviews (1): Last reviewed commit: "test(e2e): regression coverage for deplo..." | Re-trigger Greptile

Comment on lines +81 to +83
for relative in expected_assets:
asset = REPO_ROOT / relative
assert asset.exists(), f"{scenario}: expected asset path missing on disk: {asset}"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P2 Test asserts path existence but not module attribute values

The loop checks that each expected path exists on disk (computed independently via REPO_ROOT), but never compares those expected paths against the module's own path attributes. For upstream_prefect_ecs_fargate, module.PREFECT_DOCKERFILE and module.TRIGGER_LAMBDA_CODE are module-level constants that could drift (e.g., someone drops E2E_DIR back to TESTS_DIR for one of them) without this test catching it, as long as module.project_root stays correct and the on-disk asset still exists. Adding assertions like assert module.PREFECT_DOCKERFILE == REPO_ROOT / "tests/e2e/upstream_prefect_ecs_fargate/infrastructure_code/prefect_image/Dockerfile" for each scenario that exposes module-level path constants would close this gap.

Comment on lines +52 to +63
def _load_deploy_module(scenario: str, deploy_file: Path) -> ModuleType:
module_name = f"_deploy_helper_under_test_{scenario}"
spec = importlib.util.spec_from_file_location(module_name, deploy_file)
assert spec is not None and spec.loader is not None
module = importlib.util.module_from_spec(spec)
sys.modules[module_name] = module
try:
spec.loader.exec_module(module)
except Exception:
sys.modules.pop(module_name, None)
raise
return module

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P2 exec_module couples the test to all transitive imports

spec.loader.exec_module(module) executes the entire deploy file, including its top-level imports (from app.utils.config import load_env, from botocore.exceptions import ClientError, etc.). If any of those packages are absent or unavailable in a lightweight CI environment (unit-test-only jobs, thin containers), the test raises ImportError rather than a meaningful path-assertion failure, masking the actual purpose of the test. Consider wrapping the exec in a pytest.importorskip-style guard, or document explicitly that this test requires the full e2e dependency set so it isn't accidentally included in a restricted job.

@0xDevNinja

Copy link
Copy Markdown
Contributor Author

Closing this — I should have searched closed PRs against #1418 before opening. I now see #1419 (approved + Greptile 5/5), #1930, and #1912/#1936 were all closed with the same needs more input from the community direction from @muddlebee. Same root-cause fix here, so re-submitting it doesn't change that signal. Will leave #1418 alone until a maintainer reopens it for contribution.

@0xDevNinja 0xDevNinja closed this May 14, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

bug(e2e): deploy helpers resolve repo root incorrectly and break asset paths

1 participant