Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(26)

Side by Side Diff: third_party/WebKit/Source/devtools/scripts/migrate_test/migrate_test.js

Issue 2976833002: Script migration (Closed)
Patch Set: transformed Created 3 years, 5 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
(Empty)
1 // Copyright 2017 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 'use strict';
6
7 const fs = require('fs');
8 const path = require('path');
9
10 const cheerio = require('cheerio');
11 const mkdirp = require('mkdirp');
12 const recast = require('recast');
13 const types = recast.types;
14 const b = recast.types.builders;
15 const args = require('yargs')
16 .wrap(Math.min(process.stdout.columns, 120))
17 .example('node $0 existing-test.html')
18 .boolean('dry-run')
19 .help('help')
20 .demand(1)
21 .argv;
22
23 const utils = require('../utils');
24
25 const FRONT_END_PATH = path.resolve(__dirname, '..', '..', 'front_end');
26 const LINE_BREAK = '$$SECRET_IDENTIFIER_FOR_LINE_BREAK$$();';
27
28 function main() {
29 const inputPaths = args._;
30 const identifierMap = generateTestHelperMap();
31 for (const inputPath of inputPaths) {
32 console.log('Starting to migrate: ', inputPath);
33 migrateTest(inputPath, identifierMap);
34 console.log('Migrated: ', inputPath);
35 }
36 console.log(`Finished migrating ${inputPaths.length} tests`);
37 }
38
39 main();
40
41 function migrateTest(inputPath, identifierMap) {
42 const htmlTestFile = fs.readFileSync(inputPath, 'utf-8');
43 const $ = cheerio.load(htmlTestFile);
44 const inputCode = $('script:not([src])')[0].children[0].data;
45 const bodyText = $('body').text().trim();
46 const helperScripts = $('script[src]').toArray().map((n) => n.attribs.src).map (src => {
47 const components = src.split('/');
48 return components[components.length - 1].split('.')[0];
49 });
50 const testHelpers = mapTestHelpers(helperScripts);
51 const domFixture = $('body')
52 .html()
53 .trim()
54 // Tries to remove it if it has it's own line
55 .replace(bodyText + '\n', '')
56 // Tries to remove it if it's inline
57 .replace(bodyText, '');
58 const outputCode =
59 transformTestScript(inputCode, bodyText, identifierMap, testHelpers, getPa nel(inputPath), domFixture);
60 if (args.dryRun) {
61 console.log(outputCode);
62 } else {
63 const outPath = getOutPath(inputPath);
64 mkdirp.sync(path.dirname(outPath));
65 fs.writeFileSync(outPath, outputCode);
66 }
67 }
68
69 function transformTestScript(inputCode, bodyText, identifierMap, testHelpers, pa nel, domFixture) {
70 const ast = recast.parse(inputCode);
71 unwrapTestFunctionExpressionIfNecessary(ast);
72 unwrapTestFunctionDeclarationIfNecessary(ast);
73
74 /**
75 * Create test header based on extracted data
76 */
77 const headerLines = [];
78 headerLines.push(createExpressionNode(`TestRunner.addResult('${bodyText}\\n'); `));
79 headerLines.push(createExpressionNode(LINE_BREAK));
80 for (const helper of testHelpers) {
81 headerLines.push(createAwaitExpressionNode(`await TestRunner.loadModule('${h elper}');`));
82 }
83 headerLines.push(createAwaitExpressionNode(`await TestRunner.loadPanel('${pane l}');`));
84 headerLines.push(createAwaitExpressionNode(`await TestRunner.loadHTML(\`
85 ${domFixture.split('\n').map(line => ' ' + line).join('\n')}
86 \`)`));
87 ast.program.body = headerLines.concat(ast.program.body);
88
89 /**
90 * Wrap entire body in an async IIFE
91 */
92 const iife = b.functionExpression(null, [], b.blockStatement(ast.program.body) );
93 iife.async = true;
94 ast.program.body = [b.expressionStatement(b.callExpression(iife, []))];
95
96 /**
97 * Migrate all the call sites from InspectorTest to .*TestRunner
98 */
99 recast.visit(ast, {
100 visitIdentifier: function(path) {
101 if (path.parentPath && path.parentPath.value && path.parentPath.value.obje ct &&
102 path.parentPath.value.object.name === 'InspectorTest' && path.value.na me !== 'InspectorTest') {
103 const newParentIdentifier = identifierMap.get(path.value.name);
104 if (!newParentIdentifier) {
105 throw new Error('Could not find identifier for', path.value.name);
106 }
107 path.parentPath.value.object.name = newParentIdentifier;
108 }
109 return false;
110 }
111 });
112
113 return print(ast);
114 }
115
116 /**
117 * Unwrap test if it's a function expression
118 * var test = function () {...}
119 */
120 function unwrapTestFunctionExpressionIfNecessary(ast) {
121 const index =
122 ast.program.body.findIndex(n => n.type === 'VariableDeclaration' && n.decl arations[0].id.name === 'test');
123 if (index > -1) {
124 const testFunctionNode =
125 ast.program.body[index];
126 ast.program.body.splice(index, 1);
127 ast.program.body = ast.program.body.concat(testFunctionNode.declarations[0]. init.body.body);
128 }
129 }
130
131
132 /**
133 * Unwrap test if it's a function declaration
134 * function test () {...}
135 */
136 function unwrapTestFunctionDeclarationIfNecessary(ast) {
137 const index =
138 ast.program.body.findIndex(n => n.type === 'FunctionDeclaration' && n.id.n ame === 'test');
139 if (index > -1) {
140 const testFunctionNode =
141 ast.program.body[index];
142 ast.program.body.splice(index, 1);
143 ast.program.body = ast.program.body.concat(testFunctionNode.body.body);
144 }
145 }
146
147 function print(ast) {
148 const copyrightNotice = `// Copyright 2017 The Chromium Authors. All rights re served.
149 // Use of this source code is governed by a BSD-style license that can be
150 // found in the LICENSE file.
151
152 `;
153
154 /**
155 * Not using clang-format because certain tests look bad when formatted by it.
156 * Recast pretty print is smarter about preserving existing spacing.
157 */
158 const code = recast.prettyPrint(ast, {tabWidth: 2, wrapColumn: 120, quote: 'si ngle'}).code;
159 return copyrightNotice + code.split(LINE_BREAK).join('') + '\n';
160 }
161
162
163 function getOutPath(inputPath) {
164 // TODO: Only works for non-http tests
165 const layoutTestPrefix = 'LayoutTests/inspector';
166 const postfix =
167 inputPath.slice(inputPath.indexOf(layoutTestPrefix) + layoutTestPrefix.len gth + 1).replace('.html', '.js');
168 const out =
169 path.resolve('.', '..', '..', '..', '..', 'LayoutTests', 'http', 'tests', 'inspector', 'devtools-js', postfix);
170 return out;
171 }
172
173 function getPanel(inputPath) {
174 const panelByFolder = {
175 'animation': 'elements',
176 'audits': 'audits',
177 'console': 'console',
178 'elements': 'elements',
179 'editor': 'sources',
180 'layers': 'layers',
181 'network': 'network',
182 'profiler': 'heap_profiler',
183 'resource-tree': 'resources',
184 'search': 'sources',
185 'security': 'security',
186 'service-workers': 'resources',
187 'sources': 'sources',
188 'timeline': 'timeline',
189 'tracing': 'timeline',
190 };
191 // Only works for non-http tests
192 const folder = inputPath.slice(inputPath.indexOf('LayoutTests/')).split('/')[2 ];
193 const panel = panelByFolder[folder];
194 if (!panel) {
195 throw new Error('Could not figure out which panel to map folder: ' + folder) ;
196 }
197 return panel;
198 }
199
200 function mapTestHelpers(testHelpers) {
201 const SKIP = 'SKIP';
202 const testHelperMap = {
203 'inspector-test': SKIP,
204 'console-test': 'console_test_runner',
205 'elements-test': 'elements_test_runner',
206 };
207 const mappedHelpers = [];
208 for (const helper of testHelpers) {
209 const mappedHelper = testHelperMap[helper];
210 if (!mappedHelper) {
211 throw Error('Could not map helper ' + helper);
212 }
213 if (mappedHelper !== SKIP) {
214 mappedHelpers.push(mappedHelper);
215 }
216 }
217 return mappedHelpers;
218 }
219
220 function generateTestHelperMap() {
221 const map = new Map();
222 for (const folder of fs.readdirSync(FRONT_END_PATH)) {
223 if (folder.indexOf('test_runner') === -1) {
224 continue;
225 }
226 const testRunnerModulePath = path.resolve(FRONT_END_PATH, folder);
227 if (!utils.isDir(testRunnerModulePath)) {
228 continue;
229 }
230 for (const filename of fs.readdirSync(testRunnerModulePath)) {
231 if (filename === 'module.json') {
232 continue;
233 }
234 scrapeTestHelperIdentifiers(path.resolve(testRunnerModulePath, filename));
235 }
236 }
237 return map;
238
239 function scrapeTestHelperIdentifiers(filePath) {
240 var content = fs.readFileSync(filePath).toString();
241 var lines = content.split('\n');
242 for (var line of lines) {
243 var line = line.trim();
244 if (line.indexOf('TestRunner.') === -1)
245 continue;
246 var match = line.match(/^(\b\w*TestRunner.[a-z_A-Z0-9]+)\s*(\=[^,}]|[;])/) ||
247 line.match(/^(TestRunner.[a-z_A-Z0-9]+)\s*\=$/);
248 if (!match)
249 continue;
250 var name = match[1];
251 var components = name.split('.');
252 if (components.length !== 2)
253 continue;
254 map.set(components[1], components[0]);
255 }
256 }
257 }
258
259 /**
260 * Hack to quickly create an AST node
261 */
262 function createExpressionNode(code) {
263 return recast.parse(code).program.body[0];
264 }
265
266 /**
267 * Hack to quickly create an AST node
268 */
269 function createAwaitExpressionNode(code) {
270 return recast.parse(`(async function(){${code}})`).program.body[0].expression. body.body[0];
271 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698