Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactor utility to run buck2 commands as a method to run arbitrary commands #48370

Open
wants to merge 5 commits into
base: main
Choose a base branch
from
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
58 changes: 40 additions & 18 deletions packages/react-native-fantom/runner/runner.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
*/

import type {TestSuiteResult} from '../runtime/setup';
import type {ConsoleLogMessage} from './utils';

import entrypointTemplate from './entrypoint-template';
import getFantomTestConfig from './getFantomTestConfig';
Expand All @@ -22,7 +23,8 @@ import {
getBuckModesForPlatform,
getDebugInfoFromCommandResult,
getShortHash,
runBuck2,
printConsoleLogs,
runBuck2Sync,
symbolicateStackTrace,
} from './utils';
import fs from 'fs';
Expand All @@ -41,31 +43,51 @@ const BUILD_OUTPUT_PATH = fs.mkdtempSync(

const PRINT_FANTOM_OUTPUT: false = false;

function parseRNTesterCommandResult(result: ReturnType<typeof runBuck2>): {
logs: string,
function parseRNTesterCommandResult(result: ReturnType<typeof runBuck2Sync>): {
logs: $ReadOnlyArray<ConsoleLogMessage>,
testResult: TestSuiteResult,
} {
const stdout = result.stdout.toString();

const outputArray = stdout
.trim()
.split('\n')
.filter(log => !log.startsWith('Running "')); // remove AppRegistry logs.
const logs = [];
let testResult;

// The last line should be the test output in JSON format
const testResultJSON = outputArray.pop();
const lines = stdout
.split('\n')
.map(line => line.trim())
.filter(Boolean);

for (const line of lines) {
let parsed;
try {
parsed = JSON.parse(line);
} catch {}

switch (parsed?.type) {
case 'test-result':
testResult = parsed;
break;
case 'console-log':
logs.push(parsed);
break;
default:
logs.push({
type: 'console-log',
message: line,
level: 'info',
});
break;
}
}

let testResult;
try {
testResult = JSON.parse(nullthrows(testResultJSON));
} catch (error) {
if (testResult == null) {
throw new Error(
'Failed to parse test results from RN tester binary result.\n' +
'Failed to find test results in RN tester binary output.\n' +
getDebugInfoFromCommandResult(result),
);
}

return {logs: outputArray.join('\n'), testResult};
return {logs, testResult};
}

function generateBytecodeBundle({
Expand All @@ -77,7 +99,7 @@ function generateBytecodeBundle({
bytecodePath: string,
isOptimizedMode: boolean,
}): void {
const hermesCompilerCommandResult = runBuck2(
const hermesCompilerCommandResult = runBuck2Sync(
[
'run',
...getBuckModesForPlatform(isOptimizedMode),
Expand Down Expand Up @@ -180,7 +202,7 @@ module.exports = async function runTest(
});
}

const rnTesterCommandResult = runBuck2([
const rnTesterCommandResult = runBuck2Sync([
'run',
...getBuckModesForPlatform(
testConfig.mode === FantomTestConfigMode.Optimized,
Expand Down Expand Up @@ -219,7 +241,7 @@ module.exports = async function runTest(
const endTime = Date.now();

if (process.env.SANDCASTLE == null) {
console.log(rnTesterParsedOutput.logs);
printConsoleLogs(rnTesterParsedOutput.logs);
}

const testResults =
Expand Down
65 changes: 50 additions & 15 deletions packages/react-native-fantom/runner/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -44,24 +44,17 @@ export function getBuckModesForPlatform(
return ['@//xplat/mode/react-force-cxx-platform', osPlatform];
}

type SpawnResultWithOriginalCommand = {
type SyncCommandResult = {
...ReturnType<typeof spawnSync>,
originalCommand: string,
...
};

export function runBuck2(args: Array<string>): SpawnResultWithOriginalCommand {
// If these tests are already running from withing a buck2 process, e.g. when
// they are scheduled by a `buck2 test` wrapper, calling `buck2` again would
// cause a daemon-level deadlock.
// To prevent this - explicitly pass custom `--isolation-dir`. Reuse the same
// dir across tests (even running in different jest processes) to properly
// employ caching.
if (process.env.BUCK2_WRAPPER != null) {
args.unshift('--isolation-dir', BUCK_ISOLATION_DIR);
}

const result = spawnSync('buck2', args, {
export function runCommandSync(
command: string,
args: Array<string>,
): SyncCommandResult {
const result = spawnSync(command, args, {
encoding: 'utf8',
env: {
...process.env,
Expand All @@ -71,12 +64,12 @@ export function runBuck2(args: Array<string>): SpawnResultWithOriginalCommand {

return {
...result,
originalCommand: `buck2 ${args.join(' ')}`,
originalCommand: `${command} ${args.join(' ')}`,
};
}

export function getDebugInfoFromCommandResult(
commandResult: SpawnResultWithOriginalCommand,
commandResult: SyncCommandResult,
): string {
const maybeSignal =
commandResult.signal != null ? `, signal: ${commandResult.signal}` : '';
Expand All @@ -102,6 +95,20 @@ export function getDebugInfoFromCommandResult(
return logLines.join('\n');
}

export function runBuck2Sync(args: Array<string>): SyncCommandResult {
// If these tests are already running from withing a buck2 process, e.g. when
// they are scheduled by a `buck2 test` wrapper, calling `buck2` again would
// cause a daemon-level deadlock.
// To prevent this - explicitly pass custom `--isolation-dir`. Reuse the same
// dir across tests (even running in different jest processes) to properly
// employ caching.
if (process.env.BUCK2_WRAPPER != null) {
args.unshift('--isolation-dir', BUCK_ISOLATION_DIR);
}

return runCommandSync('buck2', args);
}

export function getShortHash(contents: string): string {
return crypto.createHash('md5').update(contents).digest('hex').slice(0, 8);
}
Expand Down Expand Up @@ -134,3 +141,31 @@ export function symbolicateStackTrace(
})
.join('\n');
}

export type ConsoleLogMessage = {
type: 'console-log',
level: 'info' | 'warn' | 'error',
message: string,
};

export function printConsoleLogs(
logs: $ReadOnlyArray<ConsoleLogMessage>,
): void {
for (const log of logs) {
switch (log.type) {
case 'console-log':
switch (log.level) {
case 'info':
console.log(log.message);
break;
case 'warn':
console.warn(log.message);
break;
case 'error':
console.error(log.message);
break;
}
break;
}
}
}
6 changes: 3 additions & 3 deletions packages/react-native-fantom/runner/warmup/warmup.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
import {
getBuckModesForPlatform,
getDebugInfoFromCommandResult,
runBuck2,
runBuck2Sync,
} from '../utils';
// $FlowExpectedError[untyped-import]
import fs from 'fs';
Expand Down Expand Up @@ -94,7 +94,7 @@ async function warmUpMetro(isOptimizedMode: boolean): Promise<void> {
}

function warmUpHermesCompiler(isOptimizedMode: boolean): void {
const buildHermesCompilerCommandResult = runBuck2([
const buildHermesCompilerCommandResult = runBuck2Sync([
'build',
...getBuckModesForPlatform(isOptimizedMode),
'//xplat/hermes/tools/hermesc:hermesc',
Expand All @@ -108,7 +108,7 @@ function warmUpHermesCompiler(isOptimizedMode: boolean): void {
}

function warmUpRNTesterCLI(isOptimizedMode: boolean): void {
const buildRNTesterCommandResult = runBuck2([
const buildRNTesterCommandResult = runBuck2Sync([
'build',
...getBuckModesForPlatform(isOptimizedMode),
'//xplat/ReactNative/react-native-cxx/samples/tester:tester',
Expand Down
8 changes: 7 additions & 1 deletion packages/react-native-fantom/runtime/setup.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@

import type {SnapshotConfig, TestSnapshotResults} from './snapshotContext';

import NativeFantom from '../src/specs/NativeFantom';
import expect from './expect';
import {createMockFunction} from './mocks';
import {setupSnapshotConfig, snapshotContext} from './snapshotContext';
Expand Down Expand Up @@ -190,7 +191,12 @@ function executeTests() {
}

function reportTestSuiteResult(testSuiteResult: TestSuiteResult): void {
console.log(JSON.stringify(testSuiteResult));
NativeFantom.reportTestSuiteResultsJSON(
JSON.stringify({
type: 'test-result',
...testSuiteResult,
}),
);
}

global.$$RunTests$$ = () => {
Expand Down
68 changes: 43 additions & 25 deletions packages/react-native-fantom/src/__tests__/Fantom-itest.js
Original file line number Diff line number Diff line change
Expand Up @@ -76,28 +76,30 @@ describe('Fantom', () => {

// TODO: when error handling is fixed, this should verify using `toThrow`
it('should throw when running a task inside another task', () => {
let lastCallbackExecuted = 0;
let threw = false;

runTask(() => {
lastCallbackExecuted = 1;
runTask(() => {
lastCallbackExecuted = 2;
throw new Error('Recursive runTask should be unreachable');
});
// TODO replace with expect(() => { ... }).toThrow() when error handling is fixed
try {
runTask(() => {});
} catch {
threw = true;
}
});
expect(lastCallbackExecuted).toBe(1);
expect(threw).toBe(true);

threw = false;

runTask(() => {
queueMicrotask(() => {
lastCallbackExecuted = 3;
runTask(() => {
lastCallbackExecuted = 4;
throw new Error(
'Recursive runTask from micro-task should be unreachable',
);
});
try {
runTask(() => {});
} catch {
threw = true;
}
});
});
expect(lastCallbackExecuted).toBe(3);
expect(threw).toBe(true);
});
});

Expand Down Expand Up @@ -125,16 +127,24 @@ describe('Fantom', () => {
runTask(() => {
root.render(
<>
<View style={{width: 100, height: 100}} collapsable={false} />
<View style={{width: 100, height: 100}} collapsable={false} />
<View
key="first"
style={{width: 100, height: 100}}
collapsable={false}
/>
<View
key="second"
style={{width: 100, height: 100}}
collapsable={false}
/>
</>,
);
});

expect(root.getRenderedOutput().toJSX()).toEqual(
<>
<rn-view width="100.000000" height="100.000000" />
<rn-view width="100.000000" height="100.000000" />
<rn-view key="0" width="100.000000" height="100.000000" />
<rn-view key="1" width="100.000000" height="100.000000" />
</>,
);

Expand Down Expand Up @@ -233,18 +243,26 @@ describe('Fantom', () => {
runTask(() => {
root.render(
<>
<View style={{width: 100, height: 100}} collapsable={false} />
<Text>hello world!</Text>
<View style={{width: 200, height: 300}} collapsable={false} />
<View
key="first"
style={{width: 100, height: 100}}
collapsable={false}
/>
<Text key="second">hello world!</Text>
<View
key="third"
style={{width: 200, height: 300}}
collapsable={false}
/>
</>,
);
});

expect(root.getRenderedOutput({props: []}).toJSX()).toEqual(
<>
<rn-view />
<rn-paragraph>hello world!</rn-paragraph>
<rn-view />
<rn-view key="0" />
<rn-paragraph key="1">hello world!</rn-paragraph>
<rn-view key="2" />
</>,
);

Expand Down
Loading
Loading