summaryrefslogtreecommitdiffstats
path: root/intl/l10n/test/test_l10nregistry_fuzzed.js
blob: f99c3b61e5dca64bb47f95b0617520ae03371120 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
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.")
});