tor-browser

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

test_l10nregistry_fuzzed.js (5446B)


      1 /* Any copyright is dedicated to the Public Domain.
      2   http://creativecommons.org/publicdomain/zero/1.0/ */
      3 
      4 /**
      5 * This test is a fuzzing test for the L10nRegistry API. It was written to find
      6 * a hard to reproduce bug in the L10nRegistry code. If it fails, place the seed
      7 * from the failing run in the code directly below to make it consistently reproducible.
      8 */
      9 let seed = Math.floor(Math.random() * 1e9);
     10 
     11 console.log(`Starting a fuzzing run with seed: ${seed}.`);
     12 console.log("To reproduce this test locally, re-run it locally with:");
     13 console.log(`let seed = ${seed};`);
     14 
     15 /**
     16 * A simple non-robust psuedo-random number generator.
     17 *
     18 * It is implemented using a Lehmer random number generator.
     19 * https://en.wikipedia.org/wiki/16,807
     20 *
     21 * @returns {number} Ranged [0, 1)
     22 */
     23 function prng() {
     24  const multiplier = 16807;
     25  const prime = 2147483647;
     26  seed = seed * multiplier % prime
     27  return (seed - 1) / prime
     28 }
     29 
     30 /**
     31 * Generate a name like "mock-dmsxfodrqboljmxdeayt".
     32 * @returns {string}
     33 */
     34 function generateRandomName() {
     35  let name = 'mock-'
     36  const letters = "abcdefghijklmnopqrstuvwxyz";
     37  for (let i = 0; i < 20; i++) {
     38    name += letters[Math.floor(prng() * letters.length)];
     39  }
     40  return name;
     41 }
     42 
     43 /**
     44 * Picks one item from an array.
     45 *
     46 * @param {Array<T>}
     47 * @returns {T}
     48 */
     49 function pickOne(list) {
     50  return list[Math.floor(prng() * list.length)]
     51 }
     52 
     53 /**
     54 * Picks a random subset from an array.
     55 *
     56 * @param {Array<T>}
     57 * @returns {Array<T>}
     58 */
     59 function pickN(list, count) {
     60  list = list.slice();
     61  const result = [];
     62  for (let i = 0; i < count && i < list.length; i++) {
     63    // Pick a random item.
     64    const index = Math.floor(prng() * list.length);
     65 
     66    // Swap item to the end.
     67    const a = list[index];
     68    const b = list[list.length - 1];
     69    list[index] = b;
     70    list[list.length - 1] = a
     71 
     72    // Now that the random item is on the end, pop it off and add it to the results.
     73    result.push(list.pop());
     74  }
     75 
     76  return result
     77 }
     78 
     79 /**
     80 * Generate a random number
     81 * @param {number} min
     82 * @param {number} max
     83 * @returns {number}
     84 */
     85 function random(min, max) {
     86  const delta = max - min;
     87  return min + delta * prng();
     88 }
     89 
     90 /**
     91 * Generate a random number generator with a distribution more towards the lower end.
     92 * @param {number} min
     93 * @param {number} max
     94 * @returns {number}
     95 */
     96 function randomPow(min, max) {
     97  const delta = max - min;
     98  const r = prng()
     99  return min + delta * r * r;
    100 }
    101 
    102 add_task(async function test_fuzzing_sources() {
    103    const iterations = 100;
    104    const maxSources = 10;
    105  
    106    const metasources = ["app", "langpack", ""];
    107    const availableLocales = ["en", "en-US", "pl", "en-CA", "es-AR", "es-ES"];
    108 
    109    const l10nReg = new L10nRegistry();
    110 
    111    for (let i = 0; i < iterations; i++) {
    112      console.log("----------------------------------------------------------------------");
    113      console.log("Iteration", i);
    114      let sourceCount = randomPow(0, maxSources);
    115 
    116      const mocks = [];
    117      const fs = [];
    118 
    119      const locales = new Set();
    120      const filenames = new Set();
    121 
    122      for (let j = 0; j < sourceCount; j++) {
    123        const locale = pickOne(availableLocales);
    124        locales.add(locale);
    125 
    126        let metasource = pickOne(metasources);
    127        if (metasource === "langpack") {
    128          metasource = `${metasource}-${locale}`
    129        }
    130  
    131        const dir = generateRandomName();
    132        const filename = generateRandomName() + j + ".ftl";
    133        const path = `${dir}/${locale}/${filename}`
    134        const name = metasource || "app";
    135        const source = "key = value";
    136 
    137        filenames.add(filename);
    138 
    139        console.log("Add source", { name, metasource, path, source });
    140        fs.push({ path, source });
    141 
    142        mocks.push([
    143          name, // name
    144          metasource, // metasource,
    145          [locale], // locales,
    146          dir + "/{locale}/",
    147          fs
    148        ])
    149      }
    150 
    151      l10nReg.registerSources(mocks.map(args => L10nFileSource.createMock(...args)));
    152 
    153      const bundleLocales = pickN([...locales], random(1, 4));
    154      const bundleFilenames = pickN([...filenames], random(1, 10));
    155 
    156      console.log("generateBundles", {bundleLocales, bundleFilenames});
    157      const bundles = l10nReg.generateBundles(
    158        bundleLocales,
    159        bundleFilenames
    160      );
    161 
    162      function next() {
    163        console.log("Getting next bundle");
    164        const bundle = bundles.next()
    165        console.log("Next bundle obtained", bundle);
    166        return bundle;
    167      }
    168 
    169      const ops = [
    170        // Increase the frequency of next being called.
    171        next,
    172        next,
    173        next,
    174        () => {
    175          const newMocks = [];
    176          for (const mock of pickN(mocks, random(0, 3))) {
    177            const newMock = mock.slice();
    178            newMocks.push(newMock)
    179          }
    180          console.log("l10nReg.updateSources");
    181          l10nReg.updateSources(newMocks.map(mock => L10nFileSource.createMock(...mock)));
    182        },
    183        () => {
    184          console.log("l10nReg.clearSources");
    185          l10nReg.clearSources();
    186        }
    187      ];
    188 
    189      console.log("Start the operation loop");
    190      while (true) {
    191        console.log("Next operation");
    192        const op = pickOne(ops);
    193        const result = await op();
    194        if (result?.done) {
    195          // The iterator completed.
    196          break;
    197        }
    198      }
    199 
    200      console.log("Clear sources");
    201      l10nReg.clearSources();
    202    }
    203 
    204    ok(true, "The L10nRegistry fuzzing did not crash.")
    205 });