Bug 981251 - Test app uninstallation. r=myk
☠☠ backed out by 621bb79845e3 ☠ ☠
authorMarco Castelluccio <mcastelluccio@mozilla.com>
Wed, 23 Apr 2014 06:57:43 -0700
changeset 180160 a7289f22cafd1a4290fde3e8bb613030b3bff4f6
parent 180159 427e3e62d7f3fac1bda0396254abe2f03bd318fd
child 180161 1e0f8b517b86e3fe3775edc3df05ea8fe49cf9b3
push id272
push userpvanderbeken@mozilla.com
push dateMon, 05 May 2014 16:31:18 +0000
reviewersmyk
bugs981251
milestone31.0a1
Bug 981251 - Test app uninstallation. r=myk
toolkit/webapps/tests/chrome.ini
toolkit/webapps/tests/head.js
toolkit/webapps/tests/test_hosted_uninstall.xul
toolkit/webapps/tests/test_packaged_uninstall.xul
--- a/toolkit/webapps/tests/chrome.ini
+++ b/toolkit/webapps/tests/chrome.ini
@@ -10,8 +10,10 @@ support-files =
 [test_hosted_launch.xul]
 skip-if = asan
 [test_hosted_launch_no_registry.xul]
 skip-if = asan
 [test_packaged_launch.xul]
 skip-if = asan
 [test_packaged_launch_no_registry.xul]
 skip-if = asan
+[test_hosted_uninstall.xul]
+[test_packaged_uninstall.xul]
--- a/toolkit/webapps/tests/head.js
+++ b/toolkit/webapps/tests/head.js
@@ -39,16 +39,49 @@ function checkDateHigherThan(files, date
         return false;
       }
     }
 
     return true;
   });
 }
 
+function dirContainsOnly(dir, expectedFiles) {
+  return Task.spawn(function*() {
+    let iterator = new OS.File.DirectoryIterator(dir);
+
+    let entries;
+    try {
+      entries = yield iterator.nextBatch();
+    } finally {
+      iterator.close();
+    }
+
+    let ret = true;
+
+    // Find unexpected files
+    for each (let {path} in entries) {
+      if (expectedFiles.indexOf(path) == -1) {
+        info("Unexpected file: " + path);
+        ret = false;
+      }
+    }
+
+    // Find missing files
+    for each (let expectedPath in expectedFiles) {
+      if (entries.findIndex(({path}) => path == expectedPath) == -1) {
+        info("Missing file: " + expectedPath);
+        ret = false;
+      }
+    }
+
+    return ret;
+  });
+}
+
 function wait(time) {
   let deferred = Promise.defer();
 
   setTimeout(function() {
     deferred.resolve();
   }, time);
 
   return deferred.promise;
copy from toolkit/webapps/tests/test_hosted.xul
copy to toolkit/webapps/tests/test_hosted_uninstall.xul
--- a/toolkit/webapps/tests/test_hosted.xul
+++ b/toolkit/webapps/tests/test_hosted_uninstall.xul
@@ -42,20 +42,19 @@ let app = {
   installOrigin: "http://example.com/",
   receipts: [],
   installTime: Date.now(),
 };
 
 let profileDir;
 let profilesIni;
 let installPath;
+let trashDir;
 
 let installedFiles;
-let tempUpdatedFiles;
-let updatedFiles;
 
 let cleanup;
 
 if (LINUX) {
   installPath = OS.Path.join(OS.Constants.Path.homeDir, "." + WebappOSUtils.getUniqueName(app));
 
   let xdg_data_home = Cc["@mozilla.org/process/environment;1"].
                       getService(Ci.nsIEnvironment).
@@ -69,27 +68,16 @@ if (LINUX) {
 
   installedFiles = [
     OS.Path.join(installPath, "icon.png"),
     OS.Path.join(installPath, "webapprt-stub"),
     OS.Path.join(installPath, "webapp.json"),
     OS.Path.join(installPath, "webapp.ini"),
     desktopINI,
   ];
-  tempUpdatedFiles = [
-    OS.Path.join(installPath, "update", "icon.png"),
-    OS.Path.join(installPath, "update", "webapp.json"),
-    OS.Path.join(installPath, "update", "webapp.ini"),
-  ];
-  updatedFiles = [
-    OS.Path.join(installPath, "icon.png"),
-    OS.Path.join(installPath, "webapp.json"),
-    OS.Path.join(installPath, "webapp.ini"),
-    desktopINI,
-  ];
 
   profilesIni = OS.Path.join(installPath, "profiles.ini");
 
   cleanup = function() {
     return Task.spawn(function*() {
       if (profileDir) {
         yield OS.File.removeDir(profileDir.parent.path, { ignoreAbsent: true });
       }
@@ -111,33 +99,16 @@ if (LINUX) {
     OS.Path.join(installPath, "webapp.json"),
     OS.Path.join(installPath, "webapp.ini"),
     OS.Path.join(installPath, "uninstall", "shortcuts_log.ini"),
     OS.Path.join(installPath, "uninstall", "uninstall.log"),
     OS.Path.join(installPath, "uninstall", "webapp-uninstaller.exe"),
     desktopShortcut,
     startMenuShortcut,
   ];
-  tempUpdatedFiles = [
-    OS.Path.join(installPath, "update", "chrome", "icons", "default", "default.ico"),
-    OS.Path.join(installPath, "update", "webapp.json"),
-    OS.Path.join(installPath, "update", "webapp.ini"),
-    OS.Path.join(installPath, "update", "uninstall", "shortcuts_log.ini"),
-    OS.Path.join(installPath, "update", "uninstall", "uninstall.log"),
-    OS.Path.join(installPath, "update", "uninstall", "webapp-uninstaller.exe"),
-  ];
-  updatedFiles = [
-    OS.Path.join(installPath, "chrome", "icons", "default", "default.ico"),
-    OS.Path.join(installPath, "webapp.json"),
-    OS.Path.join(installPath, "webapp.ini"),
-    OS.Path.join(installPath, "uninstall", "shortcuts_log.ini"),
-    OS.Path.join(installPath, "uninstall", "uninstall.log"),
-    desktopShortcut,
-    startMenuShortcut,
-  ];
 
   profilesIni = OS.Path.join(installPath, "profiles.ini");
 
   cleanup = function() {
     return Task.spawn(function*() {
       let uninstallKey;
       try {
         uninstallKey = Cc["@mozilla.org/windows-registry-key;1"].
@@ -172,37 +143,29 @@ if (LINUX) {
 
   installedFiles = [
     OS.Path.join(installPath, "Contents", "Info.plist"),
     OS.Path.join(installPath, "Contents", "MacOS", "webapprt"),
     OS.Path.join(installPath, "Contents", "MacOS", "webapp.ini"),
     OS.Path.join(installPath, "Contents", "Resources", "appicon.icns"),
     OS.Path.join(appProfileDir, "webapp.json"),
   ];
-  tempUpdatedFiles = [
-    OS.Path.join(installPath, "update", "Contents", "Info.plist"),
-    OS.Path.join(installPath, "update", "Contents", "MacOS", "webapp.ini"),
-    OS.Path.join(installPath, "update", "Contents", "Resources", "appicon.icns"),
-    OS.Path.join(installPath, "update", "webapp.json")
-  ];
-  updatedFiles = [
-    OS.Path.join(installPath, "Contents", "Info.plist"),
-    OS.Path.join(installPath, "Contents", "MacOS", "webapp.ini"),
-    OS.Path.join(installPath, "Contents", "Resources", "appicon.icns"),
-    OS.Path.join(appProfileDir, "webapp.json"),
-  ];
 
   profilesIni = OS.Path.join(appProfileDir, "profiles.ini");
 
   cleanup = function() {
     return Task.spawn(function*() {
       if (profileDir) {
         yield OS.File.removeDir(profileDir.parent.path, { ignoreAbsent: true });
       }
 
+      if (trashDir) {
+        yield OS.File.removeDir(trashDir, { ignoreAbsent: true });
+      }
+
       yield OS.File.removeDir(installPath, { ignoreAbsent: true });
 
       yield OS.File.removeDir(appProfileDir, { ignoreAbsent: true });
     });
   };
 }
 
 let runTest = Task.async(function*() {
@@ -211,24 +174,16 @@ let runTest = Task.async(function*() {
 
   SimpleTest.registerCleanupFunction(cleanup);
 
   setDryRunPref();
 
   let nativeApp = new NativeApp(app, manifest, app.categories);
   ok(nativeApp, "NativeApp object created");
 
-  info("Test update for an uninstalled application");
-  try {
-    yield nativeApp.prepareUpdate(manifest);
-    ok(false, "Didn't thrown");
-  } catch (ex) {
-    is(ex, "The application isn't installed", "Exception thrown");
-  }
-
   profileDir = nativeApp.createProfile();
   ok(profileDir && profileDir.exists(), "Profile directory created");
   ok((yield OS.File.exists(profilesIni)), "profiles.ini file created");
 
   // On Mac build servers, we don't have enough privileges to write to /Applications,
   // so we install apps in a user-owned directory.
   if (MAC) {
     nativeApp._rootInstallDir = OS.Path.join(OS.Constants.Path.homeDir, "Applications");
@@ -240,65 +195,64 @@ let runTest = Task.async(function*() {
   yield nativeApp.install(manifest);
   while (!WebappOSUtils.isLaunchable(app)) {
     yield wait(1000);
   }
   ok(true, "App launchable");
   ok((yield checkFiles(installedFiles)), "Files correctly written");
   is(WebappOSUtils.getInstallPath(app), installPath, "getInstallPath == installPath");
 
-  let stat = yield OS.File.stat(installPath);
-  let installTime = stat.lastModificationDate;
+  // Uninstall application
+  info("Test uninstallation");
+  ok((yield WebappOSUtils.uninstall(app)), "Application uninstalled");
 
-  // Wait one second, otherwise the last modification date is the same.
-  yield wait(1000);
+  ok(profileDir.exists(), "Profile directory still existent");
 
-  // Reinstall application
-  info("Test reinstallation");
-  yield nativeApp.install(manifest);
-  while (!WebappOSUtils.isLaunchable(app)) {
-    yield wait(1000);
+  if (LINUX) {
+    ok((yield dirContainsOnly(installPath, [ profileDir.path, profilesIni ])),
+       "Files correctly removed");
+  } else if (WIN) {
+    ok((yield dirContainsOnly(installPath, [ profileDir.parent.path,
+                                             profilesIni,
+                                             OS.Path.join(installPath, "uninstall") ])),
+       "Files correctly removed");
+  } else if (MAC) {
+    ok(!(yield OS.File.exists(installPath)), "Files correctly removed");
   }
-  ok(true, "App launchable");
-  ok((yield checkFiles(installedFiles)), "Installation not corrupted");
-  ok((yield checkFiles(tempUpdatedFiles)), "Files correctly written in the update subdirectory");
-
-  yield nativeApp.applyUpdate();
-  while (!WebappOSUtils.isLaunchable(app)) {
-    yield wait(1000);
-  }
-  ok(true, "App launchable");
-  ok((yield checkFiles(installedFiles)), "Installation not corrupted");
-  ok(!(yield OS.File.exists(OS.Path.join(installPath, "update"))), "Update directory removed");
-  ok((yield checkDateHigherThan(updatedFiles, installTime)), "Modification date higher");
 
-  stat = yield OS.File.stat(installPath);
-  installTime = stat.lastModificationDate;
-
-  // Wait one second, otherwise the last modification date is the same.
-  yield wait(1000);
-
-  // Update application
-  info("Test update");
-  yield nativeApp.prepareUpdate(manifest);
-  while (!WebappOSUtils.isLaunchable(app)) {
-    yield wait(1000);
+  // On Mac, the app is moved to the trash, it is still considered launchable
+  // (because it does have a install path).
+  if (!MAC) {
+    ok(!WebappOSUtils.isLaunchable(app), "App not launchable");
+    is(WebappOSUtils.getInstallPath(app), null, "getInstallPath == null");
+  } else {
+    trashDir = WebappOSUtils.getInstallPath(app);
+    ok(trashDir.contains(".Trash"), "App moved to Trash");
   }
-  ok(true, "App launchable");
-  ok((yield checkFiles(installedFiles)), "Installation not corrupted");
-  ok((yield checkFiles(tempUpdatedFiles)), "Files correctly written in the update subdirectory");
+
+  is(WebappOSUtils.launch(app), false, "Launch fails");
+
+  // On Mac, after we've tried to launch the app, its install path becomes null
+  // We can now repeat the tests we've already done on the other platforms:
+  if (MAC) {
+    while (WebappOSUtils.isLaunchable(app)) {
+      yield wait(1000);
+    }
+    ok(true, "App not launchable");
 
-  yield nativeApp.applyUpdate();
-  while (!WebappOSUtils.isLaunchable(app)) {
-    yield wait(1000);
+    is(WebappOSUtils.getInstallPath(app), null, "getInstallPath == null");
   }
-  ok(true, "App launchable");
-  ok((yield checkFiles(installedFiles)), "Installation not corrupted");
-  ok(!(yield OS.File.exists(OS.Path.join(installPath, "update"))), "Update directory removed");
-  ok((yield checkDateHigherThan(updatedFiles, installTime)), "Modification date higher");
+
+  let exc;
+  try {
+    yield WebappOSUtils.uninstall(app);
+  } catch (e) {
+    exc = e;
+  }
+  ok(!!exc, "Re-uninstalling failed");
 
   SimpleTest.finish();
 });
 
 // The test doesn't work yet on Mac OS X 10.6 machines.
 // See bug 993690.
 if (MAC_106) {
   todo(false, "The test doesn't work on Mac OS X 10.6 machines");
copy from toolkit/webapps/tests/test_packaged.xul
copy to toolkit/webapps/tests/test_packaged_uninstall.xul
--- a/toolkit/webapps/tests/test_packaged.xul
+++ b/toolkit/webapps/tests/test_packaged_uninstall.xul
@@ -48,20 +48,19 @@ let app = {
   installOrigin: "http://example.com/",
   receipts: [],
   installTime: Date.now(),
 };
 
 let profileDir;
 let profilesIni;
 let installPath;
+let trashDir;
 
 let installedFiles;
-let tempUpdatedFiles;
-let updatedFiles;
 
 let cleanup;
 
 if (LINUX) {
   installPath = OS.Path.join(OS.Constants.Path.homeDir, "." + WebappOSUtils.getUniqueName(app));
 
   let xdg_data_home = Cc["@mozilla.org/process/environment;1"].
                       getService(Ci.nsIEnvironment).
@@ -76,29 +75,16 @@ if (LINUX) {
   installedFiles = [
     OS.Path.join(installPath, "icon.png"),
     OS.Path.join(installPath, "webapprt-stub"),
     OS.Path.join(installPath, "webapp.json"),
     OS.Path.join(installPath, "webapp.ini"),
     OS.Path.join(installPath, "application.zip"),
     desktopINI,
   ];
-  tempUpdatedFiles = [
-    OS.Path.join(installPath, "update", "icon.png"),
-    OS.Path.join(installPath, "update", "webapp.json"),
-    OS.Path.join(installPath, "update", "webapp.ini"),
-    OS.Path.join(installPath, "update", "application.zip"),
-  ];
-  updatedFiles = [
-    OS.Path.join(installPath, "icon.png"),
-    OS.Path.join(installPath, "webapp.json"),
-    OS.Path.join(installPath, "webapp.ini"),
-    OS.Path.join(installPath, "application.zip"),
-    desktopINI,
-  ];
 
   profilesIni = OS.Path.join(installPath, "profiles.ini");
 
   cleanup = function() {
     return Task.spawn(function*() {
       if (profileDir) {
         yield OS.File.removeDir(profileDir.parent.path, { ignoreAbsent: true });
       }
@@ -121,35 +107,16 @@ if (LINUX) {
     OS.Path.join(installPath, "webapp.ini"),
     OS.Path.join(installPath, "application.zip"),
     OS.Path.join(installPath, "uninstall", "shortcuts_log.ini"),
     OS.Path.join(installPath, "uninstall", "uninstall.log"),
     OS.Path.join(installPath, "uninstall", "webapp-uninstaller.exe"),
     desktopShortcut,
     startMenuShortcut,
   ];
-  tempUpdatedFiles = [
-    OS.Path.join(installPath, "update", "chrome", "icons", "default", "default.ico"),
-    OS.Path.join(installPath, "update", "webapp.json"),
-    OS.Path.join(installPath, "update", "webapp.ini"),
-    OS.Path.join(installPath, "update", "application.zip"),
-    OS.Path.join(installPath, "update", "uninstall", "shortcuts_log.ini"),
-    OS.Path.join(installPath, "update", "uninstall", "uninstall.log"),
-    OS.Path.join(installPath, "update", "uninstall", "webapp-uninstaller.exe"),
-  ];
-  updatedFiles = [
-    OS.Path.join(installPath, "chrome", "icons", "default", "default.ico"),
-    OS.Path.join(installPath, "webapp.json"),
-    OS.Path.join(installPath, "webapp.ini"),
-    OS.Path.join(installPath, "application.zip"),
-    OS.Path.join(installPath, "uninstall", "shortcuts_log.ini"),
-    OS.Path.join(installPath, "uninstall", "uninstall.log"),
-    desktopShortcut,
-    startMenuShortcut,
-  ];
 
   profilesIni = OS.Path.join(installPath, "profiles.ini");
 
   cleanup = function() {
     return Task.spawn(function*() {
       let uninstallKey;
       try {
         uninstallKey = Cc["@mozilla.org/windows-registry-key;1"].
@@ -185,39 +152,29 @@ if (LINUX) {
   installedFiles = [
     OS.Path.join(installPath, "Contents", "Info.plist"),
     OS.Path.join(installPath, "Contents", "MacOS", "webapprt"),
     OS.Path.join(installPath, "Contents", "MacOS", "webapp.ini"),
     OS.Path.join(installPath, "Contents", "Resources", "appicon.icns"),
     OS.Path.join(installPath, "Contents", "Resources", "application.zip"),
     OS.Path.join(appProfileDir, "webapp.json"),
   ];
-  tempUpdatedFiles = [
-    OS.Path.join(installPath, "update", "Contents", "Info.plist"),
-    OS.Path.join(installPath, "update", "Contents", "MacOS", "webapp.ini"),
-    OS.Path.join(installPath, "update", "Contents", "Resources", "appicon.icns"),
-    OS.Path.join(installPath, "update", "Contents", "Resources", "application.zip"),
-    OS.Path.join(installPath, "update", "webapp.json"),
-  ];
-  updatedFiles = [
-    OS.Path.join(installPath, "Contents", "Info.plist"),
-    OS.Path.join(installPath, "Contents", "MacOS", "webapp.ini"),
-    OS.Path.join(installPath, "Contents", "Resources", "appicon.icns"),
-    OS.Path.join(installPath, "Contents", "Resources", "application.zip"),
-    OS.Path.join(appProfileDir, "webapp.json"),
-  ];
 
   profilesIni = OS.Path.join(appProfileDir, "profiles.ini");
 
   cleanup = function() {
     return Task.spawn(function*() {
       if (profileDir) {
         yield OS.File.removeDir(profileDir.parent.path, { ignoreAbsent: true });
       }
 
+      if (trashDir) {
+        yield OS.File.removeDir(trashDir, { ignoreAbsent: true });
+      }
+
       yield OS.File.removeDir(installPath, { ignoreAbsent: true });
 
       yield OS.File.removeDir(appProfileDir, { ignoreAbsent: true });
     });
   };
 }
 
 let runTest = Task.async(function*() {
@@ -229,24 +186,16 @@ let runTest = Task.async(function*() {
   setDryRunPref();
 
   let zipFile = yield OS.File.open(zipPath, { create: true });
   yield zipFile.close();
 
   let nativeApp = new NativeApp(app, manifest, app.categories);
   ok(nativeApp, "NativeApp object created");
 
-  info("Test update for an application that isn't installed");
-  try {
-    yield nativeApp.prepareUpdate(manifest, zipPath);
-    ok(false, "Didn't thrown");
-  } catch (ex) {
-    is(ex, "The application isn't installed", "Exception thrown");
-  }
-
   profileDir = nativeApp.createProfile();
   ok(profileDir && profileDir.exists(), "Profile directory created");
   ok((yield OS.File.exists(profilesIni)), "profiles.ini file created");
 
   // On Mac build servers, we don't have enough privileges to write to /Applications,
   // so we install apps in a user-owned directory.
   if (MAC) {
     nativeApp._rootInstallDir = OS.Path.join(OS.Constants.Path.homeDir, "Applications");
@@ -258,73 +207,64 @@ let runTest = Task.async(function*() {
   yield nativeApp.install(manifest, zipPath);
   while (!WebappOSUtils.isLaunchable(app)) {
     yield wait(1000);
   }
   ok(true, "App launchable");
   ok((yield checkFiles(installedFiles)), "Files correctly written");
   is(WebappOSUtils.getInstallPath(app), installPath, "getInstallPath == installPath");
 
-  let stat = yield OS.File.stat(installPath);
-  let installTime = stat.lastModificationDate;
-
-  // Wait one second, otherwise the last modification date is the same.
-  yield wait(1000);
+  // Uninstall application
+  info("Test uninstallation");
+  ok((yield WebappOSUtils.uninstall(app)), "Application uninstalled");
 
-  // Reinstall application
-  info("Test reinstallation");
-
-  zipFile = yield OS.File.open(zipPath, { create: true });
-  yield zipFile.close();
+  ok(profileDir.exists(), "Profile directory still existent");
 
-  yield nativeApp.install(manifest, zipPath);
-  while (!WebappOSUtils.isLaunchable(app)) {
-    yield wait(1000);
+  if (LINUX) {
+    ok((yield dirContainsOnly(installPath, [ profileDir.path, profilesIni ])),
+       "Files correctly removed");
+  } else if (WIN) {
+    ok((yield dirContainsOnly(installPath, [ profileDir.parent.path,
+                                             profilesIni,
+                                             OS.Path.join(installPath, "uninstall") ])),
+       "Files correctly removed");
+  } else if (MAC) {
+    ok(!(yield OS.File.exists(installPath)), "Files correctly removed");
   }
-  ok(true, "App launchable");
-  ok((yield checkFiles(installedFiles)), "Installation not corrupted");
-  ok((yield checkFiles(tempUpdatedFiles)), "Files correctly written in the update subdirectory");
-
-  yield nativeApp.applyUpdate();
-  while (!WebappOSUtils.isLaunchable(app)) {
-    yield wait(1000);
-  }
-  ok(true, "App launchable");
-  ok((yield checkFiles(installedFiles)), "Installation not corrupted");
-  ok(!(yield OS.File.exists(OS.Path.join(installPath, "update"))), "Update directory removed");
-  ok((yield checkDateHigherThan(updatedFiles, installTime)), "Modification date higher");
 
-  stat = yield OS.File.stat(installPath);
-  installTime = stat.lastModificationDate;
-
-  // Wait one second, otherwise the last modification date is the same.
-  yield wait(1000);
+  // On Mac, the app is moved to the trash, it is still considered launchable
+  // (because it does have a install path).
+  if (!MAC) {
+    ok(!WebappOSUtils.isLaunchable(app), "App not launchable");
+    is(WebappOSUtils.getInstallPath(app), null, "getInstallPath == null");
+  } else {
+    trashDir = WebappOSUtils.getInstallPath(app);
+    ok(trashDir.contains(".Trash"), "App moved to Trash");
+  }
 
-  // Update application
-  info("Test update");
-
-  zipFile = yield OS.File.open(zipPath, { create: true });
-  yield zipFile.close();
+  is(WebappOSUtils.launch(app), false, "Launch fails");
 
-  yield nativeApp.prepareUpdate(manifest, zipPath);
-  while (!WebappOSUtils.isLaunchable(app)) {
-    yield wait(1000);
-  }
-  ok(true, "App launchable");
-  ok((yield checkFiles(installedFiles)), "Installation not corrupted");
-  ok((yield checkFiles(tempUpdatedFiles)), "Files correctly written in the update subdirectory");
+  // On Mac, after we've tried to launch the app, its install path becomes null
+  // We can now repeat the tests we've already done on the other platforms:
+  if (MAC) {
+    while (WebappOSUtils.isLaunchable(app)) {
+      yield wait(1000);
+    }
+    ok(true, "App not launchable");
 
-  yield nativeApp.applyUpdate();
-  while (!WebappOSUtils.isLaunchable(app)) {
-    yield wait(1000);
+    is(WebappOSUtils.getInstallPath(app), null, "getInstallPath == null");
   }
-  ok(true, "App launchable");
-  ok((yield checkFiles(installedFiles)), "Installation not corrupted");
-  ok(!(yield OS.File.exists(OS.Path.join(installPath, "update"))), "Update directory removed");
-  ok((yield checkDateHigherThan(updatedFiles, installTime)), "Modification date higher");
+
+  let exc;
+  try {
+    yield WebappOSUtils.uninstall(app);
+  } catch (e) {
+    exc = e;
+  }
+  ok(!!exc, "Re-uninstalling failed");
 
   SimpleTest.finish();
 });
 
 // The test doesn't work yet on Mac OS X 10.6 machines.
 // See bug 993690.
 if (MAC_106) {
   todo(false, "The test doesn't work on Mac OS X 10.6 machines");