Skip to content

fix(e2e): use unique SSM paths per CLI test run to prevent race conditions#130

Open
macalbert wants to merge 4 commits intomainfrom
fix/e2e-ssm-path-race-condition
Open

fix(e2e): use unique SSM paths per CLI test run to prevent race conditions#130
macalbert wants to merge 4 commits intomainfrom
fix/e2e-ssm-path-race-condition

Conversation

@macalbert
Copy link
Copy Markdown
Owner

@macalbert macalbert commented Mar 26, 2026

Summary

Concurrent CI runs of e2e/cli.test.ts shared hardcoded SSM paths (/Test/Token, /Test/SingleVariable) against real AWS SSM. When two runs overlapped, one run's cleanUpSsm could delete parameters that another run had just created, causing flaky ParameterNotFound errors in the Assert phase.

This fix generates a unique runId per test run and creates temporary map files with isolated SSM paths, eliminating the race condition.

Changes

  • e2e/cli.test.ts
    • Generate a runId (randomUUID().slice(0, 8)) at describe-block scope
    • SSM paths are now /Test/<runId>/Token and /Test/<runId>/SingleVariable
    • Map files are created dynamically in beforeAll (param-map-<runId>.json, param-map-with-aws-config-<runId>.json)
    • Temporary map files are cleaned up in afterAll

Testing

  • pnpm test passes (19/19 unit test files)
  • pnpm lint passes
  • pnpm build passes
  • E2E verification in CI (requires AWS credentials)

Related

Fixes flaky Should_GenerateEnvironmentFile_When_ValidArgumentsAreProvided failure observed in publish-action CI workflow.


Open with Devin

…tions

Concurrent CI runs shared hardcoded SSM paths (/Test/Token,
/Test/SingleVariable) against real AWS SSM, causing ParameterNotFound
errors when one run's cleanup deleted parameters another run had just
created.

Each test run now generates a unique runId and creates temporary map
files with isolated SSM paths (/Test/<runId>/Token). Temp files are
cleaned up in afterAll.
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Mar 26, 2026

Important

Review skipped

Review was skipped due to path filters

⛔ Files ignored due to path filters (1)
  • e2e/cli.test.ts is excluded by none and included by none

CodeRabbit blocks several paths by default. You can override this behavior by explicitly including those paths in the path filters. For example, including **/dist/** will override the default block on the dist directory, by removing the pattern from both the lists.

⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 7fcfc3e8-ad90-4cb7-8c89-114577d24ff2

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/e2e-ssm-path-race-condition

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@gemini-code-assist
Copy link
Copy Markdown
Contributor

Summary of Changes

Hello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request addresses a critical flakiness issue in the E2E CLI tests, particularly in CI environments where concurrent test runs could lead to race conditions. By introducing unique identifiers for each test execution and dynamically managing AWS SSM paths and local map files, the changes ensure that tests operate in isolated environments, preventing one run from interfering with another's resources. This significantly improves the reliability and stability of the E2E test suite.

Highlights

  • Unique Test Run Identifiers: A unique runId is now generated for each E2E test run to ensure isolation between concurrent CI executions.
  • Dynamic SSM Paths: AWS SSM parameter paths are now dynamically constructed to include the runId, preventing conflicts when multiple tests interact with the same base paths.
  • Temporary Map File Generation: Parameter map files (param-map-<runId>.json and param-map-with-aws-config-<runId>.json) are created dynamically in the beforeAll hook, using the unique runId for their filenames and content.
  • Resource Cleanup: The dynamically generated temporary map files are explicitly removed in the afterAll hook to ensure a clean state after each test run.

🧠 New Feature in Public Preview: You can now enable Memory to help Gemini Code Assist learn from your team's feedback. This makes future code reviews more consistent and personalized to your project's style. Click here to enable Memory in your admin console.

Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for GitHub and other Google products, sign up here.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

@macalbert macalbert self-assigned this Mar 26, 2026
@macalbert macalbert added the test label Mar 26, 2026
@macalbert macalbert requested a review from Copilot March 26, 2026 01:40
@macalbert macalbert added the bug Something isn't working label Mar 26, 2026
Copy link
Copy Markdown
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request enhances E2E tests by introducing unique identifiers for each test run, dynamically generating test-specific configuration files, and implementing cleanup logic for these temporary files. This approach prevents race conditions and ensures better test isolation. Feedback includes suggestions to improve file I/O operations: using fs.promises.writeFile with Promise.all for parallel and asynchronous file creation in beforeAll, and fs.promises.rm with force: true and Promise.all for more efficient and simplified file cleanup in afterAll.

Comment on lines +59 to +73
writeFileSync(
mapFilePath,
JSON.stringify({ TOKEN_SECRET: `${ssmPrefix}/Token` }, null, 2),
);
writeFileSync(
mapFileWithConfigPath,
JSON.stringify(
{
$config: { provider: 'aws' },
TOKEN_SECRET: `${ssmPrefix}/Token`,
},
null,
2,
),
);
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.

medium

In an async function like beforeAll, it's a good practice to use asynchronous file operations to avoid blocking the event loop. You can replace the synchronous writeFileSync with the asynchronous writeFile from fs/promises. This also allows you to write both files in parallel using Promise.all, which is more efficient.

To apply this suggestion, you'll need to import writeFile from node:fs/promises and adjust the existing fs imports.

Suggested change
writeFileSync(
mapFilePath,
JSON.stringify({ TOKEN_SECRET: `${ssmPrefix}/Token` }, null, 2),
);
writeFileSync(
mapFileWithConfigPath,
JSON.stringify(
{
$config: { provider: 'aws' },
TOKEN_SECRET: `${ssmPrefix}/Token`,
},
null,
2,
),
);
await Promise.all([
writeFile(
mapFilePath,
JSON.stringify({ TOKEN_SECRET: `${ssmPrefix}/Token` }, null, 2),
),
writeFile(
mapFileWithConfigPath,
JSON.stringify(
{
$config: { provider: 'aws' },
TOKEN_SECRET: `${ssmPrefix}/Token`,
},
null,
2,
),
),
]);

Comment on lines +95 to +99
for (const f of [mapFilePath, mapFileWithConfigPath]) {
if (existsSync(f)) {
await unlink(f);
}
}
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.

medium

You can simplify the file cleanup by using fs.promises.rm with the force: true option, which is already imported in this file. This avoids the need for a synchronous existsSync check and will not throw an error if the file doesn't exist. Using Promise.all also performs the cleanup in parallel, which is more efficient than a sequential loop.

    await Promise.all(
      [mapFilePath, mapFileWithConfigPath].map((f) => rm(f, { force: true })),
    );

Copy link
Copy Markdown

@devin-ai-integration devin-ai-integration bot left a comment

Choose a reason for hiding this comment

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

✅ Devin Review: No Issues Found

Devin Review analyzed this PR and found no potential bugs to report.

View in Devin Review to see 3 additional findings.

Open in Devin Review

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR updates the E2E CLI tests to avoid cross-run interference in real AWS SSM by isolating parameter names per test run, eliminating a race where one CI run could delete another run’s parameters.

Changes:

  • Generate a per-run runId and use it to namespace SSM parameter paths under /Test/<runId>/....
  • Create per-run temporary map files (param-map-<runId>.json, param-map-with-aws-config-<runId>.json) during beforeAll.
  • Remove the generated map files during afterAll.

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

Labels

bug Something isn't working test

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants