Skip to content

Commit a218b63

Browse files
rubennortefacebook-github-bot
authored andcommitted
Refactor test setup to avoid importing the Fantom module before the body of the test (#51696)
Summary: Pull Request resolved: #51696 Changelog: [internal] I realized that the Fantom module was being initialized before the execution of the test module itself (as part of the code generated by the runner), which could have problems if the test sets up the global environment that Fantom consumes. For example, if Fantom needs to use the `EventTarget` type from the global scope, it needs to wait until the initialization of the runtime so it can use it safely. This refactors all the code calling into Fantom as part of our infra to always initialize it lazily, so the first thing that runs as part of the test execution is the test itself (apart from our test setup, which is supposed to be side-effect free). Reviewed By: sammy-SC Differential Revision: D75681181 fbshipit-source-id: 91e4b903a49fcee59c5875e73db314cde0adea03
1 parent 3fb965d commit a218b63

6 files changed

Lines changed: 69 additions & 22 deletions

File tree

packages/react-native-fantom/runner/entrypoint-template.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,8 @@ module.exports = function entrypointTemplate({
4040
*/
4141
4242
import {registerTest} from '${setupModulePath}';
43-
import {setConstants} from '@react-native/fantom';
43+
import {setConstants} from '@react-native/fantom/src/Constants';
44+
4445
${
4546
Object.keys(testConfig.flags.jsOnly).length > 0
4647
? `import * as ReactNativeFeatureFlags from '${featureFlagsModulePath}';

packages/react-native-fantom/runtime/patchWeakRef.js

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,6 @@
88
* @format
99
*/
1010

11-
import * as Fantom from '@react-native/fantom';
12-
1311
let initialized = false;
1412

1513
/**
@@ -31,6 +29,9 @@ export default function patchWeakRef(): void {
3129

3230
// $FlowExpectedError[cannot-write]
3331
WeakRef.prototype.deref = function patchedDeref<T>(this: WeakRef<T>): T {
32+
// Lazily require the module to avoid loading it before the test logic.
33+
const Fantom = require('@react-native/fantom');
34+
3435
if (!Fantom.isInWorkLoop()) {
3536
throw new Error(
3637
'Unexpected call to `WeakRef.deref()` outside of the Event Loop. Please use this method within `Fantom.runTask()`.',
@@ -43,6 +44,9 @@ export default function patchWeakRef(): void {
4344
const OriginalWeakRef = WeakRef;
4445

4546
global.WeakRef = function WeakRef(...args) {
47+
// Lazily require the module to avoid loading it before the test logic.
48+
const Fantom = require('@react-native/fantom');
49+
4650
if (!Fantom.isInWorkLoop()) {
4751
throw new Error(
4852
'Unexpected instantiation of `WeakRef` outside of the Event Loop. Please create the instance within `Fantom.runTask()`.',

packages/react-native-fantom/runtime/setup.js

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@ import expect from './expect';
1414
import {createMockFunction} from './mocks';
1515
import patchWeakRef from './patchWeakRef';
1616
import {setupSnapshotConfig, snapshotContext} from './snapshotContext';
17-
import NativeFantom from 'react-native/src/private/testing/fantom/specs/NativeFantom';
1817

1918
export type TestCaseResult = {
2019
ancestorTitles: Array<string>,
@@ -380,6 +379,10 @@ function runSuite(suite: Suite): TestCaseResult[] {
380379
}
381380

382381
function reportTestSuiteResult(testSuiteResult: TestSuiteResult): void {
382+
// Force the import of the native module to be lazy
383+
const NativeFantom =
384+
require('react-native/src/private/testing/fantom/specs/NativeFantom').default;
385+
383386
NativeFantom.reportTestSuiteResultsJSON(
384387
JSON.stringify({
385388
type: 'test-result',
@@ -389,6 +392,10 @@ function reportTestSuiteResult(testSuiteResult: TestSuiteResult): void {
389392
}
390393

391394
function validateEmptyMessageQueue(): void {
395+
// Force the import of the native module to be lazy
396+
const NativeFantom =
397+
require('react-native/src/private/testing/fantom/specs/NativeFantom').default;
398+
392399
NativeFantom.validateEmptyMessageQueue();
393400
}
394401

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
/**
2+
* Copyright (c) Meta Platforms, Inc. and affiliates.
3+
*
4+
* This source code is licensed under the MIT license found in the
5+
* LICENSE file in the root directory of this source tree.
6+
*
7+
* @flow strict-local
8+
* @format
9+
*/
10+
11+
type FantomConstants = $ReadOnly<{
12+
isRunningFromCI: boolean,
13+
fantomConfigSummary: string,
14+
}>;
15+
16+
let constants: FantomConstants = {
17+
isRunningFromCI: false,
18+
fantomConfigSummary: '',
19+
};
20+
21+
export function getConstants(): FantomConstants {
22+
return constants;
23+
}
24+
25+
export function setConstants(newConstants: FantomConstants): void {
26+
constants = newConstants;
27+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
/**
2+
* Copyright (c) Meta Platforms, Inc. and affiliates.
3+
*
4+
* This source code is licensed under the MIT license found in the
5+
* LICENSE file in the root directory of this source tree.
6+
*
7+
* @flow strict-local
8+
* @format
9+
*/
10+
11+
describe('FantomModuleInit', () => {
12+
it('should not load the Fantom module eagerly', () => {
13+
expect(global.__FANTOM_PACKAGE_LOADED__).toBeUndefined();
14+
});
15+
16+
it('should load the Fantom module lazily', () => {
17+
require('@react-native/fantom');
18+
19+
expect(global.__FANTOM_PACKAGE_LOADED__).toBe(true);
20+
});
21+
});

packages/react-native-fantom/src/index.js

Lines changed: 5 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import type ReactNativeDocument from 'react-native/src/private/webapis/dom/nodes
1818

1919
import ReactNativeElement from '../../react-native/src/private/webapis/dom/nodes/ReadOnlyNode';
2020
import * as Benchmark from './Benchmark';
21+
import {getConstants} from './Constants';
2122
import getFantomRenderedOutput from './getFantomRenderedOutput';
2223
import {LogBox} from 'react-native';
2324
import {createRootTag} from 'react-native/Libraries/ReactNative/RootTag';
@@ -38,6 +39,8 @@ export type RootConfig = {
3839
devicePixelRatio?: number,
3940
};
4041

42+
export {getConstants} from './Constants';
43+
4144
// Defaults use iPhone 14 values (very common device).
4245
const DEFAULT_VIEWPORT_WIDTH = 390;
4346
const DEFAULT_VIEWPORT_HEIGHT = 844;
@@ -520,24 +523,6 @@ export function enqueueModalSizeUpdate(
520523

521524
export const unstable_benchmark = Benchmark;
522525

523-
type FantomConstants = $ReadOnly<{
524-
isRunningFromCI: boolean,
525-
fantomConfigSummary: string,
526-
}>;
527-
528-
let constants: FantomConstants = {
529-
isRunningFromCI: false,
530-
fantomConfigSummary: '',
531-
};
532-
533-
export function getConstants(): FantomConstants {
534-
return constants;
535-
}
536-
537-
export function setConstants(newConstants: FantomConstants): void {
538-
constants = newConstants;
539-
}
540-
541526
/**
542527
* Quick and dirty polyfills required by tinybench.
543528
*/
@@ -662,3 +647,5 @@ function runLogBoxCheck() {
662647
throw new Error(message);
663648
}
664649
}
650+
651+
global.__FANTOM_PACKAGE_LOADED__ = true;

0 commit comments

Comments
 (0)