Skip to content

Commit a136398

Browse files
committed
Simulate hag condition
1 parent 6d457ec commit a136398

3 files changed

Lines changed: 99 additions & 12 deletions

File tree

.ado/jobs/e2e-test.yml

Lines changed: 32 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -9,17 +9,25 @@ parameters:
99
- name: AgentPool
1010
type: object
1111
- name: simulateCrashForTesting
12+
type: boolean
13+
default: false
14+
# When true, drops a sentinel file that RNTesterApp-Fabric reads on startup
15+
# and uses to trigger a deliberate access violation. Used to validate that
16+
# the crash-dump collection path (in-process minidump writer + artifact
17+
# publish) actually produces a usable .dmp in CI. Already validated on this
18+
# branch — leaving the mechanism in place but disabled by default.
19+
- name: simulateHangForTesting
1220
# !!! TEMPORARY DEFAULT: true !!!
1321
# This branch (PR/0.84-fix-e2e-test-flakiness) flips the default to true so
14-
# that PR validation actually exercises the crash-dump-collection path end
15-
# to end. MUST be flipped back to false before merging anywhere — with it
16-
# enabled the E2E app crashes on startup and no real tests run.
22+
# that PR validation exercises the hang-dump capture path end to end.
23+
# MUST be flipped back to false before merging anywhere — with it enabled
24+
# the E2E app deliberately hangs and the test step always fails.
1725
type: boolean
1826
default: true
19-
# When true, drops a sentinel file that RNTesterApp-Fabric reads on startup
20-
# and uses to trigger a deliberate access violation. Used to validate that
21-
# the crash-dump collection path (WER LocalDumps + artifact publish)
22-
# actually produces a usable .dmp in CI.
27+
# When true, sets the env var RNW_SIMULATE_HANG=1 on the `yarn e2etest`
28+
# step. The HangSimulationTest test then invokes the `HangForTesting`
29+
# automation command, which jams the app's UI thread; the post-failure
30+
# ProcDump step captures a full dump of the still-alive hung process.
2331
- name: buildMatrix
2432
type: object
2533
default:
@@ -109,6 +117,16 @@ jobs:
109117
}
110118
displayName: Arm crash-simulation sentinel (TEST ONLY)
111119
120+
# Test-only: arm the hang-simulation env var, which switches on
121+
# the HangSimulationTest.test.ts test. That test invokes the
122+
# `HangForTesting` automation command, jamming the app's UI thread
123+
# so the post-failure ProcDump path captures a hang dump.
124+
- ${{ if eq(parameters.simulateHangForTesting, true) }}:
125+
- pwsh: |
126+
Write-Host "##vso[task.setvariable variable=RNW_SIMULATE_HANG]1"
127+
Write-Host "Hang simulation armed (RNW_SIMULATE_HANG=1)"
128+
displayName: Arm hang-simulation env var (TEST ONLY)
129+
112130
# Test-only: directly launch the built exe (bypassing WinAppDriver
113131
# and the packaged-app activation path) with the flag armed, so we
114132
# can isolate whether WER writes a dump at all. If this produces a
@@ -144,12 +162,14 @@ jobs:
144162
displayName: yarn e2etest
145163
workingDirectory: packages/e2e-test-app-fabric
146164
# Time to wait for this task to complete before the server kills it.
147-
# When simulateCrashForTesting=true the app crashes immediately on startup, so a
148-
# 10-minute wait is just dead time. Drop to 2 min during simulation. REVERT this
149-
# to a single `timeoutInMinutes: 10` when removing the simulation parameter.
150-
${{ if eq(parameters.simulateCrashForTesting, true) }}:
165+
# When EITHER simulation is on the test step is guaranteed to fail (crash
166+
# exits the app immediately; hang jams the UI thread). The ADO step timeout
167+
# is then the primary cutoff that lets the post-failure capture step run, so
168+
# 2 minutes is plenty. REVERT this whole conditional block to a single line
169+
# `timeoutInMinutes: 10` when removing both simulation parameters.
170+
${{ if or(eq(parameters.simulateCrashForTesting, true), eq(parameters.simulateHangForTesting, true)) }}:
151171
timeoutInMinutes: 2
152-
${{ if not(eq(parameters.simulateCrashForTesting, true)) }}:
172+
${{ if and(not(eq(parameters.simulateCrashForTesting, true)), not(eq(parameters.simulateHangForTesting, true))) }}:
153173
timeoutInMinutes: 10
154174
155175
# Always disarm the crash sentinel so it cannot leak to a rerun on
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
/**
2+
* Copyright (c) Microsoft Corporation.
3+
* Licensed under the MIT License.
4+
*
5+
* @format
6+
*/
7+
8+
// Test-only: validates the hang-dump capture path of the E2E pipeline.
9+
//
10+
// Normally skipped. When the pipeline parameter `simulateHangForTesting=true`
11+
// (see .ado/jobs/e2e-test.yml), the env var RNW_SIMULATE_HANG=1 is set on the
12+
// `yarn e2etest` step, this test runs, and it tells the app to jam its UI
13+
// thread via the `HangForTesting` automation command. After the resulting jest
14+
// timeout, the post-failure ProcDump step in the pipeline captures a full
15+
// memory dump of the still-alive packaged-app process.
16+
//
17+
// Must be flipped off (along with the pipeline parameter) before merging.
18+
19+
import {app} from '@react-native-windows/automation';
20+
import {AutomationClient} from '@react-native-windows/automation-channel';
21+
22+
declare const automationClient: AutomationClient | undefined;
23+
24+
const shouldRun = process.env.RNW_SIMULATE_HANG === '1';
25+
26+
(shouldRun ? describe : describe.skip)('Hang Simulation (TEST ONLY)', () => {
27+
test('jams the UI thread until the test times out', async () => {
28+
if (!automationClient) {
29+
throw new Error('RPC client is not enabled');
30+
}
31+
32+
// Asks the app to Post a Sleep(INFINITE) onto its UI dispatcher. The
33+
// command itself returns quickly; the UI thread is jammed on the next
34+
// queued work item, so any subsequent UIA query will block.
35+
await automationClient.invoke('HangForTesting', {});
36+
37+
// Touch the UI to surface the hang to jest. This call would normally
38+
// return quickly; with the UI thread jammed it blocks until jest's
39+
// testTimeout fires.
40+
const anyElement = await app.findElementByTestID('components-tab');
41+
await anyElement.waitForDisplayed({timeout: 60000});
42+
});
43+
});
44+
45+
export {};

packages/e2e-test-app-fabric/windows/RNTesterApp-Fabric/RNTesterApp-Fabric.cpp

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ winrt::Microsoft::ReactNative::IReactContext global_reactContext{nullptr};
4040
// Forward declarations of functions included in this code module:
4141
winrt::Windows::Data::Json::JsonObject ListErrors(winrt::Windows::Data::Json::JsonValue payload);
4242
winrt::Windows::Data::Json::JsonObject DumpVisualTree(winrt::Windows::Data::Json::JsonValue payload);
43+
winrt::Windows::Data::Json::JsonObject HangForTesting(winrt::Windows::Data::Json::JsonValue payload);
4344
winrt::Windows::Foundation::IAsyncAction LoopServer(winrt::AutomationChannel::Server &server);
4445

4546
// Create and configure the ReactNativeHost
@@ -242,6 +243,26 @@ static void MaybeSimulateCrashForTesting() {
242243
*reinterpret_cast<volatile int *>(nullptr) = 0xC0FFEE;
243244
}
244245

246+
// Test-only: when invoked over the automation channel, jam the UI thread
247+
// forever. Used by the E2E pipeline (see .ado/jobs/e2e-test.yml
248+
// `simulateHangForTesting` parameter) to validate that the post-failure
249+
// ProcDump capture step actually produces a usable dump of a hung app.
250+
//
251+
// We Post the sleep onto the UI dispatcher rather than blocking inline so the
252+
// channel handler returns a normal response to the test client — that's the
253+
// realistic scenario (the app appears to acknowledge a request, then locks up
254+
// on the next UI-thread work item, exactly like a deadlock in production).
255+
winrt::Windows::Data::Json::JsonObject HangForTesting(winrt::Windows::Data::Json::JsonValue /*payload*/) {
256+
if (global_reactContext) {
257+
global_reactContext.UIDispatcher().Post([]() { ::Sleep(INFINITE); });
258+
} else {
259+
// Fallback: hang the channel-loop thread itself. Less realistic but still
260+
// produces a hung process that the post-failure ProcDump path can capture.
261+
::Sleep(INFINITE);
262+
}
263+
return {};
264+
}
265+
245266
_Use_decl_annotations_ int CALLBACK
246267
WinMain(HINSTANCE /* instance */, HINSTANCE, PSTR /* commandLine */, int /* showCmd */) {
247268
// Install our in-process crash handler before anything else can crash. This
@@ -301,6 +322,7 @@ WinMain(HINSTANCE /* instance */, HINSTANCE, PSTR /* commandLine */, int /* show
301322
winrt::AutomationChannel::CommandHandler handler;
302323
handler.BindOperation(L"DumpVisualTree", DumpVisualTree);
303324
handler.BindOperation(L"ListErrors", ListErrors);
325+
handler.BindOperation(L"HangForTesting", HangForTesting);
304326
global_rootView = reactNativeWindow.ReactNativeIsland();
305327

306328
auto server = winrt::AutomationChannel::Server(handler);

0 commit comments

Comments
 (0)