Skip to content

Commit be9b076

Browse files
rubennortefacebook-github-bot
authored andcommitted
Add support for specifying feature flags in pragmas (#48097)
Summary: Pull Request resolved: #48097 Changelog: [internal] As per title, this allows us to specify both common and JS-only feature flags for tests in the docblock as pragmas (in the same pragma separated by spaces, or in different pragmas). E.g.: ``` /** * fantom_flags commonTestFlag:true * fantom_flags jsOnlyTestFlag:true */ ``` The feature flags are overridden automatically for us before the tests start. Reviewed By: javache Differential Revision: D66760121 fbshipit-source-id: 7e227e0035a170dab81b1e6ce39600a01a748867
1 parent db70b79 commit be9b076

File tree

9 files changed

+161
-25
lines changed

9 files changed

+161
-25
lines changed

jest/integration/runner/entrypoint-template.js

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,18 @@
99
* @oncall react_native
1010
*/
1111

12+
import type {FantomTestConfigJsOnlyFeatureFlags} from './getFantomTestConfig';
13+
1214
module.exports = function entrypointTemplate({
1315
testPath,
1416
setupModulePath,
17+
featureFlagsModulePath,
18+
featureFlags,
1519
}: {
1620
testPath: string,
1721
setupModulePath: string,
22+
featureFlagsModulePath: string,
23+
featureFlags: FantomTestConfigJsOnlyFeatureFlags,
1824
}): string {
1925
return `/**
2026
* Copyright (c) Meta Platforms, Inc. and affiliates.
@@ -29,6 +35,17 @@ module.exports = function entrypointTemplate({
2935
*/
3036
3137
import {registerTest} from '${setupModulePath}';
38+
${
39+
Object.keys(featureFlags).length > 0
40+
? `import * as ReactNativeFeatureFlags from '${featureFlagsModulePath}';
41+
42+
ReactNativeFeatureFlags.override({
43+
${Object.entries(featureFlags)
44+
.map(([name, value]) => ` ${name}: () => ${JSON.stringify(value)},`)
45+
.join('\n')}
46+
});`
47+
: ''
48+
}
3249
3350
registerTest(() => require('${testPath}'));
3451
`;

jest/integration/runner/getFantomTestConfig.js

Lines changed: 109 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -9,17 +9,37 @@
99
* @oncall react_native
1010
*/
1111

12+
import ReactNativeFeatureFlags from '../../../packages/react-native/scripts/featureflags/ReactNativeFeatureFlags.config';
1213
import fs from 'fs';
1314
// $FlowExpectedError[untyped-import]
1415
import {extract, parse} from 'jest-docblock';
1516

17+
type CommonFeatureFlags = (typeof ReactNativeFeatureFlags)['common'];
18+
type JsOnlyFeatureFlags = (typeof ReactNativeFeatureFlags)['jsOnly'];
19+
1620
type DocblockPragmas = {[key: string]: string | string[]};
17-
type FantomTestMode = 'dev' | 'opt';
18-
type FantomTestConfig = {
19-
mode: FantomTestMode,
21+
22+
export type FantomTestConfigMode = 'dev' | 'opt';
23+
24+
export type FantomTestConfigCommonFeatureFlags = Partial<{
25+
[key in keyof CommonFeatureFlags]: CommonFeatureFlags[key]['defaultValue'],
26+
}>;
27+
28+
export type FantomTestConfigJsOnlyFeatureFlags = Partial<{
29+
[key in keyof JsOnlyFeatureFlags]: JsOnlyFeatureFlags[key]['defaultValue'],
30+
}>;
31+
32+
export type FantomTestConfig = {
33+
mode: FantomTestConfigMode,
34+
flags: {
35+
common: FantomTestConfigCommonFeatureFlags,
36+
jsOnly: FantomTestConfigJsOnlyFeatureFlags,
37+
},
2038
};
2139

22-
const DEFAULT_MODE: FantomTestMode = 'dev';
40+
const DEFAULT_MODE: FantomTestConfigMode = 'dev';
41+
42+
const FANTOM_FLAG_FORMAT = /^(\w+):(\w+)$/;
2343

2444
/**
2545
* Extracts the Fantom configuration from the test file, specified as part of
@@ -28,20 +48,31 @@ const DEFAULT_MODE: FantomTestMode = 'dev';
2848
* ```
2949
* /**
3050
* * @flow strict-local
31-
* * @fantom mode:opt
51+
* * @fantom_mode opt
52+
* * @fantom_flags commonTestFlag:true
53+
* * @fantom_flags jsOnlyTestFlag:true
3254
* *
3355
* ```
3456
*
35-
* So far the only supported option is `mode`, which can be 'dev' or 'opt'.
57+
* The supported options are:
58+
* - `fantom_mode`: specifies the level of optimization to compile the test
59+
* with. Valid values are `dev` and `opt`.
60+
* - `fantom_flags`: specifies the configuration for common and JS-only feature
61+
* flags. They can be specified in the same pragma or in different ones, and
62+
* the format is `<flag_name>:<value>`.
3663
*/
3764
export default function getFantomTestConfig(
3865
testPath: string,
3966
): FantomTestConfig {
4067
const docblock = extract(fs.readFileSync(testPath, 'utf8'));
4168
const pragmas = parse(docblock) as DocblockPragmas;
4269

43-
const config = {
70+
const config: FantomTestConfig = {
4471
mode: DEFAULT_MODE,
72+
flags: {
73+
common: {},
74+
jsOnly: {},
75+
},
4576
};
4677

4778
const maybeMode = pragmas.fantom_mode;
@@ -60,5 +91,76 @@ export default function getFantomTestConfig(
6091
}
6192
}
6293

94+
const maybeRawFlagConfig = pragmas.fantom_flags;
95+
96+
if (maybeRawFlagConfig != null) {
97+
const rawFlagConfigs = (
98+
Array.isArray(maybeRawFlagConfig)
99+
? maybeRawFlagConfig
100+
: [maybeRawFlagConfig]
101+
).flatMap(value => value.split(/\s+/g));
102+
103+
for (const rawFlagConfig of rawFlagConfigs) {
104+
const matches = FANTOM_FLAG_FORMAT.exec(rawFlagConfig);
105+
if (matches == null) {
106+
throw new Error(
107+
`Invalid format for Fantom feature flag: ${rawFlagConfig}. Expected <flag_name>:<value>`,
108+
);
109+
}
110+
111+
const [, name, rawValue] = matches;
112+
113+
if (ReactNativeFeatureFlags.common[name]) {
114+
const flagConfig = ReactNativeFeatureFlags.common[name];
115+
const value = parseFeatureFlagValue(flagConfig.defaultValue, rawValue);
116+
config.flags.common[name] = value;
117+
} else if (ReactNativeFeatureFlags.jsOnly[name]) {
118+
const flagConfig = ReactNativeFeatureFlags.jsOnly[name];
119+
const value = parseFeatureFlagValue(flagConfig.defaultValue, rawValue);
120+
config.flags.jsOnly[name] = value;
121+
} else {
122+
const validKeys = Object.keys(ReactNativeFeatureFlags.common)
123+
.concat(Object.keys(ReactNativeFeatureFlags.jsOnly))
124+
.join(', ');
125+
126+
throw new Error(
127+
`Invalid Fantom feature flag: ${name}. Valid flags are: ${validKeys}`,
128+
);
129+
}
130+
}
131+
}
132+
63133
return config;
64134
}
135+
136+
function parseFeatureFlagValue<T: boolean | number | string>(
137+
defaultValue: T,
138+
value: string,
139+
): T {
140+
switch (typeof defaultValue) {
141+
case 'boolean':
142+
if (value === 'true') {
143+
// $FlowExpectedError[incompatible-return] at this point we know T is a boolean
144+
return true;
145+
} else if (value === 'false') {
146+
// $FlowExpectedError[incompatible-return] at this point we know T is a boolean
147+
return false;
148+
} else {
149+
throw new Error(`Invalid value for boolean flag: ${value}`);
150+
}
151+
case 'number':
152+
const parsed = Number(value);
153+
154+
if (Number.isNaN(parsed)) {
155+
throw new Error(`Invalid value for number flag: ${value}`);
156+
}
157+
158+
// $FlowExpectedError[incompatible-return] at this point we know T is a number
159+
return parsed;
160+
case 'string':
161+
// $FlowExpectedError[incompatible-return] at this point we know T is a string
162+
return value;
163+
default:
164+
throw new Error(`Unsupported feature flag type: ${typeof defaultValue}`);
165+
}
166+
}

jest/integration/runner/runner.js

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,10 +104,16 @@ module.exports = async function runTest(
104104
});
105105

106106
const setupModulePath = path.resolve(__dirname, '../runtime/setup.js');
107+
const featureFlagsModulePath = path.resolve(
108+
__dirname,
109+
'../../../packages/react-native/src/private/featureflags/ReactNativeFeatureFlags.js',
110+
);
107111

108112
const entrypointContents = entrypointTemplate({
109113
testPath: `${path.relative(BUILD_OUTPUT_PATH, testPath)}`,
110114
setupModulePath: `${path.relative(BUILD_OUTPUT_PATH, setupModulePath)}`,
115+
featureFlagsModulePath: `${path.relative(BUILD_OUTPUT_PATH, featureFlagsModulePath)}`,
116+
featureFlags: testConfig.flags.jsOnly,
111117
});
112118

113119
const entrypointPath = path.join(
@@ -151,6 +157,8 @@ module.exports = async function runTest(
151157
'--',
152158
'--bundlePath',
153159
testBundlePath,
160+
'--featureFlags',
161+
JSON.stringify(testConfig.flags.common),
154162
]);
155163

156164
if (rnTesterCommandResult.status !== 0) {

packages/react-native/Libraries/Components/TextInput/__tests__/TextInput-itest.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
* @flow strict-local
88
* @format
99
* @oncall react_native
10+
* @fantom_flags enableFixForViewCommandRace:true
1011
*/
1112

1213
import '../../../Core/InitializeCore.js';
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
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+
* @oncall react_native
10+
* @fantom_flags commonTestFlag:true jsOnlyTestFlag:true
11+
*/
12+
13+
import * as ReactNativeFeatureFlags from '../featureflags/ReactNativeFeatureFlags';
14+
15+
describe('FantomFeatureFlags', () => {
16+
it('allows overridding common feature flags', () => {
17+
expect(ReactNativeFeatureFlags.commonTestFlag()).toBe(true);
18+
});
19+
20+
it('allows overridding JS-only feature flags', () => {
21+
expect(ReactNativeFeatureFlags.jsOnlyTestFlag()).toBe(true);
22+
});
23+
});

packages/react-native/src/private/webapis/dom/nodes/__tests__/ReactNativeElement-itest.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,9 @@
77
* @flow strict-local
88
* @format
99
* @oncall react_native
10+
* @fantom_flags enableAccessToHostTreeInFabric:true
1011
*/
1112

12-
import './setUpFeatureFlags';
1313
import '../../../../../../Libraries/Core/InitializeCore.js';
1414

1515
import * as ReactNativeTester from '../../../../__tests__/ReactNativeTester';

packages/react-native/src/private/webapis/dom/nodes/__tests__/ReadOnlyText-itest.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,9 @@
77
* @flow strict-local
88
* @format
99
* @oncall react_native
10+
* @fantom_flags enableAccessToHostTreeInFabric:true
1011
*/
1112

12-
import './setUpFeatureFlags';
1313
import '../../../../../../Libraries/Core/InitializeCore.js';
1414

1515
import {NativeText} from '../../../../../../Libraries/Text/TextNativeComponent';

packages/react-native/src/private/webapis/dom/nodes/__tests__/setUpFeatureFlags.js

Lines changed: 0 additions & 16 deletions
This file was deleted.

packages/react-native/src/private/webapis/performance/__tests__/LongTaskAPI-itest.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
* @flow strict-local
88
* @format
99
* @oncall react_native
10+
* @fantom_flags enableLongTaskAPI:true
1011
*/
1112

1213
import type {PerformanceObserverCallbackOptions} from '../PerformanceObserver';

0 commit comments

Comments
 (0)