tor-browser

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

server.ts (9205B)


      1 /* eslint-disable no-console, n/no-restricted-import */
      2 
      3 import * as fs from 'fs';
      4 import * as http from 'http';
      5 import { AddressInfo } from 'net';
      6 
      7 import { dataCache } from '../framework/data_cache.js';
      8 import { getResourcePath, setBaseResourcePath } from '../framework/resources.js';
      9 import { globalTestConfig } from '../framework/test_config.js';
     10 import { DefaultTestFileLoader } from '../internal/file_loader.js';
     11 import { prettyPrintLog } from '../internal/logging/log_message.js';
     12 import { Logger } from '../internal/logging/logger.js';
     13 import { LiveTestCaseResult, Status } from '../internal/logging/result.js';
     14 import { parseQuery } from '../internal/query/parseQuery.js';
     15 import { TestQueryWithExpectation } from '../internal/query/query.js';
     16 import { TestTreeLeaf } from '../internal/tree.js';
     17 import { Colors } from '../util/colors.js';
     18 import { setDefaultRequestAdapterOptions, setGPUProvider } from '../util/navigator_gpu.js';
     19 
     20 import sys from './helper/sys.js';
     21 
     22 function usage(rc: number): never {
     23  console.log(`Usage:
     24  tools/server [OPTIONS...]
     25 Options:
     26  --colors                  Enable ANSI colors in output.
     27  --compat                  Run tests in compatibility mode.
     28  --coverage                Add coverage data to each result.
     29  --verbose                 Print result/log of every test as it runs.
     30  --debug                   Include debug messages in logging.
     31  --gpu-provider            Path to node module that provides the GPU implementation.
     32  --gpu-provider-flag       Flag to set on the gpu-provider as <flag>=<value>
     33  --unroll-const-eval-loops Unrolls loops in constant-evaluation shader execution tests
     34  --enforce-default-limits  Enforce the default limits (note: powerPreference tests may fail)
     35  --force-fallback-adapter  Force a fallback adapter
     36  --log-to-websocket        Log to a websocket
     37  --u                       Flag to set on the gpu-provider as <flag>=<value>
     38 
     39 Provides an HTTP server used for running tests via an HTTP RPC interface
     40 First, load some tree or subtree of tests:
     41  http://localhost:port/load?unittests:basic:*
     42 To run a single test case, perform an HTTP GET or POST at the URL:
     43  http://localhost:port/run?unittests:basic:test,sync
     44 To shutdown the server perform an HTTP GET or POST at the URL:
     45  http://localhost:port/terminate
     46 `);
     47  return sys.exit(rc);
     48 }
     49 
     50 interface RunResult {
     51  // The result of the test
     52  status: Status;
     53  // Any additional messages printed
     54  message: string;
     55  // The time it took to execute the test
     56  durationMS: number;
     57  // Code coverage data, if the server was started with `--coverage`
     58  // This data is opaque (implementation defined).
     59  coverageData?: string;
     60 }
     61 
     62 // The interface that exposes creation of the GPU, and optional interface to code coverage.
     63 interface GPUProviderModule {
     64  // @returns a GPU with the given flags
     65  create(flags: string[]): GPU;
     66  // An optional interface to a CodeCoverageProvider
     67  coverage?: CodeCoverageProvider;
     68 }
     69 
     70 interface CodeCoverageProvider {
     71  // Starts collecting code coverage
     72  begin(): void;
     73  // Ends collecting of code coverage, returning the coverage data.
     74  // This data is opaque (implementation defined).
     75  end(): string;
     76 }
     77 
     78 if (!sys.existsSync('src/common/runtime/cmdline.ts')) {
     79  console.log('Must be run from repository root');
     80  usage(1);
     81 }
     82 setBaseResourcePath('out-node/resources');
     83 
     84 Colors.enabled = false;
     85 
     86 let emitCoverage = false;
     87 let verbose = false;
     88 let gpuProviderModule: GPUProviderModule | undefined = undefined;
     89 
     90 const gpuProviderFlags: string[] = [];
     91 for (let i = 0; i < sys.args.length; ++i) {
     92  const a = sys.args[i];
     93  if (a.startsWith('-')) {
     94    if (a === '--colors') {
     95      Colors.enabled = true;
     96    } else if (a === '--compat') {
     97      globalTestConfig.compatibility = true;
     98    } else if (a === '--coverage') {
     99      emitCoverage = true;
    100    } else if (a === '--force-fallback-adapter') {
    101      globalTestConfig.forceFallbackAdapter = true;
    102    } else if (a === '--enforce-default-limits') {
    103      globalTestConfig.enforceDefaultLimits = true;
    104    } else if (a === '--block-all-features') {
    105      globalTestConfig.blockAllFeatures = true;
    106    } else if (a === '--subcases-between-attempting-gc') {
    107      globalTestConfig.subcasesBetweenAttemptingGC = Number(sys.args[++i]);
    108    } else if (a === '--cases-between-replacing-device') {
    109      globalTestConfig.casesBetweenReplacingDevice = Number(sys.args[++i]);
    110    } else if (a === '--log-to-websocket') {
    111      globalTestConfig.logToWebSocket = true;
    112    } else if (a === '--gpu-provider') {
    113      const modulePath = sys.args[++i];
    114      gpuProviderModule = require(modulePath);
    115    } else if (a === '--gpu-provider-flag') {
    116      gpuProviderFlags.push(sys.args[++i]);
    117    } else if (a === '--debug') {
    118      globalTestConfig.enableDebugLogs = true;
    119    } else if (a === '--unroll-const-eval-loops') {
    120      globalTestConfig.unrollConstEvalLoops = true;
    121    } else if (a === '--help') {
    122      usage(1);
    123    } else if (a === '--verbose') {
    124      verbose = true;
    125    } else {
    126      console.log(`unrecognized flag: ${a}`);
    127    }
    128  }
    129 }
    130 
    131 let codeCoverage: CodeCoverageProvider | undefined = undefined;
    132 
    133 if (globalTestConfig.compatibility || globalTestConfig.forceFallbackAdapter) {
    134  setDefaultRequestAdapterOptions({
    135    featureLevel: globalTestConfig.compatibility ? 'compatibility' : 'core',
    136    forceFallbackAdapter: globalTestConfig.forceFallbackAdapter,
    137  });
    138 }
    139 
    140 if (gpuProviderModule) {
    141  setGPUProvider(() => gpuProviderModule!.create(gpuProviderFlags));
    142 
    143  if (emitCoverage) {
    144    codeCoverage = gpuProviderModule.coverage;
    145    if (codeCoverage === undefined) {
    146      console.error(
    147        `--coverage specified, but the GPUProviderModule does not support code coverage.
    148 Did you remember to build with code coverage instrumentation enabled?`
    149      );
    150      sys.exit(1);
    151    }
    152  }
    153 }
    154 
    155 dataCache.setStore({
    156  load: (path: string) => {
    157    return new Promise<Uint8Array>((resolve, reject) => {
    158      fs.readFile(getResourcePath(`cache/${path}`), (err, data) => {
    159        if (err !== null) {
    160          reject(err.message);
    161        } else {
    162          resolve(data);
    163        }
    164      });
    165    });
    166  },
    167 });
    168 
    169 if (verbose) {
    170  dataCache.setDebugLogger(console.log);
    171 }
    172 
    173 // eslint-disable-next-line @typescript-eslint/require-await
    174 (async () => {
    175  const log = new Logger();
    176  const testcases = new Map<string, TestTreeLeaf>();
    177 
    178  async function runTestcase(
    179    testcase: TestTreeLeaf,
    180    expectations: TestQueryWithExpectation[] = []
    181  ): Promise<LiveTestCaseResult> {
    182    const name = testcase.query.toString();
    183    const [rec, res] = log.record(name);
    184    await testcase.run(rec, expectations);
    185    return res;
    186  }
    187 
    188  const server = http.createServer(
    189    async (request: http.IncomingMessage, response: http.ServerResponse) => {
    190      if (request.url === undefined) {
    191        response.end('invalid url');
    192        return;
    193      }
    194 
    195      const loadCasesPrefix = '/load?';
    196      const runPrefix = '/run?';
    197      const terminatePrefix = '/terminate';
    198 
    199      if (request.url.startsWith(loadCasesPrefix)) {
    200        const query = request.url.substr(loadCasesPrefix.length);
    201        try {
    202          const webgpuQuery = parseQuery(query);
    203          const loader = new DefaultTestFileLoader();
    204          for (const testcase of await loader.loadCases(webgpuQuery)) {
    205            testcases.set(testcase.query.toString(), testcase);
    206          }
    207          response.statusCode = 200;
    208          response.end();
    209        } catch (err) {
    210          response.statusCode = 500;
    211          response.end(`load failed with error: ${err}\n${(err as Error).stack}`);
    212        }
    213      } else if (request.url.startsWith(runPrefix)) {
    214        const name = request.url.substr(runPrefix.length);
    215        try {
    216          const testcase = testcases.get(name);
    217          if (testcase) {
    218            if (codeCoverage !== undefined) {
    219              codeCoverage.begin();
    220            }
    221            const start = performance.now();
    222            const result = await runTestcase(testcase);
    223            const durationMS = performance.now() - start;
    224            const coverageData = codeCoverage !== undefined ? codeCoverage.end() : undefined;
    225            let message = '';
    226            if (result.logs !== undefined) {
    227              message = result.logs.map(log => prettyPrintLog(log)).join('\n');
    228            }
    229            const status = result.status;
    230            const res: RunResult = { status, message, durationMS, coverageData };
    231            response.statusCode = 200;
    232            response.end(JSON.stringify(res));
    233          } else {
    234            response.statusCode = 404;
    235            response.end(`test case '${name}' not found`);
    236          }
    237        } catch (err) {
    238          response.statusCode = 500;
    239          response.end(`run failed with error: ${err}`);
    240        }
    241      } else if (request.url.startsWith(terminatePrefix)) {
    242        server.close();
    243        sys.exit(1);
    244      } else {
    245        response.statusCode = 404;
    246        response.end('unhandled url request');
    247      }
    248    }
    249  );
    250 
    251  server.listen(0, () => {
    252    const address = server.address() as AddressInfo;
    253    console.log(`Server listening at [[${address.port}]]`);
    254  });
    255 })().catch(ex => {
    256  console.error(ex.stack ?? ex.toString());
    257  sys.exit(1);
    258 });