Add tests for worker code

Summary: This adds unit tests for the new buck worker code, including a test for source map generation.

Reviewed By: cpojer

Differential Revision: D4193657

fbshipit-source-id: 06f7bfb5efa4f411178543a728ac7e42511caa3c
This commit is contained in:
David Aurelio 2016-11-17 08:48:29 -08:00 committed by Facebook Github Bot
parent 7181a2d436
commit 1c16124e1c
5 changed files with 341 additions and 6 deletions

View File

@ -93,10 +93,9 @@ export type PackageData = {|
'react-native'?: Object | string,
|};
export type TransformFnResult = {|
export type TransformFnResult = {
ast: Object,
map?: Object,
|};
};
export type TransformFn = (
data: {|

View File

@ -0,0 +1,104 @@
/**
* Copyright (c) 2016-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
'use strict';
jest.disableAutomock();
const optimizeModule = require('../optimize-module');
const transformModule = require('../transform-module');
const transform = require('../../../../../transformer.js');
const {SourceMapConsumer} = require('source-map');
const {objectContaining} = jasmine;
describe('optimizing JS modules', () => {
const filename = 'arbitrary/file.js';
const optimizationOptions = {
dev: false,
platform: 'android',
};
const originalCode =
`if (Platform.OS !== 'android') {
require('arbitrary-dev');
} else {
__DEV__ ? require('arbitrary-android-dev') : require('arbitrary-android-prod');
}`;
let transformResult;
beforeAll(done => {
transformModule(originalCode, {filename, transform}, (error, result) => {
if (error) {
throw error;
}
transformResult = JSON.stringify(result);
done();
});
});
it('copies everything from the transformed file, except for transform results', done => {
optimizeModule(transformResult, optimizationOptions, (error, result) => {
const expected = JSON.parse(transformResult);
delete expected.transformed;
expect(result).toEqual(objectContaining(expected));
done();
});
});
describe('code optimization', () => {
let dependencyMapName, injectedVars, optimized, requireName;
beforeAll(done => {
optimizeModule(transformResult, optimizationOptions, (error, result) => {
optimized = result.transformed.default;
injectedVars = optimized.code.match(/function\(([^)]*)/)[1].split(',');
[requireName,,,, dependencyMapName] = injectedVars;
done();
});
});
it('optimizes code', () => {
expect(optimized.code)
.toEqual(`__d(function(${injectedVars}){${requireName}(${dependencyMapName}[0])});`);
});
it('extracts dependencies', () => {
expect(optimized.dependencies).toEqual(['arbitrary-android-prod']);
});
it('creates source maps', () => {
const consumer = new SourceMapConsumer(optimized.map);
const column = optimized.code.lastIndexOf(requireName + '(');
const loc = findLast(originalCode, 'require');
expect(consumer.originalPositionFor({line: 1, column}))
.toEqual(objectContaining(loc));
});
it('does not extract dependencies for polyfills', done => {
optimizeModule(
transformResult,
{...optimizationOptions, isPolyfill: true},
(error, result) => {
expect(result.transformed.default.dependencies).toEqual([]);
done();
},
);
});
});
});
function findLast(code, needle) {
const lines = code.split(/(?:(?!.)\s)+/);
let line = lines.length;
while (line--) {
const column = lines[line].lastIndexOf(needle);
if (column !== -1) {
return {line: line + 1, column};
}
}
}

View File

@ -0,0 +1,232 @@
/**
* Copyright (c) 2016-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
'use strict';
jest.disableAutomock();
const transformModule = require('../transform-module');
const t = require('babel-types');
const {SourceMapConsumer} = require('source-map');
const {fn} = require('../../test-helpers');
const {parse} = require('babylon');
const generate = require('babel-generator').default;
const {traverse} = require('babel-core');
const {any, objectContaining} = jasmine;
describe('transforming JS modules:', () => {
const filename = 'arbitrary';
let transform;
beforeEach(() => {
transform = fn();
transform.stub.yields(null, transformResult());
});
const {bodyAst, sourceCode, transformedCode} = createTestData();
const options = variants => ({
filename,
transform,
variants,
});
const transformResult = (body = bodyAst) => ({
ast: t.file(t.program(body)),
});
it('passes through file name and code', done => {
transformModule(sourceCode, options(), (error, result) => {
expect(result).toEqual(objectContaining({
code: sourceCode,
file: filename,
}));
done();
});
});
it('exposes a haste ID if present', done => {
const hasteID = 'TheModule';
const codeWithHasteID = `/** @providesModule ${hasteID} */`;
transformModule(codeWithHasteID, options(), (error, result) => {
expect(result).toEqual(objectContaining({hasteID}));
done();
});
});
it('sets `isPolyfill` to `false` by default', done => {
transformModule(sourceCode, options(), (error, result) => {
expect(result).toEqual(objectContaining({isPolyfill: false}));
done();
});
});
it('sets `isPolyfill` to `true` if the input is a polyfill', done => {
transformModule(sourceCode, {...options(), polyfill: true}, (error, result) => {
expect(result).toEqual(objectContaining({isPolyfill: true}));
done();
});
});
it('calls the passed-in transform function with code, file name, and options for all passed in variants', done => {
const variants = {dev: {dev: true}, prod: {dev: false}};
transformModule(sourceCode, options(variants), () => {
expect(transform)
.toBeCalledWith({filename, sourceCode, options: variants.dev}, any(Function));
expect(transform)
.toBeCalledWith({filename, sourceCode, options: variants.prod}, any(Function));
done();
});
});
it('calls back with any error yielded by the transform function', done => {
const error = new Error();
transform.stub.yields(error);
transformModule(sourceCode, options(), e => {
expect(e).toBe(error);
done();
});
});
it('wraps the code produced by the transform function into a module factory', done => {
transformModule(sourceCode, options(), (error, result) => {
expect(error).toEqual(null);
const {code, dependencyMapName} = result.transformed.default;
expect(code.replace(/\s+/g, ''))
.toEqual(
`__d(function(require,module,global,exports,${
dependencyMapName}){${transformedCode}});`
);
done();
});
});
it('wraps the code produced by the transform function into an immediately invoked function expression for polyfills', done => {
transformModule(sourceCode, {...options(), polyfill: true}, (error, result) => {
expect(error).toEqual(null);
const {code} = result.transformed.default;
expect(code.replace(/\s+/g, ''))
.toEqual(`(function(global){${transformedCode}})(this);`);
done();
});
});
it('creates source maps', done => {
transformModule(sourceCode, options(), (error, result) => {
const {code, map} = result.transformed.default;
const column = code.indexOf('code');
const consumer = new SourceMapConsumer(map);
expect(consumer.originalPositionFor({line: 1, column}))
.toEqual(objectContaining({line: 1, column: sourceCode.indexOf('code')}));
done();
});
});
it('extracts dependencies (require calls)', done => {
const dep1 = 'foo', dep2 = 'bar';
const code = `require('${dep1}'),require('${dep2}')`;
const {body} = parse(code).program;
transform.stub.yields(null, transformResult(body));
transformModule(code, options(), (error, result) => {
expect(result.transformed.default)
.toEqual(objectContaining({dependencies: [dep1, dep2]}));
done();
});
});
it('transforms for all variants', done => {
const variants = {dev: {dev: true}, prod: {dev: false}};
transform.stub
.withArgs(filename, sourceCode, variants.dev)
.yields(null, transformResult(bodyAst))
.withArgs(filename, sourceCode, variants.prod)
.yields(null, transformResult([]));
transformModule(sourceCode, options(variants), (error, result) => {
const {dev, prod} = result.transformed;
expect(dev.code.replace(/\s+/g, ''))
.toEqual(
`__d(function(require,module,global,exports,${
dev.dependencyMapName}){arbitrary(code);});`
);
expect(prod.code.replace(/\s+/g, ''))
.toEqual(
`__d(function(require,module,global,exports,${
prod.dependencyMapName}){arbitrary(code);});`
);
done();
});
});
it('prefixes JSON files with `module.exports = `', done => {
const json = '{"foo":"bar"}';
transformModule(json, {...options(), filename: 'some.json'}, (error, result) => {
const {code} = result.transformed.default;
expect(code.replace(/\s+/g, ''))
.toEqual(
'__d(function(require,module,global,exports){' +
`module.exports=${json}});`
);
done();
});
});
it('does not create source maps for JSON files', done => {
transformModule('{}', {...options(), filename: 'some.json'}, (error, result) => {
expect(result.transformed.default)
.toEqual(objectContaining({map: null}));
done();
});
});
it('adds package data for `package.json` files', done => {
const pkg = {
name: 'package-name',
main: 'package/main',
browser: {browser: 'defs'},
'react-native': {'react-native': 'defs'},
};
transformModule(
JSON.stringify(pkg),
{...options(), filename: 'arbitrary/package.json'},
(error, result) => {
expect(result.package).toEqual(pkg);
done();
},
);
});
});
function createTestData() {
// creates test data with an transformed AST, so that we can test source
// map generation.
const sourceCode = 'some(arbitrary(code));';
const fileAst = parse(sourceCode);
traverse(fileAst, {
CallExpression(path) {
if (path.node.callee.name === 'some') {
path.replaceWith(path.node.arguments[0]);
}
}
});
return {
bodyAst: fileAst.program.body,
sourceCode,
transformedCode: generate(fileAst).code,
};
}

View File

@ -21,9 +21,9 @@ const sourceMap = require('source-map');
import type {Callback, TransformedFile, TransformResult} from '../types.flow';
export type OptimizationOptions = {|
dev?: boolean,
dev: boolean,
isPolyfill?: boolean,
platform?: string,
platform: string,
|};
function optimizeModule(

View File

@ -88,7 +88,7 @@ function transformJSON(json, options, callback) {
const code =
`__d(function(${moduleFactoryParameters.join(', ')}) { module.exports = \n${
json
}\n})`;
}\n});`;
const moduleData = {
code,