/* Any copyright is dedicated to the Public Domain. http://creativecommons.org/publicdomain/zero/1.0/ */ /** * This test is a fuzzing test for the L10nRegistry API. It was written to find * a hard to reproduce bug in the L10nRegistry code. If it fails, place the seed * from the failing run in the code directly below to make it consistently reproducible. */ let seed = Math.floor(Math.random() * 1e9); console.log(`Starting a fuzzing run with seed: ${seed}.`); console.log("To reproduce this test locally, re-run it locally with:"); console.log(`let seed = ${seed};`); /** * A simple non-robust psuedo-random number generator. * * It is implemented using a Lehmer random number generator. * https://en.wikipedia.org/wiki/16,807 * * @returns {number} Ranged [0, 1) */ function prng() { const multiplier = 16807; const prime = 2147483647; seed = seed * multiplier % prime return (seed - 1) / prime } /** * Generate a name like "mock-dmsxfodrqboljmxdeayt". * @returns {string} */ function generateRandomName() { let name = 'mock-' const letters = "abcdefghijklmnopqrstuvwxyz"; for (let i = 0; i < 20; i++) { name += letters[Math.floor(prng() * letters.length)]; } return name; } /** * Picks one item from an array. * * @param {Array} * @returns {T} */ function pickOne(list) { return list[Math.floor(prng() * list.length)] } /** * Picks a random subset from an array. * * @param {Array} * @returns {Array} */ function pickN(list, count) { list = list.slice(); const result = []; for (let i = 0; i < count && i < list.length; i++) { // Pick a random item. const index = Math.floor(prng() * list.length); // Swap item to the end. const a = list[index]; const b = list[list.length - 1]; list[index] = b; list[list.length - 1] = a // Now that the random item is on the end, pop it off and add it to the results. result.push(list.pop()); } return result } /** * Generate a random number * @param {number} min * @param {number} max * @returns {number} */ function random(min, max) { const delta = max - min; return min + delta * prng(); } /** * Generate a random number generator with a distribution more towards the lower end. * @param {number} min * @param {number} max * @returns {number} */ function randomPow(min, max) { const delta = max - min; const r = prng() return min + delta * r * r; } add_task(async function test_fuzzing_sources() { const iterations = 100; const maxSources = 10; const metasources = ["app", "langpack", ""]; const availableLocales = ["en", "en-US", "pl", "en-CA", "es-AR", "es-ES"]; const l10nReg = new L10nRegistry(); for (let i = 0; i < iterations; i++) { console.log("----------------------------------------------------------------------"); console.log("Iteration", i); let sourceCount = randomPow(0, maxSources); const mocks = []; const fs = []; const locales = new Set(); const filenames = new Set(); for (let j = 0; j < sourceCount; j++) { const locale = pickOne(availableLocales); locales.add(locale); let metasource = pickOne(metasources); if (metasource === "langpack") { metasource = `${metasource}-${locale}` } const dir = generateRandomName(); const filename = generateRandomName() + j + ".ftl"; const path = `${dir}/${locale}/${filename}` const name = metasource || "app"; const source = "key = value"; filenames.add(filename); console.log("Add source", { name, metasource, path, source }); fs.push({ path, source }); mocks.push([ name, // name metasource, // metasource, [locale], // locales, dir + "/{locale}/", fs ]) } l10nReg.registerSources(mocks.map(args => L10nFileSource.createMock(...args))); const bundleLocales = pickN([...locales], random(1, 4)); const bundleFilenames = pickN([...filenames], random(1, 10)); console.log("generateBundles", {bundleLocales, bundleFilenames}); const bundles = l10nReg.generateBundles( bundleLocales, bundleFilenames ); function next() { console.log("Getting next bundle"); const bundle = bundles.next() console.log("Next bundle obtained", bundle); return bundle; } const ops = [ // Increase the frequency of next being called. next, next, next, () => { const newMocks = []; for (const mock of pickN(mocks, random(0, 3))) { const newMock = mock.slice(); newMocks.push(newMock) } console.log("l10nReg.updateSources"); l10nReg.updateSources(newMocks.map(mock => L10nFileSource.createMock(...mock))); }, () => { console.log("l10nReg.clearSources"); l10nReg.clearSources(); } ]; console.log("Start the operation loop"); while (true) { console.log("Next operation"); const op = pickOne(ops); const result = await op(); if (result?.done) { // The iterator completed. break; } } console.log("Clear sources"); l10nReg.clearSources(); } ok(true, "The L10nRegistry fuzzing did not crash.") });