mirror of
				https://github.com/microsoft/playwright.git
				synced 2025-06-26 21:40:17 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			205 lines
		
	
	
		
			6.4 KiB
		
	
	
	
		
			JavaScript
		
	
	
		
			Executable File
		
	
	
	
	
			
		
		
	
	
			205 lines
		
	
	
		
			6.4 KiB
		
	
	
	
		
			JavaScript
		
	
	
		
			Executable File
		
	
	
	
	
| #!/usr/bin/env node
 | |
| /**
 | |
|  * Copyright 2018 Google Inc. All rights reserved.
 | |
|  *
 | |
|  * Licensed under the Apache License, Version 2.0 (the "License");
 | |
|  * you may not use this file except in compliance with the License.
 | |
|  * You may obtain a copy of the License at
 | |
|  *
 | |
|  *     http://www.apache.org/licenses/LICENSE-2.0
 | |
|  *
 | |
|  * Unless required by applicable law or agreed to in writing, software
 | |
|  * distributed under the License is distributed on an "AS IS" BASIS,
 | |
|  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | |
|  * See the License for the specific language governing permissions and
 | |
|  * limitations under the License.
 | |
|  */
 | |
| 
 | |
| const URL = require('url');
 | |
| const debug = require('debug');
 | |
| const playwright = require('..');
 | |
| const browserFetcher = playwright.chromium._createBrowserFetcher();
 | |
| const path = require('path');
 | |
| const fs = require('fs');
 | |
| const {fork} = require('child_process');
 | |
| 
 | |
| const COLOR_RESET = '\x1b[0m';
 | |
| const COLOR_RED = '\x1b[31m';
 | |
| const COLOR_GREEN = '\x1b[32m';
 | |
| const COLOR_YELLOW = '\x1b[33m';
 | |
| 
 | |
| const argv = require('minimist')(process.argv.slice(2), {});
 | |
| 
 | |
| const help = `
 | |
| Usage:
 | |
|   node bisect.js --good <revision> --bad <revision> <script>
 | |
| 
 | |
| Parameters:
 | |
|   --good    revision that is known to be GOOD
 | |
|   --bad     revision that is known to be BAD
 | |
|   <script>  path to the script that returns non-zero code for BAD revisions and 0 for good
 | |
| 
 | |
| Example:
 | |
|   node utils/bisect.js --good 577361 --bad 599821 simple.js
 | |
| `;
 | |
| 
 | |
| if (argv.h || argv.help) {
 | |
|   console.log(help);
 | |
|   process.exit(0);
 | |
| }
 | |
| 
 | |
| if (typeof argv.good !== 'number') {
 | |
|   console.log(COLOR_RED + 'ERROR: expected --good argument to be a number' + COLOR_RESET);
 | |
|   console.log(help);
 | |
|   process.exit(1);
 | |
| }
 | |
| 
 | |
| if (typeof argv.bad !== 'number') {
 | |
|   console.log(COLOR_RED + 'ERROR: expected --bad argument to be a number' + COLOR_RESET);
 | |
|   console.log(help);
 | |
|   process.exit(1);
 | |
| }
 | |
| 
 | |
| const scriptPath = path.resolve(argv._[0]);
 | |
| if (!fs.existsSync(scriptPath)) {
 | |
|   console.log(COLOR_RED + 'ERROR: Expected to be given a path to a script to run' + COLOR_RESET);
 | |
|   console.log(help);
 | |
|   process.exit(1);
 | |
| }
 | |
| 
 | |
| (async(scriptPath, good, bad) => {
 | |
|   const span = Math.abs(good - bad);
 | |
|   console.log(`Bisecting ${COLOR_YELLOW}${span}${COLOR_RESET} revisions in ${COLOR_YELLOW}~${span.toString(2).length}${COLOR_RESET} iterations`);
 | |
| 
 | |
|   while (true) {
 | |
|     const middle = Math.round((good + bad) / 2);
 | |
|     const revision = await findDownloadableRevision(middle, good, bad);
 | |
|     if (!revision || revision === good || revision === bad)
 | |
|       break;
 | |
|     let info = browserFetcher.revisionInfo(revision);
 | |
|     const shouldRemove = !info.local;
 | |
|     info = await downloadRevision(revision);
 | |
|     const exitCode = await runScript(scriptPath, info);
 | |
|     if (shouldRemove)
 | |
|       await browserFetcher.remove(revision);
 | |
|     let outcome;
 | |
|     if (exitCode) {
 | |
|       bad = revision;
 | |
|       outcome = COLOR_RED + 'BAD' + COLOR_RESET;
 | |
|     } else {
 | |
|       good = revision;
 | |
|       outcome = COLOR_GREEN + 'GOOD' + COLOR_RESET;
 | |
|     }
 | |
|     const span = Math.abs(good - bad);
 | |
|     let fromText = '';
 | |
|     let toText = '';
 | |
|     if (good < bad) {
 | |
|       fromText = COLOR_GREEN + good + COLOR_RESET;
 | |
|       toText = COLOR_RED + bad + COLOR_RESET;
 | |
|     } else {
 | |
|       fromText = COLOR_RED + bad + COLOR_RESET;
 | |
|       toText = COLOR_GREEN + good + COLOR_RESET;
 | |
|     }
 | |
|     console.log(`- ${COLOR_YELLOW}r${revision}${COLOR_RESET} was ${outcome}. Bisecting [${fromText}, ${toText}] - ${COLOR_YELLOW}${span}${COLOR_RESET} revisions and ${COLOR_YELLOW}~${span.toString(2).length}${COLOR_RESET} iterations`);
 | |
|   }
 | |
| 
 | |
|   const [fromSha, toSha] = await Promise.all([
 | |
|     revisionToSha(Math.min(good, bad)),
 | |
|     revisionToSha(Math.max(good, bad)),
 | |
|   ]);
 | |
|   console.log(`RANGE: https://chromium.googlesource.com/chromium/src/+log/${fromSha}..${toSha}`);
 | |
| })(scriptPath, argv.good, argv.bad);
 | |
| 
 | |
| function runScript(scriptPath, revisionInfo) {
 | |
|   const log = debug('bisect:runscript');
 | |
|   log('Running script');
 | |
|   const child = fork(scriptPath, [], {
 | |
|     stdio: ['inherit', 'inherit', 'inherit', 'ipc'],
 | |
|     env: {
 | |
|       ...process.env,
 | |
|       PLAYWRIGHT_EXECUTABLE_PATH: revisionInfo.executablePath,
 | |
|     },
 | |
|   });
 | |
|   return new Promise((resolve, reject) => {
 | |
|     child.on('error', err => reject(err));
 | |
|     child.on('exit', code => resolve(code));
 | |
|   });
 | |
| }
 | |
| 
 | |
| async function downloadRevision(revision) {
 | |
|   const log = debug('bisect:download');
 | |
|   log(`Downloading ${revision}`);
 | |
|   let progressBar = null;
 | |
|   let lastDownloadedBytes = 0;
 | |
|   return await browserFetcher.download(revision, (downloadedBytes, totalBytes) => {
 | |
|     if (!progressBar) {
 | |
|       const ProgressBar = require('progress');
 | |
|       progressBar = new ProgressBar(`- downloading Chromium r${revision} - ${toMegabytes(totalBytes)} [:bar] :percent :etas `, {
 | |
|         complete: '=',
 | |
|         incomplete: ' ',
 | |
|         width: 20,
 | |
|         total: totalBytes,
 | |
|       });
 | |
|     }
 | |
|     const delta = downloadedBytes - lastDownloadedBytes;
 | |
|     lastDownloadedBytes = downloadedBytes;
 | |
|     progressBar.tick(delta);
 | |
|   });
 | |
|   function toMegabytes(bytes) {
 | |
|     const mb = bytes / 1024 / 1024;
 | |
|     return `${Math.round(mb * 10) / 10} Mb`;
 | |
|   }
 | |
| }
 | |
| 
 | |
| async function findDownloadableRevision(rev, from, to) {
 | |
|   const log = debug('bisect:findrev');
 | |
|   const min = Math.min(from, to);
 | |
|   const max = Math.max(from, to);
 | |
|   log(`Looking around ${rev} from [${min}, ${max}]`);
 | |
|   if (await browserFetcher.canDownload(rev))
 | |
|     return rev;
 | |
|   let down = rev;
 | |
|   let up = rev;
 | |
|   while (min <= down || up <= max) {
 | |
|     const [downOk, upOk] = await Promise.all([
 | |
|       down > min ? probe(--down) : Promise.resolve(false),
 | |
|       up < max ? probe(++up) : Promise.resolve(false),
 | |
|     ]);
 | |
|     if (downOk)
 | |
|       return down;
 | |
|     if (upOk)
 | |
|       return up;
 | |
|   }
 | |
|   return null;
 | |
| 
 | |
|   async function probe(rev) {
 | |
|     const result = await browserFetcher.canDownload(rev);
 | |
|     log(`  ${rev} - ${result ? 'OK' : 'missing'}`);
 | |
|     return result;
 | |
|   }
 | |
| }
 | |
| 
 | |
| async function revisionToSha(revision) {
 | |
|   const json = await fetchJSON('https://cr-rev.appspot.com/_ah/api/crrev/v1/redirect/' + revision);
 | |
|   return json.git_sha;
 | |
| }
 | |
| 
 | |
| function fetchJSON(url) {
 | |
|   return new Promise((resolve, reject) => {
 | |
|     const agent = url.startsWith('https://') ? require('https') : require('http');
 | |
|     const options = URL.parse(url);
 | |
|     options.method = 'GET';
 | |
|     options.headers = {
 | |
|       'Content-Type': 'application/json'
 | |
|     };
 | |
|     const req = agent.request(options, function(res) {
 | |
|       let result = '';
 | |
|       res.setEncoding('utf8');
 | |
|       res.on('data', chunk => result += chunk);
 | |
|       res.on('end', () => resolve(JSON.parse(result)));
 | |
|     });
 | |
|     req.on('error', err => reject(err));
 | |
|     req.end();
 | |
|   });
 | |
| }
 | 
