From c9b313f23c2665e14b9a4f8c44bba8be80cadd6c Mon Sep 17 00:00:00 2001 From: Salakar Date: Sun, 25 Mar 2018 21:17:26 +0100 Subject: [PATCH] [tests][bridge] added source map support / stack trace conversion --- tests-new/bridge/env/node/coverage.js | 8 ++- tests-new/bridge/env/node/index.js | 1 + tests-new/bridge/env/node/source-map.js | 54 +++++++++++++++ tests-new/bridge/env/node/vm.js | 31 ++++++--- tests-new/package-lock.json | 88 +++++++++++++++++++++++-- tests-new/package.json | 8 +-- 6 files changed, 167 insertions(+), 23 deletions(-) create mode 100644 tests-new/bridge/env/node/source-map.js diff --git a/tests-new/bridge/env/node/coverage.js b/tests-new/bridge/env/node/coverage.js index aa949cd5..bed58fcd 100644 --- a/tests-new/bridge/env/node/coverage.js +++ b/tests-new/bridge/env/node/coverage.js @@ -5,8 +5,12 @@ const rootMap = createCoverageMap({}); module.exports = { collect() { if (bridge.context && bridge.context.__coverage__) { - rootMap.merge(Object.assign({}, bridge.context.__coverage__)); - global.__coverage__ = rootMap.toJSON(); + try { + rootMap.merge(Object.assign({}, bridge.context.__coverage__)); + global.__coverage__ = rootMap.toJSON(); + } catch (e) { + // ignore + } } }, summary() { diff --git a/tests-new/bridge/env/node/index.js b/tests-new/bridge/env/node/index.js index 56dab45e..de6a5a4a 100644 --- a/tests-new/bridge/env/node/index.js +++ b/tests-new/bridge/env/node/index.js @@ -1,5 +1,6 @@ /* eslint-disable no-param-reassign */ global.bridge = {}; +require('./source-map'); const detox = require('detox'); const ws = require('./ws'); diff --git a/tests-new/bridge/env/node/source-map.js b/tests-new/bridge/env/node/source-map.js new file mode 100644 index 00000000..17fac1e1 --- /dev/null +++ b/tests-new/bridge/env/node/source-map.js @@ -0,0 +1,54 @@ +/* eslint-disable no-param-reassign */ +const Mocha = require('mocha'); +const { parse } = require('stacktrace-parser'); +const { SourceMapConsumer } = require('source-map'); + +let bundleFileName = null; +const Runner = Mocha.Runner; +let sourceMapConsumer = null; +const originalFail = Runner.prototype.fail; + +/** + * Convert an error frame into a source mapped string + * @param parsed + * @returns {string} + */ +function frameToStr(parsed) { + const { name, line, column, source } = sourceMapConsumer.originalPositionFor({ + line: parsed.lineNumber, + column: parsed.column, + }); + return ` at ${name || parsed.methodName} (${source || + parsed.file}:${line || parsed.lineNumber}:${column || parsed.column})`; +} + +// override mocha fail so we can replace stack traces +Runner.prototype.fail = function fail(test, error) { + console.dir(error); + const original = error.stack.split('\n'); + const parsed = parse(error.stack); + + const newStack = [original[0]]; + + for (let i = 0; i < parsed.length; i++) { + const { file } = parsed[i]; + if (file === bundleFileName) newStack.push(frameToStr(parsed[i])); + else newStack.push(original[i + 1]); + } + + error.stack = newStack.join('\n'); + return originalFail.call(this, test, error); +}; + +module.exports = { + /** + * Build a source map consumer from source map bundle contents + * @param str + * @param fileName + * @returns {Promise} + */ + async buildSourceMap(str, fileName) { + bundleFileName = fileName; + sourceMapConsumer = await new SourceMapConsumer(str); + }, +}; diff --git a/tests-new/bridge/env/node/vm.js b/tests-new/bridge/env/node/vm.js index ac95ea36..5b4e6157 100644 --- a/tests-new/bridge/env/node/vm.js +++ b/tests-new/bridge/env/node/vm.js @@ -6,13 +6,14 @@ const invariant = require('assert'); const { Script } = require('vm'); const context = require('./context'); const coverage = require('./coverage'); +const { buildSourceMap } = require('./source-map'); let send; let bundle; const PREPARE = 'prepareJSRuntime'; +const BUNDLE_FILE_NAME = 'app.bundle.js'; const EXECUTE = 'executeApplicationScript'; -const TEMP_BUNDLE_PATH = '/tmp/bridge/react-native.js'; function reply(id, result) { send({ @@ -25,23 +26,35 @@ function handleError(message) { throw new Error(message); } -async function downloadBundle(bundleUrl) { +async function downloadUrl(url) { const res = await new Promise((resolve, reject) => - http.get(bundleUrl, resolve).on('error', reject) + http.get(url, resolve).on('error', reject) ); let buffer = ''; - res.setEncoding('utf8'); res.on('data', chunk => (buffer += chunk)); await new Promise(resolve => res.on('end', resolve)); + return buffer; +} - bundle = new Script(buffer, { +async function downloadBundle(bundleUrl) { + const bundleStr = await downloadUrl(bundleUrl); + + bundle = new Script(bundleStr, { timeout: 120000, displayErrors: true, - filename: TEMP_BUNDLE_PATH, + filename: BUNDLE_FILE_NAME, }); + const sourceMapUrl = bundleStr + .slice(bundleStr.lastIndexOf('\n')) + .replace('//# sourceMappingURL=', ''); + + const sourceMapSource = await downloadUrl(sourceMapUrl); + + await buildSourceMap(sourceMapSource, BUNDLE_FILE_NAME); + return bundle; } @@ -58,6 +71,7 @@ async function getBundle(request) { parsedUrl.query.inlineSourceMap = true; delete parsedUrl.search; + console.log(url.format(parsedUrl)); return downloadBundle(url.format(parsedUrl)); } @@ -87,9 +101,8 @@ module.exports = { global.bridge.context[name] = JSON.parse(request.inject[name]); } } - script.runInContext(global.bridge.context, { - filename: TEMP_BUNDLE_PATH, - }); + + script.runInContext(global.bridge.context, BUNDLE_FILE_NAME); reply(request.id); break; } diff --git a/tests-new/package-lock.json b/tests-new/package-lock.json index 33d32686..a192cc33 100644 --- a/tests-new/package-lock.json +++ b/tests-new/package-lock.json @@ -576,6 +576,14 @@ "slash": "1.0.0", "source-map": "0.5.7", "v8flags": "2.1.1" + }, + "dependencies": { + "source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", + "dev": true + } } }, "babel-code-frame": { @@ -636,6 +644,13 @@ "private": "0.1.8", "slash": "1.0.0", "source-map": "0.5.7" + }, + "dependencies": { + "source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=" + } } }, "babel-eslint": { @@ -663,6 +678,13 @@ "lodash": "4.17.5", "source-map": "0.5.7", "trim-right": "1.0.1" + }, + "dependencies": { + "source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=" + } } }, "babel-helper-builder-binary-assignment-operator-visitor": { @@ -1447,6 +1469,21 @@ "version": "2.5.3", "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.5.3.tgz", "integrity": "sha1-isw4NFgk8W2DZbfJtCWRaOjtYD4=" + }, + "source-map-support": { + "version": "0.4.18", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.4.18.tgz", + "integrity": "sha512-try0/JqxPLF9nOjvSta7tVondkP5dwgyLDjVoyMDlmjugT2lRZ1OfsrYTkCd2hkDnJTKRbO/Rl3orm8vlsUzbA==", + "requires": { + "source-map": "0.5.7" + }, + "dependencies": { + "source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=" + } + } } } }, @@ -2675,6 +2712,14 @@ "is-arrayish": "0.2.1" } }, + "error-stack-parser": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/error-stack-parser/-/error-stack-parser-2.0.1.tgz", + "integrity": "sha1-oyArj7AxFKqbQKDjZp5IsrZaAQo=", + "requires": { + "stackframe": "1.0.4" + } + }, "errorhandler": { "version": "1.4.3", "resolved": "https://registry.npmjs.org/errorhandler/-/errorhandler-1.4.3.tgz", @@ -5762,6 +5807,11 @@ "mime-db": "1.23.0" } }, + "source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=" + }, "statuses": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.3.1.tgz", @@ -5802,6 +5852,13 @@ "integrity": "sha512-12WEgolY5CGvHeHkF5QlM2qatdQC1DyjWkXLK9LzCqzd8YhUZww1+ZCM6E67rJwpeuCU9o1Mkiwd1h7dS+RBvA==", "requires": { "source-map": "0.5.7" + }, + "dependencies": { + "source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=" + } } }, "micromatch": { @@ -10728,6 +10785,11 @@ "version": "5.1.0", "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==" + }, + "source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=" } } }, @@ -10773,9 +10835,9 @@ } }, "source-map": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=" + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.2.tgz", + "integrity": "sha512-NDJB/R2BS7YJG0tP9SbE4DKwKj1idLT5RJqfVYZ7dreFX7wulZT3xxVhbYKrQo9n0JkRptl51TrX/5VK3HodMA==" }, "source-map-resolve": { "version": "0.5.1", @@ -10790,11 +10852,18 @@ } }, "source-map-support": { - "version": "0.4.18", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.4.18.tgz", - "integrity": "sha512-try0/JqxPLF9nOjvSta7tVondkP5dwgyLDjVoyMDlmjugT2lRZ1OfsrYTkCd2hkDnJTKRbO/Rl3orm8vlsUzbA==", + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.4.tgz", + "integrity": "sha512-PETSPG6BjY1AHs2t64vS2aqAgu6dMIMXJULWFBGbh2Gr8nVLbCFDo6i/RMMvviIQ2h1Z8+5gQhVKSn2je9nmdg==", "requires": { - "source-map": "0.5.7" + "source-map": "0.6.1" + }, + "dependencies": { + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" + } } }, "source-map-url": { @@ -10864,6 +10933,11 @@ "tweetnacl": "0.14.5" } }, + "stackframe": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/stackframe/-/stackframe-1.0.4.tgz", + "integrity": "sha512-to7oADIniaYwS3MhtCa/sQhrxidCCQiF/qp4/m5iN3ipf0Y7Xlri0f6eG29r08aL7JYl8n32AF3Q5GYBZ7K8vw==" + }, "stacktrace-parser": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/stacktrace-parser/-/stacktrace-parser-0.1.4.tgz", diff --git a/tests-new/package.json b/tests-new/package.json index ff49e9c1..c4039624 100755 --- a/tests-new/package.json +++ b/tests-new/package.json @@ -4,17 +4,13 @@ "private": true, "scripts": { "packager-chrome": "node node_modules/react-native/local-cli/cli.js start --platforms ios,android", - "packager-bridge": "REACT_DEBUGGER='echo nope' node node_modules/react-native/local-cli/cli.js start --platforms ios,android", - + "packager-bridge": "REACT_DEBUGGER='echo nope' node node_modules/react-native/local-cli/cli.js start --platforms ios,android --reset-cache", "build-android": "detox build --configuration android.emu.debug", - "test": "npm run test-android && test-ios", - "test-android": "detox test --configuration android.emu.debug", "test-android-reuse": "detox test --configuration android.emu.debug --reuse", "test-android-cover": "nyc detox test --configuration android.emu.debug", "test-android-cover-reuse": "nyc detox test --configuration android.emu.debug --reuse", - "test-ios": "detox test --configuration ios.sim.debug", "test-ios-cover": "nyc detox test --configuration ios.sim.debug", "ios:pod:install": "cd ios && rm -rf ReactNativeFirebaseDemo.xcworkspace && pod install && cd .." @@ -33,6 +29,8 @@ "should": "^13.2.1", "should-sinon": "0.0.6", "sinon": "^4.4.8", + "source-map": "^0.7.2", + "stacktrace-parser": "^0.1.4", "ws": "^5.1.0" }, "devDependencies": {