tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

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