From 7ccc5934d0f341f9bc8157f18913a7b340f5db2d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rub=C3=A9n=20Norte?= Date: Thu, 28 Nov 2024 07:59:26 -0800 Subject: [PATCH] Implement symbolication of error stack traces (#48006) Summary: Pull Request resolved: https://github.com/facebook/react-native/pull/48006 Changelog: [internal] This implements symbolication of error stack traces in Fantom tests. We just needed to ask Metro to generate source maps and use the `source-map` package to process the stack traces that we get back from the runtime. Reviewed By: sammy-SC Differential Revision: D66577818 fbshipit-source-id: 672c66c246ad8646646d5ed31cabca39eb4f7aca --- jest/integration/runner/runner.js | 43 ++++++++++++++++++++++++++++++- 1 file changed, 42 insertions(+), 1 deletion(-) diff --git a/jest/integration/runner/runner.js b/jest/integration/runner/runner.js index ab1be82180d..03132b507e6 100644 --- a/jest/integration/runner/runner.js +++ b/jest/integration/runner/runner.js @@ -21,6 +21,8 @@ import Metro from 'metro'; import nullthrows from 'nullthrows'; import os from 'os'; import path from 'path'; +// $FlowExpectedError[untyped-import] +import {SourceMapConsumer} from 'source-map'; const BUILD_OUTPUT_PATH = path.resolve(__dirname, '..', 'build'); @@ -130,6 +132,35 @@ function generateBytecodeBundle({ } } +function symbolicateStackTrace( + sourceMapPath: string, + stackTrace: string, +): string { + const sourceMapData = JSON.parse(fs.readFileSync(sourceMapPath, 'utf8')); + const consumer = new SourceMapConsumer(sourceMapData); + + return stackTrace + .split('\n') + .map(line => { + const match = line.match(/at (.*) \((.*):(\d+):(\d+)\)/); + if (match) { + const functionName = match[1]; + // const fileName = match[2]; + const lineNumber = parseInt(match[3], 10); + const columnNumber = parseInt(match[4], 10); + // Get the original position + const originalPosition = consumer.originalPositionFor({ + line: lineNumber, + column: columnNumber, + }); + return `at ${originalPosition.name ?? functionName} (${originalPosition.source}:${originalPosition.line}:${originalPosition.column})`; + } else { + return line; + } + }) + .join('\n'); +} + module.exports = async function runTest( globalConfig: {...}, config: {...}, @@ -163,12 +194,19 @@ module.exports = async function runTest( fs.mkdirSync(path.dirname(entrypointPath), {recursive: true}); fs.writeFileSync(entrypointPath, entrypointContents, 'utf8'); + const sourceMapPath = path.join( + path.dirname(testJSBundlePath), + path.basename(testJSBundlePath, '.js') + '.map', + ); + await Metro.runBuild(metroConfig, { entry: entrypointPath, out: testJSBundlePath, platform: 'android', minify: isOptimizedMode, dev: !isOptimizedMode, + sourceMap: true, + sourceMapUrl: sourceMapPath, }); if (isOptimizedMode) { @@ -232,7 +270,7 @@ module.exports = async function runTest( const testResultError = rnTesterParsedOutput.testResult.error; if (testResultError) { const error = new Error(testResultError.message); - error.stack = testResultError.stack; + error.stack = symbolicateStackTrace(sourceMapPath, testResultError.stack); throw error; } @@ -248,6 +286,9 @@ module.exports = async function runTest( failureDetails: [] as Array, testFilePath: testPath, ...testResult, + failureMessages: testResult.failureMessages.map(maybeStackTrace => + symbolicateStackTrace(sourceMapPath, maybeStackTrace), + ), })) ?? []; return {