Skip to content
Open
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
51 changes: 47 additions & 4 deletions .github/workflows/scripts/boot-simulator.sh
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,51 @@ describe_booted_device() {
|| true
}

resolve_device_udid() {
local device="$1"
local udid

udid="$(
xcrun simctl list devices booted 2>/dev/null \
| grep -F "${device} (" \
| grep -v 'unavailable' \
| head -1 \
| sed -E 's/.*\(([A-F0-9-]+)\).*/\1/' \
|| true
)"
if [[ -n "$udid" ]]; then
echo "$udid"
return 0
fi

udid="$(
xcrun simctl list devices available 2>/dev/null \
| grep -F "${device} (" \
| grep -v 'unavailable' \
| head -1 \
| sed -E 's/.*\(([A-F0-9-]+)\).*/\1/' \
|| true
)"
echo "$udid"
}

kill_resolved_simulator() {
local device="$1"
local udid

udid="$(resolve_device_udid "$device")"
if [[ -z "$udid" ]]; then
log_boot_status "phase=kill_resolved device=\"${device}\" not found, skipping"
return 0
fi

log_boot_status "phase=kill_resolved udid=${udid} device=\"${device}\""
killall Simulator 2>/dev/null || true
xcrun simctl terminate "$udid" com.invertase.testing 2>/dev/null || true
xcrun simctl shutdown "$udid" 2>/dev/null || true
xcrun simctl shutdown "$device" 2>/dev/null || true
}

log_migration_status() {
local device="$1"
local migration_output probe_rc
Expand Down Expand Up @@ -117,10 +162,8 @@ popd >/dev/null || exit 1

log_boot_status "phase=resolve_device name=\"${SIM}\" (from tests/.detoxrc.js)"

# Clear up any existing attempts in case we are re-trying
log_boot_status "phase=shutdown_existing killing Simulator.app if running..."
killall Simulator 2>/dev/null || true
xcrun simctl shutdown "$SIM" 2>/dev/null || true
# Kill the resolved simulator first when present (CI pre-boot and e2e Jet retries).
kill_resolved_simulator "$SIM"

log_boot_status "phase=boot_command starting simctl boot..."
set +e
Expand Down
105 changes: 103 additions & 2 deletions .yarn/patches/mocha-remote-server-npm-1.13.2-619a29d2e3.patch
Original file line number Diff line number Diff line change
@@ -1,8 +1,54 @@
diff --git a/dist/Server.js b/dist/Server.js
index ad9debe2086ab9b96e97a69aec966da8114ad102..e3c1c964956023df876dfcac3f73000b4eaf1bba 100644
index ad9debe2086ab9b96e97a69aec966da8114ad102..c8f4e2a1b9d3f6e8a7c5d4b2e1f0a9c8b7d6e5f4 100644
--- a/dist/Server.js
+++ b/dist/Server.js
@@ -130,6 +130,17 @@ class Server extends ServerEventEmitter_1.ServerEventEmitter {
@@ -73,6 +73,8 @@
/** The options to send to the next connecting running client */
this.clientOptions = {};
this._listening = false;
+ this._clientResetTimer = null;
+ this._awaitingInitialClientRun = false;
this.handleConnection = (ws, req) => {
this.debug("Client connected");
// Check that the protocol matches
@@ -88,6 +90,11 @@
ws.close(1002, `Expected "${expectedProtocol}" protocol got "${ws.protocol}"`);
return;
}
+ if (this._clientResetTimer) {
+ clearTimeout(this._clientResetTimer);
+ this._clientResetTimer = null;
+ console.warn(`[mocha-remote-ws] reconnect_recovered preserving_runner`);
+ }
if (this.client) {
this.debug("A client was already connected");
this.client.close(1013 /* try again later */, "Got a connection from another client");
@@ -97,14 +104,20 @@
// Hang onto the client
this.client = ws;
this.client.on("message", this.handleMessage.bind(this, this.client));
- this.client.once("close", this.handleReset);
+ this.client.once("close", (code) => this.handleClientDisconnect(code));
// If we already have a runner, it can run now that we have a client
if (this.runner) {
- if (this.clientOptions) {
- this.send({ action: "run", options: this.clientOptions });
+ if (this._awaitingInitialClientRun) {
+ if (this.clientOptions) {
+ this.send({ action: "run", options: this.clientOptions });
+ this._awaitingInitialClientRun = false;
+ }
+ else {
+ throw new Error("Internal error: Expected a clientOptions");
+ }
}
else {
- throw new Error("Internal error: Expected a clientOptions");
+ this.debug("Client reconnected while runner active; resuming without re-run");
}
Comment thread
mikehardy marked this conversation as resolved.
}
else if (this.config.autoRun) {
@@ -130,6 +143,17 @@
throw new Error("Received a message from the client, but server wasn't running");
}
}
Expand All @@ -20,3 +66,58 @@ index ad9debe2086ab9b96e97a69aec966da8114ad102..e3c1c964956023df876dfcac3f73000b
else if (msg.action === "error") {
if (typeof msg.message !== "string") {
throw new Error("Expected 'error' action to have an error argument with a message");
@@ -149,13 +173,46 @@
}
else {
throw err;
+ }
+ }
+ };
+ this.handleClientDisconnect = (code) => {
+ const transientCodes = [1006, 1001];
+ const graceMs = this.config.reconnectGraceMs ?? 15000;
+ if (this.runner && transientCodes.includes(code)) {
+ if (this._clientResetTimer) {
+ clearTimeout(this._clientResetTimer);
}
+ console.warn(`[mocha-remote-ws] transient_disconnect code=${code} grace_ms=${graceMs} preserving_runner`);
+ const client = this.client;
+ delete this.client;
+ if (client) {
+ client.removeAllListeners();
+ }
+ this._clientResetTimer = setTimeout(() => {
+ this._clientResetTimer = null;
+ if (!this.client) {
+ console.error(`[mocha-remote-ws] fatal_disconnect code=${code} grace_expired_ms=${graceMs}`);
+ this.handleReset();
+ }
+ }, graceMs);
+ return;
}
+ if (this._clientResetTimer) {
+ clearTimeout(this._clientResetTimer);
+ this._clientResetTimer = null;
+ }
+ this.handleReset();
};
/**
* Resets the server for another test run.
*/
this.handleReset = () => {
+ if (this._clientResetTimer) {
+ clearTimeout(this._clientResetTimer);
+ this._clientResetTimer = null;
+ }
+ this._awaitingInitialClientRun = false;
// Forget everything about the runner and the client
const { runner, client } = this;
delete this.runner;
@@ -263,6 +320,7 @@
// this.runner = new Mocha.Runner(this.suite, this.options.delay || false);
// TODO: Stub this to match the Runner's interface even better
this.runner = new FakeRunner_1.FakeRunner();
+ this._awaitingInitialClientRun = true;
// Attach event listeners to update stats
(0, stats_collector_1.createStatsCollector)(this.runner);
// Set the client options, to be passed to the next running client
2 changes: 1 addition & 1 deletion eslint.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ export default defineConfig([
},
{
name: 'Prettier',
...eslintPluginPrettierRecommended.recommended,
...eslintPluginPrettierRecommended,
},
{
name: 'React',
Expand Down
2 changes: 1 addition & 1 deletion packages/ai/lib/requests/request.ts
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,7 @@ function createAbortError(reason?: unknown): Error {
}

function getAbortSignalReason(signal?: AbortSignal): unknown {
return (signal as AbortSignal & { reason?: unknown } | undefined)?.reason;
return (signal as (AbortSignal & { reason?: unknown }) | undefined)?.reason;
}

export class TemplateRequestUrl {
Expand Down
168 changes: 63 additions & 105 deletions packages/analytics/e2e/analytics.e2e.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,67 @@
*
*/
describe('analytics()', function () {
beforeEach(async function () {
const { getAnalytics, logEvent } = analyticsModular;
await logEvent(getAnalytics(), 'screen_view');
// Probe session ID before any other analytics API usage (including suite beforeEach).
// Namespace APIs not tested here as only one set can run reliably, and modular-only is the future state.
describe('getSessionId() [modular — runs before all other analytics tests]', function () {
it('calls native fn without error', async function () {
const { getAnalytics, getSessionId } = analyticsModular;
await getSessionId(getAnalytics());
});

it('returns a non empty session ID', async function () {
if (Platform.other) {
this.skip();
}
const { getAnalytics, getSessionId } = analyticsModular;
let sessionId = await getSessionId(getAnalytics());
// On iOS it can take ~ 3 minutes for the session ID to be generated
// Otherwise, `Analytics uninitialized` error will be thrown
// On CI we're only going to give it a few tries
const retries = global.isCI ? 3 : 240;
let attemptsLeft = retries;
while (!sessionId && attemptsLeft > 0) {
await Utils.sleep(1000);
sessionId = await getSessionId(getAnalytics());
attemptsLeft -= 1;
}

// on CI this will be ignored so it doesn't flake
if (!sessionId && global.isCI) {
this.skip();
return;
}
if (!sessionId) {
return Promise.reject(
new Error('Firebase SDK did not return a session ID after 4 minutes'),
);
}

sessionId.should.not.equal(0);
});

it('returns a null value if session expires', async function () {
if (Platform.ios) {
// TODO - 20251030 iOS no longer correctly expires sessions
this.skip();
}
const { getAnalytics, getSessionId, setSessionTimeoutDuration } = analyticsModular;

// Set session duration to 1 millisecond
setSessionTimeoutDuration(getAnalytics(), 1);
// Wait 100 millisecond to ensure session expires
await Utils.sleep(100);
const sessionId = await getSessionId(getAnalytics());
should.equal(sessionId, null);
});
});

describe('firebase v8 compatibility', function () {
beforeEach(async function () {
const { getAnalytics, logEvent } = analyticsModular;
await logEvent(getAnalytics(), 'screen_view');
});

beforeEach(async function beforeEachTest() {
// @ts-ignore
globalThis.RNFB_SILENCE_MODULAR_DEPRECATION_WARNINGS = true;
Expand Down Expand Up @@ -62,55 +117,6 @@ describe('analytics()', function () {
});
});

describe('getSessionId()', function () {
it('calls native fn without error', async function () {
await firebase.analytics().getSessionId();
});

it('returns a non empty session ID', async function () {
if (Platform.other) {
this.skip();
}
let sessionId = await firebase.analytics().getSessionId();
// On iOS it can take ~ 3 minutes for the session ID to be generated
// Otherwise, `Analytics uninitialized` error will be thrown
// On CI we're only going to give it a few tries
const retries = global.isCI ? 3 : 240;
let attemptsLeft = retries;
while (!sessionId && attemptsLeft > 0) {
await Utils.sleep(1000);
sessionId = await firebase.analytics().getSessionId();
attemptsLeft -= 1;
}

// on CI this will be ignored so it doesn't flake
if (!sessionId && global.isCI) {
this.skip();
return;
}
if (!sessionId) {
return Promise.reject(
new Error('Firebase SDK did not return a session ID after 4 minutes'),
);
}

sessionId.should.not.equal(0);
});

it('returns a null value if session expires', async function () {
if (Platform.ios) {
// TODO - 20251030 iOS no longer correctly expires sessions
this.skip();
}
// Set session duration to 1 millisecond
firebase.analytics().setSessionTimeoutDuration(1);
// Wait 100 millisecond to ensure session expires
await Utils.sleep(100);
const sessionId = await firebase.analytics().getSessionId();
should.equal(sessionId, null);
});
});

describe('setUserId()', function () {
it('allows a null values to be set', async function () {
await firebase.analytics().setUserId(null);
Expand Down Expand Up @@ -569,6 +575,11 @@ describe('analytics()', function () {
});

describe('modular', function () {
beforeEach(async function () {
const { getAnalytics, logEvent } = analyticsModular;
await logEvent(getAnalytics(), 'screen_view');
});

describe('getAnalytics', function () {
it('pass app as argument', function () {
const { getApp } = modular;
Expand Down Expand Up @@ -997,59 +1008,6 @@ describe('analytics()', function () {
});
});

describe('getSessionId()', function () {
it('calls native fn without error', async function () {
const { getAnalytics, getSessionId } = analyticsModular;
await getSessionId(getAnalytics());
});

it('returns a non empty session ID', async function () {
if (Platform.other) {
this.skip();
}
const { getAnalytics, getSessionId } = analyticsModular;
let sessionId = await getSessionId(getAnalytics());
// On iOS it can take ~ 3 minutes for the session ID to be generated
// Otherwise, `Analytics uninitialized` error will be thrown
// On CI we're only going to give it a few tries
const retries = global.isCI ? 3 : 240;
let attemptsLeft = retries;
while (!sessionId && attemptsLeft > 0) {
await Utils.sleep(1000);
sessionId = await getSessionId(getAnalytics());
attemptsLeft -= 1;
}

// on CI this will be ignored so it doesn't flake
if (!sessionId && global.isCI) {
this.skip();
return;
}
if (!sessionId) {
return Promise.reject(
new Error('Firebase SDK did not return a session ID after 4 minutes'),
);
}

sessionId.should.not.equal(0);
});

it('returns a null value if session expires', async function () {
if (Platform.ios) {
// TODO - 20251030 iOS no longer correctly expires sessions
this.skip();
}
const { getAnalytics, getSessionId, setSessionTimeoutDuration } = analyticsModular;

// Set session duration to 1 millisecond
setSessionTimeoutDuration(getAnalytics(), 1);
// Wait 100 millisecond to ensure session expires
await Utils.sleep(100);
const sessionId = await getSessionId(getAnalytics());
should.equal(sessionId, null);
});
});

describe('setUserId()', function () {
it('allows a null values to be set', async function () {
const { getAnalytics, setUserId } = analyticsModular;
Expand Down
Loading
Loading