tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

utils.ts (8121B)


      1 /**
      2 * @license
      3 * Copyright 2022 Google Inc.
      4 * SPDX-License-Identifier: Apache-2.0
      5 */
      6 
      7 import fs from 'node:fs';
      8 import path from 'node:path';
      9 
     10 import type {
     11  MochaTestResult,
     12  TestExpectation,
     13  MochaResults,
     14  TestResult,
     15 } from './types.js';
     16 
     17 export function extendProcessEnv(envs: object[]): NodeJS.ProcessEnv {
     18  const env = envs.reduce(
     19    (acc: object, item: object) => {
     20      Object.assign(acc, item);
     21      return acc;
     22    },
     23    {
     24      ...process.env,
     25    },
     26  );
     27 
     28  if (process.env['CI']) {
     29    const puppeteerEnv = Object.entries(env).reduce(
     30      (acc, [key, value]) => {
     31        if (key.startsWith('PUPPETEER_')) {
     32          acc[key] = value;
     33        }
     34 
     35        return acc;
     36      },
     37      {} as Record<string, unknown>,
     38    );
     39 
     40    console.log(
     41      'PUPPETEER env:\n',
     42      JSON.stringify(puppeteerEnv, null, 2),
     43      '\n',
     44    );
     45  }
     46 
     47  return env as NodeJS.ProcessEnv;
     48 }
     49 
     50 export function getFilename(file: string): string {
     51  return path.basename(file).replace(path.extname(file), '');
     52 }
     53 
     54 export function readJSON(path: string): unknown {
     55  return JSON.parse(fs.readFileSync(path, 'utf-8'));
     56 }
     57 
     58 export function writeJSON(path: string, json: unknown): unknown {
     59  return fs.writeFileSync(path, JSON.stringify(json, null, 2));
     60 }
     61 
     62 export function filterByPlatform<T extends {platforms: NodeJS.Platform[]}>(
     63  items: T[],
     64  platform: NodeJS.Platform,
     65 ): T[] {
     66  return items.filter(item => {
     67    return item.platforms.includes(platform);
     68  });
     69 }
     70 
     71 export function prettyPrintJSON(json: unknown): void {
     72  console.log(JSON.stringify(json, null, 2));
     73 }
     74 
     75 export function getSuggestionsForAction(
     76  recommendations: RecommendedExpectation[],
     77  action: RecommendedExpectation['action'],
     78 ): RecommendedExpectation[] {
     79  return recommendations.filter(item => {
     80    return item.action === action;
     81  });
     82 }
     83 
     84 export function printSuggestions(
     85  recommendations: RecommendedExpectation[],
     86  message: string,
     87  printBasedOn = false,
     88 ): void {
     89  if (recommendations.length) {
     90    console.log(message);
     91    prettyPrintJSON(
     92      recommendations.map(item => {
     93        return item.expectation;
     94      }),
     95    );
     96    if (printBasedOn) {
     97      console.log(
     98        'The recommendations are based on the following applied expectations:',
     99      );
    100      prettyPrintJSON(
    101        recommendations.map(item => {
    102          return item.basedOn;
    103        }),
    104      );
    105    }
    106  }
    107 }
    108 
    109 export function filterByParameters(
    110  expectations: TestExpectation[],
    111  parameters: string[],
    112 ): TestExpectation[] {
    113  const querySet = new Set(parameters);
    114  return expectations.filter(ex => {
    115    return ex.parameters.every(param => {
    116      return querySet.has(param);
    117    });
    118  });
    119 }
    120 
    121 /**
    122 * The last expectation that matches an empty string as all tests pattern
    123 * or the name of the file or the whole name of the test the filter wins.
    124 */
    125 export function findEffectiveExpectationForTest(
    126  expectations: TestExpectation[],
    127  result: MochaTestResult,
    128 ): TestExpectation | undefined {
    129  return expectations.find(expectation => {
    130    return testIdMatchesExpectationPattern(result, expectation.testIdPattern);
    131  });
    132 }
    133 
    134 export interface RecommendedExpectation {
    135  expectation: TestExpectation;
    136  action: 'remove' | 'add' | 'update';
    137  basedOn?: TestExpectation;
    138 }
    139 
    140 export function isWildCardPattern(testIdPattern: string): boolean {
    141  return testIdPattern.includes('*');
    142 }
    143 
    144 export function getExpectationUpdates(
    145  results: MochaResults,
    146  expectations: TestExpectation[],
    147  context: {
    148    platforms: NodeJS.Platform[];
    149    parameters: string[];
    150  },
    151  skipPassing: boolean,
    152 ): RecommendedExpectation[] {
    153  const output = new Map<string, RecommendedExpectation>();
    154 
    155  const passesByKey = results.passes.reduce((acc, pass) => {
    156    acc.add(getTestId(pass.file, pass.fullTitle));
    157    return acc;
    158  }, new Set());
    159 
    160  for (const pass of results.passes) {
    161    if (skipPassing) {
    162      continue;
    163    }
    164 
    165    const expectationEntry = findEffectiveExpectationForTest(
    166      expectations,
    167      pass,
    168    );
    169    if (expectationEntry && !expectationEntry.expectations.includes('PASS')) {
    170      if (isWildCardPattern(expectationEntry.testIdPattern)) {
    171        addEntry({
    172          expectation: {
    173            testIdPattern: getTestId(pass.file, pass.fullTitle),
    174            platforms: context.platforms,
    175            parameters: context.parameters,
    176            expectations: ['PASS'],
    177          },
    178          action: 'add',
    179          basedOn: expectationEntry,
    180        });
    181      } else {
    182        addEntry({
    183          expectation: expectationEntry,
    184          action: 'remove',
    185          basedOn: expectationEntry,
    186        });
    187      }
    188    }
    189  }
    190 
    191  for (const failure of results.failures) {
    192    // If an error occurs during a hook
    193    // the error not have a file associated with it
    194    if (!failure.file) {
    195      console.error('Hook failed:', failure.err);
    196      addEntry({
    197        expectation: {
    198          testIdPattern: failure.fullTitle,
    199          platforms: context.platforms,
    200          parameters: context.parameters,
    201          expectations: [],
    202        },
    203        action: 'add',
    204      });
    205      continue;
    206    }
    207 
    208    if (passesByKey.has(getTestId(failure.file, failure.fullTitle))) {
    209      continue;
    210    }
    211 
    212    const expectationEntry = findEffectiveExpectationForTest(
    213      expectations,
    214      failure,
    215    );
    216    if (expectationEntry && !expectationEntry.expectations.includes('SKIP')) {
    217      if (
    218        !expectationEntry.expectations.includes(
    219          getTestResultForFailure(failure),
    220        )
    221      ) {
    222        // If the effective explanation is a wildcard, we recommend adding a new
    223        // expectation instead of updating the wildcard that might affect multiple
    224        // tests.
    225        if (isWildCardPattern(expectationEntry.testIdPattern)) {
    226          addEntry({
    227            expectation: {
    228              testIdPattern: getTestId(failure.file, failure.fullTitle),
    229              platforms: context.platforms,
    230              parameters: context.parameters,
    231              expectations: [getTestResultForFailure(failure)],
    232            },
    233            action: 'add',
    234            basedOn: expectationEntry,
    235          });
    236        } else {
    237          addEntry({
    238            expectation: {
    239              ...expectationEntry,
    240              expectations: [
    241                ...expectationEntry.expectations,
    242                getTestResultForFailure(failure),
    243              ],
    244            },
    245            action: 'update',
    246            basedOn: expectationEntry,
    247          });
    248        }
    249      }
    250    } else if (!expectationEntry) {
    251      addEntry({
    252        expectation: {
    253          testIdPattern: getTestId(failure.file, failure.fullTitle),
    254          platforms: context.platforms,
    255          parameters: context.parameters,
    256          expectations: [getTestResultForFailure(failure)],
    257        },
    258        action: 'add',
    259      });
    260    }
    261  }
    262 
    263  function addEntry(value: RecommendedExpectation) {
    264    const key = JSON.stringify(value);
    265    if (!output.has(key)) {
    266      output.set(key, value);
    267    }
    268  }
    269 
    270  return [...output.values()];
    271 }
    272 
    273 export function getTestResultForFailure(
    274  test: Pick<MochaTestResult, 'err'>,
    275 ): TestResult {
    276  return test.err?.code === 'ERR_MOCHA_TIMEOUT' ? 'TIMEOUT' : 'FAIL';
    277 }
    278 
    279 export function getTestId(file: string, fullTitle?: string): string {
    280  return fullTitle
    281    ? `[${getFilename(file)}] ${fullTitle}`
    282    : `[${getFilename(file)}]`;
    283 }
    284 
    285 export function testIdMatchesExpectationPattern(
    286  test: MochaTestResult | Pick<Mocha.Test, 'title' | 'file' | 'fullTitle'>,
    287  pattern: string,
    288 ): boolean {
    289  const patternRegExString = pattern
    290    // Replace `*` with non special character
    291    .replace(/\*/g, '--STAR--')
    292    // Escape special characters https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions#escaping
    293    .replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
    294    // Replace placeholder with greedy match
    295    .replace(/--STAR--/g, '(.*)?');
    296  // Match beginning and end explicitly
    297  const patternRegEx = new RegExp(`^${patternRegExString}$`);
    298  const fullTitle =
    299    typeof test.fullTitle === 'string' ? test.fullTitle : test.fullTitle();
    300 
    301  return patternRegEx.test(getTestId(test.file ?? '', fullTitle));
    302 }