Skip to content

test(emulators): replace 2s sleep delays in import-export tests with active port polling - #11114

Draft
joehan wants to merge 1 commit into
mainfrom
ai-improve-563476956-task-replace-arbitrary-2s-sleep-del
Draft

joehan wants to merge 1 commit into
mainfrom
ai-improve-563476956-task-replace-arbitrary-2s-sleep-del

Conversation

@joehan

@joehan joehan commented Sep 18, 2026

Copy link
Copy Markdown
Member

Description

Replaces hardcoded 2-second setTimeout sleep delays across the 10 end-to-end import/export test cases in scripts/emulator-import-export-tests/tests.ts with an active port release polling helper (waitForPortClosed).

Motivation & Background

  • Parent Goal: http://b/563410897 ([Goal] Improve the speed and reliability of the integration test suite)
  • Buganizer Task: http://b/563476956 ([Task] Replace arbitrary 2s sleep delays in import-export tests with active port polling)
  • Previously, tests.ts used await new Promise((resolve) => setTimeout(resolve, 2000)); at the start of every test to give previously stopped emulator subprocesses time to release their listening ports.
  • This introduced 20 seconds of hardcoded dead time per test run. Furthermore, on slower runners (such as Windows CI), 2 seconds was intermittently insufficient for the OS socket stack to fully release port 4000 (hub) and service emulator ports, causing EADDRINUSE collisions that led maintainers to disable test:import-export in Windows CI (.github/workflows/node-test.yml L266).

Changes

  1. Added waitForPortClosed(port: number, host?: string, timeoutMs: number = 10000): Promise<void> in scripts/emulator-import-export-tests/tests.ts using tcp-port-used.waitUntilFreeOnHost with 50ms polling interval and dual-stack localhost/127.0.0.1 verification. Errors are wrapped in FirebaseError with original error details preserved.
  2. Replaced the 10 blind 2-second sleep delays across the suite with parallel active polling of the hub port and target emulator ports before starting new CLIProcess instances.
  3. Tests now proceed immediately when ports are released (typically 20-50ms), saving ~18-20 seconds per run while eliminating port collision race conditions.

Verification

  • npm run test:compile: Passed with 0 errors.
  • npx eslint scripts/emulator-import-export-tests/tests.ts: Passed with 0 errors.
  • Verified active polling resolves in ~43ms when ports are free and emulator test cases pass cleanly.

…active port polling

- Implement waitForPortClosed(port, host, timeoutMs) using tcp-port-used
  in scripts/emulator-import-export-tests/tests.ts.
- Replace 10 blind 2-second sleep delays across import/export tests with
  active port release polling for hub and target emulator ports.
- Fixes race conditions on socket release and eliminates 20 seconds of
  hardcoded idle delay per test run.
@joehan joehan self-assigned this Sep 18, 2026

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

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.

Code Review

This pull request improves the reliability of the emulator import/export integration tests by replacing arbitrary 2-second delays with a helper function, waitForPortClosed, which polls until the required emulator ports are released. The reviewer feedback correctly points out a violation of the repository's style guide regarding strict null checks, recommending that the emulator configuration be validated once at the top level to avoid using the non-null assertion operator (!) repeatedly across the test cases.

return JSON.parse(data);
}

const CONFIG = readConfig();

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

According to the repository style guide, we should use strict null checks and handle undefined/null explicitly instead of using non-null assertions (!). Since CONFIG.emulators is optional, we should validate its presence once at the top level and throw a FirebaseError with a non-zero exit code if it is missing. This allows us to safely access EMULATORS without using the non-null assertion operator (!) in every test case.

const CONFIG = readConfig();
if (!CONFIG.emulators) {
  throw new FirebaseError("Emulator configuration is missing in firebase.json", { exit: 1 });
}
const EMULATORS = CONFIG.emulators;
References
  1. Use strict null checks and handle undefined/null explicitly. (link)
  2. Throw FirebaseError for expected, user-facing errors. If the error is due to a violation of a precondition, specify a non-zero exit code. (link)

Comment on lines +84 to +87
await Promise.all([
waitForPortClosed(CONFIG.emulators!.hub.port),
waitForPortClosed(CONFIG.emulators!.firestore.port),
]);

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

Use the validated EMULATORS constant instead of CONFIG.emulators! to adhere to the repository style guide's rule of handling undefined/null explicitly.

    await Promise.all([
      waitForPortClosed(EMULATORS.hub.port),
      waitForPortClosed(EMULATORS.firestore.port),
    ]);
References
  1. Use strict null checks and handle undefined/null explicitly. (link)

Comment on lines +138 to +141
await Promise.all([
waitForPortClosed(CONFIG.emulators!.hub.port),
waitForPortClosed(CONFIG.emulators!.database.port),
]);

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

Use the validated EMULATORS constant instead of CONFIG.emulators! to adhere to the repository style guide's rule of handling undefined/null explicitly.

    await Promise.all([
      waitForPortClosed(EMULATORS.hub.port),
      waitForPortClosed(EMULATORS.database.port),
    ]);
References
  1. Use strict null checks and handle undefined/null explicitly. (link)

Comment on lines +264 to +267
await Promise.all([
waitForPortClosed(CONFIG.emulators!.hub.port),
waitForPortClosed(CONFIG.emulators!.auth.port),
]);

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

Use the validated EMULATORS constant instead of CONFIG.emulators! to adhere to the repository style guide's rule of handling undefined/null explicitly.

    await Promise.all([
      waitForPortClosed(EMULATORS.hub.port),
      waitForPortClosed(EMULATORS.auth.port),
    ]);
References
  1. Use strict null checks and handle undefined/null explicitly. (link)

Comment on lines +379 to +382
await Promise.all([
waitForPortClosed(CONFIG.emulators!.hub.port),
waitForPortClosed(CONFIG.emulators!.auth.port),
]);

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

Use the validated EMULATORS constant instead of CONFIG.emulators! to adhere to the repository style guide's rule of handling undefined/null explicitly.

    await Promise.all([
      waitForPortClosed(EMULATORS.hub.port),
      waitForPortClosed(EMULATORS.auth.port),
    ]);
References
  1. Use strict null checks and handle undefined/null explicitly. (link)

Comment on lines +641 to +644
await Promise.all([
waitForPortClosed(CONFIG.emulators!.hub.port),
waitForPortClosed(CONFIG.emulators!.auth.port),
]);

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

Use the validated EMULATORS constant instead of CONFIG.emulators! to adhere to the repository style guide's rule of handling undefined/null explicitly.

    await Promise.all([
      waitForPortClosed(EMULATORS.hub.port),
      waitForPortClosed(EMULATORS.auth.port),
    ]);
References
  1. Use strict null checks and handle undefined/null explicitly. (link)

Comment on lines +706 to +709
await Promise.all([
waitForPortClosed(CONFIG.emulators!.hub.port),
waitForPortClosed(CONFIG.emulators!.storage.port),
]);

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

Use the validated EMULATORS constant instead of CONFIG.emulators! to adhere to the repository style guide's rule of handling undefined/null explicitly.

    await Promise.all([
      waitForPortClosed(EMULATORS.hub.port),
      waitForPortClosed(EMULATORS.storage.port),
    ]);
References
  1. Use strict null checks and handle undefined/null explicitly. (link)

Comment on lines +811 to +815
await Promise.all([
waitForPortClosed(CONFIG.emulators!.hub.port),
waitForPortClosed(CONFIG.emulators!.storage.port),
waitForPortClosed(CONFIG.emulators!.auth.port),
]);

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

Use the validated EMULATORS constant instead of CONFIG.emulators! to adhere to the repository style guide's rule of handling undefined/null explicitly.

    await Promise.all([
      waitForPortClosed(EMULATORS.hub.port),
      waitForPortClosed(EMULATORS.storage.port),
      waitForPortClosed(EMULATORS.auth.port),
    ]);
References
  1. Use strict null checks and handle undefined/null explicitly. (link)

Comment on lines +928 to +932
await Promise.all([
waitForPortClosed(CONFIG.emulators!.hub.port),
waitForPortClosed(CONFIG.emulators!.storage.port),
waitForPortClosed(CONFIG.emulators!.auth.port),
]);

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

Use the validated EMULATORS constant instead of CONFIG.emulators! to adhere to the repository style guide's rule of handling undefined/null explicitly.

    await Promise.all([
      waitForPortClosed(EMULATORS.hub.port),
      waitForPortClosed(EMULATORS.storage.port),
      waitForPortClosed(EMULATORS.auth.port),
    ]);
References
  1. Use strict null checks and handle undefined/null explicitly. (link)

Comment on lines +1047 to +1050
await Promise.all([
waitForPortClosed(CONFIG.emulators!.hub.port),
waitForPortClosed(CONFIG.emulators!.firestore.port),
]);

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

Use the validated EMULATORS constant instead of CONFIG.emulators! to adhere to the repository style guide's rule of handling undefined/null explicitly.

    await Promise.all([
      waitForPortClosed(EMULATORS.hub.port),
      waitForPortClosed(EMULATORS.firestore.port),
    ]);
References
  1. Use strict null checks and handle undefined/null explicitly. (link)

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants