test_fx_telemetry.js (13955B)
1 /* Any copyright is dedicated to the Public Domain. 2 * http://creativecommons.org/publicdomain/zero/1.0/ */ 3 4 "use strict"; 5 const { FirefoxProfileMigrator } = ChromeUtils.importESModule( 6 "resource:///modules/FirefoxProfileMigrator.sys.mjs" 7 ); 8 const { InternalTestingProfileMigrator } = ChromeUtils.importESModule( 9 "resource:///modules/InternalTestingProfileMigrator.sys.mjs" 10 ); 11 const { LoginCSVImport } = ChromeUtils.importESModule( 12 "resource://gre/modules/LoginCSVImport.sys.mjs" 13 ); 14 const { PasswordFileMigrator } = ChromeUtils.importESModule( 15 "resource:///modules/FileMigrators.sys.mjs" 16 ); 17 const { sinon } = ChromeUtils.importESModule( 18 "resource://testing-common/Sinon.sys.mjs" 19 ); 20 21 // These preferences are set to true anytime MigratorBase.migrate 22 // successfully completes a migration of their type. 23 const BOOKMARKS_PREF = "browser.migrate.interactions.bookmarks"; 24 const CSV_PASSWORDS_PREF = "browser.migrate.interactions.csvpasswords"; 25 const HISTORY_PREF = "browser.migrate.interactions.history"; 26 const PASSWORDS_PREF = "browser.migrate.interactions.passwords"; 27 28 function readFile(file) { 29 let stream = Cc["@mozilla.org/network/file-input-stream;1"].createInstance( 30 Ci.nsIFileInputStream 31 ); 32 stream.init(file, -1, -1, Ci.nsIFileInputStream.CLOSE_ON_EOF); 33 34 let sis = Cc["@mozilla.org/scriptableinputstream;1"].createInstance( 35 Ci.nsIScriptableInputStream 36 ); 37 sis.init(stream); 38 let contents = sis.read(file.fileSize); 39 sis.close(); 40 return contents; 41 } 42 43 function checkDirectoryContains(dir, files) { 44 print("checking " + dir.path + " - should contain " + Object.keys(files)); 45 let seen = new Set(); 46 for (let file of dir.directoryEntries) { 47 print("found file: " + file.path); 48 Assert.ok(file.leafName in files, file.leafName + " exists, but shouldn't"); 49 50 let expectedContents = files[file.leafName]; 51 if (typeof expectedContents != "string") { 52 // it's a subdir - recurse! 53 Assert.ok(file.isDirectory(), "should be a subdir"); 54 let newDir = dir.clone(); 55 newDir.append(file.leafName); 56 checkDirectoryContains(newDir, expectedContents); 57 } else { 58 Assert.ok(!file.isDirectory(), "should be a regular file"); 59 let contents = readFile(file); 60 Assert.equal(contents, expectedContents); 61 } 62 seen.add(file.leafName); 63 } 64 let missing = []; 65 for (let x in files) { 66 if (!seen.has(x)) { 67 missing.push(x); 68 } 69 } 70 Assert.deepEqual(missing, [], "no missing files in " + dir.path); 71 } 72 73 function getTestDirs() { 74 // we make a directory structure in a temp dir which mirrors what we are 75 // testing. 76 let tempDir = do_get_tempdir(); 77 let srcDir = tempDir.clone(); 78 srcDir.append("test_source_dir"); 79 srcDir.createUnique(Ci.nsIFile.DIRECTORY_TYPE, FileUtils.PERMS_DIRECTORY); 80 81 let targetDir = tempDir.clone(); 82 targetDir.append("test_target_dir"); 83 targetDir.createUnique(Ci.nsIFile.DIRECTORY_TYPE, FileUtils.PERMS_DIRECTORY); 84 85 // no need to cleanup these dirs - the xpcshell harness will do it for us. 86 return [srcDir, targetDir]; 87 } 88 89 function writeToFile(dir, leafName, contents) { 90 let file = dir.clone(); 91 file.append(leafName); 92 93 let outputStream = FileUtils.openFileOutputStream(file); 94 outputStream.write(contents, contents.length); 95 outputStream.close(); 96 } 97 98 function createSubDir(dir, subDirName) { 99 let subDir = dir.clone(); 100 subDir.append(subDirName); 101 subDir.create(Ci.nsIFile.DIRECTORY_TYPE, FileUtils.PERMS_DIRECTORY); 102 return subDir; 103 } 104 105 async function promiseMigrator(name, srcDir, targetDir) { 106 // As the FirefoxProfileMigrator is a startup-only migrator, we import its 107 // module and instantiate it directly rather than going through MigrationUtils, 108 // to bypass that availability check. 109 let migrator = new FirefoxProfileMigrator(); 110 let migrators = migrator._getResourcesInternal(srcDir, targetDir); 111 for (let m of migrators) { 112 if (m.name == name) { 113 return new Promise(resolve => m.migrate(resolve)); 114 } 115 } 116 throw new Error("failed to find the " + name + " migrator"); 117 } 118 119 function promiseTelemetryMigrator(srcDir, targetDir) { 120 return promiseMigrator("telemetry", srcDir, targetDir); 121 } 122 123 add_task(async function test_empty() { 124 let [srcDir, targetDir] = getTestDirs(); 125 let ok = await promiseTelemetryMigrator(srcDir, targetDir); 126 Assert.ok(ok, "callback should have been true with empty directories"); 127 // check both are empty 128 checkDirectoryContains(srcDir, {}); 129 checkDirectoryContains(targetDir, {}); 130 }); 131 132 add_task(async function test_migrate_files() { 133 let [srcDir, targetDir] = getTestDirs(); 134 135 // Set up datareporting files, some to copy, some not. 136 let stateContent = JSON.stringify({ 137 clientId: "68d5474e-19dc-45c1-8e9a-81fca592707c", 138 }); 139 let sessionStateContent = "foobar 5432"; 140 let subDir = createSubDir(srcDir, "datareporting"); 141 writeToFile(subDir, "state.json", stateContent); 142 writeToFile(subDir, "session-state.json", sessionStateContent); 143 writeToFile(subDir, "other.file", "do not copy"); 144 145 let archived = createSubDir(subDir, "archived"); 146 writeToFile(archived, "other.file", "do not copy"); 147 148 // Set up FHR files, they should not be copied. 149 writeToFile(srcDir, "healthreport.sqlite", "do not copy"); 150 writeToFile(srcDir, "healthreport.sqlite-wal", "do not copy"); 151 subDir = createSubDir(srcDir, "healthreport"); 152 writeToFile(subDir, "state.json", "do not copy"); 153 writeToFile(subDir, "other.file", "do not copy"); 154 155 // Perform migration. 156 let ok = await promiseTelemetryMigrator(srcDir, targetDir); 157 Assert.ok( 158 ok, 159 "callback should have been true with important telemetry files copied" 160 ); 161 162 checkDirectoryContains(targetDir, { 163 datareporting: { 164 "state.json": stateContent, 165 "session-state.json": sessionStateContent, 166 }, 167 }); 168 }); 169 170 add_task(async function test_datareporting_not_dir() { 171 let [srcDir, targetDir] = getTestDirs(); 172 173 writeToFile(srcDir, "datareporting", "I'm a file but should be a directory"); 174 175 let ok = await promiseTelemetryMigrator(srcDir, targetDir); 176 Assert.ok( 177 ok, 178 "callback should have been true even though the directory was a file" 179 ); 180 181 checkDirectoryContains(targetDir, {}); 182 }); 183 184 add_task(async function test_datareporting_empty() { 185 let [srcDir, targetDir] = getTestDirs(); 186 187 // Migrate with an empty 'datareporting' subdir. 188 createSubDir(srcDir, "datareporting"); 189 let ok = await promiseTelemetryMigrator(srcDir, targetDir); 190 Assert.ok(ok, "callback should have been true"); 191 192 // We should end up with no migrated files. 193 checkDirectoryContains(targetDir, { 194 datareporting: {}, 195 }); 196 }); 197 198 add_task(async function test_healthreport_empty() { 199 let [srcDir, targetDir] = getTestDirs(); 200 201 // Migrate with no 'datareporting' and an empty 'healthreport' subdir. 202 createSubDir(srcDir, "healthreport"); 203 let ok = await promiseTelemetryMigrator(srcDir, targetDir); 204 Assert.ok(ok, "callback should have been true"); 205 206 // We should end up with no migrated files. 207 checkDirectoryContains(targetDir, {}); 208 }); 209 210 add_task(async function test_datareporting_many() { 211 let [srcDir, targetDir] = getTestDirs(); 212 213 // Create some datareporting files. 214 let subDir = createSubDir(srcDir, "datareporting"); 215 let shouldBeCopied = "should be copied"; 216 writeToFile(subDir, "state.json", shouldBeCopied); 217 writeToFile(subDir, "session-state.json", shouldBeCopied); 218 writeToFile(subDir, "something.else", "should not"); 219 createSubDir(subDir, "emptyDir"); 220 221 let ok = await promiseTelemetryMigrator(srcDir, targetDir); 222 Assert.ok(ok, "callback should have been true"); 223 224 checkDirectoryContains(targetDir, { 225 datareporting: { 226 "state.json": shouldBeCopied, 227 "session-state.json": shouldBeCopied, 228 }, 229 }); 230 }); 231 232 add_task(async function test_no_session_state() { 233 let [srcDir, targetDir] = getTestDirs(); 234 235 // Check that migration still works properly if we only have state.json. 236 let subDir = createSubDir(srcDir, "datareporting"); 237 let stateContent = "abcd984"; 238 writeToFile(subDir, "state.json", stateContent); 239 240 let ok = await promiseTelemetryMigrator(srcDir, targetDir); 241 Assert.ok(ok, "callback should have been true"); 242 243 checkDirectoryContains(targetDir, { 244 datareporting: { 245 "state.json": stateContent, 246 }, 247 }); 248 }); 249 250 add_task(async function test_no_state() { 251 let [srcDir, targetDir] = getTestDirs(); 252 253 // Check that migration still works properly if we only have session-state.json. 254 let subDir = createSubDir(srcDir, "datareporting"); 255 let sessionStateContent = "abcd512"; 256 writeToFile(subDir, "session-state.json", sessionStateContent); 257 258 let ok = await promiseTelemetryMigrator(srcDir, targetDir); 259 Assert.ok(ok, "callback should have been true"); 260 261 checkDirectoryContains(targetDir, { 262 datareporting: { 263 "session-state.json": sessionStateContent, 264 }, 265 }); 266 }); 267 268 add_task(async function test_times_migration() { 269 let [srcDir, targetDir] = getTestDirs(); 270 271 // create a times.json in the source directory. 272 let contents = JSON.stringify({ created: 1234 }); 273 writeToFile(srcDir, "times.json", contents); 274 275 let earliest = Date.now(); 276 let ok = await promiseMigrator("times", srcDir, targetDir); 277 Assert.ok(ok, "callback should have been true"); 278 let latest = Date.now(); 279 280 let timesFile = targetDir.clone(); 281 timesFile.append("times.json"); 282 283 let raw = readFile(timesFile); 284 let times = JSON.parse(raw); 285 Assert.ok(times.reset >= earliest && times.reset <= latest); 286 // and it should have left the creation time alone. 287 Assert.equal(times.created, 1234); 288 }); 289 290 /** 291 * Tests that when importing bookmarks, history, or passwords, we 292 * set interaction prefs. These preferences are sent using 293 * TelemetryEnvironment.sys.mjs. 294 */ 295 add_task(async function test_interaction_telemetry() { 296 let testingMigrator = await MigrationUtils.getMigrator( 297 InternalTestingProfileMigrator.key 298 ); 299 300 Services.prefs.clearUserPref(BOOKMARKS_PREF); 301 Services.prefs.clearUserPref(HISTORY_PREF); 302 Services.prefs.clearUserPref(PASSWORDS_PREF); 303 304 // Ensure that these prefs start false. 305 Assert.ok(!Services.prefs.getBoolPref(BOOKMARKS_PREF)); 306 Assert.ok(!Services.prefs.getBoolPref(HISTORY_PREF)); 307 Assert.ok(!Services.prefs.getBoolPref(PASSWORDS_PREF)); 308 309 await testingMigrator.migrate( 310 MigrationUtils.resourceTypes.BOOKMARKS, 311 false, 312 InternalTestingProfileMigrator.testProfile 313 ); 314 Assert.ok( 315 Services.prefs.getBoolPref(BOOKMARKS_PREF), 316 "Bookmarks pref should have been set." 317 ); 318 Assert.ok(!Services.prefs.getBoolPref(HISTORY_PREF)); 319 Assert.ok(!Services.prefs.getBoolPref(PASSWORDS_PREF)); 320 321 await testingMigrator.migrate( 322 MigrationUtils.resourceTypes.HISTORY, 323 false, 324 InternalTestingProfileMigrator.testProfile 325 ); 326 Assert.ok( 327 Services.prefs.getBoolPref(BOOKMARKS_PREF), 328 "Bookmarks pref should have been set." 329 ); 330 Assert.ok( 331 Services.prefs.getBoolPref(HISTORY_PREF), 332 "History pref should have been set." 333 ); 334 Assert.ok(!Services.prefs.getBoolPref(PASSWORDS_PREF)); 335 336 await testingMigrator.migrate( 337 MigrationUtils.resourceTypes.PASSWORDS, 338 false, 339 InternalTestingProfileMigrator.testProfile 340 ); 341 Assert.ok( 342 Services.prefs.getBoolPref(BOOKMARKS_PREF), 343 "Bookmarks pref should have been set." 344 ); 345 Assert.ok( 346 Services.prefs.getBoolPref(HISTORY_PREF), 347 "History pref should have been set." 348 ); 349 Assert.ok( 350 Services.prefs.getBoolPref(PASSWORDS_PREF), 351 "Passwords pref should have been set." 352 ); 353 354 // Now make sure that we still record these if we migrate a 355 // series of resources at the same time. 356 Services.prefs.clearUserPref(BOOKMARKS_PREF); 357 Services.prefs.clearUserPref(HISTORY_PREF); 358 Services.prefs.clearUserPref(PASSWORDS_PREF); 359 360 await testingMigrator.migrate( 361 MigrationUtils.resourceTypes.ALL, 362 false, 363 InternalTestingProfileMigrator.testProfile 364 ); 365 Assert.ok( 366 Services.prefs.getBoolPref(BOOKMARKS_PREF), 367 "Bookmarks pref should have been set." 368 ); 369 Assert.ok( 370 Services.prefs.getBoolPref(HISTORY_PREF), 371 "History pref should have been set." 372 ); 373 Assert.ok( 374 Services.prefs.getBoolPref(PASSWORDS_PREF), 375 "Passwords pref should have been set." 376 ); 377 }); 378 379 /** 380 * Tests that when importing passwords from a CSV file using the 381 * migration wizard, we set an interaction pref. This preference 382 * is sent using TelemetryEnvironment.sys.mjs. 383 */ 384 add_task(async function test_csv_password_interaction_telemetry() { 385 let sandbox = sinon.createSandbox(); 386 registerCleanupFunction(() => { 387 sandbox.restore(); 388 }); 389 390 let testingMigrator = new PasswordFileMigrator(); 391 392 Services.prefs.clearUserPref(CSV_PASSWORDS_PREF); 393 Assert.ok(!Services.prefs.getBoolPref(CSV_PASSWORDS_PREF)); 394 395 sandbox.stub(LoginCSVImport, "importFromCSV").resolves([]); 396 await testingMigrator.migrate("some/fake/path.csv"); 397 398 Assert.ok( 399 Services.prefs.getBoolPref(CSV_PASSWORDS_PREF), 400 "CSV import pref should have been set." 401 ); 402 403 sandbox.restore(); 404 }); 405 406 /** 407 * Tests that interaction preferences used for TelemetryEnvironment are 408 * persisted across profile resets. 409 */ 410 add_task(async function test_interaction_telemetry_persist_across_reset() { 411 const PREFS = ` 412 user_pref("${BOOKMARKS_PREF}", true); 413 user_pref("${CSV_PASSWORDS_PREF}", true); 414 user_pref("${HISTORY_PREF}", true); 415 user_pref("${PASSWORDS_PREF}", true); 416 `; 417 418 let [srcDir, targetDir] = getTestDirs(); 419 writeToFile(srcDir, "prefs.js", PREFS); 420 421 let ok = await promiseTelemetryMigrator(srcDir, targetDir); 422 Assert.ok(ok, "callback should have been true"); 423 424 let prefsPath = PathUtils.join(targetDir.path, "prefs.js"); 425 Assert.ok(await IOUtils.exists(prefsPath), "Prefs should have been written."); 426 let writtenPrefsString = await IOUtils.readUTF8(prefsPath); 427 for (let prefKey of [ 428 BOOKMARKS_PREF, 429 CSV_PASSWORDS_PREF, 430 HISTORY_PREF, 431 PASSWORDS_PREF, 432 ]) { 433 const EXPECTED = `user_pref("${prefKey}", true);`; 434 Assert.ok(writtenPrefsString.includes(EXPECTED), "Found persisted pref."); 435 } 436 });