Skip to content

🧪 Add tests for promiseAny in utils.ts#538

Closed
sunnylqm wants to merge 11 commits intomasterfrom
test-promise-any-6734343866273522940
Closed

🧪 Add tests for promiseAny in utils.ts#538
sunnylqm wants to merge 11 commits intomasterfrom
test-promise-any-6734343866273522940

Conversation

@sunnylqm
Copy link
Copy Markdown
Contributor

@sunnylqm sunnylqm commented Apr 14, 2026

🎯 What: The testing gap in promiseAny utility function has been addressed. The missing tests were added to src/__tests__/utils.test.ts to ensure coverage and logic correctness.

📊 Coverage: Three main scenarios are now tested:

  • It should resolve with the value of the first resolved promise.
  • It should resolve with the value of the first resolved promise even if others reject.
  • It should reject with error_all_promises_rejected when all promises reject.

Result: Enhanced test coverage for the core utilities, offering a safety net for any future refactoring of promiseAny and ensuring errors and proper fallback work natively and uniformly.


PR created automatically by Jules for task 6734343866273522940 started by @sunnylqm

Summary by CodeRabbit

  • Tests
    • Expanded coverage for promise-based behavior and rollout/hash logic, including determinism, boundary cases, and mixed success/failure scenarios.
    • Added test setup to stabilize environment versioning for reliable runs.
  • Performance & Reliability
    • Improved concurrent handling for file operations to speed up deletions and copies.
  • Refactor
    • Made the hashing utility available as a reusable/exported routine for broader use.

Co-authored-by: sunnylqm <615282+sunnylqm@users.noreply.github.com>
@google-labs-jules
Copy link
Copy Markdown
Contributor

👋 Jules, reporting for duty! I'm here to lend a hand with this pull request.

When you start a review, I'll add a 👀 emoji to each comment to let you know I've read it. I'll focus on feedback directed at me and will do my best to stay out of conversations between you and other bots or reviewers to keep the noise down.

I'll push a commit with your requested changes shortly after. Please note there might be a delay between these steps, but rest assured I'm on the job!

For more direct control, you can switch me to Reactive Mode. When this mode is on, I will only act on comments where you specifically mention me with @jules. You can find this option in the Pull Request section of your global Jules UI settings. You can always switch back!

New to Jules? Learn more at jules.google/docs.


For security, I will only act on instructions from the user who triggered this task.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Apr 14, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Concurrent filesystem operations were introduced in DownloadTask (batched removals and parallel parent-directory creation), tests were added/updated (promiseAny tests; isInRollout tests and setup mock), and murmurhash3_32_gc was exported from src/isInRollout.ts.

Changes

Cohort / File(s) Summary
DownloadTask (filesystem concurrency)
harmony/pushy/src/main/ets/DownloadTask.ts
Replaced sequential recursive deletions with chunked, batched Promise.all removals in removeDirectory, and changed sequential parent-directory creation to concurrent Promise.all calls in copyFromResource.
Unit tests — utils
src/__tests__/utils.test.ts
Updated imports to include promiseAny and added a describe('promiseAny') suite covering first-fulfillment resolution, mixed rejection/fulfillment, and an all-rejected error case.
Unit tests — rollout & setup
src/__tests__/isInRollout.test.ts, src/__tests__/setup.ts
Added Bun test suite for isInRollout (mocking ../core UUID via mutable mockUuid, dynamic cache-busted imports) and added a mock for react-native/Libraries/Core/ReactNativeVersion.
API surface change
src/isInRollout.ts
Exported murmurhash3_32_gc (changed from file-local to export function murmurhash3_32_gc(...)).

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Poem

🐰 I hopped through code with a curious wink,

Parallel deletes and promises in sync,
Tests that chase UUIDs and hashing light,
Exports set free to leap into sight,
A tiny carrot commit—chew, then blink.

🚥 Pre-merge checks | ✅ 1 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Title check ⚠️ Warning The pull request title mentions testing promiseAny, but the changeset includes significant modifications to unrelated files (DownloadTask.ts, isInRollout.ts, setup.ts, isInRollout.test.ts) that are not reflected in the title. Update the title to reflect all meaningful changes, such as 'Add tests for promiseAny and improve concurrent operations in DownloadTask', or split into separate PRs by concern.
Docstring Coverage ⚠️ Warning Docstring coverage is 50.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (1 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch test-promise-any-6734343866273522940

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.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/__tests__/utils.test.ts`:
- Around line 50-75: The promiseAny implementation (promiseAny in src/utils.ts)
never settles for an empty array (lines ~23-38), so add a test case in the
utils.test.ts suite that calls promiseAny([]) and expects it to reject (e.g.,
toThrow('error_all_promises_rejected') or the canonical error you choose), and
also update the promiseAny function to immediately reject when input.length ===
0 (throw or return Promise.reject with the same 'error_all_promises_rejected'
error) so the test and callers do not hang.
🪄 Autofix (Beta)

✅ Autofix completed


ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 374ff035-c285-4448-a363-b4684af4c593

📥 Commits

Reviewing files that changed from the base of the PR and between 9317f66 and d251494.

📒 Files selected for processing (1)
  • src/__tests__/utils.test.ts

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Apr 14, 2026

Note

Autofix is a beta feature. Expect some limitations and changes as we gather feedback and continue to improve it.

Fixes Applied Successfully

Fixed 2 file(s) based on 1 unresolved review comment.

Files modified:

  • src/__tests__/utils.test.ts
  • src/utils.ts

Commit: 61b0b2464dafc42f9781898db656d9f91b989e43

The changes have been pushed to the test-promise-any-6734343866273522940 branch.

Time taken: 2m 31s

Fixed 2 file(s) based on 1 unresolved review comment.

Co-authored-by: CodeRabbit <noreply@coderabbit.ai>
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/utils.ts`:
- Around line 25-27: The rejection for the empty-input case currently calls
reject('error_all_promises_rejected') which returns a raw string; change it to
reject(new Error('error_all_promises_rejected')) so the error shape matches the
other rejection (the Error object used elsewhere), ensuring callers can access
.message/.stack; update the reject call that references the promises array (the
branch where promises.length === 0) to construct and pass an Error instance
instead of a string.
🪄 Autofix (Beta)

❌ Autofix failed (check again to retry)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: b8441f11-00ab-4e3b-abda-4a2174c66544

📥 Commits

Reviewing files that changed from the base of the PR and between d251494 and 61b0b24.

📒 Files selected for processing (2)
  • src/__tests__/utils.test.ts
  • src/utils.ts
✅ Files skipped from review due to trivial changes (1)
  • src/tests/utils.test.ts

src/utils.ts Outdated
Comment on lines +25 to +27
if (promises.length === 0) {
reject('error_all_promises_rejected');
return;
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.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Verify promiseAny call sites and inspect catch handling assumptions.
rg -nP --type=ts --type=tsx -C4 '\bpromiseAny\s*\('
rg -nP --type=ts --type=tsx -C3 '\bcatch\s*\(\w+\)\s*\{'

Repository: reactnativecn/react-native-update

Length of output: 132


🏁 Script executed:

# First, read the src/utils.ts file to inspect the disputed lines
head -50 src/utils.ts | cat -n

Repository: reactnativecn/react-native-update

Length of output: 1640


🏁 Script executed:

# Search for promiseAny call sites (adjust file type)
rg -nP --type=ts -C4 'promiseAny\s*\('

Repository: reactnativecn/react-native-update

Length of output: 2905


🏁 Script executed:

# Also check error handling patterns without the tsx flag
rg -nP --type=ts -C3 'catch\s*\(\s*\w+\s*\)'

Repository: reactnativecn/react-native-update

Length of output: 13920


🏁 Script executed:

# Verify test behavior - check if tests actually pass with current code
cat -n src/__tests__/utils.test.ts | sed -n '69,78p'

Repository: reactnativecn/react-native-update

Length of output: 646


Use the same error shape for empty-input rejection.

Line 26 rejects a raw string, but Line 38 rejects an Error object. For the same failure mode, this creates inconsistent behavior that breaks callers expecting Error properties like message or stack. The tests themselves use different matchers (.toBe() vs .toThrow()) reflecting this inconsistency.

Suggested fix
     if (promises.length === 0) {
-      reject('error_all_promises_rejected');
+      reject(Error(i18n.t('error_all_promises_rejected')));
       return;
     }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if (promises.length === 0) {
reject('error_all_promises_rejected');
return;
if (promises.length === 0) {
reject(Error(i18n.t('error_all_promises_rejected')));
return;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/utils.ts` around lines 25 - 27, The rejection for the empty-input case
currently calls reject('error_all_promises_rejected') which returns a raw
string; change it to reject(new Error('error_all_promises_rejected')) so the
error shape matches the other rejection (the Error object used elsewhere),
ensuring callers can access .message/.stack; update the reject call that
references the promises array (the branch where promises.length === 0) to
construct and pass an Error instance instead of a string.

google-labs-jules bot and others added 2 commits April 14, 2026 05:21
Co-authored-by: sunnylqm <615282+sunnylqm@users.noreply.github.com>
Co-authored-by: sunnylqm <615282+sunnylqm@users.noreply.github.com>
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@harmony/pushy/src/main/ets/DownloadTask.ts`:
- Around line 79-83: The parallel filesystem batches (where DELETE_CONCURRENCY
is used) call Promise.all directly (e.g., in the batch loop that maps to
this.removeDirectory and similar calls in copyFromResource/execute) so the first
rejection causes Promise.all to throw while other sibling delete/mkdir
operations continue mutating the FS; change these sites to use
Promise.allSettled and await the settled results, then detect any rejections and
throw a single aggregated error (or the first error) only after all tasks have
completed; apply this replacement to the batch loops that call
this.removeDirectory and the corresponding mkdir/copy batches so the tree is
fully settled before execute() proceeds to cleanup.

In `@src/__tests__/isInRollout.test.ts`:
- Around line 50-83: The tests use Date.now() to bust module cache for repeated
dynamic imports of ../isInRollout, which can collide across fast runs and cause
stale module state (mockUuid) to persist; replace Date.now() with a monotonic
counter (e.g., declare a let importNonce = 0 outside the tests and use
`${importNonce++}` in each dynamic import) so every import gets a unique
identifier and isInRollout picks up the latest mockUuid for each test.
🪄 Autofix (Beta)

❌ Autofix failed (check again to retry)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: e77c3374-f532-4ac2-a6fb-718853f1adb1

📥 Commits

Reviewing files that changed from the base of the PR and between f6a6aa2 and 3012828.

📒 Files selected for processing (4)
  • harmony/pushy/src/main/ets/DownloadTask.ts
  • src/__tests__/isInRollout.test.ts
  • src/__tests__/setup.ts
  • src/isInRollout.ts
✅ Files skipped from review due to trivial changes (1)
  • src/tests/setup.ts

Comment on lines +79 to +83
for (let i = 0; i < entries.length; i += DELETE_CONCURRENCY) {
const batch = entries.slice(i, i + DELETE_CONCURRENCY);
await Promise.all(
batch.map(file => this.removeDirectory(`${path}/${file}`)),
);
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.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

wc -l harmony/pushy/src/main/ets/DownloadTask.ts

Repository: reactnativecn/react-native-update

Length of output: 123


🏁 Script executed:

cat -n harmony/pushy/src/main/ets/DownloadTask.ts | sed -n '70,90p'

Repository: reactnativecn/react-native-update

Length of output: 950


🏁 Script executed:

cat -n harmony/pushy/src/main/ets/DownloadTask.ts | sed -n '525,560p'

Repository: reactnativecn/react-native-update

Length of output: 1608


🏁 Script executed:

cat -n harmony/pushy/src/main/ets/DownloadTask.ts | sed -n '1,100p'

Repository: reactnativecn/react-native-update

Length of output: 3668


🏁 Script executed:

cat -n harmony/pushy/src/main/ets/DownloadTask.ts | sed -n '150,250p'

Repository: reactnativecn/react-native-update

Length of output: 3635


🏁 Script executed:

rg -n "execute\|removeDirectory\|recreateDirectory" harmony/pushy/src/main/ets/DownloadTask.ts | head -40

Repository: reactnativecn/react-native-update

Length of output: 59


🏁 Script executed:

cat -n harmony/pushy/src/main/ets/DownloadTask.ts | grep -A 50 "async execute"

Repository: reactnativecn/react-native-update

Length of output: 1660


🏁 Script executed:

rg -n "Promise\.all" harmony/pushy/src/main/ets/DownloadTask.ts

Repository: reactnativecn/react-native-update

Length of output: 409


🏁 Script executed:

cat -n harmony/pushy/src/main/ets/DownloadTask.ts | sed -n '500,565p'

Repository: reactnativecn/react-native-update

Length of output: 2708


Wait for these filesystem batches to fully settle before throwing.

Promise.all() on lines 79-83, 532-534, and 547-549 rejects on the first failure while sibling deletes/mkdirs keep running. This causes removeDirectory() and copyFromResource() to return early into execute()'s cleanup while the same tree is still being mutated, creating a nondeterministic failure window.

🛠️ Example fix
+  private async awaitFsBatch(tasks: Array<Promise<void>>): Promise<void> {
+    const results = await Promise.all(
+      tasks.map(task => task.then(() => null, error => error)),
+    );
+    const error = results.find(result => result !== null);
+    if (error) {
+      throw error;
+    }
+  }
+
   ...
-            await Promise.all(
-              batch.map(file => this.removeDirectory(`${path}/${file}`)),
-            );
+            await this.awaitFsBatch(
+              batch.map(file => this.removeDirectory(`${path}/${file}`)),
+            );
   ...
-          await Promise.all(
-            parentDirs.map(dir => this.ensureDirectory(dir)),
-          );
+          await this.awaitFsBatch(
+            parentDirs.map(dir => this.ensureDirectory(dir)),
+          );
   ...
-        await Promise.all(
-          parentDirs.map(dir => this.ensureDirectory(dir))
-        );
+        await this.awaitFsBatch(
+          parentDirs.map(dir => this.ensureDirectory(dir)),
+        );
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@harmony/pushy/src/main/ets/DownloadTask.ts` around lines 79 - 83, The
parallel filesystem batches (where DELETE_CONCURRENCY is used) call Promise.all
directly (e.g., in the batch loop that maps to this.removeDirectory and similar
calls in copyFromResource/execute) so the first rejection causes Promise.all to
throw while other sibling delete/mkdir operations continue mutating the FS;
change these sites to use Promise.allSettled and await the settled results, then
detect any rejections and throw a single aggregated error (or the first error)
only after all tasks have completed; apply this replacement to the batch loops
that call this.removeDirectory and the corresponding mkdir/copy batches so the
tree is fully settled before execute() proceeds to cleanup.

google-labs-jules bot and others added 2 commits April 14, 2026 05:35
Co-authored-by: sunnylqm <615282+sunnylqm@users.noreply.github.com>
Co-authored-by: sunnylqm <615282+sunnylqm@users.noreply.github.com>
@sunnylqm
Copy link
Copy Markdown
Contributor Author

@copilot resolve the merge conflicts in this pull request

@google-labs-jules
Copy link
Copy Markdown
Contributor

@copilot resolve the merge conflicts in this pull request

I am Jules, not Copilot, but I will resolve the merge conflicts for you.

google-labs-jules bot and others added 2 commits April 14, 2026 09:42
Co-authored-by: sunnylqm <615282+sunnylqm@users.noreply.github.com>
Co-authored-by: sunnylqm <615282+sunnylqm@users.noreply.github.com>
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Apr 14, 2026

Note

Autofix is a beta feature. Expect some limitations and changes as we gather feedback and continue to improve it.

Cannot run autofix: This PR has merge conflicts.

Please resolve the conflicts with the base branch and try again.

Alternatively, use @coderabbitai resolve merge conflict to automatically resolve the conflicts.

@sunnylqm
Copy link
Copy Markdown
Contributor Author

@jules resolve merge conflict

@google-labs-jules
Copy link
Copy Markdown
Contributor

@jules resolve merge conflict

I have successfully resolved the merge conflicts, and additionally, I patched an issue with the tests where Date.now() did not guarantee cache-busting, replacing it with an incremented counter to assure cache invalidation in all environments. Thanks!

Co-authored-by: sunnylqm <615282+sunnylqm@users.noreply.github.com>
@sunnylqm
Copy link
Copy Markdown
Contributor Author

@coderabbitai resolve merge conflict

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Apr 14, 2026

Merge conflicts resolved successfully!

Resolved 1 conflict file(s). Commit: 067ebe53e08141c3c47d2078964c0ffe3008c86f pushed to test-promise-any-6734343866273522940.

1 file operation(s)
  • src/__tests__/utils.test.ts (update)
View agent analysis
I'll resolve the merge conflict in `src/__tests__/isInRollout.test.ts`. Let me start by inspecting the git state.

The file is both-added (AA) - both branches added a new file with the same path. Let me examine both versions.

The two versions are nearly identical. The key differences are:

- **Stage 2 (master)**: Uses `let importCount = 0` (at the top, before imports) and `++importCount`
- **Stage 3 (PR branch)**: Uses `let importCounter = 0` (after imports, with a descriptive comment) and `++importCounter`

The PR branch version is better: it uses the more descriptive `importCounter` name and has an explanatory comment about why a monotonic counter is used instead of `Date.now()`. The test logic is identical. I'll use the PR branch version (stage 3).

`git ls-files -u` is empty (no unmerged files). The file is now staged as `M` (modified/resolved). Now let me run the build and lint checks.

Bun is not available in this environment. Let me check for TypeScript compilation (tsc) to run the lint/build check:

Dependencies aren't installed. Let me do the behavioral snapshot comparison instead to verify correctness.

The resolved file (stage 3 / PR branch) compared to both versions:

**Stage 2 (master)** differences from resolved:
- `let importCount = 0;` at the very top (before all imports) → removed
- Used `++importCount` in all dynamic imports

**Stage 3 (PR branch / resolved)** - what was kept:
- `let importCounter = 0;` placed after the mock setup block, with a comment explaining why a monotonic counter is used instead of `Date.now()`
- Used `++importCounter` in all dynamic imports
- All test logic is identical

The resolution is correct: both sides added the same new test file. The only substantive difference was the variable name (`importCount` vs `importCounter`) and placement/comment. The PR branch version is superior because:
1. The variable name `importCounter` is more descriptive
2. It includes a comment explaining the design decision (monotonic counter vs `Date.now()`)
3. The placement after the mock setup (rather than before all imports) is cleaner

All test cases, mock setup, imports, and assertions are preserved from both sides (they were identical in both versions).

**Summary:**

- **Stage 2 (master)**: Added `isInRollout.test.ts` using `let importCount = 0` (unnamed, placed before imports)
- **Stage 3 (PR branch)**: Added the same file with `let importCounter = 0` plus a descriptive comment, placed after mock setup
- **Resolution**: Used the PR branch version — it's functionally equivalent but has better naming and documentation. All test logic is identical between both sides.

Resolved conflicts in:
- src/__tests__/isInRollout.test.ts (both-modified)

Co-authored-by: CodeRabbit <noreply@coderabbit.ai>
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Apr 14, 2026

Note

Autofix is a beta feature. Expect some limitations and changes as we gather feedback and continue to improve it.

⚠️ Branch updated during autofix.

The branch was updated while autofix was in progress. Please try again.

Co-authored-by: sunnylqm <615282+sunnylqm@users.noreply.github.com>
@sunnylqm sunnylqm closed this Apr 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.

1 participant