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 });