Skip to content

Commit

Permalink
Implement streaming mode for console logs in tests (facebook#48372)
Browse files Browse the repository at this point in the history
Summary:
Pull Request resolved: facebook#48372

Changelog: [internal]

Implements log streaming in Fantom tests. This allows us to see the logs emitted from tests as they're logged, so we don't need to wait until the test completes to flush all of them at the same time.

Differential Revision: D67600609
  • Loading branch information
rubennorte authored and facebook-github-bot committed Dec 23, 2024
1 parent 723af93 commit 8a2cc1a
Show file tree
Hide file tree
Showing 2 changed files with 84 additions and 58 deletions.
94 changes: 57 additions & 37 deletions packages/react-native-fantom/runner/runner.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
*/

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

import entrypointTemplate from './entrypoint-template';
import getFantomTestConfig from './getFantomTestConfig';
Expand All @@ -23,7 +23,8 @@ import {
getBuckModesForPlatform,
getDebugInfoFromCommandResult,
getShortHash,
printConsoleLogs,
printConsoleLog,
runBuck2,
runBuck2Sync,
symbolicateStackTrace,
} from './utils';
Expand All @@ -34,6 +35,7 @@ import {SnapshotState, buildSnapshotResolver} from 'jest-snapshot';
import Metro from 'metro';
import nullthrows from 'nullthrows';
import path from 'path';
import readline from 'readline';

const BUILD_OUTPUT_ROOT = path.resolve(__dirname, '..', 'build');
fs.mkdirSync(BUILD_OUTPUT_ROOT, {recursive: true});
Expand All @@ -43,41 +45,71 @@ const BUILD_OUTPUT_PATH = fs.mkdtempSync(

const PRINT_FANTOM_OUTPUT: false = false;

function parseRNTesterCommandResult(result: SyncCommandResult): {
logs: $ReadOnlyArray<ConsoleLogMessage>,
testResult: TestSuiteResult,
} {
const stdout = result.stdout.toString();
async function processRNTesterCommandResult(
result: AsyncCommandResult,
): Promise<TestSuiteResult> {
const stdoutChunks = [];
const stderrChunks = [];

result.childProcess.stdout.on('data', chunk => {
stdoutChunks.push(chunk);
});

result.childProcess.stderr.on('data', chunk => {
stderrChunks.push(chunk);
});

const logs = [];
let testResult;

const lines = stdout
.split('\n')
.map(line => line.trim())
.filter(Boolean);
const rl = readline.createInterface({input: result.childProcess.stdout});
rl.on('line', (rawLine: string) => {
const line = rawLine.trim();
if (!line) {
return;
}

for (const line of lines) {
let parsed;
try {
parsed = JSON.parse(line);
} catch {}
} catch {
parsed = {
type: 'console-log',
level: 'info',
message: line,
};
}

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

await result.done;

const getResultWithOutput = () => ({
...result,
stdout: stdoutChunks.join(''),
stderr: stderrChunks.join(''),
});

if (result.status !== 0) {
throw new Error(getDebugInfoFromCommandResult(getResultWithOutput()));
}

if (PRINT_FANTOM_OUTPUT) {
console.log(getDebugInfoFromCommandResult(getResultWithOutput()));
}

if (testResult == null) {
Expand All @@ -87,7 +119,7 @@ function parseRNTesterCommandResult(result: SyncCommandResult): {
);
}

return {logs, testResult};
return testResult;
}

function generateBytecodeBundle({
Expand Down Expand Up @@ -202,7 +234,7 @@ module.exports = async function runTest(
});
}

const rnTesterCommandResult = runBuck2Sync([
const rnTesterCommandResult = runBuck2([
'run',
...getBuckModesForPlatform(
testConfig.mode === FantomTestConfigMode.Optimized,
Expand All @@ -219,19 +251,11 @@ module.exports = async function runTest(
PRINT_FANTOM_OUTPUT ? 'info' : 'error',
]);

if (rnTesterCommandResult.status !== 0) {
throw new Error(getDebugInfoFromCommandResult(rnTesterCommandResult));
}

if (PRINT_FANTOM_OUTPUT) {
console.log(getDebugInfoFromCommandResult(rnTesterCommandResult));
}

const rnTesterParsedOutput = parseRNTesterCommandResult(
const processedResult = await processRNTesterCommandResult(
rnTesterCommandResult,
);

const testResultError = rnTesterParsedOutput.testResult.error;
const testResultError = processedResult.error;
if (testResultError) {
const error = new Error(testResultError.message);
error.stack = symbolicateStackTrace(sourceMapPath, testResultError.stack);
Expand All @@ -240,12 +264,8 @@ module.exports = async function runTest(

const endTime = Date.now();

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

const testResults =
nullthrows(rnTesterParsedOutput.testResult.testResults).map(testResult => ({
nullthrows(processedResult.testResults).map(testResult => ({
ancestorTitles: [] as Array<string>,
failureDetails: [] as Array<string>,
testFilePath: testPath,
Expand All @@ -255,9 +275,9 @@ module.exports = async function runTest(
),
})) ?? [];

const snapshotResults = nullthrows(
rnTesterParsedOutput.testResult.testResults,
).map(testResult => testResult.snapshotResults);
const snapshotResults = nullthrows(processedResult.testResults).map(
testResult => testResult.snapshotResults,
);

const snapshotResult = updateSnapshotsAndGetJestSnapshotResult(
snapshotState,
Expand Down
48 changes: 27 additions & 21 deletions packages/react-native-fantom/runner/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,8 @@ export type AsyncCommandResult = {
status: ?number,
signal: ?string,
error: ?Error,
stdout: ?string,
stderr: ?string,
};

export type SyncCommandResult = {
Expand Down Expand Up @@ -90,6 +92,8 @@ export function runCommand(
status: null,
signal: null,
error: null,
stdout: null,
stderr: null,
};

childProcess.on('error', error => {
Expand Down Expand Up @@ -123,22 +127,24 @@ export function runCommandSync(
}

export function getDebugInfoFromCommandResult(
commandResult: SyncCommandResult,
commandResult: SyncCommandResult | AsyncCommandResult,
): string {
const maybeSignal =
commandResult.signal != null ? `, signal: ${commandResult.signal}` : '';
const resultByStatus =
commandResult.status === 0
? 'succeeded'
: `failed (status code: ${commandResult.status}${maybeSignal})`;
: `failed (status code: ${commandResult.status ?? '(empty)'}${maybeSignal})`;

const logLines = [
`Command ${resultByStatus}: ${commandResult.originalCommand}`,
'',
'stdout:',
// $FlowExpectedError[sketchy-null-string]
commandResult.stdout || '(empty)',
'',
'stderr:',
// $FlowExpectedError[sketchy-null-string]
commandResult.stderr || '(empty)',
];

Expand Down Expand Up @@ -210,24 +216,24 @@ export type ConsoleLogMessage = {
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;
}
export function printConsoleLog(log: ConsoleLogMessage): void {
if (process.env.SANDCASTLE != null) {
return;
}

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;
}
}

0 comments on commit 8a2cc1a

Please sign in to comment.