summaryrefslogtreecommitdiffstats
path: root/intl/l10n/test/test_l10nregistry_fuzzed.js
diff options
context:
space:
mode:
Diffstat (limited to 'intl/l10n/test/test_l10nregistry_fuzzed.js')
-rw-r--r--intl/l10n/test/test_l10nregistry_fuzzed.js205
1 files changed, 205 insertions, 0 deletions
diff --git a/intl/l10n/test/test_l10nregistry_fuzzed.js b/intl/l10n/test/test_l10nregistry_fuzzed.js
new file mode 100644
index 0000000000..f99c3b61e5
--- /dev/null
+++ b/intl/l10n/test/test_l10nregistry_fuzzed.js
@@ -0,0 +1,205 @@
+/* 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<T>}
+ * @returns {T}
+ */
+function pickOne(list) {
+ return list[Math.floor(prng() * list.length)]
+}
+
+/**
+ * Picks a random subset from an array.
+ *
+ * @param {Array<T>}
+ * @returns {Array<T>}
+ */
+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.")
+});