| Index: third_party/WebKit/Source/devtools/scripts/migrate_test/migrate_test.js
|
| diff --git a/third_party/WebKit/Source/devtools/scripts/migrate_test/migrate_test.js b/third_party/WebKit/Source/devtools/scripts/migrate_test/migrate_test.js
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..a8de6b6ec2b940aa12cf25a57fc1b6bf707b9a83
|
| --- /dev/null
|
| +++ b/third_party/WebKit/Source/devtools/scripts/migrate_test/migrate_test.js
|
| @@ -0,0 +1,271 @@
|
| +// Copyright 2017 The Chromium Authors. All rights reserved.
|
| +// Use of this source code is governed by a BSD-style license that can be
|
| +// found in the LICENSE file.
|
| +
|
| +'use strict';
|
| +
|
| +const fs = require('fs');
|
| +const path = require('path');
|
| +
|
| +const cheerio = require('cheerio');
|
| +const mkdirp = require('mkdirp');
|
| +const recast = require('recast');
|
| +const types = recast.types;
|
| +const b = recast.types.builders;
|
| +const args = require('yargs')
|
| + .wrap(Math.min(process.stdout.columns, 120))
|
| + .example('node $0 existing-test.html')
|
| + .boolean('dry-run')
|
| + .help('help')
|
| + .demand(1)
|
| + .argv;
|
| +
|
| +const utils = require('../utils');
|
| +
|
| +const FRONT_END_PATH = path.resolve(__dirname, '..', '..', 'front_end');
|
| +const LINE_BREAK = '$$SECRET_IDENTIFIER_FOR_LINE_BREAK$$();';
|
| +
|
| +function main() {
|
| + const inputPaths = args._;
|
| + const identifierMap = generateTestHelperMap();
|
| + for (const inputPath of inputPaths) {
|
| + console.log('Starting to migrate: ', inputPath);
|
| + migrateTest(inputPath, identifierMap);
|
| + console.log('Migrated: ', inputPath);
|
| + }
|
| + console.log(`Finished migrating ${inputPaths.length} tests`);
|
| +}
|
| +
|
| +main();
|
| +
|
| +function migrateTest(inputPath, identifierMap) {
|
| + const htmlTestFile = fs.readFileSync(inputPath, 'utf-8');
|
| + const $ = cheerio.load(htmlTestFile);
|
| + const inputCode = $('script:not([src])')[0].children[0].data;
|
| + const bodyText = $('body').text().trim();
|
| + const helperScripts = $('script[src]').toArray().map((n) => n.attribs.src).map(src => {
|
| + const components = src.split('/');
|
| + return components[components.length - 1].split('.')[0];
|
| + });
|
| + const testHelpers = mapTestHelpers(helperScripts);
|
| + const domFixture = $('body')
|
| + .html()
|
| + .trim()
|
| + // Tries to remove it if it has it's own line
|
| + .replace(bodyText + '\n', '')
|
| + // Tries to remove it if it's inline
|
| + .replace(bodyText, '');
|
| + const outputCode =
|
| + transformTestScript(inputCode, bodyText, identifierMap, testHelpers, getPanel(inputPath), domFixture);
|
| + if (args.dryRun) {
|
| + console.log(outputCode);
|
| + } else {
|
| + const outPath = getOutPath(inputPath);
|
| + mkdirp.sync(path.dirname(outPath));
|
| + fs.writeFileSync(outPath, outputCode);
|
| + }
|
| +}
|
| +
|
| +function transformTestScript(inputCode, bodyText, identifierMap, testHelpers, panel, domFixture) {
|
| + const ast = recast.parse(inputCode);
|
| + unwrapTestFunctionExpressionIfNecessary(ast);
|
| + unwrapTestFunctionDeclarationIfNecessary(ast);
|
| +
|
| + /**
|
| + * Create test header based on extracted data
|
| + */
|
| + const headerLines = [];
|
| + headerLines.push(createExpressionNode(`TestRunner.addResult('${bodyText}\\n');`));
|
| + headerLines.push(createExpressionNode(LINE_BREAK));
|
| + for (const helper of testHelpers) {
|
| + headerLines.push(createAwaitExpressionNode(`await TestRunner.loadModule('${helper}');`));
|
| + }
|
| + headerLines.push(createAwaitExpressionNode(`await TestRunner.loadPanel('${panel}');`));
|
| + headerLines.push(createAwaitExpressionNode(`await TestRunner.loadHTML(\`
|
| +${domFixture.split('\n').map(line => ' ' + line).join('\n')}
|
| + \`)`));
|
| + ast.program.body = headerLines.concat(ast.program.body);
|
| +
|
| + /**
|
| + * Wrap entire body in an async IIFE
|
| + */
|
| + const iife = b.functionExpression(null, [], b.blockStatement(ast.program.body));
|
| + iife.async = true;
|
| + ast.program.body = [b.expressionStatement(b.callExpression(iife, []))];
|
| +
|
| + /**
|
| + * Migrate all the call sites from InspectorTest to .*TestRunner
|
| + */
|
| + recast.visit(ast, {
|
| + visitIdentifier: function(path) {
|
| + if (path.parentPath && path.parentPath.value && path.parentPath.value.object &&
|
| + path.parentPath.value.object.name === 'InspectorTest' && path.value.name !== 'InspectorTest') {
|
| + const newParentIdentifier = identifierMap.get(path.value.name);
|
| + if (!newParentIdentifier) {
|
| + throw new Error('Could not find identifier for', path.value.name);
|
| + }
|
| + path.parentPath.value.object.name = newParentIdentifier;
|
| + }
|
| + return false;
|
| + }
|
| + });
|
| +
|
| + return print(ast);
|
| +}
|
| +
|
| +/**
|
| + * Unwrap test if it's a function expression
|
| + * var test = function () {...}
|
| + */
|
| +function unwrapTestFunctionExpressionIfNecessary(ast) {
|
| + const index =
|
| + ast.program.body.findIndex(n => n.type === 'VariableDeclaration' && n.declarations[0].id.name === 'test');
|
| + if (index > -1) {
|
| + const testFunctionNode =
|
| + ast.program.body[index];
|
| + ast.program.body.splice(index, 1);
|
| + ast.program.body = ast.program.body.concat(testFunctionNode.declarations[0].init.body.body);
|
| + }
|
| +}
|
| +
|
| +
|
| +/**
|
| + * Unwrap test if it's a function declaration
|
| + * function test () {...}
|
| + */
|
| +function unwrapTestFunctionDeclarationIfNecessary(ast) {
|
| + const index =
|
| + ast.program.body.findIndex(n => n.type === 'FunctionDeclaration' && n.id.name === 'test');
|
| + if (index > -1) {
|
| + const testFunctionNode =
|
| + ast.program.body[index];
|
| + ast.program.body.splice(index, 1);
|
| + ast.program.body = ast.program.body.concat(testFunctionNode.body.body);
|
| + }
|
| +}
|
| +
|
| +function print(ast) {
|
| + const copyrightNotice = `// Copyright 2017 The Chromium Authors. All rights reserved.
|
| +// Use of this source code is governed by a BSD-style license that can be
|
| +// found in the LICENSE file.
|
| +
|
| +`;
|
| +
|
| + /**
|
| + * Not using clang-format because certain tests look bad when formatted by it.
|
| + * Recast pretty print is smarter about preserving existing spacing.
|
| + */
|
| + const code = recast.prettyPrint(ast, {tabWidth: 2, wrapColumn: 120, quote: 'single'}).code;
|
| + return copyrightNotice + code.split(LINE_BREAK).join('') + '\n';
|
| +}
|
| +
|
| +
|
| +function getOutPath(inputPath) {
|
| + // TODO: Only works for non-http tests
|
| + const layoutTestPrefix = 'LayoutTests/inspector';
|
| + const postfix =
|
| + inputPath.slice(inputPath.indexOf(layoutTestPrefix) + layoutTestPrefix.length + 1).replace('.html', '.js');
|
| + const out =
|
| + path.resolve('.', '..', '..', '..', '..', 'LayoutTests', 'http', 'tests', 'inspector', 'devtools-js', postfix);
|
| + return out;
|
| +}
|
| +
|
| +function getPanel(inputPath) {
|
| + const panelByFolder = {
|
| + 'animation': 'elements',
|
| + 'audits': 'audits',
|
| + 'console': 'console',
|
| + 'elements': 'elements',
|
| + 'editor': 'sources',
|
| + 'layers': 'layers',
|
| + 'network': 'network',
|
| + 'profiler': 'heap_profiler',
|
| + 'resource-tree': 'resources',
|
| + 'search': 'sources',
|
| + 'security': 'security',
|
| + 'service-workers': 'resources',
|
| + 'sources': 'sources',
|
| + 'timeline': 'timeline',
|
| + 'tracing': 'timeline',
|
| + };
|
| + // Only works for non-http tests
|
| + const folder = inputPath.slice(inputPath.indexOf('LayoutTests/')).split('/')[2];
|
| + const panel = panelByFolder[folder];
|
| + if (!panel) {
|
| + throw new Error('Could not figure out which panel to map folder: ' + folder);
|
| + }
|
| + return panel;
|
| +}
|
| +
|
| +function mapTestHelpers(testHelpers) {
|
| + const SKIP = 'SKIP';
|
| + const testHelperMap = {
|
| + 'inspector-test': SKIP,
|
| + 'console-test': 'console_test_runner',
|
| + 'elements-test': 'elements_test_runner',
|
| + };
|
| + const mappedHelpers = [];
|
| + for (const helper of testHelpers) {
|
| + const mappedHelper = testHelperMap[helper];
|
| + if (!mappedHelper) {
|
| + throw Error('Could not map helper ' + helper);
|
| + }
|
| + if (mappedHelper !== SKIP) {
|
| + mappedHelpers.push(mappedHelper);
|
| + }
|
| + }
|
| + return mappedHelpers;
|
| +}
|
| +
|
| +function generateTestHelperMap() {
|
| + const map = new Map();
|
| + for (const folder of fs.readdirSync(FRONT_END_PATH)) {
|
| + if (folder.indexOf('test_runner') === -1) {
|
| + continue;
|
| + }
|
| + const testRunnerModulePath = path.resolve(FRONT_END_PATH, folder);
|
| + if (!utils.isDir(testRunnerModulePath)) {
|
| + continue;
|
| + }
|
| + for (const filename of fs.readdirSync(testRunnerModulePath)) {
|
| + if (filename === 'module.json') {
|
| + continue;
|
| + }
|
| + scrapeTestHelperIdentifiers(path.resolve(testRunnerModulePath, filename));
|
| + }
|
| + }
|
| + return map;
|
| +
|
| + function scrapeTestHelperIdentifiers(filePath) {
|
| + var content = fs.readFileSync(filePath).toString();
|
| + var lines = content.split('\n');
|
| + for (var line of lines) {
|
| + var line = line.trim();
|
| + if (line.indexOf('TestRunner.') === -1)
|
| + continue;
|
| + var match = line.match(/^(\b\w*TestRunner.[a-z_A-Z0-9]+)\s*(\=[^,}]|[;])/) ||
|
| + line.match(/^(TestRunner.[a-z_A-Z0-9]+)\s*\=$/);
|
| + if (!match)
|
| + continue;
|
| + var name = match[1];
|
| + var components = name.split('.');
|
| + if (components.length !== 2)
|
| + continue;
|
| + map.set(components[1], components[0]);
|
| + }
|
| + }
|
| +}
|
| +
|
| +/**
|
| + * Hack to quickly create an AST node
|
| + */
|
| +function createExpressionNode(code) {
|
| + return recast.parse(code).program.body[0];
|
| +}
|
| +
|
| +/**
|
| + * Hack to quickly create an AST node
|
| + */
|
| +function createAwaitExpressionNode(code) {
|
| + return recast.parse(`(async function(){${code}})`).program.body[0].expression.body.body[0];
|
| +}
|
|
|