Skip to content

Commit 975c0f1

Browse files
committed
fix: Warm up the animation pipeline before auto-run tests and stop orphaning Metro
1 parent b0e5177 commit 975c0f1

4 files changed

Lines changed: 65 additions & 10 deletions

File tree

apps/common-app/runtime-tests/AutoRunRuntimeTestsApp.tsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,12 +58,14 @@ interface AutoRunRuntimeTestsAppProps {
5858
tests: RuntimeTestSuite[];
5959
library: string;
6060
forbidReanimated?: boolean;
61+
warmUp?: () => Promise<void>;
6162
}
6263

6364
export default function AutoRunRuntimeTestsApp({
6465
tests,
6566
library,
6667
forbidReanimated,
68+
warmUp,
6769
}: AutoRunRuntimeTestsAppProps) {
6870
const wsUrl = deriveWsUrl();
6971
return (
@@ -76,6 +78,7 @@ export default function AutoRunRuntimeTestsApp({
7678
autoRun={{ wsUrl }}
7779
library={library}
7880
forbidReanimated={forbidReanimated}
81+
warmUp={warmUp}
7982
/>
8083
</View>
8184
</View>

apps/common-app/runtime-tests/ReJest/AutoRunRuntimeTestsRunner.tsx

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ interface AutoRunRuntimeTestsRunnerProps {
2626
autoRun: AutoRunConfig;
2727
library: string;
2828
forbidReanimated?: boolean;
29+
warmUp?: () => Promise<void>;
2930
}
3031

3132
interface ProgressState {
@@ -50,6 +51,7 @@ export default function AutoRunRuntimeTestsRunner({
5051
autoRun,
5152
library,
5253
forbidReanimated,
54+
warmUp,
5355
}: AutoRunRuntimeTestsRunnerProps) {
5456
const [component, setComponent] = useState<ReactNode | null>(null);
5557
const [status, setStatus] = useState<string>(
@@ -98,10 +100,9 @@ export default function AutoRunRuntimeTestsRunner({
98100
});
99101

100102
const known = new Set(tests.map((test) => test.testSuiteName));
101-
const unknown = [
102-
...(filterSet ?? []),
103-
...(includeSet ?? []),
104-
].filter((name) => !known.has(name));
103+
const unknown = [...(filterSet ?? []), ...(includeSet ?? [])].filter(
104+
(name) => !known.has(name)
105+
);
105106
if (unknown.length > 0) {
106107
throw new Error(`Unknown test suites: ${unknown.join(', ')}`);
107108
}
@@ -125,6 +126,9 @@ export default function AutoRunRuntimeTestsRunner({
125126
render: setComponent,
126127
onProgress: setProgress,
127128
});
129+
if (warmUp) {
130+
await warmUp();
131+
}
128132
const summary = await runTests();
129133
if (forbidReanimated && isReanimatedLoaded()) {
130134
summary.failed += 1;
@@ -140,7 +144,7 @@ export default function AutoRunRuntimeTestsRunner({
140144
cancelled = true;
141145
teardown();
142146
};
143-
}, [autoRun.wsUrl, tests, library, forbidReanimated]);
147+
}, [autoRun.wsUrl, tests, library, forbidReanimated, warmUp]);
144148

145149
return (
146150
<View style={styles.flexOne}>
Lines changed: 44 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,56 @@
1-
import 'react-native-reanimated';
2-
3-
import React from 'react';
1+
import React, { useEffect } from 'react';
42
import { StyleSheet } from 'react-native';
53
import { GestureHandlerRootView } from 'react-native-gesture-handler';
4+
import Animated, {
5+
runOnJS,
6+
useAnimatedStyle,
7+
useSharedValue,
8+
withTiming,
9+
} from 'react-native-reanimated';
610

711
import AutoRunRuntimeTestsApp from '../AutoRunRuntimeTestsApp';
812
import { REANIMATED_TEST_SUITES } from './suites';
913

14+
const WARM_UP_TIMEOUT_MS = 5000;
15+
16+
let resolveWarmUp: () => void;
17+
const warmUpDone = new Promise<void>((resolve) => {
18+
resolveWarmUp = resolve;
19+
});
20+
21+
function finishWarmUp() {
22+
resolveWarmUp();
23+
}
24+
25+
function warmUp() {
26+
return warmUpDone;
27+
}
28+
29+
// Pays UI-runtime init and frame-pipeline costs at app boot, so the first
30+
// test's fixed wait budget starts against a warm pipeline. Capped so a broken
31+
// warm-up can never block the run.
32+
function WarmUpView() {
33+
const width = useSharedValue(1);
34+
const style = useAnimatedStyle(() => ({ width: width.value }));
35+
useEffect(() => {
36+
const timeoutId = setTimeout(finishWarmUp, WARM_UP_TIMEOUT_MS);
37+
width.value = withTiming(100, { duration: 100 }, () => {
38+
'worklet';
39+
runOnJS(finishWarmUp)();
40+
});
41+
return () => clearTimeout(timeoutId);
42+
}, [width]);
43+
return <Animated.View style={[styles.warmUpView, style]} />;
44+
}
45+
1046
export default function ReanimatedAutoRunApp() {
1147
return (
1248
<GestureHandlerRootView style={styles.container}>
49+
<WarmUpView />
1350
<AutoRunRuntimeTestsApp
1451
tests={REANIMATED_TEST_SUITES}
1552
library="Reanimated"
53+
warmUp={warmUp}
1654
/>
1755
</GestureHandlerRootView>
1856
);
@@ -22,4 +60,7 @@ const styles = StyleSheet.create({
2260
container: {
2361
flex: 1,
2462
},
63+
warmUpView: {
64+
height: 1,
65+
},
2566
});

apps/fabric-example/scripts/runtime-tests-server.js

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -357,9 +357,13 @@ function shutdown(code) {
357357
clearTimer('idle');
358358
if (metroChild && !metroChild.killed) {
359359
try {
360-
metroChild.kill('SIGTERM');
360+
process.kill(-metroChild.pid, 'SIGTERM');
361361
} catch {
362-
/* ignore */
362+
try {
363+
metroChild.kill('SIGTERM');
364+
} catch {
365+
/* ignore */
366+
}
363367
}
364368
}
365369
wss.close(() => {
@@ -450,12 +454,15 @@ async function ensureMetroRunning() {
450454
);
451455
// --reset-cache: a Metro cache produced under a different Bundle Mode
452456
// setting serves stale module maps ("Requiring unknown module").
457+
// detached: Metro must get its own process group so shutdown can kill the
458+
// whole tree — killing just the yarn wrapper orphans the actual Metro process.
453459
metroChild = spawn(
454460
'yarn',
455461
['start', '--port', String(METRO_PORT), '--reset-cache'],
456462
{
457463
cwd: projectRoot,
458464
stdio: ['ignore', 'pipe', 'pipe'],
465+
detached: true,
459466
}
460467
);
461468
metroChild.stdout.on('data', (chunk) => {

0 commit comments

Comments
 (0)