summaryrefslogtreecommitdiffstats
path: root/toolkit/mozapps/update/tests
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 17:32:43 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 17:32:43 +0000
commit6bf0a5cb5034a7e684dcc3500e841785237ce2dd (patch)
treea68f146d7fa01f0134297619fbe7e33db084e0aa /toolkit/mozapps/update/tests
parentInitial commit. (diff)
downloadthunderbird-upstream.tar.xz
thunderbird-upstream.zip
Adding upstream version 1:115.7.0.upstream/1%115.7.0upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
-rw-r--r--toolkit/mozapps/update/tests/Makefile.in13
-rw-r--r--toolkit/mozapps/update/tests/TestAUSHelper.cpp462
-rw-r--r--toolkit/mozapps/update/tests/TestAUSReadStrings.cpp210
-rw-r--r--toolkit/mozapps/update/tests/TestAUSReadStrings1.ini47
-rw-r--r--toolkit/mozapps/update/tests/TestAUSReadStrings2.ini39
-rw-r--r--toolkit/mozapps/update/tests/TestAUSReadStrings3.ini39
-rw-r--r--toolkit/mozapps/update/tests/TestAUSReadStrings4.ini5
-rw-r--r--toolkit/mozapps/update/tests/browser/browser.bits.ini81
-rw-r--r--toolkit/mozapps/update/tests/browser/browser.ini122
-rw-r--r--toolkit/mozapps/update/tests/browser/browser_aboutDialog_AppUpdater_stop_checking.js32
-rw-r--r--toolkit/mozapps/update/tests/browser/browser_aboutDialog_AppUpdater_stop_download_and_install.js41
-rw-r--r--toolkit/mozapps/update/tests/browser/browser_aboutDialog_AppUpdater_stop_download_failed.js43
-rw-r--r--toolkit/mozapps/update/tests/browser/browser_aboutDialog_AppUpdater_stop_downloading.js47
-rw-r--r--toolkit/mozapps/update/tests/browser/browser_aboutDialog_AppUpdater_stop_internal_error.js45
-rw-r--r--toolkit/mozapps/update/tests/browser/browser_aboutDialog_AppUpdater_stop_no_update.js29
-rw-r--r--toolkit/mozapps/update/tests/browser/browser_aboutDialog_AppUpdater_stop_ready_for_restart.js27
-rw-r--r--toolkit/mozapps/update/tests/browser/browser_aboutDialog_AppUpdater_stop_staging.js64
-rw-r--r--toolkit/mozapps/update/tests/browser/browser_aboutDialog_AppUpdater_stop_swap.js64
-rw-r--r--toolkit/mozapps/update/tests/browser/browser_aboutDialog_bc_downloaded.js17
-rw-r--r--toolkit/mozapps/update/tests/browser/browser_aboutDialog_bc_downloaded_staged.js28
-rw-r--r--toolkit/mozapps/update/tests/browser/browser_aboutDialog_bc_downloaded_staging.js55
-rw-r--r--toolkit/mozapps/update/tests/browser/browser_aboutDialog_bc_downloaded_stagingFailure.js31
-rw-r--r--toolkit/mozapps/update/tests/browser/browser_aboutDialog_bc_downloading.js76
-rw-r--r--toolkit/mozapps/update/tests/browser/browser_aboutDialog_bc_downloading_notify.js67
-rw-r--r--toolkit/mozapps/update/tests/browser/browser_aboutDialog_bc_downloading_staging.js68
-rw-r--r--toolkit/mozapps/update/tests/browser/browser_aboutDialog_bc_multiUpdate.js52
-rw-r--r--toolkit/mozapps/update/tests/browser/browser_aboutDialog_fc_apply_blocked.js94
-rw-r--r--toolkit/mozapps/update/tests/browser/browser_aboutDialog_fc_check_cantApply.js24
-rw-r--r--toolkit/mozapps/update/tests/browser/browser_aboutDialog_fc_check_malformedXML.js22
-rw-r--r--toolkit/mozapps/update/tests/browser/browser_aboutDialog_fc_check_noUpdate.js22
-rw-r--r--toolkit/mozapps/update/tests/browser/browser_aboutDialog_fc_check_otherInstance.js19
-rw-r--r--toolkit/mozapps/update/tests/browser/browser_aboutDialog_fc_check_unsupported.js22
-rw-r--r--toolkit/mozapps/update/tests/browser/browser_aboutDialog_fc_downloadAuto.js62
-rw-r--r--toolkit/mozapps/update/tests/browser/browser_aboutDialog_fc_downloadAuto_staging.js46
-rw-r--r--toolkit/mozapps/update/tests/browser/browser_aboutDialog_fc_downloadOptIn.js44
-rw-r--r--toolkit/mozapps/update/tests/browser/browser_aboutDialog_fc_downloadOptIn_staging.js52
-rw-r--r--toolkit/mozapps/update/tests/browser/browser_aboutDialog_fc_network_failure.js18
-rw-r--r--toolkit/mozapps/update/tests/browser/browser_aboutDialog_fc_network_offline.js31
-rw-r--r--toolkit/mozapps/update/tests/browser/browser_aboutDialog_fc_patch_completeBadSize.js36
-rw-r--r--toolkit/mozapps/update/tests/browser/browser_aboutDialog_fc_patch_partialBadSize.js36
-rw-r--r--toolkit/mozapps/update/tests/browser/browser_aboutDialog_fc_patch_partialBadSize_complete.js38
-rw-r--r--toolkit/mozapps/update/tests/browser/browser_aboutDialog_fc_patch_partialBadSize_completeBadSize.js53
-rw-r--r--toolkit/mozapps/update/tests/browser/browser_aboutDialog_internalError.js35
-rw-r--r--toolkit/mozapps/update/tests/browser/browser_aboutPrefs_backgroundUpdateSetting.js172
-rw-r--r--toolkit/mozapps/update/tests/browser/browser_aboutPrefs_bc_downloaded.js17
-rw-r--r--toolkit/mozapps/update/tests/browser/browser_aboutPrefs_bc_downloaded_staged.js28
-rw-r--r--toolkit/mozapps/update/tests/browser/browser_aboutPrefs_bc_downloaded_staging.js59
-rw-r--r--toolkit/mozapps/update/tests/browser/browser_aboutPrefs_bc_downloaded_stagingFailure.js29
-rw-r--r--toolkit/mozapps/update/tests/browser/browser_aboutPrefs_bc_downloading.js63
-rw-r--r--toolkit/mozapps/update/tests/browser/browser_aboutPrefs_bc_downloading_staging.js72
-rw-r--r--toolkit/mozapps/update/tests/browser/browser_aboutPrefs_bc_multiUpdate.js52
-rw-r--r--toolkit/mozapps/update/tests/browser/browser_aboutPrefs_fc_apply_blocked.js96
-rw-r--r--toolkit/mozapps/update/tests/browser/browser_aboutPrefs_fc_check_cantApply.js24
-rw-r--r--toolkit/mozapps/update/tests/browser/browser_aboutPrefs_fc_check_malformedXML.js22
-rw-r--r--toolkit/mozapps/update/tests/browser/browser_aboutPrefs_fc_check_noUpdate.js22
-rw-r--r--toolkit/mozapps/update/tests/browser/browser_aboutPrefs_fc_check_otherInstance.js19
-rw-r--r--toolkit/mozapps/update/tests/browser/browser_aboutPrefs_fc_check_unsupported.js22
-rw-r--r--toolkit/mozapps/update/tests/browser/browser_aboutPrefs_fc_downloadAuto.js37
-rw-r--r--toolkit/mozapps/update/tests/browser/browser_aboutPrefs_fc_downloadAuto_staging.js46
-rw-r--r--toolkit/mozapps/update/tests/browser/browser_aboutPrefs_fc_downloadOptIn.js44
-rw-r--r--toolkit/mozapps/update/tests/browser/browser_aboutPrefs_fc_downloadOptIn_staging.js52
-rw-r--r--toolkit/mozapps/update/tests/browser/browser_aboutPrefs_fc_network_failure.js17
-rw-r--r--toolkit/mozapps/update/tests/browser/browser_aboutPrefs_fc_network_offline.js31
-rw-r--r--toolkit/mozapps/update/tests/browser/browser_aboutPrefs_fc_patch_completeBadSize.js36
-rw-r--r--toolkit/mozapps/update/tests/browser/browser_aboutPrefs_fc_patch_partialBadSize.js36
-rw-r--r--toolkit/mozapps/update/tests/browser/browser_aboutPrefs_fc_patch_partialBadSize_complete.js38
-rw-r--r--toolkit/mozapps/update/tests/browser/browser_aboutPrefs_fc_patch_partialBadSize_completeBadSize.js53
-rw-r--r--toolkit/mozapps/update/tests/browser/browser_aboutPrefs_internalError.js35
-rw-r--r--toolkit/mozapps/update/tests/browser/browser_aboutPrefs_settings.js151
-rw-r--r--toolkit/mozapps/update/tests/browser/browser_doorhanger_bc_check_cantApply.js18
-rw-r--r--toolkit/mozapps/update/tests/browser/browser_doorhanger_bc_check_malformedXML.js26
-rw-r--r--toolkit/mozapps/update/tests/browser/browser_doorhanger_bc_check_unsupported.js94
-rw-r--r--toolkit/mozapps/update/tests/browser/browser_doorhanger_bc_downloadAutoFailures.js35
-rw-r--r--toolkit/mozapps/update/tests/browser/browser_doorhanger_bc_downloadAutoFailures_bgWin.js80
-rw-r--r--toolkit/mozapps/update/tests/browser/browser_doorhanger_bc_downloadOptIn.js25
-rw-r--r--toolkit/mozapps/update/tests/browser/browser_doorhanger_bc_downloadOptIn_bgWin.js63
-rw-r--r--toolkit/mozapps/update/tests/browser/browser_doorhanger_bc_downloadOptIn_staging.js29
-rw-r--r--toolkit/mozapps/update/tests/browser/browser_doorhanger_bc_downloaded.js18
-rw-r--r--toolkit/mozapps/update/tests/browser/browser_doorhanger_bc_downloaded_disableBITS.js31
-rw-r--r--toolkit/mozapps/update/tests/browser/browser_doorhanger_bc_downloaded_staged.js22
-rw-r--r--toolkit/mozapps/update/tests/browser/browser_doorhanger_bc_multiUpdate.js93
-rw-r--r--toolkit/mozapps/update/tests/browser/browser_doorhanger_bc_multiUpdate_promptWaitTime.js89
-rw-r--r--toolkit/mozapps/update/tests/browser/browser_doorhanger_bc_patch_completeBadSize.js33
-rw-r--r--toolkit/mozapps/update/tests/browser/browser_doorhanger_bc_patch_partialBadSize.js33
-rw-r--r--toolkit/mozapps/update/tests/browser/browser_doorhanger_bc_patch_partialBadSize_complete.js18
-rw-r--r--toolkit/mozapps/update/tests/browser/browser_doorhanger_bc_patch_partialBadSize_completeBadSize.js33
-rw-r--r--toolkit/mozapps/update/tests/browser/browser_doorhanger_sp_patch_completeApplyFailure.js23
-rw-r--r--toolkit/mozapps/update/tests/browser/browser_doorhanger_sp_patch_partialApplyFailure.js22
-rw-r--r--toolkit/mozapps/update/tests/browser/browser_doorhanger_sp_patch_partialApplyFailure_complete.js22
-rw-r--r--toolkit/mozapps/update/tests/browser/browser_doorhanger_sp_patch_partialApplyFailure_completeBadSize.js32
-rw-r--r--toolkit/mozapps/update/tests/browser/browser_doorhanger_sp_patch_partialApplyFailure_complete_staging.js28
-rw-r--r--toolkit/mozapps/update/tests/browser/browser_elevationDialog.js139
-rw-r--r--toolkit/mozapps/update/tests/browser/browser_memory_allocation_error_fallback.js81
-rw-r--r--toolkit/mozapps/update/tests/browser/browser_telemetry_updatePing_downloaded_ready.js68
-rw-r--r--toolkit/mozapps/update/tests/browser/browser_telemetry_updatePing_staged_ready.js72
-rw-r--r--toolkit/mozapps/update/tests/browser/downloadPage.html13
-rw-r--r--toolkit/mozapps/update/tests/browser/head.js1347
-rw-r--r--toolkit/mozapps/update/tests/browser/manual_app_update_only/browser.ini18
-rw-r--r--toolkit/mozapps/update/tests/browser/manual_app_update_only/browser_aboutDialog_fc_autoUpdateFalse.js43
-rw-r--r--toolkit/mozapps/update/tests/browser/manual_app_update_only/browser_aboutDialog_fc_autoUpdateTrue.js43
-rw-r--r--toolkit/mozapps/update/tests/browser/manual_app_update_only/browser_aboutPrefs_fc_autoUpdateFalse.js55
-rw-r--r--toolkit/mozapps/update/tests/browser/manual_app_update_only/browser_aboutPrefs_fc_autoUpdateTrue.js55
-rw-r--r--toolkit/mozapps/update/tests/browser/manual_app_update_only/browser_noBackgroundUpdate.js17
-rw-r--r--toolkit/mozapps/update/tests/browser/manual_app_update_only/config_manual_app_update_only.json5
-rw-r--r--toolkit/mozapps/update/tests/browser/manual_app_update_only/head.js9
-rw-r--r--toolkit/mozapps/update/tests/browser/testConstants.js7
-rw-r--r--toolkit/mozapps/update/tests/data/app_update.sjs251
-rw-r--r--toolkit/mozapps/update/tests/data/complete.marbin0 -> 86612 bytes
-rw-r--r--toolkit/mozapps/update/tests/data/complete.pngbin0 -> 878 bytes
-rw-r--r--toolkit/mozapps/update/tests/data/complete_log_success_mac332
-rw-r--r--toolkit/mozapps/update/tests/data/complete_log_success_win320
-rw-r--r--toolkit/mozapps/update/tests/data/complete_mac.marbin0 -> 87129 bytes
-rw-r--r--toolkit/mozapps/update/tests/data/complete_precomplete18
-rw-r--r--toolkit/mozapps/update/tests/data/complete_precomplete_mac21
-rw-r--r--toolkit/mozapps/update/tests/data/complete_removed-files41
-rw-r--r--toolkit/mozapps/update/tests/data/complete_removed-files_mac41
-rw-r--r--toolkit/mozapps/update/tests/data/complete_update_manifest59
-rw-r--r--toolkit/mozapps/update/tests/data/old_version.marbin0 -> 709 bytes
-rw-r--r--toolkit/mozapps/update/tests/data/partial.marbin0 -> 9872 bytes
-rw-r--r--toolkit/mozapps/update/tests/data/partial.pngbin0 -> 776 bytes
-rw-r--r--toolkit/mozapps/update/tests/data/partial_log_failure_mac192
-rw-r--r--toolkit/mozapps/update/tests/data/partial_log_failure_win192
-rw-r--r--toolkit/mozapps/update/tests/data/partial_log_success_mac279
-rw-r--r--toolkit/mozapps/update/tests/data/partial_log_success_win279
-rw-r--r--toolkit/mozapps/update/tests/data/partial_mac.marbin0 -> 10361 bytes
-rw-r--r--toolkit/mozapps/update/tests/data/partial_precomplete19
-rw-r--r--toolkit/mozapps/update/tests/data/partial_precomplete_mac22
-rw-r--r--toolkit/mozapps/update/tests/data/partial_removed-files41
-rw-r--r--toolkit/mozapps/update/tests/data/partial_removed-files_mac41
-rw-r--r--toolkit/mozapps/update/tests/data/partial_update_manifest63
-rw-r--r--toolkit/mozapps/update/tests/data/replace_log_success6
-rw-r--r--toolkit/mozapps/update/tests/data/shared.js928
-rw-r--r--toolkit/mozapps/update/tests/data/sharedUpdateXML.js417
-rw-r--r--toolkit/mozapps/update/tests/data/simple.marbin0 -> 1419 bytes
-rw-r--r--toolkit/mozapps/update/tests/data/syncManagerTestChild.js55
-rw-r--r--toolkit/mozapps/update/tests/data/xpcshellConstantsPP.js29
-rw-r--r--toolkit/mozapps/update/tests/data/xpcshellUtilsAUS.js4803
-rw-r--r--toolkit/mozapps/update/tests/diff_base_service.bash116
-rw-r--r--toolkit/mozapps/update/tests/marionette/marionette.ini3
-rw-r--r--toolkit/mozapps/update/tests/marionette/test_no_window_update_restart.py255
-rw-r--r--toolkit/mozapps/update/tests/moz.build118
-rw-r--r--toolkit/mozapps/update/tests/unit_aus_update/ausReadStrings.js33
-rw-r--r--toolkit/mozapps/update/tests/unit_aus_update/backgroundUpdateTaskInternalUpdater.js85
-rw-r--r--toolkit/mozapps/update/tests/unit_aus_update/canCheckForAndCanApplyUpdates.js62
-rw-r--r--toolkit/mozapps/update/tests/unit_aus_update/cleanupDownloadingForDifferentChannel.js60
-rw-r--r--toolkit/mozapps/update/tests/unit_aus_update/cleanupDownloadingForOlderAppVersion.js58
-rw-r--r--toolkit/mozapps/update/tests/unit_aus_update/cleanupDownloadingForSameVersionAndBuildID.js59
-rw-r--r--toolkit/mozapps/update/tests/unit_aus_update/cleanupDownloadingIncorrectStatus.js57
-rw-r--r--toolkit/mozapps/update/tests/unit_aus_update/cleanupPendingVersionFileIncorrectStatus.js57
-rw-r--r--toolkit/mozapps/update/tests/unit_aus_update/cleanupSuccessLogMove.js61
-rw-r--r--toolkit/mozapps/update/tests/unit_aus_update/cleanupSuccessLogsFIFO.js63
-rw-r--r--toolkit/mozapps/update/tests/unit_aus_update/disableBackgroundUpdatesBackgroundTask.js48
-rw-r--r--toolkit/mozapps/update/tests/unit_aus_update/disableBackgroundUpdatesNonBackgroundTask.js41
-rw-r--r--toolkit/mozapps/update/tests/unit_aus_update/downloadInterruptedNoRecovery.js23
-rw-r--r--toolkit/mozapps/update/tests/unit_aus_update/downloadInterruptedOffline.js21
-rw-r--r--toolkit/mozapps/update/tests/unit_aus_update/downloadInterruptedRecovery.js26
-rw-r--r--toolkit/mozapps/update/tests/unit_aus_update/downloadResumeForSameAppVersion.js38
-rw-r--r--toolkit/mozapps/update/tests/unit_aus_update/ensureExperimentToRolloutTransitionPerformed.js111
-rw-r--r--toolkit/mozapps/update/tests/unit_aus_update/head_update.js8
-rw-r--r--toolkit/mozapps/update/tests/unit_aus_update/languagePackUpdates.js291
-rw-r--r--toolkit/mozapps/update/tests/unit_aus_update/multiUpdate.js396
-rw-r--r--toolkit/mozapps/update/tests/unit_aus_update/onlyDownloadUpdatesThisSession.js69
-rw-r--r--toolkit/mozapps/update/tests/unit_aus_update/perInstallationPrefs.js238
-rw-r--r--toolkit/mozapps/update/tests/unit_aus_update/remoteUpdateXML.js327
-rw-r--r--toolkit/mozapps/update/tests/unit_aus_update/testConstants.js8
-rw-r--r--toolkit/mozapps/update/tests/unit_aus_update/updateAutoPrefMigrate.js74
-rw-r--r--toolkit/mozapps/update/tests/unit_aus_update/updateCheckCombine.js38
-rw-r--r--toolkit/mozapps/update/tests/unit_aus_update/updateDirectoryMigrate.js246
-rw-r--r--toolkit/mozapps/update/tests/unit_aus_update/updateManagerXML.js593
-rw-r--r--toolkit/mozapps/update/tests/unit_aus_update/updateSyncManager.js105
-rw-r--r--toolkit/mozapps/update/tests/unit_aus_update/urlConstruction.js26
-rw-r--r--toolkit/mozapps/update/tests/unit_aus_update/verifyChannelPrefsFile.js38
-rw-r--r--toolkit/mozapps/update/tests/unit_aus_update/xpcshell.ini76
-rw-r--r--toolkit/mozapps/update/tests/unit_background_update/head.js58
-rw-r--r--toolkit/mozapps/update/tests/unit_background_update/test_backgroundupdate_exitcodes.js80
-rw-r--r--toolkit/mozapps/update/tests/unit_background_update/test_backgroundupdate_glean.js224
-rw-r--r--toolkit/mozapps/update/tests/unit_background_update/test_backgroundupdate_reason.js66
-rw-r--r--toolkit/mozapps/update/tests/unit_background_update/test_backgroundupdate_reason_schedule.js136
-rw-r--r--toolkit/mozapps/update/tests/unit_background_update/test_backgroundupdate_reason_update.js241
-rw-r--r--toolkit/mozapps/update/tests/unit_background_update/xpcshell.ini24
-rw-r--r--toolkit/mozapps/update/tests/unit_base_updater/head_update.js7
-rw-r--r--toolkit/mozapps/update/tests/unit_base_updater/invalidArgCallbackFileNotInInstallDirFailure.js32
-rw-r--r--toolkit/mozapps/update/tests/unit_base_updater/invalidArgCallbackFilePathTooLongFailure.js39
-rw-r--r--toolkit/mozapps/update/tests/unit_base_updater/invalidArgInstallDirPathTooLongFailure.js53
-rw-r--r--toolkit/mozapps/update/tests/unit_base_updater/invalidArgInstallDirPathTraversalFailure.js50
-rw-r--r--toolkit/mozapps/update/tests/unit_base_updater/invalidArgInstallWorkingDirPathNotSameFailure_win.js45
-rw-r--r--toolkit/mozapps/update/tests/unit_base_updater/invalidArgPatchDirPathTraversalFailure.js32
-rw-r--r--toolkit/mozapps/update/tests/unit_base_updater/invalidArgStageDirNotInInstallDirFailure_win.js45
-rw-r--r--toolkit/mozapps/update/tests/unit_base_updater/invalidArgWorkingDirPathLocalUNCFailure_win.js45
-rw-r--r--toolkit/mozapps/update/tests/unit_base_updater/invalidArgWorkingDirPathRelativeFailure.js44
-rw-r--r--toolkit/mozapps/update/tests/unit_base_updater/marAppApplyDirLockedStageFailure_win.js29
-rw-r--r--toolkit/mozapps/update/tests/unit_base_updater/marAppApplyUpdateAppBinInUseStageSuccess_win.js50
-rw-r--r--toolkit/mozapps/update/tests/unit_base_updater/marAppApplyUpdateSkippedWriteAccess_win.js74
-rw-r--r--toolkit/mozapps/update/tests/unit_base_updater/marAppApplyUpdateStageOldVersionFailure.js64
-rw-r--r--toolkit/mozapps/update/tests/unit_base_updater/marAppApplyUpdateStageSuccess.js49
-rw-r--r--toolkit/mozapps/update/tests/unit_base_updater/marAppApplyUpdateSuccess.js47
-rw-r--r--toolkit/mozapps/update/tests/unit_base_updater/marAppInUseBackgroundTaskFailure_win.js51
-rw-r--r--toolkit/mozapps/update/tests/unit_base_updater/marAppInUseStageFailureComplete_win.js37
-rw-r--r--toolkit/mozapps/update/tests/unit_base_updater/marAppInUseStageSuccessComplete_unix.js68
-rw-r--r--toolkit/mozapps/update/tests/unit_base_updater/marAppInUseSuccessComplete.js26
-rw-r--r--toolkit/mozapps/update/tests/unit_base_updater/marCallbackAppStageSuccessComplete_win.js30
-rw-r--r--toolkit/mozapps/update/tests/unit_base_updater/marCallbackAppStageSuccessPartial_win.js30
-rw-r--r--toolkit/mozapps/update/tests/unit_base_updater/marCallbackAppSuccessComplete_win.js24
-rw-r--r--toolkit/mozapps/update/tests/unit_base_updater/marCallbackAppSuccessPartial_win.js24
-rw-r--r--toolkit/mozapps/update/tests/unit_base_updater/marCallbackUmask_unix.js42
-rw-r--r--toolkit/mozapps/update/tests/unit_base_updater/marFailurePartial.js39
-rw-r--r--toolkit/mozapps/update/tests/unit_base_updater/marFileInUseStageFailureComplete_win.js40
-rw-r--r--toolkit/mozapps/update/tests/unit_base_updater/marFileInUseStageFailurePartial_win.js40
-rw-r--r--toolkit/mozapps/update/tests/unit_base_updater/marFileInUseSuccessComplete_win.js29
-rw-r--r--toolkit/mozapps/update/tests/unit_base_updater/marFileInUseSuccessPartial_win.js29
-rw-r--r--toolkit/mozapps/update/tests/unit_base_updater/marFileLockedFailureComplete_win.js27
-rw-r--r--toolkit/mozapps/update/tests/unit_base_updater/marFileLockedFailurePartial_win.js26
-rw-r--r--toolkit/mozapps/update/tests/unit_base_updater/marFileLockedStageFailureComplete_win.js34
-rw-r--r--toolkit/mozapps/update/tests/unit_base_updater/marFileLockedStageFailurePartial_win.js33
-rw-r--r--toolkit/mozapps/update/tests/unit_base_updater/marMissingUpdateSettings.js42
-rw-r--r--toolkit/mozapps/update/tests/unit_base_updater/marMissingUpdateSettingsStage.js35
-rw-r--r--toolkit/mozapps/update/tests/unit_base_updater/marPIDPersistsSuccessComplete_win.js25
-rw-r--r--toolkit/mozapps/update/tests/unit_base_updater/marRMRFDirFileInUseStageFailureComplete_win.js43
-rw-r--r--toolkit/mozapps/update/tests/unit_base_updater/marRMRFDirFileInUseStageFailurePartial_win.js41
-rw-r--r--toolkit/mozapps/update/tests/unit_base_updater/marRMRFDirFileInUseSuccessComplete_win.js31
-rw-r--r--toolkit/mozapps/update/tests/unit_base_updater/marRMRFDirFileInUseSuccessPartial_win.js29
-rw-r--r--toolkit/mozapps/update/tests/unit_base_updater/marStageFailurePartial.js31
-rw-r--r--toolkit/mozapps/update/tests/unit_base_updater/marStageSuccessComplete.js71
-rw-r--r--toolkit/mozapps/update/tests/unit_base_updater/marStageSuccessPartial.js35
-rw-r--r--toolkit/mozapps/update/tests/unit_base_updater/marSuccessComplete.js26
-rw-r--r--toolkit/mozapps/update/tests/unit_base_updater/marSuccessPartial.js29
-rw-r--r--toolkit/mozapps/update/tests/unit_base_updater/marSuccessPartialWhileBackgroundTaskRunning.js125
-rw-r--r--toolkit/mozapps/update/tests/unit_base_updater/marVersionDowngrade.js41
-rw-r--r--toolkit/mozapps/update/tests/unit_base_updater/marWrongChannel.js43
-rw-r--r--toolkit/mozapps/update/tests/unit_base_updater/marWrongChannelStage.js36
-rw-r--r--toolkit/mozapps/update/tests/unit_base_updater/xpcshell.ini136
-rw-r--r--toolkit/mozapps/update/tests/unit_service_updater/bootstrapSvc.js23
-rw-r--r--toolkit/mozapps/update/tests/unit_service_updater/checkUpdaterSigSvc.js53
-rw-r--r--toolkit/mozapps/update/tests/unit_service_updater/fallbackOnSvcFailure.js38
-rw-r--r--toolkit/mozapps/update/tests/unit_service_updater/head_update.js7
-rw-r--r--toolkit/mozapps/update/tests/unit_service_updater/invalidArgInstallDirPathTooLongFailureSvc.js53
-rw-r--r--toolkit/mozapps/update/tests/unit_service_updater/invalidArgInstallDirPathTraversalFailureSvc.js50
-rw-r--r--toolkit/mozapps/update/tests/unit_service_updater/invalidArgInstallWorkingDirPathNotSameFailureSvc_win.js45
-rw-r--r--toolkit/mozapps/update/tests/unit_service_updater/invalidArgPatchDirPathSuffixFailureSvc.js27
-rw-r--r--toolkit/mozapps/update/tests/unit_service_updater/invalidArgPatchDirPathTraversalFailureSvc.js32
-rw-r--r--toolkit/mozapps/update/tests/unit_service_updater/invalidArgStageDirNotInInstallDirFailureSvc_win.js45
-rw-r--r--toolkit/mozapps/update/tests/unit_service_updater/invalidArgWorkingDirPathLocalUNCFailureSvc_win.js45
-rw-r--r--toolkit/mozapps/update/tests/unit_service_updater/invalidArgWorkingDirPathRelativeFailureSvc.js44
-rw-r--r--toolkit/mozapps/update/tests/unit_service_updater/marAppApplyDirLockedStageFailureSvc_win.js29
-rw-r--r--toolkit/mozapps/update/tests/unit_service_updater/marAppApplyUpdateAppBinInUseStageSuccessSvc_win.js50
-rw-r--r--toolkit/mozapps/update/tests/unit_service_updater/marAppApplyUpdateStageSuccessSvc.js49
-rw-r--r--toolkit/mozapps/update/tests/unit_service_updater/marAppApplyUpdateSuccessSvc.js47
-rw-r--r--toolkit/mozapps/update/tests/unit_service_updater/marAppInUseBackgroundTaskFailureSvc_win.js51
-rw-r--r--toolkit/mozapps/update/tests/unit_service_updater/marAppInUseStageFailureCompleteSvc_win.js37
-rw-r--r--toolkit/mozapps/update/tests/unit_service_updater/marAppInUseSuccessCompleteSvc.js26
-rw-r--r--toolkit/mozapps/update/tests/unit_service_updater/marCallbackAppStageSuccessCompleteSvc_win.js30
-rw-r--r--toolkit/mozapps/update/tests/unit_service_updater/marCallbackAppStageSuccessPartialSvc_win.js30
-rw-r--r--toolkit/mozapps/update/tests/unit_service_updater/marCallbackAppSuccessCompleteSvc_win.js24
-rw-r--r--toolkit/mozapps/update/tests/unit_service_updater/marCallbackAppSuccessPartialSvc_win.js24
-rw-r--r--toolkit/mozapps/update/tests/unit_service_updater/marFailurePartialSvc.js39
-rw-r--r--toolkit/mozapps/update/tests/unit_service_updater/marFileInUseStageFailureCompleteSvc_win.js40
-rw-r--r--toolkit/mozapps/update/tests/unit_service_updater/marFileInUseStageFailurePartialSvc_win.js40
-rw-r--r--toolkit/mozapps/update/tests/unit_service_updater/marFileInUseSuccessCompleteSvc_win.js29
-rw-r--r--toolkit/mozapps/update/tests/unit_service_updater/marFileInUseSuccessPartialSvc_win.js29
-rw-r--r--toolkit/mozapps/update/tests/unit_service_updater/marFileLockedFailureCompleteSvc_win.js27
-rw-r--r--toolkit/mozapps/update/tests/unit_service_updater/marFileLockedFailurePartialSvc_win.js26
-rw-r--r--toolkit/mozapps/update/tests/unit_service_updater/marFileLockedStageFailureCompleteSvc_win.js34
-rw-r--r--toolkit/mozapps/update/tests/unit_service_updater/marFileLockedStageFailurePartialSvc_win.js33
-rw-r--r--toolkit/mozapps/update/tests/unit_service_updater/marRMRFDirFileInUseStageFailureCompleteSvc_win.js43
-rw-r--r--toolkit/mozapps/update/tests/unit_service_updater/marRMRFDirFileInUseStageFailurePartialSvc_win.js41
-rw-r--r--toolkit/mozapps/update/tests/unit_service_updater/marRMRFDirFileInUseSuccessCompleteSvc_win.js31
-rw-r--r--toolkit/mozapps/update/tests/unit_service_updater/marRMRFDirFileInUseSuccessPartialSvc_win.js29
-rw-r--r--toolkit/mozapps/update/tests/unit_service_updater/marStageFailurePartialSvc.js31
-rw-r--r--toolkit/mozapps/update/tests/unit_service_updater/marStageSuccessCompleteSvc.js71
-rw-r--r--toolkit/mozapps/update/tests/unit_service_updater/marStageSuccessPartialSvc.js35
-rw-r--r--toolkit/mozapps/update/tests/unit_service_updater/marSuccessCompleteSvc.js26
-rw-r--r--toolkit/mozapps/update/tests/unit_service_updater/marSuccessPartialSvc.js29
-rw-r--r--toolkit/mozapps/update/tests/unit_service_updater/xpcshell.ini99
273 files changed, 23520 insertions, 0 deletions
diff --git a/toolkit/mozapps/update/tests/Makefile.in b/toolkit/mozapps/update/tests/Makefile.in
new file mode 100644
index 0000000000..b07afbb0a9
--- /dev/null
+++ b/toolkit/mozapps/update/tests/Makefile.in
@@ -0,0 +1,13 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+ifndef MOZ_WINCONSOLE
+ifdef MOZ_DEBUG
+MOZ_WINCONSOLE = 1
+else
+MOZ_WINCONSOLE = 0
+endif
+endif
+
+include $(topsrcdir)/config/rules.mk
diff --git a/toolkit/mozapps/update/tests/TestAUSHelper.cpp b/toolkit/mozapps/update/tests/TestAUSHelper.cpp
new file mode 100644
index 0000000000..d1b32e3caf
--- /dev/null
+++ b/toolkit/mozapps/update/tests/TestAUSHelper.cpp
@@ -0,0 +1,462 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+#include "updatedefines.h"
+
+#ifdef XP_WIN
+# include "commonupdatedir.h"
+# include "updatehelper.h"
+# include "certificatecheck.h"
+# define NS_main wmain
+# define NS_tgetcwd _wgetcwd
+# define NS_ttoi _wtoi
+#else
+# define NS_main main
+# define NS_tgetcwd getcwd
+# define NS_ttoi atoi
+#endif
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <errno.h>
+#include <string.h>
+#include <sys/stat.h>
+
+static void WriteMsg(const NS_tchar* path, const char* status) {
+ FILE* outFP = NS_tfopen(path, NS_T("wb"));
+ if (!outFP) {
+ return;
+ }
+
+ fprintf(outFP, "%s\n", status);
+ fclose(outFP);
+ outFP = nullptr;
+}
+
+static bool CheckMsg(const NS_tchar* path, const char* expected) {
+ FILE* inFP = NS_tfopen(path, NS_T("rb"));
+ if (!inFP) {
+ return false;
+ }
+
+ struct stat ms;
+ if (fstat(fileno(inFP), &ms)) {
+ fclose(inFP);
+ inFP = nullptr;
+ return false;
+ }
+
+ char* mbuf = (char*)malloc(ms.st_size + 1);
+ if (!mbuf) {
+ fclose(inFP);
+ inFP = nullptr;
+ return false;
+ }
+
+ size_t r = ms.st_size;
+ char* rb = mbuf;
+ size_t c = fread(rb, sizeof(char), 50, inFP);
+ r -= c;
+ if (c == 0 && r) {
+ free(mbuf);
+ fclose(inFP);
+ inFP = nullptr;
+ return false;
+ }
+ mbuf[ms.st_size] = '\0';
+ rb = mbuf;
+
+ bool isMatch = strcmp(rb, expected) == 0;
+ free(mbuf);
+ fclose(inFP);
+ inFP = nullptr;
+ return isMatch;
+}
+
+int NS_main(int argc, NS_tchar** argv) {
+ if (argc == 2) {
+ if (!NS_tstrcmp(argv[1], NS_T("post-update-async")) ||
+ !NS_tstrcmp(argv[1], NS_T("post-update-sync"))) {
+ NS_tchar exePath[MAXPATHLEN];
+#ifdef XP_WIN
+ if (!::GetModuleFileNameW(0, exePath, MAXPATHLEN)) {
+ return 1;
+ }
+#else
+ if (!NS_tvsnprintf(exePath, sizeof(exePath) / sizeof(exePath[0]),
+ NS_T("%s"), argv[0])) {
+ return 1;
+ }
+#endif
+ NS_tchar runFilePath[MAXPATHLEN];
+ if (!NS_tvsnprintf(runFilePath,
+ sizeof(runFilePath) / sizeof(runFilePath[0]),
+ NS_T("%s.running"), exePath)) {
+ return 1;
+ }
+#ifdef XP_WIN
+ if (!NS_taccess(runFilePath, F_OK)) {
+ // This makes it possible to check if the post update process was
+ // launched twice which happens when the service performs an update.
+ NS_tchar runFilePathBak[MAXPATHLEN];
+ if (!NS_tvsnprintf(runFilePathBak,
+ sizeof(runFilePathBak) / sizeof(runFilePathBak[0]),
+ NS_T("%s.bak"), runFilePath)) {
+ return 1;
+ }
+ MoveFileExW(runFilePath, runFilePathBak, MOVEFILE_REPLACE_EXISTING);
+ }
+#endif
+ WriteMsg(runFilePath, "running");
+
+ if (!NS_tstrcmp(argv[1], NS_T("post-update-sync"))) {
+#ifdef XP_WIN
+ Sleep(2000);
+#else
+ sleep(2);
+#endif
+ }
+
+ NS_tchar logFilePath[MAXPATHLEN];
+ if (!NS_tvsnprintf(logFilePath,
+ sizeof(logFilePath) / sizeof(logFilePath[0]),
+ NS_T("%s.log"), exePath)) {
+ return 1;
+ }
+ WriteMsg(logFilePath, "post-update");
+ return 0;
+ }
+ }
+
+ if (argc < 3) {
+ fprintf(
+ stderr,
+ "\n"
+ "Application Update Service Test Helper\n"
+ "\n"
+ "Usage: WORKINGDIR INFILE OUTFILE -s SECONDS [FILETOLOCK]\n"
+ " or: WORKINGDIR LOGFILE [ARG2 ARG3...]\n"
+ " or: signature-check filepath\n"
+ " or: setup-symlink dir1 dir2 file symlink\n"
+ " or: remove-symlink dir1 dir2 file symlink\n"
+ " or: check-symlink symlink\n"
+ " or: check-umask existing-umask\n"
+ " or: post-update\n"
+ " or: create-update-dir\n"
+ "\n"
+ " WORKINGDIR \tThe relative path to the working directory to use.\n"
+ " INFILE \tThe relative path from the working directory for the "
+ "file to\n"
+ " \tread actions to perform such as finish.\n"
+ " OUTFILE \tThe relative path from the working directory for the "
+ "file to\n"
+ " \twrite status information.\n"
+ " SECONDS \tThe number of seconds to sleep.\n"
+ " FILETOLOCK \tThe relative path from the working directory to an "
+ "existing\n"
+ " \tfile to open exlusively.\n"
+ " \tOnly available on Windows platforms and silently "
+ "ignored on\n"
+ " \tother platforms.\n"
+ " LOGFILE \tThe relative path from the working directory to log "
+ "the\n"
+ " \tcommand line arguments.\n"
+ " ARG2 ARG3...\tArguments to write to the LOGFILE after the preceding "
+ "command\n"
+ " \tline arguments.\n"
+ "\n"
+ "Note: All paths must be relative.\n"
+ "\n");
+ return 1;
+ }
+
+ if (!NS_tstrcmp(argv[1], NS_T("check-signature"))) {
+#if defined(XP_WIN) && defined(MOZ_MAINTENANCE_SERVICE)
+ if (ERROR_SUCCESS == VerifyCertificateTrustForFile(argv[2])) {
+ return 0;
+ } else {
+ return 1;
+ }
+#else
+ // Not implemented on non-Windows platforms
+ return 1;
+#endif
+ }
+
+ if (!NS_tstrcmp(argv[1], NS_T("setup-symlink"))) {
+#ifdef XP_UNIX
+ NS_tchar path[MAXPATHLEN];
+ if (!NS_tvsnprintf(path, sizeof(path) / sizeof(path[0]), NS_T("%s/%s"),
+ NS_T("/tmp"), argv[2])) {
+ return 1;
+ }
+ if (mkdir(path, 0755)) {
+ return 1;
+ }
+ if (!NS_tvsnprintf(path, sizeof(path) / sizeof(path[0]), NS_T("%s/%s/%s"),
+ NS_T("/tmp"), argv[2], argv[3])) {
+ return 1;
+ }
+ if (mkdir(path, 0755)) {
+ return 1;
+ }
+ if (!NS_tvsnprintf(path, sizeof(path) / sizeof(path[0]),
+ NS_T("%s/%s/%s/%s"), NS_T("/tmp"), argv[2], argv[3],
+ argv[4])) {
+ return 1;
+ }
+ FILE* file = NS_tfopen(path, NS_T("w"));
+ if (file) {
+ fputs(NS_T("test"), file);
+ fclose(file);
+ }
+ if (symlink(path, argv[5]) != 0) {
+ return 1;
+ }
+ if (!NS_tvsnprintf(path, sizeof(path) / sizeof(path[0]), NS_T("%s/%s"),
+ NS_T("/tmp"), argv[2])) {
+ return 1;
+ }
+ if (argc > 6 && !NS_tstrcmp(argv[6], NS_T("change-perm"))) {
+ if (chmod(path, 0644)) {
+ return 1;
+ }
+ }
+ return 0;
+#else
+ // Not implemented on non-Unix platforms
+ return 1;
+#endif
+ }
+
+ if (!NS_tstrcmp(argv[1], NS_T("remove-symlink"))) {
+#ifdef XP_UNIX
+ // The following can be called at the start of a test in case these symlinks
+ // need to be removed if they already exist and at the end of a test to
+ // remove the symlinks created by the test so ignore file doesn't exist
+ // errors.
+ NS_tchar path[MAXPATHLEN];
+ if (!NS_tvsnprintf(path, sizeof(path) / sizeof(path[0]), NS_T("%s/%s"),
+ NS_T("/tmp"), argv[2])) {
+ return 1;
+ }
+ if (chmod(path, 0755) && errno != ENOENT) {
+ return 1;
+ }
+ if (!NS_tvsnprintf(path, sizeof(path) / sizeof(path[0]),
+ NS_T("%s/%s/%s/%s"), NS_T("/tmp"), argv[2], argv[3],
+ argv[4])) {
+ return 1;
+ }
+ if (unlink(path) && errno != ENOENT) {
+ return 1;
+ }
+ if (!NS_tvsnprintf(path, sizeof(path) / sizeof(path[0]), NS_T("%s/%s/%s"),
+ NS_T("/tmp"), argv[2], argv[3])) {
+ return 1;
+ }
+ if (rmdir(path) && errno != ENOENT) {
+ return 1;
+ }
+ if (!NS_tvsnprintf(path, sizeof(path) / sizeof(path[0]), NS_T("%s/%s"),
+ NS_T("/tmp"), argv[2])) {
+ return 1;
+ }
+ if (rmdir(path) && errno != ENOENT) {
+ return 1;
+ }
+ return 0;
+#else
+ // Not implemented on non-Unix platforms
+ return 1;
+#endif
+ }
+
+ if (!NS_tstrcmp(argv[1], NS_T("check-symlink"))) {
+#ifdef XP_UNIX
+ struct stat ss;
+ if (lstat(argv[2], &ss)) {
+ return 1;
+ }
+ return S_ISLNK(ss.st_mode) ? 0 : 1;
+#else
+ // Not implemented on non-Unix platforms
+ return 1;
+#endif
+ }
+
+ if (!NS_tstrcmp(argv[1], NS_T("check-umask"))) {
+#ifdef XP_UNIX
+ // Discover the current value of the umask. There is no way to read the
+ // umask without changing it. The system call is specified as unable to
+ // fail.
+ uint32_t umask = ::umask(0777);
+ ::umask(umask);
+
+ NS_tchar logFilePath[MAXPATHLEN];
+ if (!NS_tvsnprintf(logFilePath,
+ sizeof(logFilePath) / sizeof(logFilePath[0]), NS_T("%s"),
+ argv[2])) {
+ return 1;
+ }
+
+ FILE* logFP = NS_tfopen(logFilePath, NS_T("wb"));
+ if (!logFP) {
+ return 1;
+ }
+ fprintf(logFP, "check-umask\numask-%d\n", umask);
+
+ fclose(logFP);
+ logFP = nullptr;
+
+ return 0;
+#else
+ // Not implemented on non-Unix platforms
+ return 1;
+#endif
+ }
+
+ if (!NS_tstrcmp(argv[1], NS_T("wait-for-service-stop"))) {
+#if defined(XP_WIN) && defined(MOZ_MAINTENANCE_SERVICE)
+ const int maxWaitSeconds = NS_ttoi(argv[3]);
+ LPCWSTR serviceName = argv[2];
+ DWORD serviceState = WaitForServiceStop(serviceName, maxWaitSeconds);
+ if (SERVICE_STOPPED == serviceState) {
+ return 0;
+ } else {
+ return serviceState;
+ }
+#else
+ // Not implemented on non-Windows platforms
+ return 1;
+#endif
+ }
+
+ if (!NS_tstrcmp(argv[1], NS_T("wait-for-application-exit"))) {
+#ifdef XP_WIN
+ const int maxWaitSeconds = NS_ttoi(argv[3]);
+ LPCWSTR application = argv[2];
+ DWORD ret = WaitForProcessExit(application, maxWaitSeconds);
+ if (ERROR_SUCCESS == ret) {
+ return 0;
+ } else if (WAIT_TIMEOUT == ret) {
+ return 1;
+ } else {
+ return 2;
+ }
+#else
+ // Not implemented on non-Windows platforms
+ return 1;
+#endif
+ }
+
+ if (!NS_tstrcmp(argv[1], NS_T("launch-service"))) {
+#if defined(XP_WIN) && defined(MOZ_MAINTENANCE_SERVICE)
+ DWORD ret =
+ LaunchServiceSoftwareUpdateCommand(argc - 2, (LPCWSTR*)argv + 2);
+ if (ret != ERROR_SUCCESS) {
+ // 192 is used to avoid reusing a possible return value from the call to
+ // WaitForServiceStop
+ return 0x000000C0;
+ }
+ // Wait a maximum of 120 seconds.
+ DWORD lastState = WaitForServiceStop(SVC_NAME, 120);
+ if (SERVICE_STOPPED == lastState) {
+ return 0;
+ }
+ return lastState;
+#else
+ // Not implemented on non-Windows platforms
+ return 1;
+#endif
+ }
+
+ if (!NS_tstrcmp(argv[1], NS_T("create-update-dir"))) {
+#ifdef XP_WIN
+ mozilla::UniquePtr<wchar_t[]> updateDir;
+ HRESULT result = GetCommonUpdateDirectory(argv[2], updateDir);
+ return SUCCEEDED(result) ? 0 : 1;
+#else
+ // Not implemented on non-Windows platforms
+ return 1;
+#endif
+ }
+
+ if (NS_tchdir(argv[1]) != 0) {
+ return 1;
+ }
+
+ // File in use test helper section
+ if (!NS_tstrcmp(argv[4], NS_T("-s"))) {
+ // Note: glibc's getcwd() allocates the buffer dynamically using malloc(3)
+ // if buf (the 1st param) is NULL so free cwd when it is no longer needed.
+ NS_tchar* cwd = NS_tgetcwd(nullptr, 0);
+ NS_tchar inFilePath[MAXPATHLEN];
+ if (!NS_tvsnprintf(inFilePath, sizeof(inFilePath) / sizeof(inFilePath[0]),
+ NS_T("%s/%s"), cwd, argv[2])) {
+ return 1;
+ }
+ NS_tchar outFilePath[MAXPATHLEN];
+ if (!NS_tvsnprintf(outFilePath,
+ sizeof(outFilePath) / sizeof(outFilePath[0]),
+ NS_T("%s/%s"), cwd, argv[3])) {
+ return 1;
+ }
+ free(cwd);
+
+ int seconds = NS_ttoi(argv[5]);
+#ifdef XP_WIN
+ HANDLE hFile = INVALID_HANDLE_VALUE;
+ if (argc == 7) {
+ hFile = CreateFileW(argv[6], DELETE | GENERIC_WRITE, 0, nullptr,
+ OPEN_EXISTING, 0, nullptr);
+ if (hFile == INVALID_HANDLE_VALUE) {
+ WriteMsg(outFilePath, "error_locking");
+ return 1;
+ }
+ }
+
+ WriteMsg(outFilePath, "sleeping");
+ int i = 0;
+ while (!CheckMsg(inFilePath, "finish\n") && i++ <= seconds) {
+ Sleep(1000);
+ }
+
+ if (argc == 7) {
+ CloseHandle(hFile);
+ }
+#else
+ WriteMsg(outFilePath, "sleeping");
+ int i = 0;
+ while (!CheckMsg(inFilePath, "finish\n") && i++ <= seconds) {
+ sleep(1);
+ }
+#endif
+ WriteMsg(outFilePath, "finished");
+ return 0;
+ }
+
+ {
+ // Command line argument test helper section
+ NS_tchar logFilePath[MAXPATHLEN];
+ if (!NS_tvsnprintf(logFilePath,
+ sizeof(logFilePath) / sizeof(logFilePath[0]), NS_T("%s"),
+ argv[2])) {
+ return 1;
+ }
+
+ FILE* logFP = NS_tfopen(logFilePath, NS_T("wb"));
+ if (!logFP) {
+ return 1;
+ }
+ for (int i = 1; i < argc; ++i) {
+ fprintf(logFP, LOG_S "\n", argv[i]);
+ }
+
+ fclose(logFP);
+ logFP = nullptr;
+ }
+
+ return 0;
+}
diff --git a/toolkit/mozapps/update/tests/TestAUSReadStrings.cpp b/toolkit/mozapps/update/tests/TestAUSReadStrings.cpp
new file mode 100644
index 0000000000..d4c090772b
--- /dev/null
+++ b/toolkit/mozapps/update/tests/TestAUSReadStrings.cpp
@@ -0,0 +1,210 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/**
+ * This binary tests the updater's ReadStrings ini parser and should run in a
+ * directory with a Unicode character to test bug 473417.
+ */
+#ifdef XP_WIN
+# include <windows.h>
+# define NS_main wmain
+# define PATH_SEPARATOR_CHAR L'\\'
+// On Windows, argv[0] can also have forward slashes instead
+# define ALT_PATH_SEPARATOR_CHAR L'/'
+#else
+# define NS_main main
+# define PATH_SEPARATOR_CHAR '/'
+#endif
+
+#include <stdio.h>
+#include <stdarg.h>
+#include <string.h>
+
+#include "updater/progressui.h"
+#include "common/readstrings.h"
+#include "common/updatererrors.h"
+#include "common/updatedefines.h"
+#include "mozilla/ArrayUtils.h"
+
+#ifndef MAXPATHLEN
+# ifdef PATH_MAX
+# define MAXPATHLEN PATH_MAX
+# elif defined(MAX_PATH)
+# define MAXPATHLEN MAX_PATH
+# elif defined(_MAX_PATH)
+# define MAXPATHLEN _MAX_PATH
+# elif defined(CCHMAXPATH)
+# define MAXPATHLEN CCHMAXPATH
+# else
+# define MAXPATHLEN 1024
+# endif
+#endif
+
+#define TEST_NAME "Updater ReadStrings"
+
+using namespace mozilla;
+
+static int gFailCount = 0;
+
+/**
+ * Prints the given failure message and arguments using printf, prepending
+ * "TEST-UNEXPECTED-FAIL " for the benefit of the test harness and
+ * appending "\n" to eliminate having to type it at each call site.
+ */
+void fail(const char* msg, ...) {
+ va_list ap;
+
+ printf("TEST-UNEXPECTED-FAIL | ");
+
+ va_start(ap, msg);
+ vprintf(msg, ap);
+ va_end(ap);
+
+ putchar('\n');
+ ++gFailCount;
+}
+
+int NS_main(int argc, NS_tchar** argv) {
+ printf("Running TestAUSReadStrings tests\n");
+
+ int rv = 0;
+ int retval;
+ NS_tchar inifile[MAXPATHLEN];
+ StringTable testStrings;
+
+ NS_tchar* slash = NS_tstrrchr(argv[0], PATH_SEPARATOR_CHAR);
+#ifdef ALT_PATH_SEPARATOR_CHAR
+ NS_tchar* altslash = NS_tstrrchr(argv[0], ALT_PATH_SEPARATOR_CHAR);
+ slash = (slash > altslash) ? slash : altslash;
+#endif // ALT_PATH_SEPARATOR_CHAR
+
+ if (!slash) {
+ fail("%s | unable to find platform specific path separator (check 1)",
+ TEST_NAME);
+ return 20;
+ }
+
+ *(++slash) = '\0';
+ // Test success when the ini file exists with both Title and Info in the
+ // Strings section and the values for Title and Info.
+ NS_tsnprintf(inifile, ArrayLength(inifile), NS_T("%sTestAUSReadStrings1.ini"),
+ argv[0]);
+ retval = ReadStrings(inifile, &testStrings);
+ if (retval == OK) {
+ if (strcmp(testStrings.title.get(),
+ "Title Test - \xD0\x98\xD1\x81\xD0\xBF\xD1\x8B"
+ "\xD1\x82\xD0\xB0\xD0\xBD\xD0\xB8\xD0\xB5 "
+ "\xCE\x94\xCE\xBF\xCE\xBA\xCE\xB9\xCE\xBC\xCE\xAE "
+ "\xE3\x83\x86\xE3\x82\xB9\xE3\x83\x88 "
+ "\xE6\xB8\xAC\xE8\xA9\xA6 "
+ "\xE6\xB5\x8B\xE8\xAF\x95") != 0) {
+ rv = 21;
+ fail("%s | Title ini value incorrect (check 3)", TEST_NAME);
+ }
+
+ if (strcmp(testStrings.info.get(),
+ "Info Test - \xD0\x98\xD1\x81\xD0\xBF\xD1\x8B"
+ "\xD1\x82\xD0\xB0\xD0\xBD\xD0\xB8\xD0\xB5 "
+ "\xCE\x94\xCE\xBF\xCE\xBA\xCE\xB9\xCE\xBC\xCE\xAE "
+ "\xE3\x83\x86\xE3\x82\xB9\xE3\x83\x88 "
+ "\xE6\xB8\xAC\xE8\xA9\xA6 "
+ "\xE6\xB5\x8B\xE8\xAF\x95\xE2\x80\xA6") != 0) {
+ rv = 22;
+ fail("%s | Info ini value incorrect (check 4)", TEST_NAME);
+ }
+ } else {
+ fail("%s | ReadStrings returned %i (check 2)", TEST_NAME, retval);
+ rv = 23;
+ }
+
+ // Test failure when the ini file exists without Title and with Info in the
+ // Strings section.
+ NS_tsnprintf(inifile, ArrayLength(inifile), NS_T("%sTestAUSReadStrings2.ini"),
+ argv[0]);
+ retval = ReadStrings(inifile, &testStrings);
+ if (retval != PARSE_ERROR) {
+ rv = 24;
+ fail("%s | ReadStrings returned %i (check 5)", TEST_NAME, retval);
+ }
+
+ // Test failure when the ini file exists with Title and without Info in the
+ // Strings section.
+ NS_tsnprintf(inifile, ArrayLength(inifile), NS_T("%sTestAUSReadStrings3.ini"),
+ argv[0]);
+ retval = ReadStrings(inifile, &testStrings);
+ if (retval != PARSE_ERROR) {
+ rv = 25;
+ fail("%s | ReadStrings returned %i (check 6)", TEST_NAME, retval);
+ }
+
+ // Test failure when the ini file doesn't exist
+ NS_tsnprintf(inifile, ArrayLength(inifile),
+ NS_T("%sTestAUSReadStringsBogus.ini"), argv[0]);
+ retval = ReadStrings(inifile, &testStrings);
+ if (retval != READ_ERROR) {
+ rv = 26;
+ fail("%s | ini file doesn't exist (check 7)", TEST_NAME);
+ }
+
+ // Test reading a non-default section name
+ NS_tsnprintf(inifile, ArrayLength(inifile), NS_T("%sTestAUSReadStrings3.ini"),
+ argv[0]);
+ retval =
+ ReadStrings(inifile, "Title\0", 1, &testStrings.title, "BogusSection2");
+ if (retval == OK) {
+ if (strcmp(testStrings.title.get(), "Bogus Title") != 0) {
+ rv = 27;
+ fail("%s | Title ini value incorrect (check 9)", TEST_NAME);
+ }
+ } else {
+ fail("%s | ReadStrings returned %i (check 8)", TEST_NAME, retval);
+ rv = 28;
+ }
+
+ // Test reading an exceedingly long string
+ NS_tsnprintf(inifile, ArrayLength(inifile), NS_T("%sTestAUSReadStrings4.ini"),
+ argv[0]);
+ retval = ReadStrings(inifile, "LongValue\0", 1, &testStrings.title);
+ const char* expectedValue =
+ "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nullam id "
+ "ipsum condimentum, faucibus ante porta, vehicula metus. Nunc nec luctus "
+ "lorem. Nunc mattis viverra nisl, eu ornare dui feugiat id. Aenean "
+ "commodo ligula porttitor elit aliquam, ut luctus nunc aliquam. In eu "
+ "eros at nunc pulvinar porta. Praesent porta felis vitae massa "
+ "sollicitudin, a vestibulum dolor rutrum. Aenean finibus, felis ac "
+ "dictum hendrerit, ligula arcu semper enim, rhoncus consequat arcu orci "
+ "nec est. Sed auctor hendrerit rhoncus. Maecenas dignissim lorem et "
+ "tellus maximus, sit amet pretium urna imperdiet. Duis ut libero "
+ "volutpat, rhoncus mi non, placerat lacus. Nunc id tortor in quam "
+ "lacinia luctus. Nam eu maximus ipsum, eu bibendum enim. Ut iaculis "
+ "maximus ipsum in condimentum. Aliquam tellus nulla, congue quis pretium "
+ "a, posuere quis ligula. Donec vel quam ipsum. Pellentesque congue urna "
+ "eget porttitor pulvinar. Proin non risus lacus. Vestibulum molestie et "
+ "ligula sit amet pellentesque. Phasellus luctus auctor lorem, vel "
+ "dapibus ante iaculis sed. Cras ligula ex, vehicula a dui vel, posuere "
+ "fermentum elit. Vestibulum et nisi at libero maximus interdum a non ex. "
+ "Ut ut leo in metus convallis porta a et libero. Pellentesque fringilla "
+ "dolor sit amet eleifend fermentum. Quisque blandit dolor facilisis "
+ "purus vulputate sodales eget ac arcu. Nulla pulvinar feugiat accumsan. "
+ "Phasellus auctor nisl eget diam auctor, sit amet imperdiet mauris "
+ "condimentum. In a risus ut felis lobortis facilisis.";
+ if (retval == OK) {
+ if (strcmp(testStrings.title.get(), expectedValue) != 0) {
+ rv = 29;
+ fail("%s | LongValue ini value incorrect (check 10)", TEST_NAME);
+ }
+ } else {
+ fail("%s | ReadStrings returned %i (check 11)", TEST_NAME, retval);
+ rv = 30;
+ }
+
+ if (rv == 0) {
+ printf("TEST-PASS | %s | all checks passed\n", TEST_NAME);
+ } else {
+ fail("%s | %i out of 9 checks failed", TEST_NAME, gFailCount);
+ }
+
+ return rv;
+}
diff --git a/toolkit/mozapps/update/tests/TestAUSReadStrings1.ini b/toolkit/mozapps/update/tests/TestAUSReadStrings1.ini
new file mode 100644
index 0000000000..5ab13c185d
--- /dev/null
+++ b/toolkit/mozapps/update/tests/TestAUSReadStrings1.ini
@@ -0,0 +1,47 @@
+; This file is in the UTF-8 encoding
+
+[BogusSection1]
+
+; Comment
+
+Title=Bogus Title
+
+; Comment
+
+Info=Bogus Info
+
+; Comment
+
+[Strings]
+
+Bogus1=Bogus1
+
+; Comment
+
+Title=Title Test - Испытание Δοκιμή テスト 測試 测试
+
+; Comment
+
+Bogus2=Bogus2
+
+; Comment
+
+Info=Info Test - Испытание Δοκιμή テスト 測試 测试…
+
+; Comment
+
+Bogus3=Bogus3
+
+; Comment
+
+[BogusSection2]
+
+; Comment
+
+Title=Bogus Title
+
+; Comment
+
+Info=Bogus Info
+
+; Comment
diff --git a/toolkit/mozapps/update/tests/TestAUSReadStrings2.ini b/toolkit/mozapps/update/tests/TestAUSReadStrings2.ini
new file mode 100644
index 0000000000..8291a7c94c
--- /dev/null
+++ b/toolkit/mozapps/update/tests/TestAUSReadStrings2.ini
@@ -0,0 +1,39 @@
+; This file is in the UTF-8 encoding
+
+[BogusSection1]
+
+; Comment
+
+Title=Bogus Title
+
+; Comment
+
+Info=Bogus Info
+
+; Comment
+
+[Strings]
+
+Bogus1=Bogus1
+
+; Comment
+
+Info=Info
+
+; Comment
+
+Bogus2=Bogus2
+
+; Comment
+
+[BogusSection2]
+
+; Comment
+
+Title=Bogus Title
+
+; Comment
+
+Info=Bogus Info
+
+; Comment
diff --git a/toolkit/mozapps/update/tests/TestAUSReadStrings3.ini b/toolkit/mozapps/update/tests/TestAUSReadStrings3.ini
new file mode 100644
index 0000000000..a64d1232e2
--- /dev/null
+++ b/toolkit/mozapps/update/tests/TestAUSReadStrings3.ini
@@ -0,0 +1,39 @@
+; This file is in the UTF-8 encoding
+
+[BogusSection1]
+
+; Comment
+
+Title=Bogus Title
+
+; Comment
+
+Info=Bogus Info
+
+; Comment
+
+[Strings]
+
+Bogus1=Bogus1
+
+; Comment
+
+Title=Title
+
+; Comment
+
+Bogus2=Bogus2
+
+; Comment
+
+[BogusSection2]
+
+; Comment
+
+Title=Bogus Title
+
+; Comment
+
+Info=Bogus Info
+
+; Comment
diff --git a/toolkit/mozapps/update/tests/TestAUSReadStrings4.ini b/toolkit/mozapps/update/tests/TestAUSReadStrings4.ini
new file mode 100644
index 0000000000..b930455e8b
--- /dev/null
+++ b/toolkit/mozapps/update/tests/TestAUSReadStrings4.ini
@@ -0,0 +1,5 @@
+; This file is in the UTF-8 encoding
+
+[Strings]
+
+LongValue=Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nullam id ipsum condimentum, faucibus ante porta, vehicula metus. Nunc nec luctus lorem. Nunc mattis viverra nisl, eu ornare dui feugiat id. Aenean commodo ligula porttitor elit aliquam, ut luctus nunc aliquam. In eu eros at nunc pulvinar porta. Praesent porta felis vitae massa sollicitudin, a vestibulum dolor rutrum. Aenean finibus, felis ac dictum hendrerit, ligula arcu semper enim, rhoncus consequat arcu orci nec est. Sed auctor hendrerit rhoncus. Maecenas dignissim lorem et tellus maximus, sit amet pretium urna imperdiet. Duis ut libero volutpat, rhoncus mi non, placerat lacus. Nunc id tortor in quam lacinia luctus. Nam eu maximus ipsum, eu bibendum enim. Ut iaculis maximus ipsum in condimentum. Aliquam tellus nulla, congue quis pretium a, posuere quis ligula. Donec vel quam ipsum. Pellentesque congue urna eget porttitor pulvinar. Proin non risus lacus. Vestibulum molestie et ligula sit amet pellentesque. Phasellus luctus auctor lorem, vel dapibus ante iaculis sed. Cras ligula ex, vehicula a dui vel, posuere fermentum elit. Vestibulum et nisi at libero maximus interdum a non ex. Ut ut leo in metus convallis porta a et libero. Pellentesque fringilla dolor sit amet eleifend fermentum. Quisque blandit dolor facilisis purus vulputate sodales eget ac arcu. Nulla pulvinar feugiat accumsan. Phasellus auctor nisl eget diam auctor, sit amet imperdiet mauris condimentum. In a risus ut felis lobortis facilisis.
diff --git a/toolkit/mozapps/update/tests/browser/browser.bits.ini b/toolkit/mozapps/update/tests/browser/browser.bits.ini
new file mode 100644
index 0000000000..02dde57751
--- /dev/null
+++ b/toolkit/mozapps/update/tests/browser/browser.bits.ini
@@ -0,0 +1,81 @@
+[DEFAULT]
+skip-if =
+ os != 'win'
+ msix # Updater is disabled in MSIX builds
+reason = BITS is only available on Windows.
+dupe-manifest =
+tags = appupdate bits
+head = head.js
+support-files =
+ ../data/shared.js
+ ../data/sharedUpdateXML.js
+ ../data/app_update.sjs
+ downloadPage.html
+ testConstants.js
+
+prefs =
+ app.update.BITS.enabled=true
+ app.update.langpack.enabled=true
+
+# BITS Download Tests
+#####################
+
+# About Dialog Application Update Tests
+[browser_aboutDialog_bc_downloaded.js]
+[browser_aboutDialog_bc_downloaded_staged.js]
+[browser_aboutDialog_bc_downloaded_staging.js]
+[browser_aboutDialog_bc_downloaded_stagingFailure.js]
+[browser_aboutDialog_bc_downloading.js]
+[browser_aboutDialog_bc_downloading_notify.js]
+[browser_aboutDialog_bc_downloading_staging.js]
+[browser_aboutDialog_bc_multiUpdate.js]
+[browser_aboutDialog_fc_downloadAuto.js]
+[browser_aboutDialog_fc_downloadAuto_staging.js]
+[browser_aboutDialog_fc_downloadOptIn.js]
+[browser_aboutDialog_fc_downloadOptIn_staging.js]
+[browser_aboutDialog_fc_patch_completeBadSize.js]
+[browser_aboutDialog_fc_patch_partialBadSize.js]
+[browser_aboutDialog_fc_patch_partialBadSize_complete.js]
+[browser_aboutDialog_fc_patch_partialBadSize_completeBadSize.js]
+
+# about:preferences Application Update Tests
+[browser_aboutPrefs_bc_downloaded.js]
+[browser_aboutPrefs_bc_downloaded_staged.js]
+[browser_aboutPrefs_bc_downloaded_staging.js]
+[browser_aboutPrefs_bc_downloaded_stagingFailure.js]
+[browser_aboutPrefs_bc_downloading.js]
+[browser_aboutPrefs_bc_downloading_staging.js]
+[browser_aboutPrefs_bc_multiUpdate.js]
+[browser_aboutPrefs_fc_downloadAuto.js]
+[browser_aboutPrefs_fc_downloadAuto_staging.js]
+[browser_aboutPrefs_fc_downloadOptIn.js]
+[browser_aboutPrefs_fc_downloadOptIn_staging.js]
+[browser_aboutPrefs_fc_patch_completeBadSize.js]
+[browser_aboutPrefs_fc_patch_partialBadSize.js]
+[browser_aboutPrefs_fc_patch_partialBadSize_complete.js]
+[browser_aboutPrefs_fc_patch_partialBadSize_completeBadSize.js]
+
+# Doorhanger Application Update Tests
+[browser_doorhanger_bc_downloadAutoFailures.js]
+[browser_doorhanger_bc_downloadAutoFailures_bgWin.js]
+[browser_doorhanger_bc_downloadOptIn.js]
+[browser_doorhanger_bc_downloadOptIn_bgWin.js]
+[browser_doorhanger_bc_downloadOptIn_staging.js]
+[browser_doorhanger_bc_downloaded.js]
+[browser_doorhanger_bc_downloaded_disableBITS.js]
+[browser_doorhanger_bc_downloaded_staged.js]
+[browser_doorhanger_bc_multiUpdate.js]
+[browser_doorhanger_bc_multiUpdate_promptWaitTime.js]
+[browser_doorhanger_bc_patch_completeBadSize.js]
+[browser_doorhanger_bc_patch_partialBadSize.js]
+[browser_doorhanger_bc_patch_partialBadSize_complete.js]
+[browser_doorhanger_bc_patch_partialBadSize_completeBadSize.js]
+[browser_doorhanger_sp_patch_completeApplyFailure.js]
+[browser_doorhanger_sp_patch_partialApplyFailure.js]
+[browser_doorhanger_sp_patch_partialApplyFailure_complete.js]
+[browser_doorhanger_sp_patch_partialApplyFailure_completeBadSize.js]
+[browser_doorhanger_sp_patch_partialApplyFailure_complete_staging.js]
+
+# Telemetry Update Ping Tests
+[browser_telemetry_updatePing_downloaded_ready.js]
+[browser_telemetry_updatePing_staged_ready.js]
diff --git a/toolkit/mozapps/update/tests/browser/browser.ini b/toolkit/mozapps/update/tests/browser/browser.ini
new file mode 100644
index 0000000000..f2d7313c94
--- /dev/null
+++ b/toolkit/mozapps/update/tests/browser/browser.ini
@@ -0,0 +1,122 @@
+[DEFAULT]
+dupe-manifest =
+tags = appupdate internal
+head = head.js
+support-files =
+ ../data/shared.js
+ ../data/sharedUpdateXML.js
+ ../data/app_update.sjs
+ downloadPage.html
+ testConstants.js
+prefs =
+ app.update.BITS.enabled=false
+ app.update.langpack.enabled=true
+skip-if = os == 'win' && msix # Updater is disabled in MSIX builds
+
+# About Dialog Application Update Tests
+[browser_aboutDialog_AppUpdater_stop_checking.js]
+[browser_aboutDialog_AppUpdater_stop_download_and_install.js]
+[browser_aboutDialog_AppUpdater_stop_download_failed.js]
+[browser_aboutDialog_AppUpdater_stop_downloading.js]
+[browser_aboutDialog_AppUpdater_stop_internal_error.js]
+[browser_aboutDialog_AppUpdater_stop_no_update.js]
+[browser_aboutDialog_AppUpdater_stop_ready_for_restart.js]
+[browser_aboutDialog_AppUpdater_stop_staging.js]
+[browser_aboutDialog_AppUpdater_stop_swap.js]
+[browser_aboutDialog_bc_downloaded.js]
+[browser_aboutDialog_bc_downloaded_staged.js]
+[browser_aboutDialog_bc_downloaded_staging.js]
+[browser_aboutDialog_bc_downloaded_stagingFailure.js]
+[browser_aboutDialog_bc_downloading.js]
+[browser_aboutDialog_bc_downloading_notify.js]
+[browser_aboutDialog_bc_downloading_staging.js]
+[browser_aboutDialog_bc_multiUpdate.js]
+[browser_aboutDialog_fc_apply_blocked.js]
+[browser_aboutDialog_fc_check_cantApply.js]
+skip-if = os != 'win'
+reason = test must be able to prevent file deletion.
+[browser_aboutDialog_fc_check_malformedXML.js]
+[browser_aboutDialog_fc_check_noUpdate.js]
+[browser_aboutDialog_fc_check_otherInstance.js]
+skip-if = os != 'win'
+reason = Windows only feature.
+[browser_aboutDialog_fc_check_unsupported.js]
+[browser_aboutDialog_fc_downloadAuto.js]
+skip-if = tsan # Bug 1683730
+[browser_aboutDialog_fc_downloadAuto_staging.js]
+[browser_aboutDialog_fc_downloadOptIn.js]
+[browser_aboutDialog_fc_downloadOptIn_staging.js]
+[browser_aboutDialog_fc_network_failure.js]
+[browser_aboutDialog_fc_network_offline.js]
+[browser_aboutDialog_fc_patch_completeBadSize.js]
+[browser_aboutDialog_fc_patch_partialBadSize.js]
+[browser_aboutDialog_fc_patch_partialBadSize_complete.js]
+[browser_aboutDialog_fc_patch_partialBadSize_completeBadSize.js]
+[browser_aboutDialog_internalError.js]
+[browser_aboutPrefs_backgroundUpdateSetting.js]
+[browser_aboutPrefs_bc_downloaded.js]
+[browser_aboutPrefs_bc_downloaded_staged.js]
+[browser_aboutPrefs_bc_downloaded_staging.js]
+[browser_aboutPrefs_bc_downloaded_stagingFailure.js]
+
+# about:preferences Application Update Tests
+[browser_aboutPrefs_bc_downloading.js]
+[browser_aboutPrefs_bc_downloading_staging.js]
+[browser_aboutPrefs_bc_multiUpdate.js]
+[browser_aboutPrefs_fc_apply_blocked.js]
+[browser_aboutPrefs_fc_check_cantApply.js]
+skip-if = os != 'win'
+reason = test must be able to prevent file deletion.
+[browser_aboutPrefs_fc_check_malformedXML.js]
+[browser_aboutPrefs_fc_check_noUpdate.js]
+[browser_aboutPrefs_fc_check_otherInstance.js]
+skip-if = os != 'win'
+reason = Windows only feature.
+[browser_aboutPrefs_fc_check_unsupported.js]
+[browser_aboutPrefs_fc_downloadAuto.js]
+[browser_aboutPrefs_fc_downloadAuto_staging.js]
+[browser_aboutPrefs_fc_downloadOptIn.js]
+[browser_aboutPrefs_fc_downloadOptIn_staging.js]
+[browser_aboutPrefs_fc_network_failure.js]
+[browser_aboutPrefs_fc_network_offline.js]
+[browser_aboutPrefs_fc_patch_completeBadSize.js]
+[browser_aboutPrefs_fc_patch_partialBadSize.js]
+[browser_aboutPrefs_fc_patch_partialBadSize_complete.js]
+[browser_aboutPrefs_fc_patch_partialBadSize_completeBadSize.js]
+[browser_aboutPrefs_internalError.js]
+[browser_aboutPrefs_settings.js]
+
+# Doorhanger Application Update Tests
+[browser_doorhanger_bc_check_cantApply.js]
+skip-if = os != 'win'
+reason = test must be able to prevent file deletion.
+[browser_doorhanger_bc_check_malformedXML.js]
+[browser_doorhanger_bc_check_unsupported.js]
+[browser_doorhanger_bc_downloadAutoFailures.js]
+[browser_doorhanger_bc_downloadAutoFailures_bgWin.js]
+[browser_doorhanger_bc_downloadOptIn.js]
+[browser_doorhanger_bc_downloadOptIn_bgWin.js]
+[browser_doorhanger_bc_downloadOptIn_staging.js]
+[browser_doorhanger_bc_downloaded.js]
+[browser_doorhanger_bc_downloaded_staged.js]
+[browser_doorhanger_bc_multiUpdate.js]
+[browser_doorhanger_bc_multiUpdate_promptWaitTime.js]
+[browser_doorhanger_bc_patch_completeBadSize.js]
+[browser_doorhanger_bc_patch_partialBadSize.js]
+[browser_doorhanger_bc_patch_partialBadSize_complete.js]
+[browser_doorhanger_bc_patch_partialBadSize_completeBadSize.js]
+[browser_doorhanger_sp_patch_completeApplyFailure.js]
+[browser_doorhanger_sp_patch_partialApplyFailure.js]
+[browser_doorhanger_sp_patch_partialApplyFailure_complete.js]
+[browser_doorhanger_sp_patch_partialApplyFailure_completeBadSize.js]
+[browser_doorhanger_sp_patch_partialApplyFailure_complete_staging.js]
+
+# Elevation Dialog Tests
+[browser_elevationDialog.js]
+
+# Memory Fallback Tests
+[browser_memory_allocation_error_fallback.js]
+
+# Telemetry Update Ping Tests
+[browser_telemetry_updatePing_downloaded_ready.js]
+[browser_telemetry_updatePing_staged_ready.js]
diff --git a/toolkit/mozapps/update/tests/browser/browser_aboutDialog_AppUpdater_stop_checking.js b/toolkit/mozapps/update/tests/browser/browser_aboutDialog_AppUpdater_stop_checking.js
new file mode 100644
index 0000000000..370f658656
--- /dev/null
+++ b/toolkit/mozapps/update/tests/browser/browser_aboutDialog_AppUpdater_stop_checking.js
@@ -0,0 +1,32 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Test that while a download is in-progress, calling `AppUpdater.stop()` while
+// in the checking state causes the interface to return to the `NEVER_CHECKED`
+// state.
+// This is less a test of the About dialog than of AppUpdater, but it's easier
+// to test it via the About dialog just because there is already a testing
+// framework for the About dialog.
+add_task(async function aboutDialog_AppUpdater_stop_checking() {
+ let params = { queryString: "&noUpdates=1" };
+ await runAboutDialogUpdateTest(params, [
+ {
+ panelId: "checkingForUpdates",
+ // Omit the continue file to keep us in the checking state.
+ },
+ aboutDialog => {
+ aboutDialog.gAppUpdater._appUpdater.stop();
+ },
+ {
+ panelId: "checkForUpdates",
+ },
+ ]);
+
+ // Ideally this would go in a cleanup function. But this needs to happen
+ // before any other cleanup functions and for some reason cleanup functions
+ // do not always seem to execute in reverse registration order.
+ dump("Cleanup: Waiting for checking to finish.\n");
+ await continueFileHandler(CONTINUE_CHECK);
+});
diff --git a/toolkit/mozapps/update/tests/browser/browser_aboutDialog_AppUpdater_stop_download_and_install.js b/toolkit/mozapps/update/tests/browser/browser_aboutDialog_AppUpdater_stop_download_and_install.js
new file mode 100644
index 0000000000..74e179043d
--- /dev/null
+++ b/toolkit/mozapps/update/tests/browser/browser_aboutDialog_AppUpdater_stop_download_and_install.js
@@ -0,0 +1,41 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Test that while a download is in-progress, calling `AppUpdater.stop()` while
+// in the "download and install" state causes the interface to return to the
+// `NEVER_CHECKED` state.
+// This is less a test of the About dialog than of AppUpdater, but it's easier
+// to test it via the About dialog just because there is already a testing
+// framework for the About dialog.
+add_task(async function aboutDialog_AppUpdater_stop_download_and_install() {
+ await UpdateUtils.setAppUpdateAutoEnabled(false);
+
+ let downloadInfo = [];
+ if (Services.prefs.getBoolPref(PREF_APP_UPDATE_BITS_ENABLED)) {
+ downloadInfo[0] = { patchType: "partial", bitsResult: "0" };
+ } else {
+ downloadInfo[0] = { patchType: "partial", internalResult: "0" };
+ }
+
+ // Since the partial should be successful specify an invalid size for the
+ // complete update.
+ let params = { queryString: "&invalidCompleteSize=1" };
+ await runAboutDialogUpdateTest(params, [
+ {
+ panelId: "checkingForUpdates",
+ continueFile: CONTINUE_CHECK,
+ },
+ {
+ panelId: "downloadAndInstall",
+ noContinue: true,
+ },
+ aboutDialog => {
+ aboutDialog.gAppUpdater._appUpdater.stop();
+ },
+ {
+ panelId: "checkForUpdates",
+ },
+ ]);
+});
diff --git a/toolkit/mozapps/update/tests/browser/browser_aboutDialog_AppUpdater_stop_download_failed.js b/toolkit/mozapps/update/tests/browser/browser_aboutDialog_AppUpdater_stop_download_failed.js
new file mode 100644
index 0000000000..8900765f81
--- /dev/null
+++ b/toolkit/mozapps/update/tests/browser/browser_aboutDialog_AppUpdater_stop_download_failed.js
@@ -0,0 +1,43 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Test that while a download is in-progress, calling `AppUpdater.stop()` while
+// in the "downloadFailed" state doesn't cause a shift to any other state, such
+// as internal error.
+// This is less a test of the About dialog than of AppUpdater, but it's easier
+// to test it via the About dialog just because there is already a testing
+// framework for the About dialog.
+add_task(async function aboutDialog_AppUpdater_stop_download_failed() {
+ let downloadInfo = [];
+ if (Services.prefs.getBoolPref(PREF_APP_UPDATE_BITS_ENABLED)) {
+ downloadInfo[0] = { patchType: "complete", bitsResult: gBadSizeResult };
+ downloadInfo[1] = { patchType: "complete", internalResult: gBadSizeResult };
+ } else {
+ downloadInfo[0] = { patchType: "complete", internalResult: gBadSizeResult };
+ }
+
+ let params = { queryString: "&completePatchOnly=1&invalidCompleteSize=1" };
+ await runAboutDialogUpdateTest(params, [
+ {
+ panelId: "checkingForUpdates",
+ continueFile: CONTINUE_CHECK,
+ },
+ {
+ panelId: "downloading",
+ checkActiveUpdate: { state: STATE_DOWNLOADING },
+ continueFile: CONTINUE_DOWNLOAD,
+ downloadInfo,
+ },
+ {
+ panelId: "downloadFailed",
+ },
+ aboutDialog => {
+ aboutDialog.gAppUpdater._appUpdater.stop();
+ },
+ {
+ panelId: "downloadFailed",
+ },
+ ]);
+});
diff --git a/toolkit/mozapps/update/tests/browser/browser_aboutDialog_AppUpdater_stop_downloading.js b/toolkit/mozapps/update/tests/browser/browser_aboutDialog_AppUpdater_stop_downloading.js
new file mode 100644
index 0000000000..d0de7e03b9
--- /dev/null
+++ b/toolkit/mozapps/update/tests/browser/browser_aboutDialog_AppUpdater_stop_downloading.js
@@ -0,0 +1,47 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Test that while a download is in-progress, calling `AppUpdater.stop()` during
+// the downloading state causes the interface to return to the `NEVER_CHECKED`
+// state.
+// This is less a test of the About dialog than of AppUpdater, but it's easier
+// to test it via the About dialog just because there is already a testing
+// framework for the About dialog.
+add_task(async function aboutDialog_AppUpdater_stop_downloading() {
+ // Since the partial should be successful specify an invalid size for the
+ // complete update.
+ let params = {
+ queryString: "&useSlowDownloadMar=1&invalidCompleteSize=1",
+ backgroundUpdate: true,
+ waitForUpdateState: STATE_DOWNLOADING,
+ };
+ await runAboutDialogUpdateTest(params, [
+ {
+ panelId: "downloading",
+ checkActiveUpdate: { state: STATE_DOWNLOADING },
+ // Omit continue file to keep the UI in the downloading state.
+ },
+ aboutDialog => {
+ aboutDialog.gAppUpdater._appUpdater.stop();
+ },
+ {
+ panelId: "checkForUpdates",
+ // The update will still be in the downloading state even though
+ // AppUpdater has stopped because stopping AppUpdater doesn't stop the
+ // Application Update Service from continuing with the update.
+ checkActiveUpdate: { state: STATE_DOWNLOADING },
+ expectedStateOverride: Ci.nsIApplicationUpdateService.STATE_DOWNLOADING,
+ },
+ ]);
+
+ // Ideally this would go in a cleanup function. But this needs to happen
+ // before any other cleanup functions and for some reason cleanup functions
+ // do not always seem to execute in reverse registration order.
+ dump("Cleanup: Waiting for downloading to finish.\n");
+ await continueFileHandler(CONTINUE_DOWNLOAD);
+ if (gAUS.currentState == Ci.nsIApplicationUpdateService.STATE_DOWNLOADING) {
+ await gAUS.stateTransition;
+ }
+});
diff --git a/toolkit/mozapps/update/tests/browser/browser_aboutDialog_AppUpdater_stop_internal_error.js b/toolkit/mozapps/update/tests/browser/browser_aboutDialog_AppUpdater_stop_internal_error.js
new file mode 100644
index 0000000000..69c13783bf
--- /dev/null
+++ b/toolkit/mozapps/update/tests/browser/browser_aboutDialog_AppUpdater_stop_internal_error.js
@@ -0,0 +1,45 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+ChromeUtils.defineESModuleGetters(this, {
+ AppUpdater: "resource://gre/modules/AppUpdater.sys.mjs",
+ sinon: "resource://testing-common/Sinon.sys.mjs",
+});
+
+add_setup(function setup_internalErrorTest() {
+ const sandbox = sinon.createSandbox();
+ sandbox.stub(AppUpdater.prototype, "aus").get(() => {
+ throw new Error("intentional test error");
+ });
+ sandbox.stub(AppUpdater.prototype, "checker").get(() => {
+ throw new Error("intentional test error");
+ });
+ sandbox.stub(AppUpdater.prototype, "um").get(() => {
+ throw new Error("intentional test error");
+ });
+ registerCleanupFunction(() => {
+ sandbox.restore();
+ });
+});
+
+// Test that while a download is in-progress, calling `AppUpdater.stop()` while
+// in the "internal error" state doesn't cause a shift to any other state.
+// This is less a test of the About dialog than of AppUpdater, but it's easier
+// to test it via the About dialog just because there is already a testing
+// framework for the About dialog.
+add_task(async function aboutDialog_AppUpdater_stop_internal_error() {
+ let params = {};
+ await runAboutDialogUpdateTest(params, [
+ {
+ panelId: "internalError",
+ },
+ aboutDialog => {
+ aboutDialog.gAppUpdater._appUpdater.stop();
+ },
+ {
+ panelId: "internalError",
+ },
+ ]);
+});
diff --git a/toolkit/mozapps/update/tests/browser/browser_aboutDialog_AppUpdater_stop_no_update.js b/toolkit/mozapps/update/tests/browser/browser_aboutDialog_AppUpdater_stop_no_update.js
new file mode 100644
index 0000000000..65a52ccc87
--- /dev/null
+++ b/toolkit/mozapps/update/tests/browser/browser_aboutDialog_AppUpdater_stop_no_update.js
@@ -0,0 +1,29 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Test that while a download is in-progress, calling `AppUpdater.stop()` while
+// in the "noUpdatesFound" state doesn't cause a shift to any other state, such
+// as internal error.
+// This is less a test of the About dialog than of AppUpdater, but it's easier
+// to test it via the About dialog just because there is already a testing
+// framework for the About dialog.
+add_task(async function aboutDialog_AppUpdater_stop_no_update() {
+ let params = { queryString: "&noUpdates=1" };
+ await runAboutDialogUpdateTest(params, [
+ {
+ panelId: "checkingForUpdates",
+ continueFile: CONTINUE_CHECK,
+ },
+ {
+ panelId: "noUpdatesFound",
+ },
+ aboutDialog => {
+ aboutDialog.gAppUpdater._appUpdater.stop();
+ },
+ {
+ panelId: "noUpdatesFound",
+ },
+ ]);
+});
diff --git a/toolkit/mozapps/update/tests/browser/browser_aboutDialog_AppUpdater_stop_ready_for_restart.js b/toolkit/mozapps/update/tests/browser/browser_aboutDialog_AppUpdater_stop_ready_for_restart.js
new file mode 100644
index 0000000000..8c9d1f788f
--- /dev/null
+++ b/toolkit/mozapps/update/tests/browser/browser_aboutDialog_AppUpdater_stop_ready_for_restart.js
@@ -0,0 +1,27 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Test that while a download is in-progress, calling `AppUpdater.stop()` while
+// in the "ready for restart" state doesn't cause a shift to any other state,
+// such as internal error.
+// This is less a test of the About dialog than of AppUpdater, but it's easier
+// to test it via the About dialog just because there is already a testing
+// framework for the About dialog.
+add_task(async function aboutDialog_AppUpdater_stop_ready_for_restart() {
+ let params = { backgroundUpdate: true, waitForUpdateState: STATE_PENDING };
+ await runAboutDialogUpdateTest(params, [
+ {
+ panelId: "apply",
+ checkActiveUpdate: { state: STATE_PENDING },
+ },
+ aboutDialog => {
+ aboutDialog.gAppUpdater._appUpdater.stop();
+ },
+ {
+ panelId: "apply",
+ checkActiveUpdate: { state: STATE_PENDING },
+ },
+ ]);
+});
diff --git a/toolkit/mozapps/update/tests/browser/browser_aboutDialog_AppUpdater_stop_staging.js b/toolkit/mozapps/update/tests/browser/browser_aboutDialog_AppUpdater_stop_staging.js
new file mode 100644
index 0000000000..dd822e6391
--- /dev/null
+++ b/toolkit/mozapps/update/tests/browser/browser_aboutDialog_AppUpdater_stop_staging.js
@@ -0,0 +1,64 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Test that while a download is in-progress, calling `AppUpdater.stop()` while
+// in the staging state causes the interface to return to the `NEVER_CHECKED`
+// state.
+// This is less a test of the About dialog than of AppUpdater, but it's easier
+// to test it via the About dialog just because there is already a testing
+// framework for the About dialog.
+add_task(async function aboutDialog_AppUpdater_stop_staging() {
+ await SpecialPowers.pushPrefEnv({
+ set: [[PREF_APP_UPDATE_STAGING_ENABLED, true]],
+ });
+
+ let downloadInfo = [];
+ if (Services.prefs.getBoolPref(PREF_APP_UPDATE_BITS_ENABLED)) {
+ downloadInfo[0] = { patchType: "partial", bitsResult: "0" };
+ } else {
+ downloadInfo[0] = { patchType: "partial", internalResult: "0" };
+ }
+
+ // Since the partial should be successful specify an invalid size for the
+ // complete update.
+ let params = {
+ queryString: "&useSlowDownloadMar=1&invalidCompleteSize=1",
+ backgroundUpdate: true,
+ waitForUpdateState: STATE_DOWNLOADING,
+ };
+ await runAboutDialogUpdateTest(params, [
+ {
+ panelId: "downloading",
+ checkActiveUpdate: { state: STATE_DOWNLOADING },
+ continueFile: CONTINUE_DOWNLOAD,
+ downloadInfo,
+ },
+ {
+ panelId: "applying",
+ checkActiveUpdate: { state: STATE_PENDING },
+ // Don't pass a continue file in order to leave us in the staging state.
+ },
+ aboutDialog => {
+ aboutDialog.gAppUpdater._appUpdater.stop();
+ },
+ {
+ panelId: "checkForUpdates",
+ // The update will still be in the staging state even though AppUpdater
+ // has stopped because stopping AppUpdater doesn't stop the Application
+ // Update Service from continuing with the update.
+ checkActiveUpdate: { state: STATE_PENDING },
+ expectedStateOverride: Ci.nsIApplicationUpdateService.STATE_STAGING,
+ },
+ ]);
+
+ // Ideally this would go in a cleanup function. But this needs to happen
+ // before any other cleanup functions and for some reason cleanup functions
+ // do not always seem to execute in reverse registration order.
+ dump("Cleanup: Waiting for staging to finish.\n");
+ await continueFileHandler(CONTINUE_STAGING);
+ if (gAUS.currentState == Ci.nsIApplicationUpdateService.STATE_STAGING) {
+ await gAUS.stateTransition;
+ }
+});
diff --git a/toolkit/mozapps/update/tests/browser/browser_aboutDialog_AppUpdater_stop_swap.js b/toolkit/mozapps/update/tests/browser/browser_aboutDialog_AppUpdater_stop_swap.js
new file mode 100644
index 0000000000..02441dea53
--- /dev/null
+++ b/toolkit/mozapps/update/tests/browser/browser_aboutDialog_AppUpdater_stop_swap.js
@@ -0,0 +1,64 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+const FIRST_UPDATE_VERSION = "999998.0";
+const SECOND_UPDATE_VERSION = "999999.0";
+
+function prepareToDownloadVersion(version) {
+ setUpdateURL(
+ URL_HTTP_UPDATE_SJS +
+ `?detailsURL=${gDetailsURL}&promptWaitTime=0&appVersion=${version}`
+ );
+}
+
+add_task(async function aboutDialog_backgroundCheck_multiUpdate() {
+ await SpecialPowers.pushPrefEnv({
+ set: [[PREF_APP_UPDATE_STAGING_ENABLED, true]],
+ });
+
+ let params = {
+ version: FIRST_UPDATE_VERSION,
+ backgroundUpdate: true,
+ waitForUpdateState: STATE_PENDING,
+ };
+ await runAboutDialogUpdateTest(params, [
+ {
+ panelId: "applying",
+ checkActiveUpdate: { state: STATE_PENDING },
+ continueFile: CONTINUE_STAGING,
+ },
+ {
+ panelId: "apply",
+ checkActiveUpdate: { state: STATE_APPLIED },
+ continueFile: null,
+ },
+ () => {
+ prepareToDownloadVersion(SECOND_UPDATE_VERSION);
+ gAUS.checkForBackgroundUpdates();
+ },
+ {
+ panelId: "applying",
+ checkActiveUpdate: { state: STATE_PENDING },
+ // Don't pass a continue file in order to leave us in the staging state.
+ },
+ aboutDialog => {
+ aboutDialog.gAppUpdater._appUpdater.stop();
+ },
+ {
+ panelId: "checkForUpdates",
+ checkActiveUpdate: { state: STATE_PENDING },
+ expectedStateOverride: Ci.nsIApplicationUpdateService.STATE_STAGING,
+ },
+ ]);
+
+ // Ideally this would go in a cleanup function. But this needs to happen
+ // before any other cleanup functions and for some reason cleanup functions
+ // do not always seem to execute in reverse registration order.
+ dump("Cleanup: Waiting for staging to finish.\n");
+ await continueFileHandler(CONTINUE_STAGING);
+ if (gAUS.currentState == Ci.nsIApplicationUpdateService.STATE_STAGING) {
+ await gAUS.stateTransition;
+ }
+});
diff --git a/toolkit/mozapps/update/tests/browser/browser_aboutDialog_bc_downloaded.js b/toolkit/mozapps/update/tests/browser/browser_aboutDialog_bc_downloaded.js
new file mode 100644
index 0000000000..0955750c93
--- /dev/null
+++ b/toolkit/mozapps/update/tests/browser/browser_aboutDialog_bc_downloaded.js
@@ -0,0 +1,17 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Test for About Dialog background check for updates
+// with the update downloaded when the About Dialog is opened.
+add_task(async function aboutDialog_backgroundCheck_downloaded() {
+ let params = { backgroundUpdate: true, waitForUpdateState: STATE_PENDING };
+ await runAboutDialogUpdateTest(params, [
+ {
+ panelId: "apply",
+ checkActiveUpdate: { state: STATE_PENDING },
+ continueFile: null,
+ },
+ ]);
+});
diff --git a/toolkit/mozapps/update/tests/browser/browser_aboutDialog_bc_downloaded_staged.js b/toolkit/mozapps/update/tests/browser/browser_aboutDialog_bc_downloaded_staged.js
new file mode 100644
index 0000000000..2d7bd64d05
--- /dev/null
+++ b/toolkit/mozapps/update/tests/browser/browser_aboutDialog_bc_downloaded_staged.js
@@ -0,0 +1,28 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Test for About Dialog background check for updates
+// with the update downloaded and staged when the About Dialog is opened.
+add_task(async function aboutDialog_backgroundCheck_downloaded_staged() {
+ await SpecialPowers.pushPrefEnv({
+ set: [[PREF_APP_UPDATE_STAGING_ENABLED, true]],
+ });
+
+ // Since the partial should be successful specify an invalid size for the
+ // complete update.
+ let params = {
+ queryString: "&invalidCompleteSize=1",
+ backgroundUpdate: true,
+ continueFile: CONTINUE_STAGING,
+ waitForUpdateState: STATE_APPLIED,
+ };
+ await runAboutDialogUpdateTest(params, [
+ {
+ panelId: "apply",
+ checkActiveUpdate: { state: STATE_APPLIED },
+ continueFile: null,
+ },
+ ]);
+});
diff --git a/toolkit/mozapps/update/tests/browser/browser_aboutDialog_bc_downloaded_staging.js b/toolkit/mozapps/update/tests/browser/browser_aboutDialog_bc_downloaded_staging.js
new file mode 100644
index 0000000000..b60a8f128d
--- /dev/null
+++ b/toolkit/mozapps/update/tests/browser/browser_aboutDialog_bc_downloaded_staging.js
@@ -0,0 +1,55 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Test for About Dialog background check for updates
+// with the update downloaded and the About Dialog opened during staging.
+add_task(async function aboutDialog_backgroundCheck_downloaded_staging() {
+ await SpecialPowers.pushPrefEnv({
+ set: [[PREF_APP_UPDATE_STAGING_ENABLED, true]],
+ });
+
+ let lankpackCall = mockLangpackInstall();
+
+ // Since the partial should be successful specify an invalid size for the
+ // complete update.
+ let params = {
+ queryString: "&invalidCompleteSize=1",
+ backgroundUpdate: true,
+ waitForUpdateState: STATE_PENDING,
+ };
+ await runAboutDialogUpdateTest(params, [
+ {
+ panelId: "applying",
+ checkActiveUpdate: { state: STATE_PENDING },
+ continueFile: CONTINUE_STAGING,
+ },
+ async aboutDialog => {
+ // Once the state is applied but langpacks aren't complete the about
+ // dialog should still be showing applying.
+ TestUtils.waitForCondition(() => {
+ return readStatusFile() == STATE_APPLIED;
+ });
+
+ is(
+ aboutDialog.gAppUpdater.selectedPanel.id,
+ "applying",
+ "UI should still show as applying."
+ );
+
+ let { appVersion, resolve } = await lankpackCall;
+ is(
+ appVersion,
+ Services.appinfo.version,
+ "Should see the right app version."
+ );
+ resolve();
+ },
+ {
+ panelId: "apply",
+ checkActiveUpdate: { state: STATE_APPLIED },
+ continueFile: null,
+ },
+ ]);
+});
diff --git a/toolkit/mozapps/update/tests/browser/browser_aboutDialog_bc_downloaded_stagingFailure.js b/toolkit/mozapps/update/tests/browser/browser_aboutDialog_bc_downloaded_stagingFailure.js
new file mode 100644
index 0000000000..c05b2daa74
--- /dev/null
+++ b/toolkit/mozapps/update/tests/browser/browser_aboutDialog_bc_downloaded_stagingFailure.js
@@ -0,0 +1,31 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Test for About Dialog background check for updates
+// with the update downloaded and staging has failed when the About Dialog is
+// opened.
+add_task(
+ async function aboutDialog_backgroundCheck_downloaded_stagingFailure() {
+ Services.env.set("MOZ_TEST_STAGING_ERROR", "1");
+ await SpecialPowers.pushPrefEnv({
+ set: [[PREF_APP_UPDATE_STAGING_ENABLED, true]],
+ });
+
+ // Since the partial should be successful specify an invalid size for the
+ // complete update.
+ let params = {
+ queryString: "&completePatchOnly=1",
+ backgroundUpdate: true,
+ waitForUpdateState: STATE_PENDING,
+ };
+ await runAboutDialogUpdateTest(params, [
+ {
+ panelId: "apply",
+ checkActiveUpdate: { state: STATE_PENDING },
+ continueFile: null,
+ },
+ ]);
+ }
+);
diff --git a/toolkit/mozapps/update/tests/browser/browser_aboutDialog_bc_downloading.js b/toolkit/mozapps/update/tests/browser/browser_aboutDialog_bc_downloading.js
new file mode 100644
index 0000000000..6c2a7486a9
--- /dev/null
+++ b/toolkit/mozapps/update/tests/browser/browser_aboutDialog_bc_downloading.js
@@ -0,0 +1,76 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Test for About Dialog background check for updates
+// with the About Dialog opened during downloading.
+add_task(async function aboutDialog_backgroundCheck_downloading() {
+ await SpecialPowers.pushPrefEnv({
+ set: [[PREF_APP_UPDATE_NOTIFYDURINGDOWNLOAD, false]],
+ });
+
+ let downloadInfo = [];
+ if (Services.prefs.getBoolPref(PREF_APP_UPDATE_BITS_ENABLED)) {
+ downloadInfo[0] = { patchType: "partial", bitsResult: "0" };
+ } else {
+ downloadInfo[0] = { patchType: "partial", internalResult: "0" };
+ }
+
+ let lankpackCall = mockLangpackInstall();
+
+ // Since the partial should be successful specify an invalid size for the
+ // complete update.
+ let params = {
+ queryString: "&useSlowDownloadMar=1&invalidCompleteSize=1",
+ backgroundUpdate: true,
+ waitForUpdateState: STATE_DOWNLOADING,
+ };
+ await runAboutDialogUpdateTest(params, [
+ async function aboutDialog_downloading() {
+ is(
+ PanelUI.notificationPanel.state,
+ "closed",
+ "The window's doorhanger is closed."
+ );
+ ok(
+ !PanelUI.menuButton.hasAttribute("badge-status"),
+ "The window does not have a badge."
+ );
+ },
+ {
+ panelId: "downloading",
+ checkActiveUpdate: { state: STATE_DOWNLOADING },
+ continueFile: CONTINUE_DOWNLOAD,
+ downloadInfo,
+ },
+ async aboutDialog => {
+ // Once the state is pending but langpacks aren't complete the about
+ // dialog should still be showing downloading.
+ TestUtils.waitForCondition(() => {
+ return readStatusFile() == STATE_PENDING;
+ });
+
+ is(
+ aboutDialog.gAppUpdater.selectedPanel.id,
+ "downloading",
+ "UI should still show as downloading."
+ );
+
+ let { appVersion, resolve } = await lankpackCall;
+ is(
+ appVersion,
+ Services.appinfo.version,
+ "Should see the right app version."
+ );
+ resolve();
+ },
+ {
+ panelId: "apply",
+ checkActiveUpdate: { state: STATE_PENDING },
+ continueFile: null,
+ },
+ ]);
+
+ await SpecialPowers.popPrefEnv();
+});
diff --git a/toolkit/mozapps/update/tests/browser/browser_aboutDialog_bc_downloading_notify.js b/toolkit/mozapps/update/tests/browser/browser_aboutDialog_bc_downloading_notify.js
new file mode 100644
index 0000000000..cf067efe7d
--- /dev/null
+++ b/toolkit/mozapps/update/tests/browser/browser_aboutDialog_bc_downloading_notify.js
@@ -0,0 +1,67 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Test for About Dialog background check for updates with the
+// "notify during download" feature turned on.
+add_task(async function aboutDialog_backgroundCheck_downloading_notify() {
+ await SpecialPowers.pushPrefEnv({
+ set: [[PREF_APP_UPDATE_NOTIFYDURINGDOWNLOAD, true]],
+ });
+
+ let downloadInfo = [];
+ if (Services.prefs.getBoolPref(PREF_APP_UPDATE_BITS_ENABLED)) {
+ downloadInfo[0] = { patchType: "partial", bitsResult: "0" };
+ } else {
+ downloadInfo[0] = { patchType: "partial", internalResult: "0" };
+ }
+
+ // Since the partial should be successful specify an invalid size for the
+ // complete update.
+ let params = {
+ queryString: "&useSlowDownloadMar=1&invalidCompleteSize=1",
+ backgroundUpdate: true,
+ waitForUpdateState: STATE_DOWNLOADING,
+ };
+ await runAboutDialogUpdateTest(params, [
+ async function aboutDialog_downloading_notification() {
+ await TestUtils.waitForCondition(
+ () => PanelUI.menuButton.hasAttribute("badge-status"),
+ "Waiting for update badge",
+ undefined,
+ 200
+ ).catch(e => {
+ // Instead of throwing let the check below fail the test.
+ logTestInfo(e);
+ });
+ is(
+ PanelUI.notificationPanel.state,
+ "closed",
+ "The window's doorhanger is closed."
+ );
+ ok(
+ PanelUI.menuButton.hasAttribute("badge-status"),
+ "The window has a badge."
+ );
+ is(
+ PanelUI.menuButton.getAttribute("badge-status"),
+ "update-downloading",
+ "The downloading badge is showing for the background window"
+ );
+ },
+ {
+ panelId: "downloading",
+ checkActiveUpdate: { state: STATE_DOWNLOADING },
+ continueFile: CONTINUE_DOWNLOAD,
+ downloadInfo,
+ },
+ {
+ panelId: "apply",
+ checkActiveUpdate: { state: STATE_PENDING },
+ continueFile: null,
+ },
+ ]);
+
+ await SpecialPowers.popPrefEnv();
+});
diff --git a/toolkit/mozapps/update/tests/browser/browser_aboutDialog_bc_downloading_staging.js b/toolkit/mozapps/update/tests/browser/browser_aboutDialog_bc_downloading_staging.js
new file mode 100644
index 0000000000..3f6b476e1b
--- /dev/null
+++ b/toolkit/mozapps/update/tests/browser/browser_aboutDialog_bc_downloading_staging.js
@@ -0,0 +1,68 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Test for About Dialog background check for updates
+// with the About Dialog opened during downloading and stages the update.
+add_task(async function aboutDialog_backgroundCheck_downloading_staging() {
+ await SpecialPowers.pushPrefEnv({
+ set: [[PREF_APP_UPDATE_STAGING_ENABLED, true]],
+ });
+
+ let downloadInfo = [];
+ if (Services.prefs.getBoolPref(PREF_APP_UPDATE_BITS_ENABLED)) {
+ downloadInfo[0] = { patchType: "partial", bitsResult: "0" };
+ } else {
+ downloadInfo[0] = { patchType: "partial", internalResult: "0" };
+ }
+
+ let lankpackCall = mockLangpackInstall();
+
+ // Since the partial should be successful specify an invalid size for the
+ // complete update.
+ let params = {
+ queryString: "&useSlowDownloadMar=1&invalidCompleteSize=1",
+ backgroundUpdate: true,
+ waitForUpdateState: STATE_DOWNLOADING,
+ };
+ await runAboutDialogUpdateTest(params, [
+ {
+ panelId: "downloading",
+ checkActiveUpdate: { state: STATE_DOWNLOADING },
+ continueFile: CONTINUE_DOWNLOAD,
+ downloadInfo,
+ },
+ {
+ panelId: "applying",
+ checkActiveUpdate: { state: STATE_PENDING },
+ continueFile: CONTINUE_STAGING,
+ },
+ async aboutDialog => {
+ // Once the state is applied but langpacks aren't complete the about
+ // dialog should still be showing applying.
+ TestUtils.waitForCondition(() => {
+ return readStatusFile() == STATE_APPLIED;
+ });
+
+ is(
+ aboutDialog.gAppUpdater.selectedPanel.id,
+ "applying",
+ "UI should still show as applying."
+ );
+
+ let { appVersion, resolve } = await lankpackCall;
+ is(
+ appVersion,
+ Services.appinfo.version,
+ "Should see the right app version."
+ );
+ resolve();
+ },
+ {
+ panelId: "apply",
+ checkActiveUpdate: { state: STATE_APPLIED },
+ continueFile: null,
+ },
+ ]);
+});
diff --git a/toolkit/mozapps/update/tests/browser/browser_aboutDialog_bc_multiUpdate.js b/toolkit/mozapps/update/tests/browser/browser_aboutDialog_bc_multiUpdate.js
new file mode 100644
index 0000000000..b6c893ea15
--- /dev/null
+++ b/toolkit/mozapps/update/tests/browser/browser_aboutDialog_bc_multiUpdate.js
@@ -0,0 +1,52 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+const FIRST_UPDATE_VERSION = "999998.0";
+const SECOND_UPDATE_VERSION = "999999.0";
+
+function prepareToDownloadVersion(version) {
+ setUpdateURL(
+ URL_HTTP_UPDATE_SJS +
+ `?detailsURL=${gDetailsURL}&promptWaitTime=0&appVersion=${version}`
+ );
+}
+
+add_task(async function aboutDialog_backgroundCheck_multiUpdate() {
+ await SpecialPowers.pushPrefEnv({
+ set: [[PREF_APP_UPDATE_STAGING_ENABLED, true]],
+ });
+
+ let params = {
+ version: FIRST_UPDATE_VERSION,
+ backgroundUpdate: true,
+ waitForUpdateState: STATE_PENDING,
+ };
+ await runAboutDialogUpdateTest(params, [
+ {
+ panelId: "applying",
+ checkActiveUpdate: { state: STATE_PENDING },
+ continueFile: CONTINUE_STAGING,
+ },
+ {
+ panelId: "apply",
+ checkActiveUpdate: { state: STATE_APPLIED },
+ continueFile: null,
+ },
+ () => {
+ prepareToDownloadVersion(SECOND_UPDATE_VERSION);
+ gAUS.checkForBackgroundUpdates();
+ },
+ {
+ panelId: "applying",
+ checkActiveUpdate: { state: STATE_PENDING },
+ continueFile: CONTINUE_STAGING,
+ },
+ {
+ panelId: "apply",
+ checkActiveUpdate: { state: STATE_APPLIED },
+ continueFile: null,
+ },
+ ]);
+});
diff --git a/toolkit/mozapps/update/tests/browser/browser_aboutDialog_fc_apply_blocked.js b/toolkit/mozapps/update/tests/browser/browser_aboutDialog_fc_apply_blocked.js
new file mode 100644
index 0000000000..6cb2b9d10d
--- /dev/null
+++ b/toolkit/mozapps/update/tests/browser/browser_aboutDialog_fc_apply_blocked.js
@@ -0,0 +1,94 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+const { PromptTestUtils } = ChromeUtils.importESModule(
+ "resource://testing-common/PromptTestUtils.sys.mjs"
+);
+
+const BUILDER_URL = "https://example.com/document-builder.sjs?html=";
+const PAGE_MARKUP = `
+<html>
+<head>
+ <script>
+ window.onbeforeunload = function() {
+ return true;
+ };
+ </script>
+</head>
+<body>TEST PAGE</body>
+</html>
+`;
+const TEST_URL = BUILDER_URL + encodeURI(PAGE_MARKUP);
+
+add_setup(async () => {
+ await SpecialPowers.pushPrefEnv({
+ set: [["dom.require_user_interaction_for_beforeunload", false]],
+ });
+});
+
+// Test for About Dialog foreground check for updates
+// and apply but restart is blocked by a page.
+add_task(async function aboutDialog_foregroundCheck_apply_blocked() {
+ let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, TEST_URL);
+
+ let downloadInfo = [];
+ if (Services.prefs.getBoolPref(PREF_APP_UPDATE_BITS_ENABLED)) {
+ downloadInfo[0] = { patchType: "partial", bitsResult: "0" };
+ } else {
+ downloadInfo[0] = { patchType: "partial", internalResult: "0" };
+ }
+
+ let aboutDialog;
+ let handlePromise = (async () => {
+ let dialog = await PromptTestUtils.waitForPrompt(window, {
+ modalType: Ci.nsIPrompt.MODAL_TYPE_CONTENT,
+ promptType: "confirmEx",
+ });
+ Assert.equal(
+ aboutDialog.gAppUpdater.selectedPanel.id,
+ "restarting",
+ "The restarting panel should be displayed"
+ );
+
+ await PromptTestUtils.handlePrompt(dialog, { buttonNumClick: 1 });
+ })();
+
+ // Since the partial should be successful specify an invalid size for the
+ // complete update.
+ let params = { queryString: "&invalidCompleteSize=1&promptWaitTime=0" };
+ await runAboutDialogUpdateTest(params, [
+ {
+ panelId: "checkingForUpdates",
+ checkActiveUpdate: null,
+ continueFile: CONTINUE_CHECK,
+ },
+ {
+ panelId: "downloading",
+ checkActiveUpdate: { state: STATE_DOWNLOADING },
+ continueFile: CONTINUE_DOWNLOAD,
+ downloadInfo,
+ },
+ async function getAboutDialogHandle(dialog) {
+ aboutDialog = dialog;
+ },
+ {
+ panelId: "apply",
+ checkActiveUpdate: { state: STATE_PENDING },
+ continueFile: null,
+ forceApply: true,
+ },
+ async function ensureDialogHasBeenCanceled() {
+ await handlePromise;
+ },
+ // A final check to ensure that we are back in the apply state.
+ {
+ panelId: "apply",
+ checkActiveUpdate: { state: STATE_PENDING },
+ continueFile: null,
+ },
+ ]);
+
+ BrowserTestUtils.removeTab(tab, { skipPermitUnload: true });
+});
diff --git a/toolkit/mozapps/update/tests/browser/browser_aboutDialog_fc_check_cantApply.js b/toolkit/mozapps/update/tests/browser/browser_aboutDialog_fc_check_cantApply.js
new file mode 100644
index 0000000000..cd659ef74b
--- /dev/null
+++ b/toolkit/mozapps/update/tests/browser/browser_aboutDialog_fc_check_cantApply.js
@@ -0,0 +1,24 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Test for About Dialog foreground check for updates
+// without the ability to apply updates.
+add_task(async function aboutDialog_foregroundCheck_cantApply() {
+ lockWriteTestFile();
+
+ let params = {};
+ await runAboutDialogUpdateTest(params, [
+ {
+ panelId: "checkingForUpdates",
+ checkActiveUpdate: null,
+ continueFile: CONTINUE_CHECK,
+ },
+ {
+ panelId: "manualUpdate",
+ checkActiveUpdate: null,
+ continueFile: null,
+ },
+ ]);
+});
diff --git a/toolkit/mozapps/update/tests/browser/browser_aboutDialog_fc_check_malformedXML.js b/toolkit/mozapps/update/tests/browser/browser_aboutDialog_fc_check_malformedXML.js
new file mode 100644
index 0000000000..529a4c7a63
--- /dev/null
+++ b/toolkit/mozapps/update/tests/browser/browser_aboutDialog_fc_check_malformedXML.js
@@ -0,0 +1,22 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Test for About Dialog foreground check for updates
+// with a malformed update XML file.
+add_task(async function aboutDialog_foregroundCheck_malformedXML() {
+ let params = { queryString: "&xmlMalformed=1" };
+ await runAboutDialogUpdateTest(params, [
+ {
+ panelId: "checkingForUpdates",
+ checkActiveUpdate: null,
+ continueFile: CONTINUE_CHECK,
+ },
+ {
+ panelId: "checkingFailed",
+ checkActiveUpdate: null,
+ continueFile: null,
+ },
+ ]);
+});
diff --git a/toolkit/mozapps/update/tests/browser/browser_aboutDialog_fc_check_noUpdate.js b/toolkit/mozapps/update/tests/browser/browser_aboutDialog_fc_check_noUpdate.js
new file mode 100644
index 0000000000..2bd23cddf0
--- /dev/null
+++ b/toolkit/mozapps/update/tests/browser/browser_aboutDialog_fc_check_noUpdate.js
@@ -0,0 +1,22 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Test for About Dialog foreground check for updates
+// with no update available.
+add_task(async function aboutDialog_foregroundCheck_noUpdate() {
+ let params = { queryString: "&noUpdates=1" };
+ await runAboutDialogUpdateTest(params, [
+ {
+ panelId: "checkingForUpdates",
+ checkActiveUpdate: null,
+ continueFile: CONTINUE_CHECK,
+ },
+ {
+ panelId: "noUpdatesFound",
+ checkActiveUpdate: null,
+ continueFile: null,
+ },
+ ]);
+});
diff --git a/toolkit/mozapps/update/tests/browser/browser_aboutDialog_fc_check_otherInstance.js b/toolkit/mozapps/update/tests/browser/browser_aboutDialog_fc_check_otherInstance.js
new file mode 100644
index 0000000000..fa1110effd
--- /dev/null
+++ b/toolkit/mozapps/update/tests/browser/browser_aboutDialog_fc_check_otherInstance.js
@@ -0,0 +1,19 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Test for About Dialog foreground check for updates
+// with another application instance handling updates.
+add_task(async function aboutDialog_foregroundCheck_otherInstance() {
+ setOtherInstanceHandlingUpdates();
+
+ let params = {};
+ await runAboutDialogUpdateTest(params, [
+ {
+ panelId: "otherInstanceHandlingUpdates",
+ checkActiveUpdate: null,
+ continueFile: null,
+ },
+ ]);
+});
diff --git a/toolkit/mozapps/update/tests/browser/browser_aboutDialog_fc_check_unsupported.js b/toolkit/mozapps/update/tests/browser/browser_aboutDialog_fc_check_unsupported.js
new file mode 100644
index 0000000000..22e3425967
--- /dev/null
+++ b/toolkit/mozapps/update/tests/browser/browser_aboutDialog_fc_check_unsupported.js
@@ -0,0 +1,22 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Test for About Dialog foreground check for updates
+// with an unsupported update.
+add_task(async function aboutDialog_foregroundCheck_unsupported() {
+ let params = { queryString: "&unsupported=1" };
+ await runAboutDialogUpdateTest(params, [
+ {
+ panelId: "checkingForUpdates",
+ checkActiveUpdate: null,
+ continueFile: CONTINUE_CHECK,
+ },
+ {
+ panelId: "unsupportedSystem",
+ checkActiveUpdate: null,
+ continueFile: null,
+ },
+ ]);
+});
diff --git a/toolkit/mozapps/update/tests/browser/browser_aboutDialog_fc_downloadAuto.js b/toolkit/mozapps/update/tests/browser/browser_aboutDialog_fc_downloadAuto.js
new file mode 100644
index 0000000000..a80c9deac8
--- /dev/null
+++ b/toolkit/mozapps/update/tests/browser/browser_aboutDialog_fc_downloadAuto.js
@@ -0,0 +1,62 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Test for About Dialog foreground check for updates
+// with an automatic download.
+add_task(async function aboutDialog_foregroundCheck_downloadAuto() {
+ let downloadInfo = [];
+ if (Services.prefs.getBoolPref(PREF_APP_UPDATE_BITS_ENABLED)) {
+ downloadInfo[0] = { patchType: "partial", bitsResult: "0" };
+ } else {
+ downloadInfo[0] = { patchType: "partial", internalResult: "0" };
+ }
+
+ // Since the partial should be successful specify an invalid size for the
+ // complete update.
+ let params = { queryString: "&invalidCompleteSize=1&promptWaitTime=0" };
+ await runAboutDialogUpdateTest(params, [
+ {
+ panelId: "checkingForUpdates",
+ checkActiveUpdate: null,
+ continueFile: CONTINUE_CHECK,
+ },
+ {
+ panelId: "downloading",
+ checkActiveUpdate: { state: STATE_DOWNLOADING },
+ continueFile: CONTINUE_DOWNLOAD,
+ downloadInfo,
+ },
+ async function aboutDialog_restart_notification() {
+ await TestUtils.waitForCondition(
+ () => PanelUI.menuButton.hasAttribute("badge-status"),
+ "Waiting for update badge",
+ undefined,
+ 200
+ ).catch(e => {
+ // Instead of throwing let the check below fail the test.
+ logTestInfo(e);
+ });
+ is(
+ PanelUI.notificationPanel.state,
+ "closed",
+ "The window's doorhanger is closed."
+ );
+ ok(
+ PanelUI.menuButton.hasAttribute("badge-status"),
+ "The window has a badge."
+ );
+ is(
+ PanelUI.menuButton.getAttribute("badge-status"),
+ "update-restart",
+ "The restart badge is showing for the background window"
+ );
+ },
+ {
+ panelId: "apply",
+ checkActiveUpdate: { state: STATE_PENDING },
+ continueFile: null,
+ },
+ ]);
+});
diff --git a/toolkit/mozapps/update/tests/browser/browser_aboutDialog_fc_downloadAuto_staging.js b/toolkit/mozapps/update/tests/browser/browser_aboutDialog_fc_downloadAuto_staging.js
new file mode 100644
index 0000000000..e212b0c611
--- /dev/null
+++ b/toolkit/mozapps/update/tests/browser/browser_aboutDialog_fc_downloadAuto_staging.js
@@ -0,0 +1,46 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Test for About Dialog foreground check for updates
+// with an automatic download and update staging.
+add_task(async function aboutDialog_foregroundCheck_downloadAuto_staging() {
+ await SpecialPowers.pushPrefEnv({
+ set: [[PREF_APP_UPDATE_STAGING_ENABLED, true]],
+ });
+
+ let downloadInfo = [];
+ if (Services.prefs.getBoolPref(PREF_APP_UPDATE_BITS_ENABLED)) {
+ downloadInfo[0] = { patchType: "partial", bitsResult: "0" };
+ } else {
+ downloadInfo[0] = { patchType: "partial", internalResult: "0" };
+ }
+
+ // Since the partial should be successful specify an invalid size for the
+ // complete update.
+ let params = { queryString: "&invalidCompleteSize=1" };
+ await runAboutDialogUpdateTest(params, [
+ {
+ panelId: "checkingForUpdates",
+ checkActiveUpdate: null,
+ continueFile: CONTINUE_CHECK,
+ },
+ {
+ panelId: "downloading",
+ checkActiveUpdate: { state: STATE_DOWNLOADING },
+ continueFile: CONTINUE_DOWNLOAD,
+ downloadInfo,
+ },
+ {
+ panelId: "applying",
+ checkActiveUpdate: { state: STATE_PENDING },
+ continueFile: CONTINUE_STAGING,
+ },
+ {
+ panelId: "apply",
+ checkActiveUpdate: { state: STATE_APPLIED },
+ continueFile: null,
+ },
+ ]);
+});
diff --git a/toolkit/mozapps/update/tests/browser/browser_aboutDialog_fc_downloadOptIn.js b/toolkit/mozapps/update/tests/browser/browser_aboutDialog_fc_downloadOptIn.js
new file mode 100644
index 0000000000..5a2ff513ad
--- /dev/null
+++ b/toolkit/mozapps/update/tests/browser/browser_aboutDialog_fc_downloadOptIn.js
@@ -0,0 +1,44 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Test for About Dialog foreground check for updates
+// with a manual download.
+add_task(async function aboutDialog_foregroundCheck_downloadOptIn() {
+ await UpdateUtils.setAppUpdateAutoEnabled(false);
+
+ let downloadInfo = [];
+ if (Services.prefs.getBoolPref(PREF_APP_UPDATE_BITS_ENABLED)) {
+ downloadInfo[0] = { patchType: "partial", bitsResult: "0" };
+ } else {
+ downloadInfo[0] = { patchType: "partial", internalResult: "0" };
+ }
+
+ // Since the partial should be successful specify an invalid size for the
+ // complete update.
+ let params = { queryString: "&invalidCompleteSize=1" };
+ await runAboutDialogUpdateTest(params, [
+ {
+ panelId: "checkingForUpdates",
+ checkActiveUpdate: null,
+ continueFile: CONTINUE_CHECK,
+ },
+ {
+ panelId: "downloadAndInstall",
+ checkActiveUpdate: null,
+ continueFile: null,
+ },
+ {
+ panelId: "downloading",
+ checkActiveUpdate: { state: STATE_DOWNLOADING },
+ continueFile: CONTINUE_DOWNLOAD,
+ downloadInfo,
+ },
+ {
+ panelId: "apply",
+ checkActiveUpdate: { state: STATE_PENDING },
+ continueFile: null,
+ },
+ ]);
+});
diff --git a/toolkit/mozapps/update/tests/browser/browser_aboutDialog_fc_downloadOptIn_staging.js b/toolkit/mozapps/update/tests/browser/browser_aboutDialog_fc_downloadOptIn_staging.js
new file mode 100644
index 0000000000..3ed331ec64
--- /dev/null
+++ b/toolkit/mozapps/update/tests/browser/browser_aboutDialog_fc_downloadOptIn_staging.js
@@ -0,0 +1,52 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Test for About Dialog foreground check for updates
+// with a manual download and update staging.
+add_task(async function aboutDialog_foregroundCheck_downloadOptIn_staging() {
+ await SpecialPowers.pushPrefEnv({
+ set: [[PREF_APP_UPDATE_STAGING_ENABLED, true]],
+ });
+ await UpdateUtils.setAppUpdateAutoEnabled(false);
+
+ let downloadInfo = [];
+ if (Services.prefs.getBoolPref(PREF_APP_UPDATE_BITS_ENABLED)) {
+ downloadInfo[0] = { patchType: "partial", bitsResult: "0" };
+ } else {
+ downloadInfo[0] = { patchType: "partial", internalResult: "0" };
+ }
+
+ // Since the partial should be successful specify an invalid size for the
+ // complete update.
+ let params = { queryString: "&invalidCompleteSize=1" };
+ await runAboutDialogUpdateTest(params, [
+ {
+ panelId: "checkingForUpdates",
+ checkActiveUpdate: null,
+ continueFile: CONTINUE_CHECK,
+ },
+ {
+ panelId: "downloadAndInstall",
+ checkActiveUpdate: null,
+ continueFile: null,
+ },
+ {
+ panelId: "downloading",
+ checkActiveUpdate: { state: STATE_DOWNLOADING },
+ continueFile: CONTINUE_DOWNLOAD,
+ downloadInfo,
+ },
+ {
+ panelId: "applying",
+ checkActiveUpdate: { state: STATE_PENDING },
+ continueFile: CONTINUE_STAGING,
+ },
+ {
+ panelId: "apply",
+ checkActiveUpdate: { state: STATE_APPLIED },
+ continueFile: null,
+ },
+ ]);
+});
diff --git a/toolkit/mozapps/update/tests/browser/browser_aboutDialog_fc_network_failure.js b/toolkit/mozapps/update/tests/browser/browser_aboutDialog_fc_network_failure.js
new file mode 100644
index 0000000000..8e25b96d7f
--- /dev/null
+++ b/toolkit/mozapps/update/tests/browser/browser_aboutDialog_fc_network_failure.js
@@ -0,0 +1,18 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Test for About Dialog foreground check for updates which fails, because it is
+// impossible to connect to the update server
+add_task(async function aboutDialog_foregroundCheck_network_failure() {
+ let params = {
+ baseURL: "https://localhost:7777",
+ };
+
+ await runAboutDialogUpdateTest(params, [
+ {
+ panelId: "checkingFailed",
+ },
+ ]);
+});
diff --git a/toolkit/mozapps/update/tests/browser/browser_aboutDialog_fc_network_offline.js b/toolkit/mozapps/update/tests/browser/browser_aboutDialog_fc_network_offline.js
new file mode 100644
index 0000000000..9f81573e23
--- /dev/null
+++ b/toolkit/mozapps/update/tests/browser/browser_aboutDialog_fc_network_offline.js
@@ -0,0 +1,31 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Test for About Dialog foreground check for updates which fails, because the
+// browser is in offline mode and `localhost` cannot be resolved.
+add_task(async function aboutDialog_foregroundCheck_network_offline() {
+ info("[OFFLINE] setting Services.io.offline (do not forget to reset it!)");
+ // avoid that real network connectivity changes influence the test execution
+ Services.io.manageOfflineStatus = false;
+ Services.io.offline = true;
+ registerCleanupFunction(() => {
+ info("[ONLINE] Resetting Services.io.offline");
+ Services.io.offline = false;
+ Services.io.manageOfflineStatus = true;
+ });
+
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["network.disable-localhost-when-offline", true],
+ ["network.dns.offline-localhost", false],
+ ],
+ });
+
+ await runAboutDialogUpdateTest({}, [
+ {
+ panelId: "checkingFailed",
+ },
+ ]);
+});
diff --git a/toolkit/mozapps/update/tests/browser/browser_aboutDialog_fc_patch_completeBadSize.js b/toolkit/mozapps/update/tests/browser/browser_aboutDialog_fc_patch_completeBadSize.js
new file mode 100644
index 0000000000..fdc7d3407e
--- /dev/null
+++ b/toolkit/mozapps/update/tests/browser/browser_aboutDialog_fc_patch_completeBadSize.js
@@ -0,0 +1,36 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Test for About Dialog foreground check for updates
+// with a complete bad size patch.
+add_task(async function aboutDialog_foregroundCheck_completeBadSize() {
+ let downloadInfo = [];
+ if (Services.prefs.getBoolPref(PREF_APP_UPDATE_BITS_ENABLED)) {
+ downloadInfo[0] = { patchType: "complete", bitsResult: gBadSizeResult };
+ downloadInfo[1] = { patchType: "complete", internalResult: gBadSizeResult };
+ } else {
+ downloadInfo[0] = { patchType: "complete", internalResult: gBadSizeResult };
+ }
+
+ let params = { queryString: "&completePatchOnly=1&invalidCompleteSize=1" };
+ await runAboutDialogUpdateTest(params, [
+ {
+ panelId: "checkingForUpdates",
+ checkActiveUpdate: null,
+ continueFile: CONTINUE_CHECK,
+ },
+ {
+ panelId: "downloading",
+ checkActiveUpdate: { state: STATE_DOWNLOADING },
+ continueFile: CONTINUE_DOWNLOAD,
+ downloadInfo,
+ },
+ {
+ panelId: "downloadFailed",
+ checkActiveUpdate: null,
+ continueFile: null,
+ },
+ ]);
+});
diff --git a/toolkit/mozapps/update/tests/browser/browser_aboutDialog_fc_patch_partialBadSize.js b/toolkit/mozapps/update/tests/browser/browser_aboutDialog_fc_patch_partialBadSize.js
new file mode 100644
index 0000000000..411fb25969
--- /dev/null
+++ b/toolkit/mozapps/update/tests/browser/browser_aboutDialog_fc_patch_partialBadSize.js
@@ -0,0 +1,36 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Test for About Dialog foreground check for updates
+// with a partial bad size patch.
+add_task(async function aboutDialog_foregroundCheck_partialBadSize() {
+ let downloadInfo = [];
+ if (Services.prefs.getBoolPref(PREF_APP_UPDATE_BITS_ENABLED)) {
+ downloadInfo[0] = { patchType: "partial", bitsResult: gBadSizeResult };
+ downloadInfo[1] = { patchType: "partial", internalResult: gBadSizeResult };
+ } else {
+ downloadInfo[0] = { patchType: "partial", internalResult: gBadSizeResult };
+ }
+
+ let params = { queryString: "&partialPatchOnly=1&invalidPartialSize=1" };
+ await runAboutDialogUpdateTest(params, [
+ {
+ panelId: "checkingForUpdates",
+ checkActiveUpdate: null,
+ continueFile: CONTINUE_CHECK,
+ },
+ {
+ panelId: "downloading",
+ checkActiveUpdate: { state: STATE_DOWNLOADING },
+ continueFile: CONTINUE_DOWNLOAD,
+ downloadInfo,
+ },
+ {
+ panelId: "downloadFailed",
+ checkActiveUpdate: null,
+ continueFile: null,
+ },
+ ]);
+});
diff --git a/toolkit/mozapps/update/tests/browser/browser_aboutDialog_fc_patch_partialBadSize_complete.js b/toolkit/mozapps/update/tests/browser/browser_aboutDialog_fc_patch_partialBadSize_complete.js
new file mode 100644
index 0000000000..396be1e930
--- /dev/null
+++ b/toolkit/mozapps/update/tests/browser/browser_aboutDialog_fc_patch_partialBadSize_complete.js
@@ -0,0 +1,38 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Test for About Dialog foreground check for updates
+// with a partial bad size patch and a complete patch.
+add_task(async function aboutDialog_foregroundCheck_partialBadSize_complete() {
+ let downloadInfo = [];
+ if (Services.prefs.getBoolPref(PREF_APP_UPDATE_BITS_ENABLED)) {
+ downloadInfo[0] = { patchType: "partial", bitsResult: gBadSizeResult };
+ downloadInfo[1] = { patchType: "partial", internalResult: gBadSizeResult };
+ downloadInfo[2] = { patchType: "complete", bitsResult: "0" };
+ } else {
+ downloadInfo[0] = { patchType: "partial", internalResult: gBadSizeResult };
+ downloadInfo[1] = { patchType: "complete", internalResult: "0" };
+ }
+
+ let params = { queryString: "&invalidPartialSize=1" };
+ await runAboutDialogUpdateTest(params, [
+ {
+ panelId: "checkingForUpdates",
+ checkActiveUpdate: null,
+ continueFile: CONTINUE_CHECK,
+ },
+ {
+ panelId: "downloading",
+ checkActiveUpdate: { state: STATE_DOWNLOADING },
+ continueFile: CONTINUE_DOWNLOAD,
+ downloadInfo,
+ },
+ {
+ panelId: "apply",
+ checkActiveUpdate: { state: STATE_PENDING },
+ continueFile: null,
+ },
+ ]);
+});
diff --git a/toolkit/mozapps/update/tests/browser/browser_aboutDialog_fc_patch_partialBadSize_completeBadSize.js b/toolkit/mozapps/update/tests/browser/browser_aboutDialog_fc_patch_partialBadSize_completeBadSize.js
new file mode 100644
index 0000000000..fb10250a49
--- /dev/null
+++ b/toolkit/mozapps/update/tests/browser/browser_aboutDialog_fc_patch_partialBadSize_completeBadSize.js
@@ -0,0 +1,53 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Test for About Dialog foreground check for updates
+// with a partial bad size patch and a complete bad size patch.
+add_task(
+ async function aboutDialog_foregroundCheck_partialBadSize_completeBadSize() {
+ let downloadInfo = [];
+ if (Services.prefs.getBoolPref(PREF_APP_UPDATE_BITS_ENABLED)) {
+ downloadInfo[0] = { patchType: "partial", bitsResult: gBadSizeResult };
+ downloadInfo[1] = {
+ patchType: "partial",
+ internalResult: gBadSizeResult,
+ };
+ downloadInfo[2] = { patchType: "complete", bitsResult: gBadSizeResult };
+ downloadInfo[3] = {
+ patchType: "complete",
+ internalResult: gBadSizeResult,
+ };
+ } else {
+ downloadInfo[0] = {
+ patchType: "partial",
+ internalResult: gBadSizeResult,
+ };
+ downloadInfo[1] = {
+ patchType: "complete",
+ internalResult: gBadSizeResult,
+ };
+ }
+
+ let params = { queryString: "&invalidPartialSize=1&invalidCompleteSize=1" };
+ await runAboutDialogUpdateTest(params, [
+ {
+ panelId: "checkingForUpdates",
+ checkActiveUpdate: null,
+ continueFile: CONTINUE_CHECK,
+ },
+ {
+ panelId: "downloading",
+ checkActiveUpdate: { state: STATE_DOWNLOADING },
+ continueFile: CONTINUE_DOWNLOAD,
+ downloadInfo,
+ },
+ {
+ panelId: "downloadFailed",
+ checkActiveUpdate: null,
+ continueFile: null,
+ },
+ ]);
+ }
+);
diff --git a/toolkit/mozapps/update/tests/browser/browser_aboutDialog_internalError.js b/toolkit/mozapps/update/tests/browser/browser_aboutDialog_internalError.js
new file mode 100644
index 0000000000..d8d20ea05a
--- /dev/null
+++ b/toolkit/mozapps/update/tests/browser/browser_aboutDialog_internalError.js
@@ -0,0 +1,35 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+ChromeUtils.defineESModuleGetters(this, {
+ AppUpdater: "resource://gre/modules/AppUpdater.sys.mjs",
+ sinon: "resource://testing-common/Sinon.sys.mjs",
+});
+
+add_setup(function setup_internalErrorTest() {
+ const sandbox = sinon.createSandbox();
+ sandbox.stub(AppUpdater.prototype, "aus").get(() => {
+ throw new Error("intentional test error");
+ });
+ sandbox.stub(AppUpdater.prototype, "checker").get(() => {
+ throw new Error("intentional test error");
+ });
+ sandbox.stub(AppUpdater.prototype, "um").get(() => {
+ throw new Error("intentional test error");
+ });
+ registerCleanupFunction(() => {
+ sandbox.restore();
+ });
+});
+
+// Test for the About dialog's internal error handling.
+add_task(async function aboutDialog_internalError() {
+ let params = {};
+ await runAboutDialogUpdateTest(params, [
+ {
+ panelId: "internalError",
+ },
+ ]);
+});
diff --git a/toolkit/mozapps/update/tests/browser/browser_aboutPrefs_backgroundUpdateSetting.js b/toolkit/mozapps/update/tests/browser/browser_aboutPrefs_backgroundUpdateSetting.js
new file mode 100644
index 0000000000..88afa8fdbe
--- /dev/null
+++ b/toolkit/mozapps/update/tests/browser/browser_aboutPrefs_backgroundUpdateSetting.js
@@ -0,0 +1,172 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+"use strict";
+
+/**
+ * This file tests the background update UI in about:preferences.
+ */
+
+ChromeUtils.defineESModuleGetters(this, {
+ UpdateUtils: "resource://gre/modules/UpdateUtils.sys.mjs",
+});
+
+const BACKGROUND_UPDATE_PREF = "app.update.background.enabled";
+
+add_task(async function testBackgroundUpdateSettingUI() {
+ if (!AppConstants.MOZ_UPDATE_AGENT) {
+ // The element that we are testing in about:preferences is #ifdef'ed out of
+ // the file if MOZ_UPDATE_AGENT isn't defined. So there is nothing to
+ // test in that case.
+ logTestInfo(
+ `
+===============================================================================
+WARNING! This test involves background update, but background tasks are
+ disabled. This test will unconditionally pass since the feature it
+ wants to test isn't available.
+===============================================================================
+`
+ );
+ // Some of our testing environments do not consider a test to have passed if
+ // it didn't make any assertions.
+ ok(true, "Unconditionally passing test");
+ return;
+ }
+
+ let tab = await BrowserTestUtils.openNewForegroundTab(
+ gBrowser,
+ "about:preferences"
+ );
+
+ const originalBackgroundUpdateVal = await UpdateUtils.readUpdateConfigSetting(
+ BACKGROUND_UPDATE_PREF
+ );
+ const originalUpdateAutoVal = await UpdateUtils.getAppUpdateAutoEnabled();
+ registerCleanupFunction(async () => {
+ await BrowserTestUtils.removeTab(tab);
+ await UpdateUtils.writeUpdateConfigSetting(
+ BACKGROUND_UPDATE_PREF,
+ originalBackgroundUpdateVal
+ );
+ await UpdateUtils.setAppUpdateAutoEnabled(originalUpdateAutoVal);
+ });
+
+ // If auto update is disabled, the control for background update should be
+ // disabled, since we cannot update in the background if we can't update
+ // automatically.
+ await UpdateUtils.setAppUpdateAutoEnabled(false);
+ await SpecialPowers.spawn(
+ tab.linkedBrowser,
+ [UpdateUtils.PER_INSTALLATION_PREFS_SUPPORTED],
+ async perInstallationPrefsSupported => {
+ let backgroundUpdateCheckbox =
+ content.document.getElementById("backgroundUpdate");
+ is(
+ backgroundUpdateCheckbox.hidden,
+ !perInstallationPrefsSupported,
+ `The background update UI should ${
+ perInstallationPrefsSupported ? "not" : ""
+ } be hidden when and perInstallationPrefsSupported is ` +
+ `${perInstallationPrefsSupported}`
+ );
+ if (perInstallationPrefsSupported) {
+ is(
+ backgroundUpdateCheckbox.disabled,
+ true,
+ `The background update UI should be disabled when auto update is ` +
+ `disabled`
+ );
+ }
+ }
+ );
+
+ if (!UpdateUtils.PER_INSTALLATION_PREFS_SUPPORTED) {
+ // The remaining tests only make sense on platforms where per-installation
+ // prefs are supported and the UI will ever actually be displayed
+ return;
+ }
+
+ await UpdateUtils.setAppUpdateAutoEnabled(true);
+ await UpdateUtils.writeUpdateConfigSetting(BACKGROUND_UPDATE_PREF, true);
+
+ await SpecialPowers.spawn(tab.linkedBrowser, [], async function () {
+ let backgroundUpdateCheckbox =
+ content.document.getElementById("backgroundUpdate");
+ is(
+ backgroundUpdateCheckbox.disabled,
+ false,
+ `The background update UI should not be disabled when auto update is ` +
+ `enabled`
+ );
+
+ is(
+ backgroundUpdateCheckbox.checked,
+ true,
+ "After enabling background update, the checkbox should be checked"
+ );
+
+ // Note that this action results in asynchronous activity. Normally when
+ // we change the update config, we await on the function to wait for the
+ // value to be written to the disk. We can't easily await on the UI state
+ // though. Luckily, we don't have to because reads/writes of the config file
+ // are serialized. So when we verify the written value by awaiting on
+ // readUpdateConfigSetting(), that will also wait for the value to be
+ // written to disk and for this UI to react to that.
+ backgroundUpdateCheckbox.click();
+ });
+
+ is(
+ await UpdateUtils.readUpdateConfigSetting(BACKGROUND_UPDATE_PREF),
+ false,
+ "Toggling the checkbox should have changed the setting value to false"
+ );
+
+ await SpecialPowers.spawn(tab.linkedBrowser, [], async function () {
+ let backgroundUpdateCheckbox =
+ content.document.getElementById("backgroundUpdate");
+ is(
+ backgroundUpdateCheckbox.checked,
+ false,
+ "After toggling the checked checkbox, it should be unchecked."
+ );
+
+ // Like the last call like this one, this initiates asynchronous behavior.
+ backgroundUpdateCheckbox.click();
+ });
+
+ is(
+ await UpdateUtils.readUpdateConfigSetting(BACKGROUND_UPDATE_PREF),
+ true,
+ "Toggling the checkbox should have changed the setting value to true"
+ );
+
+ await SpecialPowers.spawn(tab.linkedBrowser, [], async function () {
+ is(
+ content.document.getElementById("backgroundUpdate").checked,
+ true,
+ "After toggling the unchecked checkbox, it should be checked"
+ );
+ });
+
+ // Test that the UI reacts to observed setting changes properly.
+ await UpdateUtils.writeUpdateConfigSetting(BACKGROUND_UPDATE_PREF, false);
+
+ await SpecialPowers.spawn(tab.linkedBrowser, [], async function () {
+ is(
+ content.document.getElementById("backgroundUpdate").checked,
+ false,
+ "Externally disabling background update should uncheck the checkbox"
+ );
+ });
+
+ await UpdateUtils.writeUpdateConfigSetting(BACKGROUND_UPDATE_PREF, true);
+
+ await SpecialPowers.spawn(tab.linkedBrowser, [], async function () {
+ is(
+ content.document.getElementById("backgroundUpdate").checked,
+ true,
+ "Externally enabling background update should check the checkbox"
+ );
+ });
+});
diff --git a/toolkit/mozapps/update/tests/browser/browser_aboutPrefs_bc_downloaded.js b/toolkit/mozapps/update/tests/browser/browser_aboutPrefs_bc_downloaded.js
new file mode 100644
index 0000000000..6df4ee7ff5
--- /dev/null
+++ b/toolkit/mozapps/update/tests/browser/browser_aboutPrefs_bc_downloaded.js
@@ -0,0 +1,17 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Test for about:preferences background check for updates
+// with the update downloaded when about:preferences is opened.
+add_task(async function aboutPrefs_backgroundCheck_downloaded() {
+ let params = { backgroundUpdate: true, waitForUpdateState: STATE_PENDING };
+ await runAboutPrefsUpdateTest(params, [
+ {
+ panelId: "apply",
+ checkActiveUpdate: { state: STATE_PENDING },
+ continueFile: null,
+ },
+ ]);
+});
diff --git a/toolkit/mozapps/update/tests/browser/browser_aboutPrefs_bc_downloaded_staged.js b/toolkit/mozapps/update/tests/browser/browser_aboutPrefs_bc_downloaded_staged.js
new file mode 100644
index 0000000000..749a8f0b07
--- /dev/null
+++ b/toolkit/mozapps/update/tests/browser/browser_aboutPrefs_bc_downloaded_staged.js
@@ -0,0 +1,28 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Test for about:preferences background check for updates
+// with the update downloaded and staged when about:preferences is opened.
+add_task(async function aboutPrefs_backgroundCheck_downloaded_staged() {
+ await SpecialPowers.pushPrefEnv({
+ set: [[PREF_APP_UPDATE_STAGING_ENABLED, true]],
+ });
+
+ // Since the partial should be successful specify an invalid size for the
+ // complete update.
+ let params = {
+ queryString: "&invalidCompleteSize=1",
+ backgroundUpdate: true,
+ continueFile: CONTINUE_STAGING,
+ waitForUpdateState: STATE_APPLIED,
+ };
+ await runAboutPrefsUpdateTest(params, [
+ {
+ panelId: "apply",
+ checkActiveUpdate: { state: STATE_APPLIED },
+ continueFile: null,
+ },
+ ]);
+});
diff --git a/toolkit/mozapps/update/tests/browser/browser_aboutPrefs_bc_downloaded_staging.js b/toolkit/mozapps/update/tests/browser/browser_aboutPrefs_bc_downloaded_staging.js
new file mode 100644
index 0000000000..73804cdf7a
--- /dev/null
+++ b/toolkit/mozapps/update/tests/browser/browser_aboutPrefs_bc_downloaded_staging.js
@@ -0,0 +1,59 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Test for about:preferences background check for updates
+// with the update downloaded and about:preferences opened during staging.
+add_task(async function aboutPrefs_backgroundCheck_downloaded_staged() {
+ await SpecialPowers.pushPrefEnv({
+ set: [[PREF_APP_UPDATE_STAGING_ENABLED, true]],
+ });
+
+ let lankpackCall = mockLangpackInstall();
+
+ // Since the partial should be successful specify an invalid size for the
+ // complete update.
+ let params = {
+ queryString: "&invalidCompleteSize=1",
+ backgroundUpdate: true,
+ waitForUpdateState: STATE_PENDING,
+ };
+ await runAboutPrefsUpdateTest(params, [
+ {
+ panelId: "applying",
+ checkActiveUpdate: { state: STATE_PENDING },
+ continueFile: CONTINUE_STAGING,
+ },
+ async tab => {
+ // Once the state is pending but langpacks aren't complete the about
+ // dialog should still be showing downloading.
+ TestUtils.waitForCondition(() => {
+ return readStatusFile() == STATE_APPLIED;
+ });
+
+ let updateDeckId = await SpecialPowers.spawn(
+ tab.linkedBrowser,
+ [],
+ () => {
+ return content.gAppUpdater.selectedPanel.id;
+ }
+ );
+
+ is(updateDeckId, "applying", "UI should still show as applying.");
+
+ let { appVersion, resolve } = await lankpackCall;
+ is(
+ appVersion,
+ Services.appinfo.version,
+ "Should see the right app version."
+ );
+ resolve();
+ },
+ {
+ panelId: "apply",
+ checkActiveUpdate: { state: STATE_APPLIED },
+ continueFile: null,
+ },
+ ]);
+});
diff --git a/toolkit/mozapps/update/tests/browser/browser_aboutPrefs_bc_downloaded_stagingFailure.js b/toolkit/mozapps/update/tests/browser/browser_aboutPrefs_bc_downloaded_stagingFailure.js
new file mode 100644
index 0000000000..9755fe167b
--- /dev/null
+++ b/toolkit/mozapps/update/tests/browser/browser_aboutPrefs_bc_downloaded_stagingFailure.js
@@ -0,0 +1,29 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Test for about:preferences background check for updates
+// with the update downloaded and staging has failed when the about:preferences
+// is opened.
+add_task(async function aboutPrefs_backgroundCheck_downloaded_stagingFailure() {
+ Services.env.set("MOZ_TEST_STAGING_ERROR", "1");
+ await SpecialPowers.pushPrefEnv({
+ set: [[PREF_APP_UPDATE_STAGING_ENABLED, true]],
+ });
+
+ // Since the partial should be successful specify an invalid size for the
+ // complete update.
+ let params = {
+ queryString: "&completePatchOnly=1",
+ backgroundUpdate: true,
+ waitForUpdateState: STATE_PENDING,
+ };
+ await runAboutDialogUpdateTest(params, [
+ {
+ panelId: "apply",
+ checkActiveUpdate: { state: STATE_PENDING },
+ continueFile: null,
+ },
+ ]);
+});
diff --git a/toolkit/mozapps/update/tests/browser/browser_aboutPrefs_bc_downloading.js b/toolkit/mozapps/update/tests/browser/browser_aboutPrefs_bc_downloading.js
new file mode 100644
index 0000000000..d9034296b0
--- /dev/null
+++ b/toolkit/mozapps/update/tests/browser/browser_aboutPrefs_bc_downloading.js
@@ -0,0 +1,63 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Test for about:preferences background check for updates
+// with about:preferences opened during downloading.
+add_task(async function aboutPrefs_backgroundCheck_downloading() {
+ let downloadInfo = [];
+ if (Services.prefs.getBoolPref(PREF_APP_UPDATE_BITS_ENABLED)) {
+ downloadInfo[0] = { patchType: "partial", bitsResult: "0" };
+ } else {
+ downloadInfo[0] = { patchType: "partial", internalResult: "0" };
+ }
+
+ let lankpackCall = mockLangpackInstall();
+
+ // Since the partial should be successful specify an invalid size for the
+ // complete update.
+ let params = {
+ queryString: "&useSlowDownloadMar=1&invalidCompleteSize=1",
+ backgroundUpdate: true,
+ waitForUpdateState: STATE_DOWNLOADING,
+ };
+ await runAboutPrefsUpdateTest(params, [
+ {
+ panelId: "downloading",
+ checkActiveUpdate: { state: STATE_DOWNLOADING },
+ continueFile: CONTINUE_DOWNLOAD,
+ downloadInfo,
+ },
+ async tab => {
+ // Once the state is pending but langpacks aren't complete the about
+ // dialog should still be showing downloading.
+ TestUtils.waitForCondition(() => {
+ return readStatusFile() == STATE_PENDING;
+ });
+
+ let updateDeckId = await SpecialPowers.spawn(
+ tab.linkedBrowser,
+ [],
+ () => {
+ return content.gAppUpdater.selectedPanel.id;
+ }
+ );
+
+ is(updateDeckId, "downloading", "UI should still show as downloading.");
+
+ let { appVersion, resolve } = await lankpackCall;
+ is(
+ appVersion,
+ Services.appinfo.version,
+ "Should see the right app version."
+ );
+ resolve();
+ },
+ {
+ panelId: "apply",
+ checkActiveUpdate: { state: STATE_PENDING },
+ continueFile: null,
+ },
+ ]);
+});
diff --git a/toolkit/mozapps/update/tests/browser/browser_aboutPrefs_bc_downloading_staging.js b/toolkit/mozapps/update/tests/browser/browser_aboutPrefs_bc_downloading_staging.js
new file mode 100644
index 0000000000..8085ec4c2c
--- /dev/null
+++ b/toolkit/mozapps/update/tests/browser/browser_aboutPrefs_bc_downloading_staging.js
@@ -0,0 +1,72 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Test for about:preferences background check for updates
+// with about:preferences opened during downloading and stages the update.
+add_task(async function aboutPrefs_backgroundCheck_downloading_staging() {
+ await SpecialPowers.pushPrefEnv({
+ set: [[PREF_APP_UPDATE_STAGING_ENABLED, true]],
+ });
+
+ let downloadInfo = [];
+ if (Services.prefs.getBoolPref(PREF_APP_UPDATE_BITS_ENABLED)) {
+ downloadInfo[0] = { patchType: "partial", bitsResult: "0" };
+ } else {
+ downloadInfo[0] = { patchType: "partial", internalResult: "0" };
+ }
+
+ let lankpackCall = mockLangpackInstall();
+
+ // Since the partial should be successful specify an invalid size for the
+ // complete update.
+ let params = {
+ queryString: "&useSlowDownloadMar=1&invalidCompleteSize=1",
+ backgroundUpdate: true,
+ waitForUpdateState: STATE_DOWNLOADING,
+ };
+ await runAboutPrefsUpdateTest(params, [
+ {
+ panelId: "downloading",
+ checkActiveUpdate: { state: STATE_DOWNLOADING },
+ continueFile: CONTINUE_DOWNLOAD,
+ downloadInfo,
+ },
+ {
+ panelId: "applying",
+ checkActiveUpdate: { state: STATE_PENDING },
+ continueFile: CONTINUE_STAGING,
+ },
+ async tab => {
+ // Once the state is pending but langpacks aren't complete the about
+ // dialog should still be showing downloading.
+ TestUtils.waitForCondition(() => {
+ return readStatusFile() == STATE_APPLIED;
+ });
+
+ let updateDeckId = await SpecialPowers.spawn(
+ tab.linkedBrowser,
+ [],
+ () => {
+ return content.gAppUpdater.selectedPanel.id;
+ }
+ );
+
+ is(updateDeckId, "applying", "UI should still show as applying.");
+
+ let { appVersion, resolve } = await lankpackCall;
+ is(
+ appVersion,
+ Services.appinfo.version,
+ "Should see the right app version."
+ );
+ resolve();
+ },
+ {
+ panelId: "apply",
+ checkActiveUpdate: { state: STATE_APPLIED },
+ continueFile: null,
+ },
+ ]);
+});
diff --git a/toolkit/mozapps/update/tests/browser/browser_aboutPrefs_bc_multiUpdate.js b/toolkit/mozapps/update/tests/browser/browser_aboutPrefs_bc_multiUpdate.js
new file mode 100644
index 0000000000..9ccf593db5
--- /dev/null
+++ b/toolkit/mozapps/update/tests/browser/browser_aboutPrefs_bc_multiUpdate.js
@@ -0,0 +1,52 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+const FIRST_UPDATE_VERSION = "999998.0";
+const SECOND_UPDATE_VERSION = "999999.0";
+
+function prepareToDownloadVersion(version) {
+ setUpdateURL(
+ URL_HTTP_UPDATE_SJS +
+ `?detailsURL=${gDetailsURL}&promptWaitTime=0&appVersion=${version}`
+ );
+}
+
+add_task(async function aboutPrefs_backgroundCheck_multiUpdate() {
+ await SpecialPowers.pushPrefEnv({
+ set: [[PREF_APP_UPDATE_STAGING_ENABLED, true]],
+ });
+
+ let params = {
+ version: FIRST_UPDATE_VERSION,
+ backgroundUpdate: true,
+ waitForUpdateState: STATE_PENDING,
+ };
+ await runAboutPrefsUpdateTest(params, [
+ {
+ panelId: "applying",
+ checkActiveUpdate: { state: STATE_PENDING },
+ continueFile: CONTINUE_STAGING,
+ },
+ {
+ panelId: "apply",
+ checkActiveUpdate: { state: STATE_APPLIED },
+ continueFile: null,
+ },
+ () => {
+ prepareToDownloadVersion(SECOND_UPDATE_VERSION);
+ gAUS.checkForBackgroundUpdates();
+ },
+ {
+ panelId: "applying",
+ checkActiveUpdate: { state: STATE_PENDING },
+ continueFile: CONTINUE_STAGING,
+ },
+ {
+ panelId: "apply",
+ checkActiveUpdate: { state: STATE_APPLIED },
+ continueFile: null,
+ },
+ ]);
+});
diff --git a/toolkit/mozapps/update/tests/browser/browser_aboutPrefs_fc_apply_blocked.js b/toolkit/mozapps/update/tests/browser/browser_aboutPrefs_fc_apply_blocked.js
new file mode 100644
index 0000000000..22daa5e256
--- /dev/null
+++ b/toolkit/mozapps/update/tests/browser/browser_aboutPrefs_fc_apply_blocked.js
@@ -0,0 +1,96 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+const { PromptTestUtils } = ChromeUtils.importESModule(
+ "resource://testing-common/PromptTestUtils.sys.mjs"
+);
+
+const BUILDER_URL = "https://example.com/document-builder.sjs?html=";
+const PAGE_MARKUP = `
+<html>
+<head>
+ <script>
+ window.onbeforeunload = function() {
+ return true;
+ };
+ </script>
+</head>
+<body>TEST PAGE</body>
+</html>
+`;
+const TEST_URL = BUILDER_URL + encodeURI(PAGE_MARKUP);
+
+add_setup(async () => {
+ await SpecialPowers.pushPrefEnv({
+ set: [["dom.require_user_interaction_for_beforeunload", false]],
+ });
+});
+
+// Test for About Dialog foreground check for updates
+// and apply but restart is blocked by a page.
+add_task(async function aboutDialog_foregroundCheck_apply_blocked() {
+ let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, TEST_URL);
+
+ let downloadInfo = [];
+ if (Services.prefs.getBoolPref(PREF_APP_UPDATE_BITS_ENABLED)) {
+ downloadInfo[0] = { patchType: "partial", bitsResult: "0" };
+ } else {
+ downloadInfo[0] = { patchType: "partial", internalResult: "0" };
+ }
+
+ let prefsTab;
+ let handlePromise = (async () => {
+ let dialog = await PromptTestUtils.waitForPrompt(window, {
+ modalType: Ci.nsIPrompt.MODAL_TYPE_CONTENT,
+ promptType: "confirmEx",
+ });
+ await SpecialPowers.spawn(prefsTab.linkedBrowser, [], async () => {
+ Assert.equal(
+ content.gAppUpdater.selectedPanel.id,
+ "restarting",
+ "The restarting panel should be displayed"
+ );
+ });
+
+ await PromptTestUtils.handlePrompt(dialog, { buttonNumClick: 1 });
+ })();
+
+ // Since the partial should be successful specify an invalid size for the
+ // complete update.
+ let params = { queryString: "&invalidCompleteSize=1&promptWaitTime=0" };
+ await runAboutPrefsUpdateTest(params, [
+ {
+ panelId: "checkingForUpdates",
+ checkActiveUpdate: null,
+ continueFile: CONTINUE_CHECK,
+ },
+ {
+ panelId: "downloading",
+ checkActiveUpdate: { state: STATE_DOWNLOADING },
+ continueFile: CONTINUE_DOWNLOAD,
+ downloadInfo,
+ },
+ async function getPrefsTab(tab) {
+ prefsTab = tab;
+ },
+ {
+ panelId: "apply",
+ checkActiveUpdate: { state: STATE_PENDING },
+ continueFile: null,
+ forceApply: true,
+ },
+ async function ensureDialogHasBeenCanceled() {
+ await handlePromise;
+ },
+ // A final check to ensure that we are back in the apply state.
+ {
+ panelId: "apply",
+ checkActiveUpdate: { state: STATE_PENDING },
+ continueFile: null,
+ },
+ ]);
+
+ BrowserTestUtils.removeTab(tab, { skipPermitUnload: true });
+});
diff --git a/toolkit/mozapps/update/tests/browser/browser_aboutPrefs_fc_check_cantApply.js b/toolkit/mozapps/update/tests/browser/browser_aboutPrefs_fc_check_cantApply.js
new file mode 100644
index 0000000000..aac9b33134
--- /dev/null
+++ b/toolkit/mozapps/update/tests/browser/browser_aboutPrefs_fc_check_cantApply.js
@@ -0,0 +1,24 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Test for about:preferences foreground check for updates
+// without the ability to apply updates.
+add_task(async function aboutPrefs_foregroundCheck_cantApply() {
+ lockWriteTestFile();
+
+ let params = {};
+ await runAboutPrefsUpdateTest(params, [
+ {
+ panelId: "checkingForUpdates",
+ checkActiveUpdate: null,
+ continueFile: CONTINUE_CHECK,
+ },
+ {
+ panelId: "manualUpdate",
+ checkActiveUpdate: null,
+ continueFile: null,
+ },
+ ]);
+});
diff --git a/toolkit/mozapps/update/tests/browser/browser_aboutPrefs_fc_check_malformedXML.js b/toolkit/mozapps/update/tests/browser/browser_aboutPrefs_fc_check_malformedXML.js
new file mode 100644
index 0000000000..6d99a58e86
--- /dev/null
+++ b/toolkit/mozapps/update/tests/browser/browser_aboutPrefs_fc_check_malformedXML.js
@@ -0,0 +1,22 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Test for about:preferences foreground check for updates
+// with a malformed update XML file.
+add_task(async function aboutPrefs_foregroundCheck_malformedXML() {
+ let params = { queryString: "&xmlMalformed=1" };
+ await runAboutPrefsUpdateTest(params, [
+ {
+ panelId: "checkingForUpdates",
+ checkActiveUpdate: null,
+ continueFile: CONTINUE_CHECK,
+ },
+ {
+ panelId: "checkingFailed",
+ checkActiveUpdate: null,
+ continueFile: null,
+ },
+ ]);
+});
diff --git a/toolkit/mozapps/update/tests/browser/browser_aboutPrefs_fc_check_noUpdate.js b/toolkit/mozapps/update/tests/browser/browser_aboutPrefs_fc_check_noUpdate.js
new file mode 100644
index 0000000000..a8f6c072f7
--- /dev/null
+++ b/toolkit/mozapps/update/tests/browser/browser_aboutPrefs_fc_check_noUpdate.js
@@ -0,0 +1,22 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Test for about:preferences foreground check for updates
+// with no update available.
+add_task(async function aboutPrefs_foregroundCheck_noUpdate() {
+ let params = { queryString: "&noUpdates=1" };
+ await runAboutPrefsUpdateTest(params, [
+ {
+ panelId: "checkingForUpdates",
+ checkActiveUpdate: null,
+ continueFile: CONTINUE_CHECK,
+ },
+ {
+ panelId: "noUpdatesFound",
+ checkActiveUpdate: null,
+ continueFile: null,
+ },
+ ]);
+});
diff --git a/toolkit/mozapps/update/tests/browser/browser_aboutPrefs_fc_check_otherInstance.js b/toolkit/mozapps/update/tests/browser/browser_aboutPrefs_fc_check_otherInstance.js
new file mode 100644
index 0000000000..271c8f2837
--- /dev/null
+++ b/toolkit/mozapps/update/tests/browser/browser_aboutPrefs_fc_check_otherInstance.js
@@ -0,0 +1,19 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Test for about:preferences foreground check for updates
+// with another application instance handling updates.
+add_task(async function aboutPrefs_foregroundCheck_otherInstance() {
+ setOtherInstanceHandlingUpdates();
+
+ let params = {};
+ await runAboutPrefsUpdateTest(params, [
+ {
+ panelId: "otherInstanceHandlingUpdates",
+ checkActiveUpdate: null,
+ continueFile: null,
+ },
+ ]);
+});
diff --git a/toolkit/mozapps/update/tests/browser/browser_aboutPrefs_fc_check_unsupported.js b/toolkit/mozapps/update/tests/browser/browser_aboutPrefs_fc_check_unsupported.js
new file mode 100644
index 0000000000..e18ce31bc4
--- /dev/null
+++ b/toolkit/mozapps/update/tests/browser/browser_aboutPrefs_fc_check_unsupported.js
@@ -0,0 +1,22 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Test for about:preferences foreground check for updates
+// with an unsupported update.
+add_task(async function aboutPrefs_foregroundCheck_unsupported() {
+ let params = { queryString: "&unsupported=1" };
+ await runAboutPrefsUpdateTest(params, [
+ {
+ panelId: "checkingForUpdates",
+ checkActiveUpdate: null,
+ continueFile: CONTINUE_CHECK,
+ },
+ {
+ panelId: "unsupportedSystem",
+ checkActiveUpdate: null,
+ continueFile: null,
+ },
+ ]);
+});
diff --git a/toolkit/mozapps/update/tests/browser/browser_aboutPrefs_fc_downloadAuto.js b/toolkit/mozapps/update/tests/browser/browser_aboutPrefs_fc_downloadAuto.js
new file mode 100644
index 0000000000..bd5fd18289
--- /dev/null
+++ b/toolkit/mozapps/update/tests/browser/browser_aboutPrefs_fc_downloadAuto.js
@@ -0,0 +1,37 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Test for about:preferences foreground check for updates
+// with an automatic download.
+add_task(async function aboutPrefs_foregroundCheck_downloadAuto() {
+ let downloadInfo = [];
+ if (Services.prefs.getBoolPref(PREF_APP_UPDATE_BITS_ENABLED)) {
+ downloadInfo[0] = { patchType: "partial", bitsResult: "0" };
+ } else {
+ downloadInfo[0] = { patchType: "partial", internalResult: "0" };
+ }
+
+ // Since the partial should be successful specify an invalid size for the
+ // complete update.
+ let params = { queryString: "&invalidCompleteSize=1" };
+ await runAboutPrefsUpdateTest(params, [
+ {
+ panelId: "checkingForUpdates",
+ checkActiveUpdate: null,
+ continueFile: CONTINUE_CHECK,
+ },
+ {
+ panelId: "downloading",
+ checkActiveUpdate: { state: STATE_DOWNLOADING },
+ continueFile: CONTINUE_DOWNLOAD,
+ downloadInfo,
+ },
+ {
+ panelId: "apply",
+ checkActiveUpdate: { state: STATE_PENDING },
+ continueFile: null,
+ },
+ ]);
+});
diff --git a/toolkit/mozapps/update/tests/browser/browser_aboutPrefs_fc_downloadAuto_staging.js b/toolkit/mozapps/update/tests/browser/browser_aboutPrefs_fc_downloadAuto_staging.js
new file mode 100644
index 0000000000..1d9d082edd
--- /dev/null
+++ b/toolkit/mozapps/update/tests/browser/browser_aboutPrefs_fc_downloadAuto_staging.js
@@ -0,0 +1,46 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Test for about:preferences foreground check for updates
+// with an automatic download and update staging.
+add_task(async function aboutPrefs_foregroundCheck_downloadAuto_staging() {
+ await SpecialPowers.pushPrefEnv({
+ set: [[PREF_APP_UPDATE_STAGING_ENABLED, true]],
+ });
+
+ let downloadInfo = [];
+ if (Services.prefs.getBoolPref(PREF_APP_UPDATE_BITS_ENABLED)) {
+ downloadInfo[0] = { patchType: "partial", bitsResult: "0" };
+ } else {
+ downloadInfo[0] = { patchType: "partial", internalResult: "0" };
+ }
+
+ // Since the partial should be successful specify an invalid size for the
+ // complete update.
+ let params = { queryString: "&invalidCompleteSize=1" };
+ await runAboutPrefsUpdateTest(params, [
+ {
+ panelId: "checkingForUpdates",
+ checkActiveUpdate: null,
+ continueFile: CONTINUE_CHECK,
+ },
+ {
+ panelId: "downloading",
+ checkActiveUpdate: { state: STATE_DOWNLOADING },
+ continueFile: CONTINUE_DOWNLOAD,
+ downloadInfo,
+ },
+ {
+ panelId: "applying",
+ checkActiveUpdate: { state: STATE_PENDING },
+ continueFile: CONTINUE_STAGING,
+ },
+ {
+ panelId: "apply",
+ checkActiveUpdate: { state: STATE_APPLIED },
+ continueFile: null,
+ },
+ ]);
+});
diff --git a/toolkit/mozapps/update/tests/browser/browser_aboutPrefs_fc_downloadOptIn.js b/toolkit/mozapps/update/tests/browser/browser_aboutPrefs_fc_downloadOptIn.js
new file mode 100644
index 0000000000..115e875b74
--- /dev/null
+++ b/toolkit/mozapps/update/tests/browser/browser_aboutPrefs_fc_downloadOptIn.js
@@ -0,0 +1,44 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Test for about:preferences foreground check for updates
+// with a manual download.
+add_task(async function aboutPrefs_foregroundCheck_downloadOptIn() {
+ await UpdateUtils.setAppUpdateAutoEnabled(false);
+
+ let downloadInfo = [];
+ if (Services.prefs.getBoolPref(PREF_APP_UPDATE_BITS_ENABLED)) {
+ downloadInfo[0] = { patchType: "partial", bitsResult: "0" };
+ } else {
+ downloadInfo[0] = { patchType: "partial", internalResult: "0" };
+ }
+
+ // Since the partial should be successful specify an invalid size for the
+ // complete update.
+ let params = { queryString: "&invalidCompleteSize=1" };
+ await runAboutPrefsUpdateTest(params, [
+ {
+ panelId: "checkingForUpdates",
+ checkActiveUpdate: null,
+ continueFile: CONTINUE_CHECK,
+ },
+ {
+ panelId: "downloadAndInstall",
+ checkActiveUpdate: null,
+ continueFile: null,
+ },
+ {
+ panelId: "downloading",
+ checkActiveUpdate: { state: STATE_DOWNLOADING },
+ continueFile: CONTINUE_DOWNLOAD,
+ downloadInfo,
+ },
+ {
+ panelId: "apply",
+ checkActiveUpdate: { state: STATE_PENDING },
+ continueFile: null,
+ },
+ ]);
+});
diff --git a/toolkit/mozapps/update/tests/browser/browser_aboutPrefs_fc_downloadOptIn_staging.js b/toolkit/mozapps/update/tests/browser/browser_aboutPrefs_fc_downloadOptIn_staging.js
new file mode 100644
index 0000000000..cff5d74701
--- /dev/null
+++ b/toolkit/mozapps/update/tests/browser/browser_aboutPrefs_fc_downloadOptIn_staging.js
@@ -0,0 +1,52 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Test for about:preferences foreground check for updates
+// with a manual download and update staging.
+add_task(async function aboutPrefs_foregroundCheck_downloadOptIn_staging() {
+ await SpecialPowers.pushPrefEnv({
+ set: [[PREF_APP_UPDATE_STAGING_ENABLED, true]],
+ });
+ await UpdateUtils.setAppUpdateAutoEnabled(false);
+
+ let downloadInfo = [];
+ if (Services.prefs.getBoolPref(PREF_APP_UPDATE_BITS_ENABLED)) {
+ downloadInfo[0] = { patchType: "partial", bitsResult: "0" };
+ } else {
+ downloadInfo[0] = { patchType: "partial", internalResult: "0" };
+ }
+
+ // Since the partial should be successful specify an invalid size for the
+ // complete update.
+ let params = { queryString: "&invalidCompleteSize=1" };
+ await runAboutPrefsUpdateTest(params, [
+ {
+ panelId: "checkingForUpdates",
+ checkActiveUpdate: null,
+ continueFile: CONTINUE_CHECK,
+ },
+ {
+ panelId: "downloadAndInstall",
+ checkActiveUpdate: null,
+ continueFile: null,
+ },
+ {
+ panelId: "downloading",
+ checkActiveUpdate: { state: STATE_DOWNLOADING },
+ continueFile: CONTINUE_DOWNLOAD,
+ downloadInfo,
+ },
+ {
+ panelId: "applying",
+ checkActiveUpdate: { state: STATE_PENDING },
+ continueFile: CONTINUE_STAGING,
+ },
+ {
+ panelId: "apply",
+ checkActiveUpdate: { state: STATE_APPLIED },
+ continueFile: null,
+ },
+ ]);
+});
diff --git a/toolkit/mozapps/update/tests/browser/browser_aboutPrefs_fc_network_failure.js b/toolkit/mozapps/update/tests/browser/browser_aboutPrefs_fc_network_failure.js
new file mode 100644
index 0000000000..fbf91c0e54
--- /dev/null
+++ b/toolkit/mozapps/update/tests/browser/browser_aboutPrefs_fc_network_failure.js
@@ -0,0 +1,17 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Test for about:preferences foreground check for updates which fails,
+// because it is impossible to connect to the update server.
+add_task(async function aboutPrefs_foregroundCheck_network_failure() {
+ let params = {
+ baseURL: "https://localhost:7777",
+ };
+ await runAboutPrefsUpdateTest(params, [
+ {
+ panelId: "checkingFailed",
+ },
+ ]);
+});
diff --git a/toolkit/mozapps/update/tests/browser/browser_aboutPrefs_fc_network_offline.js b/toolkit/mozapps/update/tests/browser/browser_aboutPrefs_fc_network_offline.js
new file mode 100644
index 0000000000..7b5b6899b3
--- /dev/null
+++ b/toolkit/mozapps/update/tests/browser/browser_aboutPrefs_fc_network_offline.js
@@ -0,0 +1,31 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Test for about:preferences foreground check for updates which fails because
+// the browser is in offline mode and `localhost` cannot be resolved.
+add_task(async function aboutPrefs_foregroundCheck_network_offline() {
+ info("[OFFLINE] Setting Services.io.offline (do not forget to reset it!)");
+ // avoid that real network connectivity changes influence the test execution
+ Services.io.manageOfflineStatus = false;
+ Services.io.offline = true;
+ registerCleanupFunction(() => {
+ info("[ONLINE] Resetting Services.io.offline");
+ Services.io.offline = false;
+ Services.io.manageOfflineStatus = true;
+ });
+
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["network.disable-localhost-when-offline", true],
+ ["network.dns.offline-localhost", false],
+ ],
+ });
+
+ await runAboutPrefsUpdateTest({}, [
+ {
+ panelId: "checkingFailed",
+ },
+ ]);
+});
diff --git a/toolkit/mozapps/update/tests/browser/browser_aboutPrefs_fc_patch_completeBadSize.js b/toolkit/mozapps/update/tests/browser/browser_aboutPrefs_fc_patch_completeBadSize.js
new file mode 100644
index 0000000000..a36d3d9807
--- /dev/null
+++ b/toolkit/mozapps/update/tests/browser/browser_aboutPrefs_fc_patch_completeBadSize.js
@@ -0,0 +1,36 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Test for about:preferences foreground check for updates
+// with a complete bad size patch.
+add_task(async function aboutPrefs_foregroundCheck_completeBadSize() {
+ let downloadInfo = [];
+ if (Services.prefs.getBoolPref(PREF_APP_UPDATE_BITS_ENABLED)) {
+ downloadInfo[0] = { patchType: "complete", bitsResult: gBadSizeResult };
+ downloadInfo[1] = { patchType: "complete", internalResult: gBadSizeResult };
+ } else {
+ downloadInfo[0] = { patchType: "complete", internalResult: gBadSizeResult };
+ }
+
+ let params = { queryString: "&completePatchOnly=1&invalidCompleteSize=1" };
+ await runAboutPrefsUpdateTest(params, [
+ {
+ panelId: "checkingForUpdates",
+ checkActiveUpdate: null,
+ continueFile: CONTINUE_CHECK,
+ },
+ {
+ panelId: "downloading",
+ checkActiveUpdate: { state: STATE_DOWNLOADING },
+ continueFile: CONTINUE_DOWNLOAD,
+ downloadInfo,
+ },
+ {
+ panelId: "downloadFailed",
+ checkActiveUpdate: null,
+ continueFile: null,
+ },
+ ]);
+});
diff --git a/toolkit/mozapps/update/tests/browser/browser_aboutPrefs_fc_patch_partialBadSize.js b/toolkit/mozapps/update/tests/browser/browser_aboutPrefs_fc_patch_partialBadSize.js
new file mode 100644
index 0000000000..060acd405a
--- /dev/null
+++ b/toolkit/mozapps/update/tests/browser/browser_aboutPrefs_fc_patch_partialBadSize.js
@@ -0,0 +1,36 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Test for about:preferences foreground check for updates
+// with a partial bad size patch.
+add_task(async function aboutPrefs_foregroundCheck_partialBadSize() {
+ let downloadInfo = [];
+ if (Services.prefs.getBoolPref(PREF_APP_UPDATE_BITS_ENABLED)) {
+ downloadInfo[0] = { patchType: "partial", bitsResult: gBadSizeResult };
+ downloadInfo[1] = { patchType: "partial", internalResult: gBadSizeResult };
+ } else {
+ downloadInfo[0] = { patchType: "partial", internalResult: gBadSizeResult };
+ }
+
+ let params = { queryString: "&partialPatchOnly=1&invalidPartialSize=1" };
+ await runAboutPrefsUpdateTest(params, [
+ {
+ panelId: "checkingForUpdates",
+ checkActiveUpdate: null,
+ continueFile: CONTINUE_CHECK,
+ },
+ {
+ panelId: "downloading",
+ checkActiveUpdate: { state: STATE_DOWNLOADING },
+ continueFile: CONTINUE_DOWNLOAD,
+ downloadInfo,
+ },
+ {
+ panelId: "downloadFailed",
+ checkActiveUpdate: null,
+ continueFile: null,
+ },
+ ]);
+});
diff --git a/toolkit/mozapps/update/tests/browser/browser_aboutPrefs_fc_patch_partialBadSize_complete.js b/toolkit/mozapps/update/tests/browser/browser_aboutPrefs_fc_patch_partialBadSize_complete.js
new file mode 100644
index 0000000000..c6e5b5b20b
--- /dev/null
+++ b/toolkit/mozapps/update/tests/browser/browser_aboutPrefs_fc_patch_partialBadSize_complete.js
@@ -0,0 +1,38 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Test for about:preferences foreground check for updates
+// with a partial bad size patch and a complete patch.
+add_task(async function aboutPrefs_foregroundCheck_partialBadSize_complete() {
+ let downloadInfo = [];
+ if (Services.prefs.getBoolPref(PREF_APP_UPDATE_BITS_ENABLED)) {
+ downloadInfo[0] = { patchType: "partial", bitsResult: gBadSizeResult };
+ downloadInfo[1] = { patchType: "partial", internalResult: gBadSizeResult };
+ downloadInfo[2] = { patchType: "complete", bitsResult: "0" };
+ } else {
+ downloadInfo[0] = { patchType: "partial", internalResult: gBadSizeResult };
+ downloadInfo[1] = { patchType: "complete", internalResult: "0" };
+ }
+
+ let params = { queryString: "&invalidPartialSize=1" };
+ await runAboutPrefsUpdateTest(params, [
+ {
+ panelId: "checkingForUpdates",
+ checkActiveUpdate: null,
+ continueFile: CONTINUE_CHECK,
+ },
+ {
+ panelId: "downloading",
+ checkActiveUpdate: { state: STATE_DOWNLOADING },
+ continueFile: CONTINUE_DOWNLOAD,
+ downloadInfo,
+ },
+ {
+ panelId: "apply",
+ checkActiveUpdate: { state: STATE_PENDING },
+ continueFile: null,
+ },
+ ]);
+});
diff --git a/toolkit/mozapps/update/tests/browser/browser_aboutPrefs_fc_patch_partialBadSize_completeBadSize.js b/toolkit/mozapps/update/tests/browser/browser_aboutPrefs_fc_patch_partialBadSize_completeBadSize.js
new file mode 100644
index 0000000000..62abdc3af5
--- /dev/null
+++ b/toolkit/mozapps/update/tests/browser/browser_aboutPrefs_fc_patch_partialBadSize_completeBadSize.js
@@ -0,0 +1,53 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Test for about:preferences foreground check for updates
+// with a partial bad size patch and a complete bad size patch.
+add_task(
+ async function aboutPrefs_foregroundCheck_partialBadSize_completeBadSize() {
+ let downloadInfo = [];
+ if (Services.prefs.getBoolPref(PREF_APP_UPDATE_BITS_ENABLED)) {
+ downloadInfo[0] = { patchType: "partial", bitsResult: gBadSizeResult };
+ downloadInfo[1] = {
+ patchType: "partial",
+ internalResult: gBadSizeResult,
+ };
+ downloadInfo[2] = { patchType: "complete", bitsResult: gBadSizeResult };
+ downloadInfo[3] = {
+ patchType: "complete",
+ internalResult: gBadSizeResult,
+ };
+ } else {
+ downloadInfo[0] = {
+ patchType: "partial",
+ internalResult: gBadSizeResult,
+ };
+ downloadInfo[1] = {
+ patchType: "complete",
+ internalResult: gBadSizeResult,
+ };
+ }
+
+ let params = { queryString: "&invalidPartialSize=1&invalidCompleteSize=1" };
+ await runAboutPrefsUpdateTest(params, [
+ {
+ panelId: "checkingForUpdates",
+ checkActiveUpdate: null,
+ continueFile: CONTINUE_CHECK,
+ },
+ {
+ panelId: "downloading",
+ checkActiveUpdate: { state: STATE_DOWNLOADING },
+ continueFile: CONTINUE_DOWNLOAD,
+ downloadInfo,
+ },
+ {
+ panelId: "downloadFailed",
+ checkActiveUpdate: null,
+ continueFile: null,
+ },
+ ]);
+ }
+);
diff --git a/toolkit/mozapps/update/tests/browser/browser_aboutPrefs_internalError.js b/toolkit/mozapps/update/tests/browser/browser_aboutPrefs_internalError.js
new file mode 100644
index 0000000000..95a7ec1063
--- /dev/null
+++ b/toolkit/mozapps/update/tests/browser/browser_aboutPrefs_internalError.js
@@ -0,0 +1,35 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+ChromeUtils.defineESModuleGetters(this, {
+ AppUpdater: "resource://gre/modules/AppUpdater.sys.mjs",
+ sinon: "resource://testing-common/Sinon.sys.mjs",
+});
+
+add_setup(function setup_internalErrorTest() {
+ const sandbox = sinon.createSandbox();
+ sandbox.stub(AppUpdater.prototype, "aus").get(() => {
+ throw new Error("intentional test error");
+ });
+ sandbox.stub(AppUpdater.prototype, "checker").get(() => {
+ throw new Error("intentional test error");
+ });
+ sandbox.stub(AppUpdater.prototype, "um").get(() => {
+ throw new Error("intentional test error");
+ });
+ registerCleanupFunction(() => {
+ sandbox.restore();
+ });
+});
+
+// Test for about:preferences internal error handling.
+add_task(async function aboutPrefs_internalError() {
+ let params = {};
+ await runAboutPrefsUpdateTest(params, [
+ {
+ panelId: "internalError",
+ },
+ ]);
+});
diff --git a/toolkit/mozapps/update/tests/browser/browser_aboutPrefs_settings.js b/toolkit/mozapps/update/tests/browser/browser_aboutPrefs_settings.js
new file mode 100644
index 0000000000..db1b538d14
--- /dev/null
+++ b/toolkit/mozapps/update/tests/browser/browser_aboutPrefs_settings.js
@@ -0,0 +1,151 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+// Changes, then verifies the value of app.update.auto via the about:preferences
+// UI. Requires a tab with about:preferences open to be passed in.
+async function changeAndVerifyPref(tab, newConfigValue) {
+ await SpecialPowers.spawn(
+ tab.linkedBrowser,
+ [{ newConfigValue }],
+ async function ({ newConfigValue }) {
+ let radioId = newConfigValue ? "autoDesktop" : "manualDesktop";
+ let radioElement = content.document.getElementById(radioId);
+ let updateRadioGroup = radioElement.radioGroup;
+ let promise = ContentTaskUtils.waitForEvent(
+ updateRadioGroup,
+ "ProcessedUpdatePrefChange"
+ );
+ radioElement.click();
+ await promise;
+
+ is(
+ updateRadioGroup.value,
+ `${newConfigValue}`,
+ "Update preference should match expected"
+ );
+ is(
+ updateRadioGroup.disabled,
+ false,
+ "Update preferences should no longer be disabled"
+ );
+ }
+ );
+
+ let configValueRead = await UpdateUtils.getAppUpdateAutoEnabled();
+ is(
+ configValueRead,
+ newConfigValue,
+ "Value returned should have matched the expected value"
+ );
+}
+
+async function changeAndVerifyUpdateWrites({
+ tab,
+ newConfigValue,
+ discardUpdate,
+ expectPrompt,
+ expectRemainingUpdate,
+}) {
+ // A value of 1 will keep the update and a value of 0 will discard the update
+ // when the prompt service is called when the value of app.update.auto is
+ // changed to false.
+ let confirmExReply = discardUpdate ? 0 : 1;
+ let didPrompt = false;
+ let promptService = {
+ QueryInterface: ChromeUtils.generateQI(["nsIPromptService"]),
+ confirmEx(...args) {
+ promptService._confirmExArgs = args;
+ didPrompt = true;
+ return confirmExReply;
+ },
+ };
+ Services.prompt = promptService;
+ await changeAndVerifyPref(tab, newConfigValue);
+ is(
+ didPrompt,
+ expectPrompt,
+ `We should ${expectPrompt ? "" : "not "}be prompted`
+ );
+ is(
+ !!gUpdateManager.readyUpdate,
+ expectRemainingUpdate,
+ `There should ${expectRemainingUpdate ? "" : "not "}be a ready update`
+ );
+}
+
+add_task(async function testUpdateAutoPrefUI() {
+ let tab = await BrowserTestUtils.openNewForegroundTab(
+ gBrowser,
+ "about:preferences"
+ );
+
+ // Hack: make the test run faster:
+ await SpecialPowers.spawn(tab.linkedBrowser, [], () => {
+ content.gMainPane._minUpdatePrefDisableTime = 10;
+ });
+
+ info("Enable automatic updates and check that works.");
+ await changeAndVerifyPref(tab, true);
+ ok(
+ !gUpdateManager.downloadingUpdate,
+ "There should not be a downloading update"
+ );
+ ok(!gUpdateManager.readyUpdate, "There should not be a ready update");
+
+ info("Disable automatic updates and check that works.");
+ await changeAndVerifyPref(tab, false);
+ ok(
+ !gUpdateManager.downloadingUpdate,
+ "There should not be a downloading update"
+ );
+ ok(!gUpdateManager.readyUpdate, "There should not be a ready update");
+
+ let patchProps = { state: STATE_PENDING };
+ let patches = getLocalPatchString(patchProps);
+ let updateProps = { checkInterval: "1" };
+ let updates = getLocalUpdateString(updateProps, patches);
+ writeUpdatesToXMLFile(getLocalUpdatesXMLString(updates), true);
+ writeStatusFile(STATE_PENDING);
+ reloadUpdateManagerData();
+ ok(!!gUpdateManager.readyUpdate, "There should be a ready update");
+
+ let { prompt } = Services;
+ registerCleanupFunction(() => {
+ Services.prompt = prompt;
+ });
+
+ // Setting the value to false will call the prompt service and when we
+ // don't discard the update there should still be an active update afterwards.
+ await changeAndVerifyUpdateWrites({
+ tab,
+ newConfigValue: false,
+ discardUpdate: false,
+ expectPrompt: true,
+ expectRemainingUpdate: true,
+ });
+
+ // Setting the value to true should not call the prompt service so there
+ // should still be an active update, even if we indicate we can discard
+ // the update in a hypothetical prompt.
+ await changeAndVerifyUpdateWrites({
+ tab,
+ newConfigValue: true,
+ discardUpdate: true,
+ expectPrompt: false,
+ expectRemainingUpdate: true,
+ });
+
+ // Setting the value to false will call the prompt service, and we do
+ // discard the update, so there should not be an active update.
+ await changeAndVerifyUpdateWrites({
+ tab,
+ newConfigValue: false,
+ discardUpdate: true,
+ expectPrompt: true,
+ expectRemainingUpdate: false,
+ });
+
+ await BrowserTestUtils.removeTab(tab);
+});
diff --git a/toolkit/mozapps/update/tests/browser/browser_doorhanger_bc_check_cantApply.js b/toolkit/mozapps/update/tests/browser/browser_doorhanger_bc_check_cantApply.js
new file mode 100644
index 0000000000..90f4c385cc
--- /dev/null
+++ b/toolkit/mozapps/update/tests/browser/browser_doorhanger_bc_check_cantApply.js
@@ -0,0 +1,18 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+add_task(async function doorhanger_bc_check_cantApply() {
+ lockWriteTestFile();
+
+ let params = { checkAttempts: 1, queryString: "&promptWaitTime=0" };
+ await runDoorhangerUpdateTest(params, [
+ {
+ notificationId: "update-manual",
+ button: "button",
+ checkActiveUpdate: null,
+ pageURLs: { whatsNew: gDetailsURL, manual: URL_MANUAL_UPDATE },
+ },
+ ]);
+});
diff --git a/toolkit/mozapps/update/tests/browser/browser_doorhanger_bc_check_malformedXML.js b/toolkit/mozapps/update/tests/browser/browser_doorhanger_bc_check_malformedXML.js
new file mode 100644
index 0000000000..d83bc70b6f
--- /dev/null
+++ b/toolkit/mozapps/update/tests/browser/browser_doorhanger_bc_check_malformedXML.js
@@ -0,0 +1,26 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+add_task(async function doorhanger_bc_check_malformedXML() {
+ const maxBackgroundErrors = 10;
+ await SpecialPowers.pushPrefEnv({
+ set: [[PREF_APP_UPDATE_BACKGROUNDMAXERRORS, maxBackgroundErrors]],
+ });
+
+ let params = {
+ checkAttempts: maxBackgroundErrors,
+ queryString: "&xmlMalformed=1",
+ };
+ await runDoorhangerUpdateTest(params, [
+ {
+ // If the update check fails 10 consecutive attempts then the manual
+ // update doorhanger.
+ notificationId: "update-manual",
+ button: "button",
+ checkActiveUpdate: null,
+ pageURLs: { whatsNew: gDetailsURL, manual: URL_MANUAL_UPDATE },
+ },
+ ]);
+});
diff --git a/toolkit/mozapps/update/tests/browser/browser_doorhanger_bc_check_unsupported.js b/toolkit/mozapps/update/tests/browser/browser_doorhanger_bc_check_unsupported.js
new file mode 100644
index 0000000000..02aaab1064
--- /dev/null
+++ b/toolkit/mozapps/update/tests/browser/browser_doorhanger_bc_check_unsupported.js
@@ -0,0 +1,94 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Test for doorhanger background check for updates
+// with an unsupported update.
+add_task(async function doorhanger_bc_check_unsupported() {
+ let params = { checkAttempts: 1, queryString: "&unsupported=1" };
+ await runDoorhangerUpdateTest(params, [
+ {
+ notificationId: "update-unsupported",
+ button: "button",
+ pageURLs: { manual: gDetailsURL },
+ },
+ async function doorhanger_unsupported_persist() {
+ await TestUtils.waitForCondition(
+ () => PanelUI.menuButton.hasAttribute("badge-status"),
+ "Waiting for update badge",
+ undefined,
+ 200
+ ).catch(e => {
+ // Instead of throwing let the check below fail the test.
+ logTestInfo(e);
+ });
+ is(
+ PanelUI.notificationPanel.state,
+ "closed",
+ "The window's doorhanger is closed."
+ );
+ ok(
+ PanelUI.menuButton.hasAttribute("badge-status"),
+ "The window has a badge."
+ );
+ is(
+ PanelUI.menuButton.getAttribute("badge-status"),
+ "update-unsupported",
+ "The correct badge is showing for the background window"
+ );
+
+ // Test persistence of the badge when the client has restarted by
+ // resetting the UpdateListener.
+ UpdateListener.reset();
+ is(
+ PanelUI.notificationPanel.state,
+ "closed",
+ "The window's doorhanger is closed."
+ );
+ ok(
+ !PanelUI.menuButton.hasAttribute("badge-status"),
+ "The window does not have a badge."
+ );
+ UpdateListener.maybeShowUnsupportedNotification();
+ is(
+ PanelUI.notificationPanel.state,
+ "closed",
+ "The window's doorhanger is closed."
+ );
+ ok(
+ PanelUI.menuButton.hasAttribute("badge-status"),
+ "The window has a badge."
+ );
+ is(
+ PanelUI.menuButton.getAttribute("badge-status"),
+ "update-unsupported",
+ "The correct badge is showing for the background window."
+ );
+ },
+ ]);
+
+ params = {
+ checkAttempts: 1,
+ queryString: "&invalidCompleteSize=1&promptWaitTime=0",
+ };
+ await runDoorhangerUpdateTest(params, [
+ {
+ notificationId: "update-restart",
+ button: "secondaryButton",
+ checkActiveUpdate: { state: STATE_PENDING },
+ },
+ async function doorhanger_unsupported_removed() {
+ // Test that finding an update removes the app.update.unsupported.url
+ // preference.
+ let unsupportedURL = Services.prefs.getCharPref(
+ PREF_APP_UPDATE_UNSUPPORTED_URL,
+ null
+ );
+ ok(
+ !unsupportedURL,
+ "The " + PREF_APP_UPDATE_UNSUPPORTED_URL + " preference was removed."
+ );
+ },
+ ]);
+});
diff --git a/toolkit/mozapps/update/tests/browser/browser_doorhanger_bc_downloadAutoFailures.js b/toolkit/mozapps/update/tests/browser/browser_doorhanger_bc_downloadAutoFailures.js
new file mode 100644
index 0000000000..3678a440d2
--- /dev/null
+++ b/toolkit/mozapps/update/tests/browser/browser_doorhanger_bc_downloadAutoFailures.js
@@ -0,0 +1,35 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+add_task(async function doorhanger_bc_downloadAutoFailures() {
+ const maxBackgroundErrors = 5;
+ await SpecialPowers.pushPrefEnv({
+ set: [[PREF_APP_UPDATE_BACKGROUNDMAXERRORS, maxBackgroundErrors]],
+ });
+
+ let params = { checkAttempts: 1, queryString: "&badURL=1" };
+ await runDoorhangerUpdateTest(params, [
+ {
+ // If the update download fails maxBackgroundErrors download attempts then
+ // show the update available prompt.
+ notificationId: "update-available",
+ button: "button",
+ checkActiveUpdate: null,
+ },
+ {
+ notificationId: "update-available",
+ button: "button",
+ checkActiveUpdate: null,
+ },
+ {
+ // If the update process is unable to install the update show the manual
+ // update doorhanger.
+ notificationId: "update-manual",
+ button: "button",
+ checkActiveUpdate: null,
+ pageURLs: { manual: URL_MANUAL_UPDATE },
+ },
+ ]);
+});
diff --git a/toolkit/mozapps/update/tests/browser/browser_doorhanger_bc_downloadAutoFailures_bgWin.js b/toolkit/mozapps/update/tests/browser/browser_doorhanger_bc_downloadAutoFailures_bgWin.js
new file mode 100644
index 0000000000..a4502d7626
--- /dev/null
+++ b/toolkit/mozapps/update/tests/browser/browser_doorhanger_bc_downloadAutoFailures_bgWin.js
@@ -0,0 +1,80 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+add_task(async function doorhanger_bc_downloadAutoFailures_bgWin() {
+ function getBackgroundWindowHandler(destroyWindow) {
+ return async function () {
+ await TestUtils.waitForCondition(
+ () => PanelUI.menuButton.hasAttribute("badge-status"),
+ "Background window has a badge.",
+ undefined,
+ 200
+ ).catch(e => {
+ // Instead of throwing let the check below fail the test.
+ logTestInfo(e);
+ });
+ ok(
+ PanelUI.menuButton.hasAttribute("badge-status"),
+ "PanelUI.menuButton should have a 'badge-status' attribute"
+ );
+ is(
+ PanelUI.notificationPanel.state,
+ "closed",
+ "The doorhanger is not showing for the background window"
+ );
+ is(
+ PanelUI.menuButton.getAttribute("badge-status"),
+ "update-available",
+ "The badge is showing for the background window"
+ );
+
+ let buttonEl = getNotificationButton(
+ extraWindow,
+ "update-available",
+ "button"
+ );
+ buttonEl.click();
+
+ if (destroyWindow) {
+ // The next popup may be shown during closeWindow or promiseFocus
+ // calls.
+ let waitForPopupShown = new Promise(resolve => {
+ window.addEventListener(
+ "popupshown",
+ () => {
+ executeSoon(resolve);
+ },
+ { once: true }
+ );
+ });
+ await BrowserTestUtils.closeWindow(extraWindow);
+ await SimpleTest.promiseFocus(window);
+ await waitForPopupShown;
+ }
+ };
+ }
+
+ const maxBackgroundErrors = 5;
+ await SpecialPowers.pushPrefEnv({
+ set: [[PREF_APP_UPDATE_BACKGROUNDMAXERRORS, maxBackgroundErrors]],
+ });
+
+ let extraWindow = await BrowserTestUtils.openNewBrowserWindow();
+ await SimpleTest.promiseFocus(extraWindow);
+
+ let params = { checkAttempts: 1, queryString: "&badURL=1", popupShown: true };
+ await runDoorhangerUpdateTest(params, [
+ getBackgroundWindowHandler(false),
+ getBackgroundWindowHandler(true),
+ {
+ // If the update process is unable to install the update show the manual
+ // update doorhanger.
+ notificationId: "update-manual",
+ button: "button",
+ checkActiveUpdate: null,
+ pageURLs: { manual: URL_MANUAL_UPDATE },
+ },
+ ]);
+});
diff --git a/toolkit/mozapps/update/tests/browser/browser_doorhanger_bc_downloadOptIn.js b/toolkit/mozapps/update/tests/browser/browser_doorhanger_bc_downloadOptIn.js
new file mode 100644
index 0000000000..2237161863
--- /dev/null
+++ b/toolkit/mozapps/update/tests/browser/browser_doorhanger_bc_downloadOptIn.js
@@ -0,0 +1,25 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+add_task(async function doorhanger_bc_downloadOptIn() {
+ await UpdateUtils.setAppUpdateAutoEnabled(false);
+
+ let params = {
+ checkAttempts: 1,
+ queryString: "&invalidCompleteSize=1&promptWaitTime=0",
+ };
+ await runDoorhangerUpdateTest(params, [
+ {
+ notificationId: "update-available",
+ button: "button",
+ checkActiveUpdate: null,
+ },
+ {
+ notificationId: "update-restart",
+ button: "secondaryButton",
+ checkActiveUpdate: { state: STATE_PENDING },
+ },
+ ]);
+});
diff --git a/toolkit/mozapps/update/tests/browser/browser_doorhanger_bc_downloadOptIn_bgWin.js b/toolkit/mozapps/update/tests/browser/browser_doorhanger_bc_downloadOptIn_bgWin.js
new file mode 100644
index 0000000000..17dcce57ce
--- /dev/null
+++ b/toolkit/mozapps/update/tests/browser/browser_doorhanger_bc_downloadOptIn_bgWin.js
@@ -0,0 +1,63 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+add_task(async function doorhanger_bc_downloadOptIn_bgWin() {
+ function getBackgroundWindowHandler() {
+ return async function () {
+ await TestUtils.waitForCondition(
+ () => PanelUI.menuButton.hasAttribute("badge-status"),
+ "Background window has a badge.",
+ undefined,
+ 200
+ ).catch(e => {
+ // Instead of throwing let the check below fail the test.
+ logTestInfo(e);
+ });
+ ok(
+ PanelUI.menuButton.hasAttribute("badge-status"),
+ "PanelUI.menuButton should have a 'badge-status' attribute"
+ );
+ is(
+ PanelUI.notificationPanel.state,
+ "closed",
+ "The doorhanger is not showing for the background window"
+ );
+ is(
+ PanelUI.menuButton.getAttribute("badge-status"),
+ "update-available",
+ "The badge is showing for the background window"
+ );
+ let popupShownPromise = BrowserTestUtils.waitForEvent(
+ PanelUI.notificationPanel,
+ "popupshown"
+ );
+ await BrowserTestUtils.closeWindow(extraWindow);
+ await SimpleTest.promiseFocus(window);
+ await popupShownPromise;
+
+ let buttonEl = getNotificationButton(
+ window,
+ "update-available",
+ "button"
+ );
+ buttonEl.click();
+ };
+ }
+
+ await UpdateUtils.setAppUpdateAutoEnabled(false);
+
+ let extraWindow = await BrowserTestUtils.openNewBrowserWindow();
+ await SimpleTest.promiseFocus(extraWindow);
+
+ let params = { checkAttempts: 1, queryString: "&promptWaitTime=0" };
+ await runDoorhangerUpdateTest(params, [
+ getBackgroundWindowHandler(),
+ {
+ notificationId: "update-restart",
+ button: "secondaryButton",
+ checkActiveUpdate: { state: STATE_PENDING },
+ },
+ ]);
+});
diff --git a/toolkit/mozapps/update/tests/browser/browser_doorhanger_bc_downloadOptIn_staging.js b/toolkit/mozapps/update/tests/browser/browser_doorhanger_bc_downloadOptIn_staging.js
new file mode 100644
index 0000000000..7ba2d67964
--- /dev/null
+++ b/toolkit/mozapps/update/tests/browser/browser_doorhanger_bc_downloadOptIn_staging.js
@@ -0,0 +1,29 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+add_task(async function doorhanger_bc_downloadOptIn_staging() {
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ // Tests the app.update.promptWaitTime pref
+ [PREF_APP_UPDATE_PROMPTWAITTIME, 0],
+ [PREF_APP_UPDATE_STAGING_ENABLED, true],
+ ],
+ });
+ await UpdateUtils.setAppUpdateAutoEnabled(false);
+
+ let params = { checkAttempts: 1, queryString: "&invalidCompleteSize=1" };
+ await runDoorhangerUpdateTest(params, [
+ {
+ notificationId: "update-available",
+ button: "button",
+ checkActiveUpdate: null,
+ },
+ {
+ notificationId: "update-restart",
+ button: "secondaryButton",
+ checkActiveUpdate: { state: STATE_APPLIED },
+ },
+ ]);
+});
diff --git a/toolkit/mozapps/update/tests/browser/browser_doorhanger_bc_downloaded.js b/toolkit/mozapps/update/tests/browser/browser_doorhanger_bc_downloaded.js
new file mode 100644
index 0000000000..e29dad26fa
--- /dev/null
+++ b/toolkit/mozapps/update/tests/browser/browser_doorhanger_bc_downloaded.js
@@ -0,0 +1,18 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+add_task(async function doorhanger_bc_downloaded() {
+ let params = {
+ checkAttempts: 1,
+ queryString: "&invalidCompleteSize=1&promptWaitTime=0",
+ };
+ await runDoorhangerUpdateTest(params, [
+ {
+ notificationId: "update-restart",
+ button: "secondaryButton",
+ checkActiveUpdate: { state: STATE_PENDING },
+ },
+ ]);
+});
diff --git a/toolkit/mozapps/update/tests/browser/browser_doorhanger_bc_downloaded_disableBITS.js b/toolkit/mozapps/update/tests/browser/browser_doorhanger_bc_downloaded_disableBITS.js
new file mode 100644
index 0000000000..5f89c95322
--- /dev/null
+++ b/toolkit/mozapps/update/tests/browser/browser_doorhanger_bc_downloaded_disableBITS.js
@@ -0,0 +1,31 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+add_task(async function doorhanger_bc_downloaded_disableBITS() {
+ await SpecialPowers.pushPrefEnv({
+ set: [[PREF_APP_UPDATE_BITS_ENABLED, true]],
+ });
+
+ let params = {
+ checkAttempts: 1,
+ queryString: "&promptWaitTime=0&disableBITS=true",
+ };
+ await runDoorhangerUpdateTest(params, [
+ {
+ notificationId: "update-restart",
+ button: "secondaryButton",
+ checkActiveUpdate: { state: STATE_PENDING },
+ },
+ ]);
+
+ let patch = getPatchOfType(
+ "partial",
+ gUpdateManager.readyUpdate
+ ).QueryInterface(Ci.nsIWritablePropertyBag);
+ ok(
+ !patch.getProperty("bitsId"),
+ "The selected patch should not have a bitsId property"
+ );
+});
diff --git a/toolkit/mozapps/update/tests/browser/browser_doorhanger_bc_downloaded_staged.js b/toolkit/mozapps/update/tests/browser/browser_doorhanger_bc_downloaded_staged.js
new file mode 100644
index 0000000000..50416608f2
--- /dev/null
+++ b/toolkit/mozapps/update/tests/browser/browser_doorhanger_bc_downloaded_staged.js
@@ -0,0 +1,22 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+add_task(async function doorhanger_bc_downloaded_staged() {
+ await SpecialPowers.pushPrefEnv({
+ set: [[PREF_APP_UPDATE_STAGING_ENABLED, true]],
+ });
+
+ let params = {
+ checkAttempts: 1,
+ queryString: "&invalidCompleteSize=1&promptWaitTime=0",
+ };
+ await runDoorhangerUpdateTest(params, [
+ {
+ notificationId: "update-restart",
+ button: "secondaryButton",
+ checkActiveUpdate: { state: STATE_APPLIED },
+ },
+ ]);
+});
diff --git a/toolkit/mozapps/update/tests/browser/browser_doorhanger_bc_multiUpdate.js b/toolkit/mozapps/update/tests/browser/browser_doorhanger_bc_multiUpdate.js
new file mode 100644
index 0000000000..07e7bf51fa
--- /dev/null
+++ b/toolkit/mozapps/update/tests/browser/browser_doorhanger_bc_multiUpdate.js
@@ -0,0 +1,93 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+/**
+ * This test downloads 2 updates sequentially, and ensures that the doorhanger
+ * and badge do what they are supposed to do:
+ * First thing after the first download, the doorhanger should be displayed.
+ * Then download the next update.
+ * While that update stages, the badge should be hidden to prevent restarting
+ * to update while the update is staging.
+ * Once the staging completes, the badge should return. The doorhanger should
+ * not be shown at this time, because it has already been shown this
+ * session.
+ */
+
+const FIRST_UPDATE_VERSION = "999998.0";
+const SECOND_UPDATE_VERSION = "999999.0";
+
+function prepareToDownloadVersion(version) {
+ setUpdateURL(
+ URL_HTTP_UPDATE_SJS +
+ `?detailsURL=${gDetailsURL}&promptWaitTime=0&appVersion=${version}`
+ );
+}
+
+add_task(async function doorhanger_bc_multiUpdate() {
+ await SpecialPowers.pushPrefEnv({
+ set: [[PREF_APP_UPDATE_STAGING_ENABLED, true]],
+ });
+
+ let params = {
+ checkAttempts: 1,
+ queryString: "&promptWaitTime=0",
+ version: FIRST_UPDATE_VERSION,
+ slowStaging: true,
+ };
+ await runDoorhangerUpdateTest(params, [
+ () => {
+ return continueFileHandler(CONTINUE_STAGING);
+ },
+ {
+ notificationId: "update-restart",
+ button: "secondaryButton",
+ checkActiveUpdate: { state: STATE_APPLIED },
+ },
+ async () => {
+ is(
+ PanelUI.menuButton.getAttribute("badge-status"),
+ "update-restart",
+ "Should have restart badge"
+ );
+
+ prepareToDownloadVersion(SECOND_UPDATE_VERSION);
+ let updateSwapped = waitForEvent("update-swap");
+ gAUS.checkForBackgroundUpdates();
+ await updateSwapped;
+ // The badge should be hidden while we swap from one update to the other
+ // to prevent restarting to update while staging is occurring. But since
+ // it will be waiting on the same event we are waiting on, wait an
+ // additional tick to let the other update-swap listeners run.
+ await TestUtils.waitForTick();
+
+ is(
+ PanelUI.menuButton.getAttribute("badge-status"),
+ "",
+ "Should not have restart badge during staging"
+ );
+
+ await continueFileHandler(CONTINUE_STAGING);
+
+ try {
+ await TestUtils.waitForCondition(
+ () =>
+ PanelUI.menuButton.getAttribute("badge-status") == "update-restart",
+ "Waiting for update restart badge to return after staging"
+ );
+ } catch (ex) {}
+
+ is(
+ PanelUI.menuButton.getAttribute("badge-status"),
+ "update-restart",
+ "Restart badge should be restored after staging completes"
+ );
+ is(
+ PanelUI.notificationPanel.state,
+ "closed",
+ "Should not open a second doorhanger"
+ );
+ },
+ ]);
+});
diff --git a/toolkit/mozapps/update/tests/browser/browser_doorhanger_bc_multiUpdate_promptWaitTime.js b/toolkit/mozapps/update/tests/browser/browser_doorhanger_bc_multiUpdate_promptWaitTime.js
new file mode 100644
index 0000000000..00c61bcbb4
--- /dev/null
+++ b/toolkit/mozapps/update/tests/browser/browser_doorhanger_bc_multiUpdate_promptWaitTime.js
@@ -0,0 +1,89 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+/**
+ * This test downloads 2 updates sequentially, and ensures that the doorhanger
+ * and badge do what they are supposed to do. However, the first update has a
+ * long promptWaitTime, and the second has a short one and the badge wait time
+ * is set to 0. This should result in this behavior:
+ * First thing after the first download, the badge should be displayed, but
+ * not the doorhanger.
+ * Then download the next update.
+ * While that update stages, the badge should be hidden to prevent restarting
+ * to update while the update is staging.
+ * Once the staging completes, the doorhanger should be shown. Despite the
+ * long promptWaitTime of the initial update, this patch's short wait time
+ * means that the doorhanger should be shown soon rather than in a long
+ * time.
+ */
+
+const FIRST_UPDATE_VERSION = "999998.0";
+const SECOND_UPDATE_VERSION = "999999.0";
+const LONG_PROMPT_WAIT_TIME_SEC = 10 * 60 * 60; // 10 hours
+
+function prepareToDownloadVersion(version, promptWaitTime) {
+ setUpdateURL(
+ URL_HTTP_UPDATE_SJS +
+ `?detailsURL=${gDetailsURL}&promptWaitTime=${promptWaitTime}&appVersion=${version}`
+ );
+}
+
+add_task(async function doorhanger_bc_multiUpdate() {
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ [PREF_APP_UPDATE_STAGING_ENABLED, true],
+ [PREF_APP_UPDATE_BADGEWAITTIME, 0],
+ ],
+ });
+
+ let params = {
+ checkAttempts: 1,
+ queryString: `&promptWaitTime=${LONG_PROMPT_WAIT_TIME_SEC}`,
+ version: FIRST_UPDATE_VERSION,
+ slowStaging: true,
+ };
+ await runDoorhangerUpdateTest(params, [
+ async () => {
+ await continueFileHandler(CONTINUE_STAGING);
+
+ try {
+ await TestUtils.waitForCondition(
+ () =>
+ PanelUI.menuButton.getAttribute("badge-status") == "update-restart",
+ "Waiting for update restart badge to return after staging"
+ );
+ } catch (ex) {}
+
+ is(
+ PanelUI.menuButton.getAttribute("badge-status"),
+ "update-restart",
+ "Should have restart badge"
+ );
+
+ prepareToDownloadVersion(SECOND_UPDATE_VERSION, 0);
+ let updateSwapped = waitForEvent("update-swap");
+ gAUS.checkForBackgroundUpdates();
+ await updateSwapped;
+ // The badge should be hidden while we swap from one update to the other
+ // to prevent restarting to update while staging is occurring. But since
+ // it will be waiting on the same event we are waiting on, wait an
+ // additional tick to let the other update-swap listeners run.
+ await TestUtils.waitForTick();
+
+ is(
+ PanelUI.menuButton.getAttribute("badge-status"),
+ "",
+ "Should not have restart badge during staging"
+ );
+
+ await continueFileHandler(CONTINUE_STAGING);
+ },
+ {
+ notificationId: "update-restart",
+ button: "secondaryButton",
+ checkActiveUpdate: { state: STATE_APPLIED },
+ },
+ ]);
+});
diff --git a/toolkit/mozapps/update/tests/browser/browser_doorhanger_bc_patch_completeBadSize.js b/toolkit/mozapps/update/tests/browser/browser_doorhanger_bc_patch_completeBadSize.js
new file mode 100644
index 0000000000..3c29d6b4b0
--- /dev/null
+++ b/toolkit/mozapps/update/tests/browser/browser_doorhanger_bc_patch_completeBadSize.js
@@ -0,0 +1,33 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+add_task(async function doorhanger_bc_patch_completeBadSize() {
+ let params = {
+ checkAttempts: 1,
+ queryString: "&completePatchOnly=1&invalidCompleteSize=1",
+ };
+ await runDoorhangerUpdateTest(params, [
+ {
+ // If the update download fails maxBackgroundErrors download attempts then
+ // show the update available prompt.
+ notificationId: "update-available",
+ button: "button",
+ checkActiveUpdate: null,
+ },
+ {
+ notificationId: "update-available",
+ button: "button",
+ checkActiveUpdate: null,
+ },
+ {
+ // If the update process is unable to install the update show the manual
+ // update doorhanger.
+ notificationId: "update-manual",
+ button: "button",
+ checkActiveUpdate: null,
+ pageURLs: { manual: URL_MANUAL_UPDATE },
+ },
+ ]);
+});
diff --git a/toolkit/mozapps/update/tests/browser/browser_doorhanger_bc_patch_partialBadSize.js b/toolkit/mozapps/update/tests/browser/browser_doorhanger_bc_patch_partialBadSize.js
new file mode 100644
index 0000000000..68854b3f26
--- /dev/null
+++ b/toolkit/mozapps/update/tests/browser/browser_doorhanger_bc_patch_partialBadSize.js
@@ -0,0 +1,33 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+add_task(async function doorhanger_bc_patch_partialBadSize() {
+ let params = {
+ checkAttempts: 1,
+ queryString: "&partialPatchOnly=1&invalidPartialSize=1",
+ };
+ await runDoorhangerUpdateTest(params, [
+ {
+ // If the update download fails maxBackgroundErrors download attempts then
+ // show the update available prompt.
+ notificationId: "update-available",
+ button: "button",
+ checkActiveUpdate: null,
+ },
+ {
+ notificationId: "update-available",
+ button: "button",
+ checkActiveUpdate: null,
+ },
+ {
+ // If the update process is unable to install the update show the manual
+ // update doorhanger.
+ notificationId: "update-manual",
+ button: "button",
+ checkActiveUpdate: null,
+ pageURLs: { manual: URL_MANUAL_UPDATE },
+ },
+ ]);
+});
diff --git a/toolkit/mozapps/update/tests/browser/browser_doorhanger_bc_patch_partialBadSize_complete.js b/toolkit/mozapps/update/tests/browser/browser_doorhanger_bc_patch_partialBadSize_complete.js
new file mode 100644
index 0000000000..f3c9b1f51f
--- /dev/null
+++ b/toolkit/mozapps/update/tests/browser/browser_doorhanger_bc_patch_partialBadSize_complete.js
@@ -0,0 +1,18 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+add_task(async function doorhanger_bc_patch_partialBadSize_complete() {
+ let params = {
+ checkAttempts: 1,
+ queryString: "&invalidPartialSize=1&promptWaitTime=0",
+ };
+ await runDoorhangerUpdateTest(params, [
+ {
+ notificationId: "update-restart",
+ button: "secondaryButton",
+ checkActiveUpdate: { state: STATE_PENDING },
+ },
+ ]);
+});
diff --git a/toolkit/mozapps/update/tests/browser/browser_doorhanger_bc_patch_partialBadSize_completeBadSize.js b/toolkit/mozapps/update/tests/browser/browser_doorhanger_bc_patch_partialBadSize_completeBadSize.js
new file mode 100644
index 0000000000..a18e2f6444
--- /dev/null
+++ b/toolkit/mozapps/update/tests/browser/browser_doorhanger_bc_patch_partialBadSize_completeBadSize.js
@@ -0,0 +1,33 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+add_task(async function doorhanger_bc_patch_partialBadSize_completeBadSize() {
+ let params = {
+ checkAttempts: 1,
+ queryString: "&invalidPartialSize=1&invalidCompleteSize=1",
+ };
+ await runDoorhangerUpdateTest(params, [
+ {
+ // If the update download fails maxBackgroundErrors download attempts then
+ // show the update available prompt.
+ notificationId: "update-available",
+ button: "button",
+ checkActiveUpdate: null,
+ },
+ {
+ notificationId: "update-available",
+ button: "button",
+ checkActiveUpdate: null,
+ },
+ {
+ // If the update process is unable to install the update show the manual
+ // update doorhanger.
+ notificationId: "update-manual",
+ button: "button",
+ checkActiveUpdate: null,
+ pageURLs: { manual: URL_MANUAL_UPDATE },
+ },
+ ]);
+});
diff --git a/toolkit/mozapps/update/tests/browser/browser_doorhanger_sp_patch_completeApplyFailure.js b/toolkit/mozapps/update/tests/browser/browser_doorhanger_sp_patch_completeApplyFailure.js
new file mode 100644
index 0000000000..5c7c937d81
--- /dev/null
+++ b/toolkit/mozapps/update/tests/browser/browser_doorhanger_sp_patch_completeApplyFailure.js
@@ -0,0 +1,23 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+add_task(async function doorhanger_sp_patch_completeApplyFailure() {
+ let patchProps = { state: STATE_PENDING };
+ let patches = getLocalPatchString(patchProps);
+ let updateProps = { checkInterval: "1" };
+ let updates = getLocalUpdateString(updateProps, patches);
+
+ let params = { updates };
+ await runDoorhangerUpdateTest(params, [
+ {
+ // If the update process is unable to install the update show the manual
+ // update doorhanger.
+ notificationId: "update-manual",
+ button: "button",
+ checkActiveUpdate: null,
+ pageURLs: { manual: URL_MANUAL_UPDATE },
+ },
+ ]);
+});
diff --git a/toolkit/mozapps/update/tests/browser/browser_doorhanger_sp_patch_partialApplyFailure.js b/toolkit/mozapps/update/tests/browser/browser_doorhanger_sp_patch_partialApplyFailure.js
new file mode 100644
index 0000000000..45434c8361
--- /dev/null
+++ b/toolkit/mozapps/update/tests/browser/browser_doorhanger_sp_patch_partialApplyFailure.js
@@ -0,0 +1,22 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+add_task(async function doorhanger_sp_patch_partialApplyFailure() {
+ let patchProps = { type: "partial", state: STATE_PENDING };
+ let patches = getLocalPatchString(patchProps);
+ let updateProps = { isCompleteUpdate: "false", checkInterval: "1" };
+ let updates = getLocalUpdateString(updateProps, patches);
+
+ let params = { updates };
+ await runDoorhangerUpdateTest(params, [
+ {
+ // If there is only an invalid patch show the manual update doorhanger.
+ notificationId: "update-manual",
+ button: "button",
+ checkActiveUpdate: null,
+ pageURLs: { manual: URL_MANUAL_UPDATE },
+ },
+ ]);
+});
diff --git a/toolkit/mozapps/update/tests/browser/browser_doorhanger_sp_patch_partialApplyFailure_complete.js b/toolkit/mozapps/update/tests/browser/browser_doorhanger_sp_patch_partialApplyFailure_complete.js
new file mode 100644
index 0000000000..bf533dab04
--- /dev/null
+++ b/toolkit/mozapps/update/tests/browser/browser_doorhanger_sp_patch_partialApplyFailure_complete.js
@@ -0,0 +1,22 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+add_task(async function doorhanger_sp_patch_partialApplyFailure_complete() {
+ let patchProps = { type: "partial", state: STATE_PENDING };
+ let patches = getLocalPatchString(patchProps);
+ patchProps = { selected: "false" };
+ patches += getLocalPatchString(patchProps);
+ let updateProps = { isCompleteUpdate: "false", promptWaitTime: "0" };
+ let updates = getLocalUpdateString(updateProps, patches);
+
+ let params = { updates };
+ await runDoorhangerUpdateTest(params, [
+ {
+ notificationId: "update-restart",
+ button: "secondaryButton",
+ checkActiveUpdate: { state: STATE_PENDING },
+ },
+ ]);
+});
diff --git a/toolkit/mozapps/update/tests/browser/browser_doorhanger_sp_patch_partialApplyFailure_completeBadSize.js b/toolkit/mozapps/update/tests/browser/browser_doorhanger_sp_patch_partialApplyFailure_completeBadSize.js
new file mode 100644
index 0000000000..df17bc1220
--- /dev/null
+++ b/toolkit/mozapps/update/tests/browser/browser_doorhanger_sp_patch_partialApplyFailure_completeBadSize.js
@@ -0,0 +1,32 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+add_task(
+ async function doorhanger_sp_patch_partialApplyFailure_completeBadSize() {
+ // Because of the way the test is simulating failure it has to pretend it has
+ // already retried.
+ await SpecialPowers.pushPrefEnv({
+ set: [[PREF_APP_UPDATE_DOWNLOAD_MAXATTEMPTS, 0]],
+ });
+
+ let patchProps = { type: "partial", state: STATE_PENDING };
+ let patches = getLocalPatchString(patchProps);
+ patchProps = { size: "1234", selected: "false" };
+ patches += getLocalPatchString(patchProps);
+ let updateProps = { isCompleteUpdate: "false" };
+ let updates = getLocalUpdateString(updateProps, patches);
+
+ let params = { updates };
+ await runDoorhangerUpdateTest(params, [
+ {
+ // If there is only an invalid patch show the manual update doorhanger.
+ notificationId: "update-manual",
+ button: "button",
+ checkActiveUpdate: null,
+ pageURLs: { manual: URL_MANUAL_UPDATE },
+ },
+ ]);
+ }
+);
diff --git a/toolkit/mozapps/update/tests/browser/browser_doorhanger_sp_patch_partialApplyFailure_complete_staging.js b/toolkit/mozapps/update/tests/browser/browser_doorhanger_sp_patch_partialApplyFailure_complete_staging.js
new file mode 100644
index 0000000000..a99c04b0be
--- /dev/null
+++ b/toolkit/mozapps/update/tests/browser/browser_doorhanger_sp_patch_partialApplyFailure_complete_staging.js
@@ -0,0 +1,28 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+add_task(
+ async function doorhanger_sp_patch_partialApplyFailure_complete_staging() {
+ await SpecialPowers.pushPrefEnv({
+ set: [[PREF_APP_UPDATE_STAGING_ENABLED, true]],
+ });
+
+ let patchProps = { type: "partial", state: STATE_PENDING };
+ let patches = getLocalPatchString(patchProps);
+ patchProps = { selected: "false" };
+ patches += getLocalPatchString(patchProps);
+ let updateProps = { isCompleteUpdate: "false", promptWaitTime: "0" };
+ let updates = getLocalUpdateString(updateProps, patches);
+
+ let params = { updates };
+ await runDoorhangerUpdateTest(params, [
+ {
+ notificationId: "update-restart",
+ button: "secondaryButton",
+ checkActiveUpdate: { state: STATE_APPLIED },
+ },
+ ]);
+ }
+);
diff --git a/toolkit/mozapps/update/tests/browser/browser_elevationDialog.js b/toolkit/mozapps/update/tests/browser/browser_elevationDialog.js
new file mode 100644
index 0000000000..6aa32a7fc9
--- /dev/null
+++ b/toolkit/mozapps/update/tests/browser/browser_elevationDialog.js
@@ -0,0 +1,139 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+add_task(async function elevation_dialog() {
+ await SpecialPowers.pushPrefEnv({
+ set: [[PREF_APP_UPDATE_DISABLEDFORTESTING, false]],
+ });
+
+ // Create a mock of nsIAppStartup's quit method so clicking the restart button
+ // won't restart the application.
+ let { startup } = Services;
+ let appStartup = {
+ QueryInterface: ChromeUtils.generateQI(["nsIAppStartup"]),
+ quit(mode) {
+ if (elevationDialog) {
+ elevationDialog.close();
+ elevationDialog = null;
+ }
+ },
+ };
+ Services.startup = appStartup;
+ registerCleanupFunction(() => {
+ Services.startup = startup;
+ });
+
+ registerCleanupFunction(async () => {
+ let win = Services.wm.getMostRecentWindow("Update:Elevation");
+ if (win) {
+ win.close();
+ await TestUtils.waitForCondition(
+ () => !Services.wm.getMostRecentWindow("Update:Elevation"),
+ "The Update Elevation dialog should have closed"
+ );
+ }
+ });
+
+ // Test clicking the "Restart Later" button
+ let elevationDialog = await waitForElevationDialog();
+ await TestUtils.waitForTick();
+ elevationDialog.document.getElementById("elevateExtra2").click();
+ await TestUtils.waitForCondition(
+ () => !Services.wm.getMostRecentWindow("Update:Elevation"),
+ "The Update Elevation dialog should have closed"
+ );
+ ok(!!gUpdateManager.readyUpdate, "There should be a ready update");
+ is(
+ gUpdateManager.readyUpdate.state,
+ STATE_PENDING_ELEVATE,
+ "The ready update state should equal " + STATE_PENDING_ELEVATE
+ );
+ is(
+ readStatusFile(),
+ STATE_PENDING_ELEVATE,
+ "The status file state should equal " + STATE_PENDING_ELEVATE
+ );
+
+ // Test clicking the "No Thanks" button
+ elevationDialog = await waitForElevationDialog();
+ await TestUtils.waitForTick();
+ elevationDialog.document.getElementById("elevateExtra1").click();
+ await TestUtils.waitForCondition(
+ () => !Services.wm.getMostRecentWindow("Update:Elevation"),
+ "The Update Elevation dialog should have closed"
+ );
+ ok(!gUpdateManager.readyUpdate, "There should not be a ready update");
+ is(
+ readStatusFile(),
+ STATE_NONE,
+ "The status file state should equal " + STATE_NONE
+ );
+
+ // Test clicking the "Restart <brandShortName>" button
+ elevationDialog = await waitForElevationDialog();
+ await TestUtils.waitForTick();
+ elevationDialog.document.getElementById("elevateAccept").click();
+ await TestUtils.waitForCondition(
+ () => !Services.wm.getMostRecentWindow("Update:Elevation"),
+ "The Update Elevation dialog should have closed"
+ );
+ ok(!!gUpdateManager.readyUpdate, "There should be a ready update");
+ is(
+ gUpdateManager.readyUpdate.state,
+ STATE_PENDING_ELEVATE,
+ "The active update state should equal " + STATE_PENDING_ELEVATE
+ );
+ is(
+ readStatusFile(),
+ STATE_PENDING,
+ "The status file state should equal " + STATE_PENDING
+ );
+});
+
+/**
+ * Waits for the Update Elevation Dialog to load.
+ *
+ * @return A promise that returns the domWindow for the Update Elevation Dialog
+ * and resolves when the Update Elevation Dialog loads.
+ */
+function waitForElevationDialog() {
+ return new Promise(resolve => {
+ var listener = {
+ onOpenWindow: aXULWindow => {
+ debugDump("Update Elevation dialog shown...");
+ Services.wm.removeListener(listener);
+
+ async function elevationDialogOnLoad() {
+ domwindow.removeEventListener("load", elevationDialogOnLoad, true);
+ let chromeURI =
+ "chrome://mozapps/content/update/updateElevation.xhtml";
+ is(
+ domwindow.document.location.href,
+ chromeURI,
+ "Update Elevation appeared"
+ );
+ resolve(domwindow);
+ }
+
+ var domwindow = aXULWindow.docShell.domWindow;
+ domwindow.addEventListener("load", elevationDialogOnLoad, true);
+ },
+ onCloseWindow: aXULWindow => {},
+ };
+
+ Services.wm.addListener(listener);
+ // Add the active-update.xml and update.status files used for these tests,
+ // reload the update manager, and then simulate startup so the Update
+ // Elevation Dialog is opened.
+ let patchProps = { state: STATE_PENDING_ELEVATE };
+ let patches = getLocalPatchString(patchProps);
+ let updateProps = { checkInterval: "1" };
+ let updates = getLocalUpdateString(updateProps, patches);
+ writeUpdatesToXMLFile(getLocalUpdatesXMLString(updates), true);
+ writeStatusFile(STATE_PENDING_ELEVATE);
+ reloadUpdateManagerData();
+ testPostUpdateProcessing();
+ });
+}
diff --git a/toolkit/mozapps/update/tests/browser/browser_memory_allocation_error_fallback.js b/toolkit/mozapps/update/tests/browser/browser_memory_allocation_error_fallback.js
new file mode 100644
index 0000000000..55ec2e14a1
--- /dev/null
+++ b/toolkit/mozapps/update/tests/browser/browser_memory_allocation_error_fallback.js
@@ -0,0 +1,81 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+/**
+ * When the updater fails with a memory allocation error, we should fall back to
+ * updating without staging.
+ */
+
+const READ_STRINGS_MEM_ERROR = 10;
+const ARCHIVE_READER_MEM_ERROR = 11;
+const BSPATCH_MEM_ERROR = 12;
+const UPDATER_MEM_ERROR = 13;
+const UPDATER_QUOTED_PATH_MEM_ERROR = 14;
+
+const EXPECTED_STATUS =
+ AppConstants.platform == "win" ? STATE_PENDING_SVC : STATE_PENDING;
+
+add_setup(async function setup() {
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ [PREF_APP_UPDATE_STAGING_ENABLED, true],
+ [PREF_APP_UPDATE_SERVICE_ENABLED, true],
+ ],
+ });
+
+ registerCleanupFunction(() => {
+ Services.env.set("MOZ_FORCE_ERROR_CODE", "");
+ });
+});
+
+async function memAllocErrorFallback(errorCode) {
+ Services.env.set("MOZ_FORCE_ERROR_CODE", errorCode.toString());
+
+ // Since the partial should be successful specify an invalid size for the
+ // complete update.
+ let params = {
+ queryString: "&invalidCompleteSize=1",
+ backgroundUpdate: true,
+ continueFile: CONTINUE_STAGING,
+ waitForUpdateState: EXPECTED_STATUS,
+ };
+ await runAboutPrefsUpdateTest(params, [
+ {
+ panelId: "apply",
+ checkActiveUpdate: { state: EXPECTED_STATUS },
+ continueFile: null,
+ },
+ ]);
+}
+
+function cleanup() {
+ reloadUpdateManagerData(true);
+ removeUpdateFiles(true);
+}
+
+add_task(async function memAllocErrorFallback_READ_STRINGS_MEM_ERROR() {
+ await memAllocErrorFallback(READ_STRINGS_MEM_ERROR);
+ cleanup();
+});
+
+add_task(async function memAllocErrorFallback_ARCHIVE_READER_MEM_ERROR() {
+ await memAllocErrorFallback(ARCHIVE_READER_MEM_ERROR);
+ cleanup();
+});
+
+add_task(async function memAllocErrorFallback_BSPATCH_MEM_ERROR() {
+ await memAllocErrorFallback(BSPATCH_MEM_ERROR);
+ cleanup();
+});
+
+add_task(async function memAllocErrorFallback_UPDATER_MEM_ERROR() {
+ await memAllocErrorFallback(UPDATER_MEM_ERROR);
+ cleanup();
+});
+
+add_task(async function memAllocErrorFallback_UPDATER_QUOTED_PATH_MEM_ERROR() {
+ await memAllocErrorFallback(UPDATER_QUOTED_PATH_MEM_ERROR);
+ cleanup();
+});
diff --git a/toolkit/mozapps/update/tests/browser/browser_telemetry_updatePing_downloaded_ready.js b/toolkit/mozapps/update/tests/browser/browser_telemetry_updatePing_downloaded_ready.js
new file mode 100644
index 0000000000..f9045bbdda
--- /dev/null
+++ b/toolkit/mozapps/update/tests/browser/browser_telemetry_updatePing_downloaded_ready.js
@@ -0,0 +1,68 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+const { TelemetryArchiveTesting } = ChromeUtils.importESModule(
+ "resource://testing-common/TelemetryArchiveTesting.sys.mjs"
+);
+
+/**
+ * Test that UpdatePing telemetry with a payload reason of ready is sent for a
+ * staged update.
+ *
+ * Please note that this is really a Telemetry test, not an
+ * "update UI" test like the rest of the tests in this directory.
+ * This test does not live in toolkit/components/telemetry/tests to prevent
+ * duplicating the code for all the test dependencies. Unfortunately, due
+ * to a limitation in the build system, we were not able to simply reference
+ * the dependencies as "support-files" in the test manifest.
+ */
+add_task(async function telemetry_updatePing_ready() {
+ let archiveChecker = new TelemetryArchiveTesting.Checker();
+ await archiveChecker.promiseInit();
+
+ let updateParams = "";
+ await runTelemetryUpdateTest(updateParams, "update-downloaded");
+
+ // We cannot control when the ping will be generated/archived after we trigger
+ // an update, so let's make sure to have one before moving on with validation.
+ let updatePing;
+ await TestUtils.waitForCondition(
+ async function () {
+ // Check that the ping made it into the Telemetry archive.
+ // The test data is defined in ../data/sharedUpdateXML.js
+ updatePing = await archiveChecker.promiseFindPing("update", [
+ [["payload", "reason"], "ready"],
+ [["payload", "targetBuildId"], "20080811053724"],
+ ]);
+ return !!updatePing;
+ },
+ "Make sure the ping is generated before trying to validate it.",
+ 500,
+ 100
+ );
+
+ ok(updatePing, "The 'update' ping must be correctly sent.");
+
+ // We don't know the exact value for the other fields, so just check
+ // that they're available.
+ for (let f of ["targetVersion", "targetChannel", "targetDisplayVersion"]) {
+ ok(
+ f in updatePing.payload,
+ `${f} must be available in the update ping payload.`
+ );
+ ok(
+ typeof updatePing.payload[f] == "string",
+ `${f} must have the correct format.`
+ );
+ }
+
+ // Also make sure that the ping contains both a client id and an
+ // environment section.
+ ok("clientId" in updatePing, "The update ping must report a client id.");
+ ok(
+ "environment" in updatePing,
+ "The update ping must report the environment."
+ );
+});
diff --git a/toolkit/mozapps/update/tests/browser/browser_telemetry_updatePing_staged_ready.js b/toolkit/mozapps/update/tests/browser/browser_telemetry_updatePing_staged_ready.js
new file mode 100644
index 0000000000..42bd0da546
--- /dev/null
+++ b/toolkit/mozapps/update/tests/browser/browser_telemetry_updatePing_staged_ready.js
@@ -0,0 +1,72 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+const { TelemetryArchiveTesting } = ChromeUtils.importESModule(
+ "resource://testing-common/TelemetryArchiveTesting.sys.mjs"
+);
+
+/**
+ * Test that UpdatePing telemetry with a payload reason of ready is sent for a
+ * staged update.
+ *
+ * Please note that this is really a Telemetry test, not an
+ * "update UI" test like the rest of the tests in this directory.
+ * This test does not live in toolkit/components/telemetry/tests to prevent
+ * duplicating the code for all the test dependencies. Unfortunately, due
+ * to a limitation in the build system, we were not able to simply reference
+ * the dependencies as "support-files" in the test manifest.
+ */
+add_task(async function telemetry_updatePing_ready() {
+ await SpecialPowers.pushPrefEnv({
+ set: [[PREF_APP_UPDATE_STAGING_ENABLED, true]],
+ });
+
+ let archiveChecker = new TelemetryArchiveTesting.Checker();
+ await archiveChecker.promiseInit();
+
+ let updateParams = "";
+ await runTelemetryUpdateTest(updateParams, "update-staged");
+
+ // We cannot control when the ping will be generated/archived after we trigger
+ // an update, so let's make sure to have one before moving on with validation.
+ let updatePing;
+ await TestUtils.waitForCondition(
+ async function () {
+ // Check that the ping made it into the Telemetry archive.
+ // The test data is defined in ../data/sharedUpdateXML.js
+ updatePing = await archiveChecker.promiseFindPing("update", [
+ [["payload", "reason"], "ready"],
+ [["payload", "targetBuildId"], "20080811053724"],
+ ]);
+ return !!updatePing;
+ },
+ "Make sure the ping is generated before trying to validate it.",
+ 500,
+ 100
+ );
+
+ ok(updatePing, "The 'update' ping must be correctly sent.");
+
+ // We don't know the exact value for the other fields, so just check
+ // that they're available.
+ for (let f of ["targetVersion", "targetChannel", "targetDisplayVersion"]) {
+ ok(
+ f in updatePing.payload,
+ `${f} must be available in the update ping payload.`
+ );
+ ok(
+ typeof updatePing.payload[f] == "string",
+ `${f} must have the correct format.`
+ );
+ }
+
+ // Also make sure that the ping contains both a client id and an
+ // environment section.
+ ok("clientId" in updatePing, "The update ping must report a client id.");
+ ok(
+ "environment" in updatePing,
+ "The update ping must report the environment."
+ );
+});
diff --git a/toolkit/mozapps/update/tests/browser/downloadPage.html b/toolkit/mozapps/update/tests/browser/downloadPage.html
new file mode 100644
index 0000000000..4810e2e0d6
--- /dev/null
+++ b/toolkit/mozapps/update/tests/browser/downloadPage.html
@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>Download page</title>
+ <meta charset="utf-8">
+</head>
+<body>
+<!-- just use simple.mar since we have it available and it will result in a download dialog -->
+<a id="download-link" href="http://example.com/browser/browser/base/content/test/appUpdate/simple.mar" data-link-type="download">
+ Download
+</a>
+</body>
+</html>
diff --git a/toolkit/mozapps/update/tests/browser/head.js b/toolkit/mozapps/update/tests/browser/head.js
new file mode 100644
index 0000000000..756b2c7ea5
--- /dev/null
+++ b/toolkit/mozapps/update/tests/browser/head.js
@@ -0,0 +1,1347 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+ChromeUtils.defineESModuleGetters(this, {
+ AppMenuNotifications: "resource://gre/modules/AppMenuNotifications.sys.mjs",
+ DownloadUtils: "resource://gre/modules/DownloadUtils.sys.mjs",
+ UpdateListener: "resource://gre/modules/UpdateListener.sys.mjs",
+});
+const { XPIInstall } = ChromeUtils.import(
+ "resource://gre/modules/addons/XPIInstall.jsm"
+);
+
+const BIN_SUFFIX = AppConstants.platform == "win" ? ".exe" : "";
+const FILE_UPDATER_BIN =
+ "updater" + (AppConstants.platform == "macosx" ? ".app" : BIN_SUFFIX);
+const FILE_UPDATER_BIN_BAK = FILE_UPDATER_BIN + ".bak";
+
+const LOG_FUNCTION = info;
+
+const MAX_UPDATE_COPY_ATTEMPTS = 10;
+
+const DATA_URI_SPEC =
+ "chrome://mochitests/content/browser/toolkit/mozapps/update/tests/browser/";
+/* import-globals-from testConstants.js */
+Services.scriptloader.loadSubScript(DATA_URI_SPEC + "testConstants.js", this);
+
+var gURLData = URL_HOST + "/" + REL_PATH_DATA;
+const URL_MANUAL_UPDATE = gURLData + "downloadPage.html";
+
+const gBadSizeResult = Cr.NS_ERROR_UNEXPECTED.toString();
+
+/* import-globals-from ../data/shared.js */
+Services.scriptloader.loadSubScript(DATA_URI_SPEC + "shared.js", this);
+
+let gOriginalUpdateAutoValue = null;
+
+// Some elements append a trailing /. After the chrome tests are removed this
+// code can be changed so URL_HOST already has a trailing /.
+const gDetailsURL = URL_HOST + "/";
+
+// Set to true to log additional information for debugging. To log additional
+// information for individual tests set gDebugTest to false here and to true
+// globally in the test.
+gDebugTest = false;
+
+// This is to accommodate the TV task which runs the tests with --verify.
+requestLongerTimeout(10);
+
+/**
+ * Common tasks to perform for all tests before each one has started.
+ */
+add_setup(async function setupTestCommon() {
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ [PREF_APP_UPDATE_BADGEWAITTIME, 1800],
+ [PREF_APP_UPDATE_DOWNLOAD_ATTEMPTS, 0],
+ [PREF_APP_UPDATE_DOWNLOAD_MAXATTEMPTS, 2],
+ [PREF_APP_UPDATE_LOG, gDebugTest],
+ [PREF_APP_UPDATE_PROMPTWAITTIME, 3600],
+ [PREF_APP_UPDATE_SERVICE_ENABLED, false],
+ ],
+ });
+
+ // We need to keep the update sync manager from thinking two instances are
+ // running because of the mochitest parent instance, which means we need to
+ // override the directory service with a fake executable path and then reset
+ // the lock. But leaving the directory service overridden causes problems for
+ // these tests, so we need to restore the real service immediately after.
+ // To form the path, we'll use the real executable path with a token appended
+ // (the path needs to be absolute, but not to point to a real file).
+ // This block is loosely copied from adjustGeneralPaths() in another update
+ // test file, xpcshellUtilsAUS.js, but this is a much more limited version;
+ // it's been copied here both because the full function is overkill and also
+ // because making it general enough to run in both xpcshell and mochitest
+ // would have been unreasonably difficult.
+ let exePath = Services.dirsvc.get(XRE_EXECUTABLE_FILE, Ci.nsIFile);
+ let dirProvider = {
+ getFile: function AGP_DP_getFile(aProp, aPersistent) {
+ // Set the value of persistent to false so when this directory provider is
+ // unregistered it will revert back to the original provider.
+ aPersistent.value = false;
+ switch (aProp) {
+ case XRE_EXECUTABLE_FILE:
+ exePath.append("browser-test");
+ return exePath;
+ }
+ return null;
+ },
+ QueryInterface: ChromeUtils.generateQI(["nsIDirectoryServiceProvider"]),
+ };
+ let ds = Services.dirsvc.QueryInterface(Ci.nsIDirectoryService);
+ ds.QueryInterface(Ci.nsIProperties).undefine(XRE_EXECUTABLE_FILE);
+ ds.registerProvider(dirProvider);
+
+ let syncManager = Cc["@mozilla.org/updates/update-sync-manager;1"].getService(
+ Ci.nsIUpdateSyncManager
+ );
+ syncManager.resetLock();
+
+ ds.unregisterProvider(dirProvider);
+
+ setUpdateTimerPrefs();
+ reloadUpdateManagerData(true);
+ removeUpdateFiles(true);
+ UpdateListener.reset();
+ AppMenuNotifications.removeNotification(/.*/);
+ // Most app update mochitest-browser-chrome tests expect auto update to be
+ // enabled. Those that don't will explicitly change this.
+ await setAppUpdateAutoEnabledHelper(true);
+});
+
+/**
+ * Common tasks to perform for all tests after each one has finished.
+ */
+registerCleanupFunction(async () => {
+ AppMenuNotifications.removeNotification(/.*/);
+ Services.env.set("MOZ_TEST_SKIP_UPDATE_STAGE", "");
+ Services.env.set("MOZ_TEST_SLOW_SKIP_UPDATE_STAGE", "");
+ Services.env.set("MOZ_TEST_STAGING_ERROR", "");
+ UpdateListener.reset();
+ AppMenuNotifications.removeNotification(/.*/);
+ reloadUpdateManagerData(true);
+ // Pass false when the log files are needed for troubleshooting the tests.
+ removeUpdateFiles(true);
+ // Always try to restore the original updater files. If none of the updater
+ // backup files are present then this is just a no-op.
+ await finishTestRestoreUpdaterBackup();
+ // Reset the update lock once again so that we know the lock we're
+ // interested in here will be closed properly (normally that happens during
+ // XPCOM shutdown, but that isn't consistent during tests).
+ let syncManager = Cc["@mozilla.org/updates/update-sync-manager;1"].getService(
+ Ci.nsIUpdateSyncManager
+ );
+ syncManager.resetLock();
+});
+
+/**
+ * Overrides the add-ons manager language pack staging with a mocked version.
+ * The returned promise resolves when language pack staging begins returning an
+ * object with the new appVersion and platformVersion and functions to resolve
+ * or reject the install.
+ */
+function mockLangpackInstall() {
+ let original = XPIInstall.stageLangpacksForAppUpdate;
+ registerCleanupFunction(() => {
+ XPIInstall.stageLangpacksForAppUpdate = original;
+ });
+
+ let stagingCall = PromiseUtils.defer();
+ XPIInstall.stageLangpacksForAppUpdate = (appVersion, platformVersion) => {
+ let result = PromiseUtils.defer();
+ stagingCall.resolve({
+ appVersion,
+ platformVersion,
+ resolve: result.resolve,
+ reject: result.reject,
+ });
+
+ return result.promise;
+ };
+
+ return stagingCall.promise;
+}
+
+/**
+ * Creates and locks the app update write test file so it is possible to test
+ * when the user doesn't have write access to update. Since this is only
+ * possible on Windows the function throws when it is called on other platforms.
+ * This uses registerCleanupFunction to remove the lock and the file when the
+ * test completes.
+ *
+ * @throws If the function is called on a platform other than Windows.
+ */
+function lockWriteTestFile() {
+ if (AppConstants.platform != "win") {
+ throw new Error("Windows only test function called");
+ }
+ let file = getUpdateDirFile(FILE_UPDATE_TEST).QueryInterface(
+ Ci.nsILocalFileWin
+ );
+ // Remove the file if it exists just in case.
+ if (file.exists()) {
+ file.readOnly = false;
+ file.remove(false);
+ }
+ file.create(file.NORMAL_FILE_TYPE, 0o444);
+ file.readOnly = true;
+ registerCleanupFunction(() => {
+ file.readOnly = false;
+ file.remove(false);
+ });
+}
+
+/**
+ * Closes the update mutex handle in nsUpdateService.js if it exists and then
+ * creates a new update mutex handle so the update code thinks there is another
+ * instance of the application handling updates.
+ *
+ * @throws If the function is called on a platform other than Windows.
+ */
+function setOtherInstanceHandlingUpdates() {
+ if (AppConstants.platform != "win") {
+ throw new Error("Windows only test function called");
+ }
+ gAUS.observe(null, "test-close-handle-update-mutex", "");
+ let handle = createMutex(getPerInstallationMutexName());
+ registerCleanupFunction(() => {
+ closeHandle(handle);
+ });
+}
+
+/**
+ * Gets the update version info for the update url parameters to send to
+ * app_update.sjs.
+ *
+ * @param aAppVersion (optional)
+ * The application version for the update snippet. If not specified the
+ * current application version will be used.
+ * @return The url parameters for the application and platform version to send
+ * to app_update.sjs.
+ */
+function getVersionParams(aAppVersion) {
+ let appInfo = Services.appinfo;
+ return "&appVersion=" + (aAppVersion ? aAppVersion : appInfo.version);
+}
+
+/**
+ * Prevent nsIUpdateTimerManager from notifying nsIApplicationUpdateService
+ * to check for updates by setting the app update last update time to the
+ * current time minus one minute in seconds and the interval time to 12 hours
+ * in seconds.
+ */
+function setUpdateTimerPrefs() {
+ let now = Math.round(Date.now() / 1000) - 60;
+ Services.prefs.setIntPref(PREF_APP_UPDATE_LASTUPDATETIME, now);
+ Services.prefs.setIntPref(PREF_APP_UPDATE_INTERVAL, 43200);
+}
+
+/*
+ * Sets the value of the App Auto Update setting and sets it back to the
+ * original value at the start of the test when the test finishes.
+ *
+ * @param enabled
+ * The value to set App Auto Update to.
+ */
+async function setAppUpdateAutoEnabledHelper(enabled) {
+ if (gOriginalUpdateAutoValue == null) {
+ gOriginalUpdateAutoValue = await UpdateUtils.getAppUpdateAutoEnabled();
+ registerCleanupFunction(async () => {
+ await UpdateUtils.setAppUpdateAutoEnabled(gOriginalUpdateAutoValue);
+ });
+ }
+ await UpdateUtils.setAppUpdateAutoEnabled(enabled);
+}
+
+/**
+ * Gets the specified button for the notification.
+ *
+ * @param win
+ * The window to get the notification button for.
+ * @param notificationId
+ * The ID of the notification to get the button for.
+ * @param button
+ * The anonid of the button to get.
+ * @return The button element.
+ */
+function getNotificationButton(win, notificationId, button) {
+ let notification = win.document.getElementById(
+ `appMenu-${notificationId}-notification`
+ );
+ ok(!notification.hidden, `${notificationId} notification is showing`);
+ return notification[button];
+}
+
+/**
+ * For staging tests the test updater must be used and this restores the backed
+ * up real updater if it exists and tries again on failure since Windows debug
+ * builds at times leave the file in use. After success moveRealUpdater is
+ * called to continue the setup of the test updater.
+ */
+function setupTestUpdater() {
+ return (async function () {
+ if (Services.prefs.getBoolPref(PREF_APP_UPDATE_STAGING_ENABLED)) {
+ try {
+ restoreUpdaterBackup();
+ } catch (e) {
+ logTestInfo(
+ "Attempt to restore the backed up updater failed... " +
+ "will try again, Exception: " +
+ e
+ );
+ await TestUtils.waitForTick();
+ await setupTestUpdater();
+ return;
+ }
+ await moveRealUpdater();
+ }
+ })();
+}
+
+/**
+ * Backs up the real updater and tries again on failure since Windows debug
+ * builds at times leave the file in use. After success it will call
+ * copyTestUpdater to continue the setup of the test updater.
+ */
+function moveRealUpdater() {
+ return (async function () {
+ try {
+ // Move away the real updater
+ let greBinDir = getGREBinDir();
+ let updater = greBinDir.clone();
+ updater.append(FILE_UPDATER_BIN);
+ updater.moveTo(greBinDir, FILE_UPDATER_BIN_BAK);
+
+ let greDir = getGREDir();
+ let updateSettingsIni = greDir.clone();
+ updateSettingsIni.append(FILE_UPDATE_SETTINGS_INI);
+ if (updateSettingsIni.exists()) {
+ updateSettingsIni.moveTo(greDir, FILE_UPDATE_SETTINGS_INI_BAK);
+ }
+
+ let precomplete = greDir.clone();
+ precomplete.append(FILE_PRECOMPLETE);
+ if (precomplete.exists()) {
+ precomplete.moveTo(greDir, FILE_PRECOMPLETE_BAK);
+ }
+ } catch (e) {
+ logTestInfo(
+ "Attempt to move the real updater out of the way failed... " +
+ "will try again, Exception: " +
+ e
+ );
+ await TestUtils.waitForTick();
+ await moveRealUpdater();
+ return;
+ }
+
+ await copyTestUpdater();
+ })();
+}
+
+/**
+ * Copies the test updater and tries again on failure since Windows debug builds
+ * at times leave the file in use.
+ */
+function copyTestUpdater(attempt = 0) {
+ return (async function () {
+ try {
+ // Copy the test updater
+ let greBinDir = getGREBinDir();
+ let testUpdaterDir = Services.dirsvc.get("CurWorkD", Ci.nsIFile);
+ let relPath = REL_PATH_DATA;
+ let pathParts = relPath.split("/");
+ for (let i = 0; i < pathParts.length; ++i) {
+ testUpdaterDir.append(pathParts[i]);
+ }
+
+ let testUpdater = testUpdaterDir.clone();
+ testUpdater.append(FILE_UPDATER_BIN);
+ testUpdater.copyToFollowingLinks(greBinDir, FILE_UPDATER_BIN);
+
+ let greDir = getGREDir();
+ let updateSettingsIni = greDir.clone();
+ updateSettingsIni.append(FILE_UPDATE_SETTINGS_INI);
+ writeFile(updateSettingsIni, UPDATE_SETTINGS_CONTENTS);
+
+ let precomplete = greDir.clone();
+ precomplete.append(FILE_PRECOMPLETE);
+ writeFile(precomplete, PRECOMPLETE_CONTENTS);
+ } catch (e) {
+ if (attempt < MAX_UPDATE_COPY_ATTEMPTS) {
+ logTestInfo(
+ "Attempt to copy the test updater failed... " +
+ "will try again, Exception: " +
+ e
+ );
+ await TestUtils.waitForTick();
+ await copyTestUpdater(attempt++);
+ }
+ }
+ })();
+}
+
+/**
+ * Restores the updater and updater related file that if there a backup exists.
+ * This is called in setupTestUpdater before the backup of the real updater is
+ * done in case the previous test failed to restore the file when a test has
+ * finished. This is also called in finishTestRestoreUpdaterBackup to restore
+ * the files when a test finishes.
+ */
+function restoreUpdaterBackup() {
+ let greBinDir = getGREBinDir();
+ let updater = greBinDir.clone();
+ let updaterBackup = greBinDir.clone();
+ updater.append(FILE_UPDATER_BIN);
+ updaterBackup.append(FILE_UPDATER_BIN_BAK);
+ if (updaterBackup.exists()) {
+ if (updater.exists()) {
+ updater.remove(true);
+ }
+ updaterBackup.moveTo(greBinDir, FILE_UPDATER_BIN);
+ }
+
+ let greDir = getGREDir();
+ let updateSettingsIniBackup = greDir.clone();
+ updateSettingsIniBackup.append(FILE_UPDATE_SETTINGS_INI_BAK);
+ if (updateSettingsIniBackup.exists()) {
+ let updateSettingsIni = greDir.clone();
+ updateSettingsIni.append(FILE_UPDATE_SETTINGS_INI);
+ if (updateSettingsIni.exists()) {
+ updateSettingsIni.remove(false);
+ }
+ updateSettingsIniBackup.moveTo(greDir, FILE_UPDATE_SETTINGS_INI);
+ }
+
+ let precomplete = greDir.clone();
+ let precompleteBackup = greDir.clone();
+ precomplete.append(FILE_PRECOMPLETE);
+ precompleteBackup.append(FILE_PRECOMPLETE_BAK);
+ if (precompleteBackup.exists()) {
+ if (precomplete.exists()) {
+ precomplete.remove(false);
+ }
+ precompleteBackup.moveTo(greDir, FILE_PRECOMPLETE);
+ } else if (precomplete.exists()) {
+ if (readFile(precomplete) == PRECOMPLETE_CONTENTS) {
+ precomplete.remove(false);
+ }
+ }
+}
+
+/**
+ * When a test finishes this will repeatedly attempt to restore the real updater
+ * and the other files for the updater if a backup of the file exists.
+ */
+function finishTestRestoreUpdaterBackup() {
+ return (async function () {
+ try {
+ // Windows debug builds keep the updater file in use for a short period of
+ // time after the updater process exits.
+ restoreUpdaterBackup();
+ } catch (e) {
+ logTestInfo(
+ "Attempt to restore the backed up updater failed... " +
+ "will try again, Exception: " +
+ e
+ );
+
+ await TestUtils.waitForTick();
+ await finishTestRestoreUpdaterBackup();
+ }
+ })();
+}
+
+/**
+ * Waits for the About Dialog to load.
+ *
+ * @return A promise that returns the domWindow for the About Dialog and
+ * resolves when the About Dialog loads.
+ */
+function waitForAboutDialog() {
+ return new Promise(resolve => {
+ var listener = {
+ onOpenWindow: aXULWindow => {
+ debugDump("About dialog shown...");
+ Services.wm.removeListener(listener);
+
+ async function aboutDialogOnLoad() {
+ domwindow.removeEventListener("load", aboutDialogOnLoad, true);
+ let chromeURI = "chrome://browser/content/aboutDialog.xhtml";
+ is(
+ domwindow.document.location.href,
+ chromeURI,
+ "About dialog appeared"
+ );
+ resolve(domwindow);
+ }
+
+ var domwindow = aXULWindow.docShell.domWindow;
+ domwindow.addEventListener("load", aboutDialogOnLoad, true);
+ },
+ onCloseWindow: aXULWindow => {},
+ };
+
+ Services.wm.addListener(listener);
+ openAboutDialog();
+ });
+}
+
+/**
+ * Return the first UpdatePatch with the given type.
+ *
+ * @param type
+ * The type of the patch ("complete" or "partial")
+ * @param update
+ * The nsIUpdate to select a patch from.
+ * @return A nsIUpdatePatch object matching the type specified
+ */
+function getPatchOfType(type, update) {
+ if (update) {
+ for (let i = 0; i < update.patchCount; ++i) {
+ let patch = update.getPatchAt(i);
+ if (patch && patch.type == type) {
+ return patch;
+ }
+ }
+ }
+ return null;
+}
+
+/**
+ * Runs a Doorhanger update test. This will set various common prefs for
+ * updating and runs the provided list of steps.
+ *
+ * @param params
+ * An object containing parameters used to run the test.
+ * @param steps
+ * An array of test steps to perform. A step will either be an object
+ * containing expected conditions and actions or a function to call.
+ * @return A promise which will resolve once all of the steps have been run.
+ */
+function runDoorhangerUpdateTest(params, steps) {
+ function processDoorhangerStep(step) {
+ if (typeof step == "function") {
+ return step();
+ }
+
+ const {
+ notificationId,
+ button,
+ checkActiveUpdate,
+ pageURLs,
+ expectedStateOverride,
+ } = step;
+ return (async function () {
+ if (!params.popupShown && !PanelUI.isNotificationPanelOpen) {
+ await BrowserTestUtils.waitForEvent(
+ PanelUI.notificationPanel,
+ "popupshown"
+ );
+ }
+ const shownNotificationId = AppMenuNotifications.activeNotification.id;
+ is(
+ shownNotificationId,
+ notificationId,
+ "The right notification showed up."
+ );
+
+ let expectedState = Ci.nsIApplicationUpdateService.STATE_IDLE;
+ if (expectedStateOverride) {
+ expectedState = expectedStateOverride;
+ } else if (notificationId == "update-restart") {
+ expectedState = Ci.nsIApplicationUpdateService.STATE_PENDING;
+ }
+ let actualState = gAUS.currentState;
+ is(
+ actualState,
+ expectedState,
+ `The current update state should be ` +
+ `"${gAUS.getStateName(expectedState)}". Actual: ` +
+ `"${gAUS.getStateName(actualState)}"`
+ );
+
+ if (checkActiveUpdate) {
+ let activeUpdate =
+ checkActiveUpdate.state == STATE_DOWNLOADING
+ ? gUpdateManager.downloadingUpdate
+ : gUpdateManager.readyUpdate;
+ ok(!!activeUpdate, "There should be an active update");
+ is(
+ activeUpdate.state,
+ checkActiveUpdate.state,
+ `The active update state should equal ${checkActiveUpdate.state}`
+ );
+ } else {
+ ok(
+ !gUpdateManager.downloadingUpdate,
+ "There should not be a downloading update"
+ );
+ ok(!gUpdateManager.readyUpdate, "There should not be a ready update");
+ }
+
+ let buttonEl = getNotificationButton(window, notificationId, button);
+ buttonEl.click();
+
+ if (pageURLs && pageURLs.manual !== undefined) {
+ await BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser);
+ is(
+ gBrowser.selectedBrowser.currentURI.spec,
+ pageURLs.manual,
+ `The page's url should equal ${pageURLs.manual}`
+ );
+ gBrowser.removeTab(gBrowser.selectedTab);
+ }
+ })();
+ }
+
+ return (async function () {
+ if (params.slowStaging) {
+ Services.env.set("MOZ_TEST_SLOW_SKIP_UPDATE_STAGE", "1");
+ } else {
+ Services.env.set("MOZ_TEST_SKIP_UPDATE_STAGE", "1");
+ }
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ [PREF_APP_UPDATE_DISABLEDFORTESTING, false],
+ [PREF_APP_UPDATE_URL_DETAILS, gDetailsURL],
+ [PREF_APP_UPDATE_URL_MANUAL, URL_MANUAL_UPDATE],
+ ],
+ });
+
+ await setupTestUpdater();
+
+ let baseURL = URL_HTTP_UPDATE_SJS;
+ if (params.baseURL) {
+ baseURL = params.baseURL;
+ }
+ let queryString = params.queryString ? params.queryString : "";
+ let updateURL =
+ baseURL +
+ "?detailsURL=" +
+ gDetailsURL +
+ queryString +
+ getVersionParams(params.version);
+ setUpdateURL(updateURL);
+ if (params.checkAttempts) {
+ // Perform a background check doorhanger test.
+ executeSoon(() => {
+ (async function () {
+ gAUS.checkForBackgroundUpdates();
+ for (var i = 0; i < params.checkAttempts - 1; i++) {
+ await waitForEvent("update-error", "check-attempt-failed");
+ gAUS.checkForBackgroundUpdates();
+ }
+ })();
+ });
+ } else {
+ // Perform a startup processing doorhanger test.
+ writeStatusFile(STATE_FAILED_CRC_ERROR);
+ writeUpdatesToXMLFile(getLocalUpdatesXMLString(params.updates), true);
+ reloadUpdateManagerData();
+ testPostUpdateProcessing();
+ }
+
+ for (let step of steps) {
+ await processDoorhangerStep(step);
+ }
+ })();
+}
+
+/**
+ * Runs an About Dialog update test. This will set various common prefs for
+ * updating and runs the provided list of steps.
+ *
+ * @param params
+ * An object containing parameters used to run the test.
+ * @param steps
+ * An array of test steps to perform. A step will either be an object
+ * containing expected conditions and actions or a function to call.
+ * @return A promise which will resolve once all of the steps have been run.
+ */
+function runAboutDialogUpdateTest(params, steps) {
+ let aboutDialog;
+ function processAboutDialogStep(step) {
+ if (typeof step == "function") {
+ return step(aboutDialog);
+ }
+
+ const {
+ panelId,
+ checkActiveUpdate,
+ continueFile,
+ downloadInfo,
+ forceApply,
+ noContinue,
+ expectedStateOverride,
+ } = step;
+ return (async function () {
+ await TestUtils.waitForCondition(
+ () =>
+ aboutDialog.gAppUpdater &&
+ aboutDialog.gAppUpdater.selectedPanel?.id == panelId,
+ "Waiting for the expected panel ID: " + panelId,
+ undefined,
+ 200
+ ).catch(e => {
+ // Instead of throwing let the check below fail the test so the panel
+ // ID and the expected panel ID is printed in the log.
+ logTestInfo(e);
+ });
+ let { selectedPanel } = aboutDialog.gAppUpdater;
+ is(selectedPanel.id, panelId, "The panel ID should equal " + panelId);
+ ok(
+ BrowserTestUtils.is_visible(selectedPanel),
+ "The panel should be visible"
+ );
+
+ if (
+ panelId == "downloading" &&
+ gAUS.currentState == Ci.nsIApplicationUpdateService.STATE_IDLE
+ ) {
+ // Now that `AUS.downloadUpdate` is async, we start showing the
+ // downloading panel while `AUS.downloadUpdate` is still resolving.
+ // But the below checks assume that this resolution has already
+ // happened. So we need to wait for things to actually resolve.
+ debugDump("Waiting for downloading state to actually start");
+ await gAUS.stateTransition;
+
+ // Check that the checks that we made above are still valid.
+ selectedPanel = aboutDialog.gAppUpdater.selectedPanel;
+ is(selectedPanel.id, panelId, "The panel ID should equal " + panelId);
+ ok(
+ BrowserTestUtils.is_visible(selectedPanel),
+ "The panel should be visible"
+ );
+ }
+
+ let expectedState = Ci.nsIApplicationUpdateService.STATE_IDLE;
+ if (expectedStateOverride) {
+ expectedState = expectedStateOverride;
+ } else if (panelId == "apply") {
+ expectedState = Ci.nsIApplicationUpdateService.STATE_PENDING;
+ } else if (panelId == "downloading") {
+ expectedState = Ci.nsIApplicationUpdateService.STATE_DOWNLOADING;
+ } else if (panelId == "applying") {
+ expectedState = Ci.nsIApplicationUpdateService.STATE_STAGING;
+ }
+ let actualState = gAUS.currentState;
+ is(
+ actualState,
+ expectedState,
+ `The current update state should be ` +
+ `"${gAUS.getStateName(expectedState)}". Actual: ` +
+ `"${gAUS.getStateName(actualState)}"`
+ );
+
+ if (checkActiveUpdate) {
+ let activeUpdate =
+ checkActiveUpdate.state == STATE_DOWNLOADING
+ ? gUpdateManager.downloadingUpdate
+ : gUpdateManager.readyUpdate;
+ ok(!!activeUpdate, "There should be an active update");
+ is(
+ activeUpdate.state,
+ checkActiveUpdate.state,
+ "The active update state should equal " + checkActiveUpdate.state
+ );
+ } else {
+ ok(
+ !gUpdateManager.downloadingUpdate,
+ "There should not be a downloading update"
+ );
+ ok(!gUpdateManager.readyUpdate, "There should not be a ready update");
+ }
+
+ // Some tests just want to stop at the downloading state. These won't
+ // include a continue file in that state.
+ if (panelId == "downloading" && continueFile) {
+ for (let i = 0; i < downloadInfo.length; ++i) {
+ let data = downloadInfo[i];
+ await continueFileHandler(continueFile);
+ let patch = getPatchOfType(
+ data.patchType,
+ gUpdateManager.downloadingUpdate
+ );
+ // The update is removed early when the last download fails so check
+ // that there is a patch before proceeding.
+ let isLastPatch = i == downloadInfo.length - 1;
+ if (!isLastPatch || patch) {
+ let resultName = data.bitsResult ? "bitsResult" : "internalResult";
+ patch.QueryInterface(Ci.nsIWritablePropertyBag);
+ await TestUtils.waitForCondition(
+ () => patch.getProperty(resultName) == data[resultName],
+ "Waiting for expected patch property " +
+ resultName +
+ " value: " +
+ data[resultName],
+ undefined,
+ 200
+ ).catch(e => {
+ // Instead of throwing let the check below fail the test so the
+ // property value and the expected property value is printed in
+ // the log.
+ logTestInfo(e);
+ });
+ is(
+ "" + patch.getProperty(resultName),
+ data[resultName],
+ "The patch property " +
+ resultName +
+ " value should equal " +
+ data[resultName]
+ );
+
+ // Check the download status text. It should be something like,
+ // "1.4 of 1.4 KB".
+ let expectedText = DownloadUtils.getTransferTotal(
+ data[resultName] == gBadSizeResult ? 0 : patch.size,
+ patch.size
+ );
+ Assert.ok(
+ expectedText,
+ "Sanity check: Expected download status text should be non-empty"
+ );
+ if (aboutDialog.document.hasPendingL10nMutations) {
+ await BrowserTestUtils.waitForEvent(
+ aboutDialog.document,
+ "L10nMutationsFinished"
+ );
+ }
+ Assert.equal(
+ aboutDialog.document.querySelector(
+ `#downloading label[data-l10n-name="download-status"]`
+ ).textContent,
+ expectedText,
+ "Download status text should be correct"
+ );
+ }
+ }
+ } else if (continueFile) {
+ await continueFileHandler(continueFile);
+ }
+
+ let linkPanels = [
+ "downloadFailed",
+ "manualUpdate",
+ "unsupportedSystem",
+ "internalError",
+ ];
+ if (linkPanels.includes(panelId)) {
+ // The unsupportedSystem panel uses the update's detailsURL and the
+ // downloadFailed and manualUpdate panels use the app.update.url.manual
+ // preference.
+ let selector = "label.text-link";
+ if (selectedPanel.ownerDocument.hasPendingL10nMutations) {
+ await BrowserTestUtils.waitForEvent(
+ selectedPanel.ownerDocument,
+ "L10nMutationsFinished"
+ );
+ }
+ let link = selectedPanel.querySelector(selector);
+ is(
+ link.href,
+ gDetailsURL,
+ `The panel's link href should equal ${gDetailsURL}`
+ );
+ const assertNonEmptyText = (node, description) => {
+ let textContent = node.textContent.trim();
+ ok(textContent, `${description}, got "${textContent}"`);
+ };
+ assertNonEmptyText(
+ link,
+ `The panel's link should have non-empty textContent`
+ );
+ let linkWrapperClone = link.parentNode.cloneNode(true);
+ linkWrapperClone.querySelector(selector).remove();
+ assertNonEmptyText(
+ linkWrapperClone,
+ `The panel's link should have text around the link`
+ );
+ }
+
+ // Automatically click the download button unless `noContinue` was passed.
+ let buttonPanels = ["downloadAndInstall", "apply"];
+ if (buttonPanels.includes(panelId) && !noContinue) {
+ let buttonEl = selectedPanel.querySelector("button");
+ await TestUtils.waitForCondition(
+ () => aboutDialog.document.activeElement == buttonEl,
+ "The button should receive focus"
+ );
+ ok(!buttonEl.disabled, "The button should be enabled");
+ // Don't click the button on the apply panel since this will restart the
+ // application.
+ if (panelId != "apply" || forceApply) {
+ buttonEl.click();
+ }
+ }
+ })();
+ }
+
+ return (async function () {
+ Services.env.set("MOZ_TEST_SLOW_SKIP_UPDATE_STAGE", "1");
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ [PREF_APP_UPDATE_DISABLEDFORTESTING, false],
+ [PREF_APP_UPDATE_URL_MANUAL, gDetailsURL],
+ ],
+ });
+
+ await setupTestUpdater();
+
+ let baseURL = URL_HTTP_UPDATE_SJS;
+ if (params.baseURL) {
+ baseURL = params.baseURL;
+ }
+ let queryString = params.queryString ? params.queryString : "";
+ let updateURL =
+ baseURL +
+ "?detailsURL=" +
+ gDetailsURL +
+ queryString +
+ getVersionParams(params.version);
+ if (params.backgroundUpdate) {
+ setUpdateURL(updateURL);
+ gAUS.checkForBackgroundUpdates();
+ if (params.continueFile) {
+ await continueFileHandler(params.continueFile);
+ }
+ if (params.waitForUpdateState) {
+ let whichUpdate =
+ params.waitForUpdateState == STATE_DOWNLOADING
+ ? "downloadingUpdate"
+ : "readyUpdate";
+ await TestUtils.waitForCondition(
+ () =>
+ gUpdateManager[whichUpdate] &&
+ gUpdateManager[whichUpdate].state == params.waitForUpdateState,
+ "Waiting for update state: " + params.waitForUpdateState,
+ undefined,
+ 200
+ ).catch(e => {
+ // Instead of throwing let the check below fail the test so the panel
+ // ID and the expected panel ID is printed in the log.
+ logTestInfo(e);
+ });
+ // Display the UI after the update state equals the expected value.
+ is(
+ gUpdateManager[whichUpdate].state,
+ params.waitForUpdateState,
+ "The update state value should equal " + params.waitForUpdateState
+ );
+ }
+ } else {
+ updateURL += "&slowUpdateCheck=1&useSlowDownloadMar=1";
+ setUpdateURL(updateURL);
+ }
+
+ aboutDialog = await waitForAboutDialog();
+ registerCleanupFunction(() => {
+ aboutDialog.close();
+ });
+
+ for (let step of steps) {
+ await processAboutDialogStep(step);
+ }
+ })();
+}
+
+/**
+ * Runs an about:preferences update test. This will set various common prefs for
+ * updating and runs the provided list of steps.
+ *
+ * @param params
+ * An object containing parameters used to run the test.
+ * @param steps
+ * An array of test steps to perform. A step will either be an object
+ * containing expected conditions and actions or a function to call.
+ * @return A promise which will resolve once all of the steps have been run.
+ */
+function runAboutPrefsUpdateTest(params, steps) {
+ let tab;
+ function processAboutPrefsStep(step) {
+ if (typeof step == "function") {
+ return step(tab);
+ }
+
+ const {
+ panelId,
+ checkActiveUpdate,
+ continueFile,
+ downloadInfo,
+ forceApply,
+ expectedStateOverride,
+ } = step;
+ return (async function () {
+ await SpecialPowers.spawn(
+ tab.linkedBrowser,
+ [{ panelId }],
+ async ({ panelId }) => {
+ await ContentTaskUtils.waitForCondition(
+ () => content.gAppUpdater.selectedPanel?.id == panelId,
+ "Waiting for the expected panel ID: " + panelId,
+ undefined,
+ 200
+ ).catch(e => {
+ // Instead of throwing let the check below fail the test so the panel
+ // ID and the expected panel ID is printed in the log. Use info here
+ // instead of logTestInfo since logTestInfo isn't available in the
+ // content task.
+ info(e);
+ });
+ is(
+ content.gAppUpdater.selectedPanel.id,
+ panelId,
+ "The panel ID should equal " + panelId
+ );
+ }
+ );
+
+ if (
+ panelId == "downloading" &&
+ gAUS.currentState == Ci.nsIApplicationUpdateService.STATE_IDLE
+ ) {
+ // Now that `AUS.downloadUpdate` is async, we start showing the
+ // downloading panel while `AUS.downloadUpdate` is still resolving.
+ // But the below checks assume that this resolution has already
+ // happened. So we need to wait for things to actually resolve.
+ debugDump("Waiting for downloading state to actually start");
+ await gAUS.stateTransition;
+
+ // Check that the checks that we made above are still valid.
+ await SpecialPowers.spawn(
+ tab.linkedBrowser,
+ [{ panelId }],
+ ({ panelId }) => {
+ is(
+ content.gAppUpdater.selectedPanel.id,
+ panelId,
+ "The panel ID should equal " + panelId
+ );
+ }
+ );
+ }
+
+ let expectedState = Ci.nsIApplicationUpdateService.STATE_IDLE;
+ if (expectedStateOverride) {
+ expectedState = expectedStateOverride;
+ } else if (panelId == "apply") {
+ expectedState = Ci.nsIApplicationUpdateService.STATE_PENDING;
+ } else if (panelId == "downloading") {
+ expectedState = Ci.nsIApplicationUpdateService.STATE_DOWNLOADING;
+ } else if (panelId == "applying") {
+ expectedState = Ci.nsIApplicationUpdateService.STATE_STAGING;
+ }
+ let actualState = gAUS.currentState;
+ is(
+ actualState,
+ expectedState,
+ `The current update state should be ` +
+ `"${gAUS.getStateName(expectedState)}". Actual: ` +
+ `"${gAUS.getStateName(actualState)}"`
+ );
+
+ if (checkActiveUpdate) {
+ let activeUpdate =
+ checkActiveUpdate.state == STATE_DOWNLOADING
+ ? gUpdateManager.downloadingUpdate
+ : gUpdateManager.readyUpdate;
+ ok(!!activeUpdate, "There should be an active update");
+ is(
+ activeUpdate.state,
+ checkActiveUpdate.state,
+ "The active update state should equal " + checkActiveUpdate.state
+ );
+ } else {
+ ok(
+ !gUpdateManager.downloadingUpdate,
+ "There should not be a downloading update"
+ );
+ ok(!gUpdateManager.readyUpdate, "There should not be a ready update");
+ }
+
+ if (panelId == "downloading") {
+ if (!downloadInfo) {
+ logTestInfo("no downloadinfo, possible error?");
+ }
+ for (let i = 0; i < downloadInfo.length; ++i) {
+ let data = downloadInfo[i];
+ // The About Dialog tests always specify a continue file.
+ await continueFileHandler(continueFile);
+ let patch = getPatchOfType(
+ data.patchType,
+ gUpdateManager.downloadingUpdate
+ );
+ // The update is removed early when the last download fails so check
+ // that there is a patch before proceeding.
+ let isLastPatch = i == downloadInfo.length - 1;
+ if (!isLastPatch || patch) {
+ let resultName = data.bitsResult ? "bitsResult" : "internalResult";
+ patch.QueryInterface(Ci.nsIWritablePropertyBag);
+ await TestUtils.waitForCondition(
+ () => patch.getProperty(resultName) == data[resultName],
+ "Waiting for expected patch property " +
+ resultName +
+ " value: " +
+ data[resultName],
+ undefined,
+ 200
+ ).catch(e => {
+ // Instead of throwing let the check below fail the test so the
+ // property value and the expected property value is printed in
+ // the log.
+ logTestInfo(e);
+ });
+ is(
+ "" + patch.getProperty(resultName),
+ data[resultName],
+ "The patch property " +
+ resultName +
+ " value should equal " +
+ data[resultName]
+ );
+
+ // Check the download status text. It should be something like,
+ // "Downloading update — 1.4 of 1.4 KB". We check only the second
+ // part to make sure that the downloaded size is updated correctly.
+ let actualText = await SpecialPowers.spawn(
+ tab.linkedBrowser,
+ [],
+ async () => {
+ const { document } = content;
+ if (document.hasPendingL10nMutations) {
+ await ContentTaskUtils.waitForEvent(
+ document,
+ "L10nMutationsFinished"
+ );
+ }
+ return document.getElementById("downloading").textContent;
+ }
+ );
+ let expectedSuffix = DownloadUtils.getTransferTotal(
+ data[resultName] == gBadSizeResult ? 0 : patch.size,
+ patch.size
+ );
+ Assert.ok(
+ expectedSuffix,
+ "Sanity check: Expected download status text should be non-empty"
+ );
+ Assert.ok(
+ actualText.endsWith(expectedSuffix),
+ "Download status text should end as expected: " +
+ JSON.stringify({ actualText, expectedSuffix })
+ );
+ }
+ }
+ } else if (continueFile) {
+ await continueFileHandler(continueFile);
+ }
+
+ await SpecialPowers.spawn(
+ tab.linkedBrowser,
+ [{ panelId, gDetailsURL, forceApply }],
+ async ({ panelId, gDetailsURL, forceApply }) => {
+ let linkPanels = [
+ "downloadFailed",
+ "manualUpdate",
+ "unsupportedSystem",
+ "internalError",
+ ];
+ if (linkPanels.includes(panelId)) {
+ let { selectedPanel } = content.gAppUpdater;
+ // The unsupportedSystem panel uses the update's detailsURL and the
+ // downloadFailed and manualUpdate panels use the app.update.url.manual
+ // preference.
+ let selector = "label.text-link";
+ // The downloadFailed panel in about:preferences uses an anchor
+ // instead of a label for the link.
+ if (selectedPanel.id == "downloadFailed") {
+ selector = "a.text-link";
+ }
+ // The manualUpdate panel in about:preferences uses
+ // the moz-support-link element which doesn't have
+ // the .text-link class.
+ if (selectedPanel.id == "manualUpdate") {
+ selector = "a.manualLink";
+ }
+ if (selectedPanel.ownerDocument.hasPendingL10nMutations) {
+ await ContentTaskUtils.waitForEvent(
+ selectedPanel.ownerDocument,
+ "L10nMutationsFinished"
+ );
+ }
+ let link = selectedPanel.querySelector(selector);
+ is(
+ link.href,
+ gDetailsURL,
+ `The panel's link href should equal ${gDetailsURL}`
+ );
+ const assertNonEmptyText = (node, description) => {
+ let textContent = node.textContent.trim();
+ ok(textContent, `${description}, got "${textContent}"`);
+ };
+ assertNonEmptyText(
+ link,
+ `The panel's link should have non-empty textContent`
+ );
+ let linkWrapperClone = link.parentNode.cloneNode(true);
+ linkWrapperClone.querySelector(selector).remove();
+ assertNonEmptyText(
+ linkWrapperClone,
+ `The panel's link should have text around the link`
+ );
+ }
+
+ let buttonPanels = ["downloadAndInstall", "apply"];
+ if (buttonPanels.includes(panelId)) {
+ let { selectedPanel } = content.gAppUpdater;
+ let buttonEl = selectedPanel.querySelector("button");
+ // Note: The about:preferences doesn't focus the button like the
+ // About Dialog does.
+ ok(!buttonEl.disabled, "The button should be enabled");
+ // Don't click the button on the apply panel since this will restart
+ // the application.
+ if (selectedPanel.id != "apply" || forceApply) {
+ buttonEl.click();
+ }
+ }
+ }
+ );
+ })();
+ }
+
+ return (async function () {
+ Services.env.set("MOZ_TEST_SLOW_SKIP_UPDATE_STAGE", "1");
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ [PREF_APP_UPDATE_DISABLEDFORTESTING, false],
+ [PREF_APP_UPDATE_URL_MANUAL, gDetailsURL],
+ ],
+ });
+
+ await setupTestUpdater();
+
+ let baseURL = URL_HTTP_UPDATE_SJS;
+ if (params.baseURL) {
+ baseURL = params.baseURL;
+ }
+ let queryString = params.queryString ? params.queryString : "";
+ let updateURL =
+ baseURL +
+ "?detailsURL=" +
+ gDetailsURL +
+ queryString +
+ getVersionParams(params.version);
+ if (params.backgroundUpdate) {
+ setUpdateURL(updateURL);
+ gAUS.checkForBackgroundUpdates();
+ if (params.continueFile) {
+ await continueFileHandler(params.continueFile);
+ }
+ if (params.waitForUpdateState) {
+ // Wait until the update state equals the expected value before
+ // displaying the UI.
+ let whichUpdate =
+ params.waitForUpdateState == STATE_DOWNLOADING
+ ? "downloadingUpdate"
+ : "readyUpdate";
+ await TestUtils.waitForCondition(
+ () =>
+ gUpdateManager[whichUpdate] &&
+ gUpdateManager[whichUpdate].state == params.waitForUpdateState,
+ "Waiting for update state: " + params.waitForUpdateState,
+ undefined,
+ 200
+ ).catch(e => {
+ // Instead of throwing let the check below fail the test so the panel
+ // ID and the expected panel ID is printed in the log.
+ logTestInfo(e);
+ });
+ is(
+ gUpdateManager[whichUpdate].state,
+ params.waitForUpdateState,
+ "The update state value should equal " + params.waitForUpdateState
+ );
+ }
+ } else {
+ updateURL += "&slowUpdateCheck=1&useSlowDownloadMar=1";
+ setUpdateURL(updateURL);
+ }
+
+ tab = await BrowserTestUtils.openNewForegroundTab(
+ gBrowser,
+ "about:preferences"
+ );
+ registerCleanupFunction(async () => {
+ await BrowserTestUtils.removeTab(tab);
+ });
+
+ // Scroll the UI into view so it is easier to troubleshoot tests.
+ await SpecialPowers.spawn(tab.linkedBrowser, [], async () => {
+ content.document.getElementById("updatesCategory").scrollIntoView();
+ });
+
+ for (let step of steps) {
+ await processAboutPrefsStep(step);
+ }
+ })();
+}
+
+/**
+ * Removes the modified update-settings.ini file so the updater will fail to
+ * stage an update.
+ */
+function removeUpdateSettingsIni() {
+ if (Services.prefs.getBoolPref(PREF_APP_UPDATE_STAGING_ENABLED)) {
+ let greDir = getGREDir();
+ let updateSettingsIniBak = greDir.clone();
+ updateSettingsIniBak.append(FILE_UPDATE_SETTINGS_INI_BAK);
+ if (updateSettingsIniBak.exists()) {
+ let updateSettingsIni = greDir.clone();
+ updateSettingsIni.append(FILE_UPDATE_SETTINGS_INI);
+ updateSettingsIni.remove(false);
+ }
+ }
+}
+
+/**
+ * Runs a telemetry update test. This will set various common prefs for
+ * updating, checks for an update, and waits for the specified observer
+ * notification.
+ *
+ * @param updateParams
+ * Params which will be sent to app_update.sjs.
+ * @param event
+ * The observer notification to wait for before proceeding.
+ * @param stageFailure (optional)
+ * Whether to force a staging failure by removing the modified
+ * update-settings.ini file.
+ * @return A promise which will resolve after the .
+ */
+function runTelemetryUpdateTest(updateParams, event, stageFailure = false) {
+ return (async function () {
+ Services.telemetry.clearScalars();
+ Services.env.set("MOZ_TEST_SKIP_UPDATE_STAGE", "1");
+ await SpecialPowers.pushPrefEnv({
+ set: [[PREF_APP_UPDATE_DISABLEDFORTESTING, false]],
+ });
+
+ await setupTestUpdater();
+
+ if (stageFailure) {
+ removeUpdateSettingsIni();
+ }
+
+ let updateURL =
+ URL_HTTP_UPDATE_SJS +
+ "?detailsURL=" +
+ gDetailsURL +
+ updateParams +
+ getVersionParams();
+ setUpdateURL(updateURL);
+ gAUS.checkForBackgroundUpdates();
+ await waitForEvent(event);
+ })();
+}
diff --git a/toolkit/mozapps/update/tests/browser/manual_app_update_only/browser.ini b/toolkit/mozapps/update/tests/browser/manual_app_update_only/browser.ini
new file mode 100644
index 0000000000..b5295f5eac
--- /dev/null
+++ b/toolkit/mozapps/update/tests/browser/manual_app_update_only/browser.ini
@@ -0,0 +1,18 @@
+[DEFAULT]
+head = head.js
+prefs =
+ app.update.BITS.enabled=false
+ browser.policies.alternatePath='<test-root>/toolkit/mozapps/update/tests/browser/manual_app_update_only/config_manual_app_update_only.json'
+support-files =
+ !/toolkit/mozapps/update/tests/browser/head.js
+ config_manual_app_update_only.json
+ ../../data/shared.js
+ ../../data/app_update.sjs
+ ../testConstants.js
+skip-if = os == 'win' && msix # Updater is disabled in MSIX builds
+
+[browser_aboutDialog_fc_autoUpdateFalse.js]
+[browser_aboutDialog_fc_autoUpdateTrue.js]
+[browser_aboutPrefs_fc_autoUpdateFalse.js]
+[browser_aboutPrefs_fc_autoUpdateTrue.js]
+[browser_noBackgroundUpdate.js]
diff --git a/toolkit/mozapps/update/tests/browser/manual_app_update_only/browser_aboutDialog_fc_autoUpdateFalse.js b/toolkit/mozapps/update/tests/browser/manual_app_update_only/browser_aboutDialog_fc_autoUpdateFalse.js
new file mode 100644
index 0000000000..169e66033a
--- /dev/null
+++ b/toolkit/mozapps/update/tests/browser/manual_app_update_only/browser_aboutDialog_fc_autoUpdateFalse.js
@@ -0,0 +1,43 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+add_task(async function test_manual_app_update_policy() {
+ await setAppUpdateAutoEnabledHelper(false);
+
+ is(
+ Services.policies.isAllowed("autoAppUpdateChecking"),
+ false,
+ "autoAppUpdateChecking should be disabled by policy"
+ );
+ is(gAUS.manualUpdateOnly, true, "gAUS.manualUpdateOnly should be true");
+
+ let downloadInfo = [{ patchType: "partial", internalResult: "0" }];
+ // Since the partial should be successful specify an invalid size for the
+ // complete update.
+ let params = { queryString: "&invalidCompleteSize=1" };
+ await runAboutDialogUpdateTest(params, [
+ {
+ panelId: "checkingForUpdates",
+ checkActiveUpdate: null,
+ continueFile: CONTINUE_CHECK,
+ },
+ {
+ panelId: "downloadAndInstall",
+ checkActiveUpdate: null,
+ continueFile: null,
+ },
+ {
+ panelId: "downloading",
+ checkActiveUpdate: { state: STATE_DOWNLOADING },
+ continueFile: CONTINUE_DOWNLOAD,
+ downloadInfo,
+ },
+ {
+ panelId: "apply",
+ checkActiveUpdate: { state: STATE_PENDING },
+ continueFile: null,
+ },
+ ]);
+});
diff --git a/toolkit/mozapps/update/tests/browser/manual_app_update_only/browser_aboutDialog_fc_autoUpdateTrue.js b/toolkit/mozapps/update/tests/browser/manual_app_update_only/browser_aboutDialog_fc_autoUpdateTrue.js
new file mode 100644
index 0000000000..0a59f59d71
--- /dev/null
+++ b/toolkit/mozapps/update/tests/browser/manual_app_update_only/browser_aboutDialog_fc_autoUpdateTrue.js
@@ -0,0 +1,43 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+add_task(async function test_manual_app_update_policy() {
+ await setAppUpdateAutoEnabledHelper(true);
+
+ is(
+ Services.policies.isAllowed("autoAppUpdateChecking"),
+ false,
+ "autoAppUpdateChecking should be disabled by policy"
+ );
+ is(gAUS.manualUpdateOnly, true, "gAUS.manualUpdateOnly should be true");
+
+ let downloadInfo = [{ patchType: "partial", internalResult: "0" }];
+ // Since the partial should be successful specify an invalid size for the
+ // complete update.
+ let params = { queryString: "&invalidCompleteSize=1" };
+ await runAboutDialogUpdateTest(params, [
+ {
+ panelId: "checkingForUpdates",
+ checkActiveUpdate: null,
+ continueFile: CONTINUE_CHECK,
+ },
+ {
+ panelId: "downloadAndInstall",
+ checkActiveUpdate: null,
+ continueFile: null,
+ },
+ {
+ panelId: "downloading",
+ checkActiveUpdate: { state: STATE_DOWNLOADING },
+ continueFile: CONTINUE_DOWNLOAD,
+ downloadInfo,
+ },
+ {
+ panelId: "apply",
+ checkActiveUpdate: { state: STATE_PENDING },
+ continueFile: null,
+ },
+ ]);
+});
diff --git a/toolkit/mozapps/update/tests/browser/manual_app_update_only/browser_aboutPrefs_fc_autoUpdateFalse.js b/toolkit/mozapps/update/tests/browser/manual_app_update_only/browser_aboutPrefs_fc_autoUpdateFalse.js
new file mode 100644
index 0000000000..2d9608951e
--- /dev/null
+++ b/toolkit/mozapps/update/tests/browser/manual_app_update_only/browser_aboutPrefs_fc_autoUpdateFalse.js
@@ -0,0 +1,55 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+add_task(async function test_manual_app_update_policy() {
+ await setAppUpdateAutoEnabledHelper(false);
+
+ is(
+ Services.policies.isAllowed("autoAppUpdateChecking"),
+ false,
+ "autoAppUpdateChecking should be disabled by policy"
+ );
+ is(gAUS.manualUpdateOnly, true, "gAUS.manualUpdateOnly should be true");
+
+ let downloadInfo = [{ patchType: "partial", internalResult: "0" }];
+ // Since the partial should be successful specify an invalid size for the
+ // complete update.
+ let params = { queryString: "&invalidCompleteSize=1" };
+ await runAboutPrefsUpdateTest(params, [
+ {
+ panelId: "checkingForUpdates",
+ checkActiveUpdate: null,
+ continueFile: CONTINUE_CHECK,
+ },
+ {
+ panelId: "downloadAndInstall",
+ checkActiveUpdate: null,
+ continueFile: null,
+ },
+ {
+ panelId: "downloading",
+ checkActiveUpdate: { state: STATE_DOWNLOADING },
+ continueFile: CONTINUE_DOWNLOAD,
+ downloadInfo,
+ },
+ {
+ panelId: "apply",
+ checkActiveUpdate: { state: STATE_PENDING },
+ continueFile: null,
+ },
+ async tab => {
+ await SpecialPowers.spawn(tab.linkedBrowser, [], async function () {
+ let setting = content.document.getElementById(
+ "updateSettingsContainer"
+ );
+ is(
+ setting.hidden,
+ true,
+ "Update choices should be disabled when manualUpdateOnly"
+ );
+ });
+ },
+ ]);
+});
diff --git a/toolkit/mozapps/update/tests/browser/manual_app_update_only/browser_aboutPrefs_fc_autoUpdateTrue.js b/toolkit/mozapps/update/tests/browser/manual_app_update_only/browser_aboutPrefs_fc_autoUpdateTrue.js
new file mode 100644
index 0000000000..b7b0c1027a
--- /dev/null
+++ b/toolkit/mozapps/update/tests/browser/manual_app_update_only/browser_aboutPrefs_fc_autoUpdateTrue.js
@@ -0,0 +1,55 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+add_task(async function test_manual_app_update_policy() {
+ await setAppUpdateAutoEnabledHelper(true);
+
+ is(
+ Services.policies.isAllowed("autoAppUpdateChecking"),
+ false,
+ "autoAppUpdateChecking should be disabled by policy"
+ );
+ is(gAUS.manualUpdateOnly, true, "gAUS.manualUpdateOnly should be true");
+
+ let downloadInfo = [{ patchType: "partial", internalResult: "0" }];
+ // Since the partial should be successful specify an invalid size for the
+ // complete update.
+ let params = { queryString: "&invalidCompleteSize=1" };
+ await runAboutPrefsUpdateTest(params, [
+ {
+ panelId: "checkingForUpdates",
+ checkActiveUpdate: null,
+ continueFile: CONTINUE_CHECK,
+ },
+ {
+ panelId: "downloadAndInstall",
+ checkActiveUpdate: null,
+ continueFile: null,
+ },
+ {
+ panelId: "downloading",
+ checkActiveUpdate: { state: STATE_DOWNLOADING },
+ continueFile: CONTINUE_DOWNLOAD,
+ downloadInfo,
+ },
+ {
+ panelId: "apply",
+ checkActiveUpdate: { state: STATE_PENDING },
+ continueFile: null,
+ },
+ async tab => {
+ await SpecialPowers.spawn(tab.linkedBrowser, [], async function () {
+ let setting = content.document.getElementById(
+ "updateSettingsContainer"
+ );
+ is(
+ setting.hidden,
+ true,
+ "Update choices should be disabled when manualUpdateOnly"
+ );
+ });
+ },
+ ]);
+});
diff --git a/toolkit/mozapps/update/tests/browser/manual_app_update_only/browser_noBackgroundUpdate.js b/toolkit/mozapps/update/tests/browser/manual_app_update_only/browser_noBackgroundUpdate.js
new file mode 100644
index 0000000000..38b27e31ad
--- /dev/null
+++ b/toolkit/mozapps/update/tests/browser/manual_app_update_only/browser_noBackgroundUpdate.js
@@ -0,0 +1,17 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+add_task(async function test_manual_app_update_policy() {
+ // Unfortunately, we can't really test the other background update entry
+ // point, gAUS.notify, because it doesn't return anything and it would be
+ // a bit overkill to edit the nsITimerCallback interface just for this test.
+ // But the two entry points just immediately call the same function, so this
+ // should probably be alright.
+ is(
+ gAUS.checkForBackgroundUpdates(),
+ false,
+ "gAUS.checkForBackgroundUpdates() should not proceed with update check"
+ );
+});
diff --git a/toolkit/mozapps/update/tests/browser/manual_app_update_only/config_manual_app_update_only.json b/toolkit/mozapps/update/tests/browser/manual_app_update_only/config_manual_app_update_only.json
new file mode 100644
index 0000000000..4e7c785bc1
--- /dev/null
+++ b/toolkit/mozapps/update/tests/browser/manual_app_update_only/config_manual_app_update_only.json
@@ -0,0 +1,5 @@
+{
+ "policies": {
+ "ManualAppUpdateOnly": true
+ }
+}
diff --git a/toolkit/mozapps/update/tests/browser/manual_app_update_only/head.js b/toolkit/mozapps/update/tests/browser/manual_app_update_only/head.js
new file mode 100644
index 0000000000..2a7576963b
--- /dev/null
+++ b/toolkit/mozapps/update/tests/browser/manual_app_update_only/head.js
@@ -0,0 +1,9 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+Services.scriptloader.loadSubScript(
+ "chrome://mochitests/content/browser/toolkit/mozapps/update/tests/browser/head.js",
+ this
+);
diff --git a/toolkit/mozapps/update/tests/browser/testConstants.js b/toolkit/mozapps/update/tests/browser/testConstants.js
new file mode 100644
index 0000000000..915391054b
--- /dev/null
+++ b/toolkit/mozapps/update/tests/browser/testConstants.js
@@ -0,0 +1,7 @@
+const REL_PATH_DATA = "browser/toolkit/mozapps/update/tests/browser/";
+const URL_HOST = "http://127.0.0.1:8888";
+const URL_PATH_UPDATE_XML = "/" + REL_PATH_DATA + "app_update.sjs";
+const URL_HTTP_UPDATE_SJS = URL_HOST + URL_PATH_UPDATE_XML;
+const CONTINUE_CHECK = "continueCheck";
+const CONTINUE_DOWNLOAD = "continueDownload";
+const CONTINUE_STAGING = "continueStaging";
diff --git a/toolkit/mozapps/update/tests/data/app_update.sjs b/toolkit/mozapps/update/tests/data/app_update.sjs
new file mode 100644
index 0000000000..2081118547
--- /dev/null
+++ b/toolkit/mozapps/update/tests/data/app_update.sjs
@@ -0,0 +1,251 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+"use strict";
+
+/**
+ * Server side http server script for application update tests.
+ */
+
+// Definitions from test and other files used by the tests
+/* global getState */
+
+function getTestDataFile(aFilename) {
+ let file = Services.dirsvc.get("CurWorkD", Ci.nsIFile);
+ let pathParts = REL_PATH_DATA.split("/");
+ for (let i = 0; i < pathParts.length; ++i) {
+ file.append(pathParts[i]);
+ }
+ if (aFilename) {
+ file.append(aFilename);
+ }
+ return file;
+}
+
+function loadHelperScript(aScriptFile) {
+ let scriptSpec = Services.io.newFileURI(aScriptFile).spec;
+ Services.scriptloader.loadSubScript(scriptSpec, this);
+}
+
+var scriptFile = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile);
+scriptFile.initWithPath(getState("__LOCATION__"));
+scriptFile = scriptFile.parent;
+/* import-globals-from ../browser/testConstants.js */
+scriptFile.append("testConstants.js");
+loadHelperScript(scriptFile);
+
+/* import-globals-from sharedUpdateXML.js */
+scriptFile = getTestDataFile("sharedUpdateXML.js");
+loadHelperScript(scriptFile);
+
+const SERVICE_URL = URL_HOST + "/" + REL_PATH_DATA + FILE_SIMPLE_MAR;
+const BAD_SERVICE_URL = URL_HOST + "/" + REL_PATH_DATA + "not_here.mar";
+
+// A value of 10 caused the tests to intermittently fail on Mac OS X so be
+// careful when changing this value.
+const SLOW_RESPONSE_INTERVAL = 100;
+const MAX_SLOW_RESPONSE_RETRIES = 200;
+var gSlowDownloadTimer;
+var gSlowCheckTimer;
+
+function handleRequest(aRequest, aResponse) {
+ let params = {};
+ if (aRequest.queryString) {
+ params = parseQueryString(aRequest.queryString);
+ }
+
+ let statusCode = params.statusCode ? parseInt(params.statusCode) : 200;
+ let statusReason = params.statusReason ? params.statusReason : "OK";
+ aResponse.setStatusLine(aRequest.httpVersion, statusCode, statusReason);
+ aResponse.setHeader("Cache-Control", "no-cache", false);
+
+ // When a mar download is started by the update service it can finish
+ // downloading before the ui has loaded. By specifying a serviceURL for the
+ // update patch that points to this file and has a slowDownloadMar param the
+ // mar will be downloaded asynchronously which will allow the ui to load
+ // before the download completes.
+ if (params.slowDownloadMar) {
+ aResponse.processAsync();
+ aResponse.setHeader("Content-Type", "binary/octet-stream");
+ aResponse.setHeader("Content-Length", SIZE_SIMPLE_MAR);
+
+ // BITS will first make a HEAD request followed by a GET request.
+ if (aRequest.method == "HEAD") {
+ aResponse.finish();
+ return;
+ }
+
+ let retries = 0;
+ gSlowDownloadTimer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
+ gSlowDownloadTimer.initWithCallback(
+ function (aTimer) {
+ let continueFile = getTestDataFile(CONTINUE_DOWNLOAD);
+ retries++;
+ if (continueFile.exists() || retries == MAX_SLOW_RESPONSE_RETRIES) {
+ try {
+ // If the continue file is in use try again the next time the timer
+ // fires unless the retries has reached the value defined by
+ // MAX_SLOW_RESPONSE_RETRIES in which case let the test remove the
+ // continue file.
+ if (retries < MAX_SLOW_RESPONSE_RETRIES) {
+ continueFile.remove(false);
+ }
+ gSlowDownloadTimer.cancel();
+ aResponse.write(readFileBytes(getTestDataFile(FILE_SIMPLE_MAR)));
+ aResponse.finish();
+ } catch (e) {}
+ }
+ },
+ SLOW_RESPONSE_INTERVAL,
+ Ci.nsITimer.TYPE_REPEATING_SLACK
+ );
+ return;
+ }
+
+ if (params.uiURL) {
+ aResponse.write(
+ '<html><head><meta http-equiv="content-type" content=' +
+ '"text/html; charset=utf-8"></head><body>' +
+ params.uiURL +
+ "<br><br>this is a test mar that will not " +
+ "affect your build.</body></html>"
+ );
+ return;
+ }
+
+ if (params.xmlMalformed) {
+ respond(aResponse, params, "xml error");
+ return;
+ }
+
+ if (params.noUpdates) {
+ respond(aResponse, params, getRemoteUpdatesXMLString(""));
+ return;
+ }
+
+ if (params.unsupported) {
+ let detailsURL = params.detailsURL ? params.detailsURL : URL_HOST;
+ let unsupportedXML = getRemoteUpdatesXMLString(
+ ' <update type="major" ' +
+ 'unsupported="true" ' +
+ 'detailsURL="' +
+ detailsURL +
+ '"></update>\n'
+ );
+ respond(aResponse, params, unsupportedXML);
+ return;
+ }
+
+ let size;
+ let patches = "";
+ let url = "";
+ if (params.useSlowDownloadMar) {
+ url = URL_HTTP_UPDATE_SJS + "?slowDownloadMar=1";
+ } else {
+ url = params.badURL ? BAD_SERVICE_URL : SERVICE_URL;
+ }
+ if (!params.partialPatchOnly) {
+ size = SIZE_SIMPLE_MAR + (params.invalidCompleteSize ? "1" : "");
+ let patchProps = { type: "complete", url, size };
+ patches += getRemotePatchString(patchProps);
+ }
+
+ if (!params.completePatchOnly) {
+ size = SIZE_SIMPLE_MAR + (params.invalidPartialSize ? "1" : "");
+ let patchProps = { type: "partial", url, size };
+ patches += getRemotePatchString(patchProps);
+ }
+
+ let updateProps = {};
+ if (params.type) {
+ updateProps.type = params.type;
+ }
+
+ if (params.name) {
+ updateProps.name = params.name;
+ }
+
+ if (params.appVersion) {
+ updateProps.appVersion = params.appVersion;
+ }
+
+ if (params.displayVersion) {
+ updateProps.displayVersion = params.displayVersion;
+ }
+
+ if (params.buildID) {
+ updateProps.buildID = params.buildID;
+ }
+
+ if (params.promptWaitTime) {
+ updateProps.promptWaitTime = params.promptWaitTime;
+ }
+
+ if (params.disableBITS) {
+ updateProps.disableBITS = params.disableBITS;
+ }
+
+ let updates = getRemoteUpdateString(updateProps, patches);
+ let xml = getRemoteUpdatesXMLString(updates);
+ respond(aResponse, params, xml);
+}
+
+function respond(aResponse, aParams, aResponseString) {
+ if (aParams.slowUpdateCheck) {
+ let retries = 0;
+ aResponse.processAsync();
+ gSlowCheckTimer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
+ gSlowCheckTimer.initWithCallback(
+ function (aTimer) {
+ retries++;
+ let continueFile = getTestDataFile(CONTINUE_CHECK);
+ if (continueFile.exists() || retries == MAX_SLOW_RESPONSE_RETRIES) {
+ try {
+ // If the continue file is in use try again the next time the timer
+ // fires unless the retries has reached the value defined by
+ // MAX_SLOW_RESPONSE_RETRIES in which case let the test remove the
+ // continue file.
+ if (retries < MAX_SLOW_RESPONSE_RETRIES) {
+ continueFile.remove(false);
+ }
+ gSlowCheckTimer.cancel();
+ aResponse.write(aResponseString);
+ aResponse.finish();
+ } catch (e) {}
+ }
+ },
+ SLOW_RESPONSE_INTERVAL,
+ Ci.nsITimer.TYPE_REPEATING_SLACK
+ );
+ } else {
+ aResponse.write(aResponseString);
+ }
+}
+
+/**
+ * Helper function to create a JS object representing the url parameters from
+ * the request's queryString.
+ *
+ * @param aQueryString
+ * The request's query string.
+ * @return A JS object representing the url parameters from the request's
+ * queryString.
+ */
+function parseQueryString(aQueryString) {
+ let paramArray = aQueryString.split("&");
+ let regex = /^([^=]+)=(.*)$/;
+ let params = {};
+ for (let i = 0, sz = paramArray.length; i < sz; i++) {
+ let match = regex.exec(paramArray[i]);
+ if (!match) {
+ throw Components.Exception(
+ "Bad parameter in queryString! '" + paramArray[i] + "'",
+ Cr.NS_ERROR_ILLEGAL_VALUE
+ );
+ }
+ params[decodeURIComponent(match[1])] = decodeURIComponent(match[2]);
+ }
+
+ return params;
+}
diff --git a/toolkit/mozapps/update/tests/data/complete.mar b/toolkit/mozapps/update/tests/data/complete.mar
new file mode 100644
index 0000000000..375fd7bd08
--- /dev/null
+++ b/toolkit/mozapps/update/tests/data/complete.mar
Binary files differ
diff --git a/toolkit/mozapps/update/tests/data/complete.png b/toolkit/mozapps/update/tests/data/complete.png
new file mode 100644
index 0000000000..2990a539ff
--- /dev/null
+++ b/toolkit/mozapps/update/tests/data/complete.png
Binary files differ
diff --git a/toolkit/mozapps/update/tests/data/complete_log_success_mac b/toolkit/mozapps/update/tests/data/complete_log_success_mac
new file mode 100644
index 0000000000..4f992a1374
--- /dev/null
+++ b/toolkit/mozapps/update/tests/data/complete_log_success_mac
@@ -0,0 +1,332 @@
+UPDATE TYPE complete
+PREPARE REMOVEFILE Contents/Resources/searchplugins/searchpluginstext0
+PREPARE REMOVEFILE Contents/Resources/searchplugins/searchpluginspng0.png
+PREPARE REMOVEFILE Contents/Resources/removed-files
+PREPARE REMOVEFILE Contents/Resources/precomplete
+PREPARE REMOVEFILE Contents/Resources/2/20/20text0
+PREPARE REMOVEFILE Contents/Resources/2/20/20png0.png
+PREPARE REMOVEFILE Contents/Resources/0/0exe0.exe
+PREPARE REMOVEFILE Contents/Resources/0/00/00text0
+PREPARE REMOVEFILE Contents/MacOS/exe0.exe
+PREPARE REMOVEDIR Contents/Resources/searchplugins/
+PREPARE REMOVEDIR Contents/Resources/defaults/pref/
+PREPARE REMOVEDIR Contents/Resources/defaults/
+PREPARE REMOVEDIR Contents/Resources/2/20/
+PREPARE REMOVEDIR Contents/Resources/2/
+PREPARE REMOVEDIR Contents/Resources/0/00/
+PREPARE REMOVEDIR Contents/Resources/0/
+PREPARE REMOVEDIR Contents/Resources/
+PREPARE REMOVEDIR Contents/MacOS/
+PREPARE REMOVEDIR Contents/
+PREPARE ADD Contents/Resources/searchplugins/searchpluginstext0
+PREPARE ADD Contents/Resources/searchplugins/searchpluginspng1.png
+PREPARE ADD Contents/Resources/searchplugins/searchpluginspng0.png
+PREPARE ADD Contents/Resources/removed-files
+PREPARE ADD Contents/Resources/precomplete
+PREPARE ADD Contents/Resources/distribution/extensions/extensions1/extensions1text0
+PREPARE ADD Contents/Resources/distribution/extensions/extensions1/extensions1png1.png
+PREPARE ADD Contents/Resources/distribution/extensions/extensions1/extensions1png0.png
+PREPARE ADD Contents/Resources/distribution/extensions/extensions0/extensions0text0
+PREPARE ADD Contents/Resources/distribution/extensions/extensions0/extensions0png1.png
+PREPARE ADD Contents/Resources/distribution/extensions/extensions0/extensions0png0.png
+PREPARE ADD Contents/Resources/1/10/10text0
+PREPARE ADD Contents/Resources/0/0exe0.exe
+PREPARE ADD Contents/Resources/0/00/00text1
+PREPARE ADD Contents/Resources/0/00/00text0
+PREPARE ADD Contents/Resources/0/00/00png0.png
+PREPARE ADD Contents/MacOS/exe0.exe
+PREPARE REMOVEDIR Contents/Resources/9/99/
+PREPARE REMOVEDIR Contents/Resources/9/99/
+PREPARE REMOVEDIR Contents/Resources/9/98/
+PREPARE REMOVEFILE Contents/Resources/9/97/970/97xtext0
+PREPARE REMOVEFILE Contents/Resources/9/97/970/97xtext1
+PREPARE REMOVEDIR Contents/Resources/9/97/970/
+PREPARE REMOVEFILE Contents/Resources/9/97/971/97xtext0
+PREPARE REMOVEFILE Contents/Resources/9/97/971/97xtext1
+PREPARE REMOVEDIR Contents/Resources/9/97/971/
+PREPARE REMOVEDIR Contents/Resources/9/97/
+PREPARE REMOVEFILE Contents/Resources/9/96/96text0
+PREPARE REMOVEFILE Contents/Resources/9/96/96text1
+PREPARE REMOVEDIR Contents/Resources/9/96/
+PREPARE REMOVEDIR Contents/Resources/9/95/
+PREPARE REMOVEDIR Contents/Resources/9/95/
+PREPARE REMOVEDIR Contents/Resources/9/94/
+PREPARE REMOVEDIR Contents/Resources/9/94/
+PREPARE REMOVEDIR Contents/Resources/9/93/
+PREPARE REMOVEDIR Contents/Resources/9/92/
+PREPARE REMOVEDIR Contents/Resources/9/91/
+PREPARE REMOVEDIR Contents/Resources/9/90/
+PREPARE REMOVEDIR Contents/Resources/9/90/
+PREPARE REMOVEDIR Contents/Resources/8/89/
+PREPARE REMOVEDIR Contents/Resources/8/89/
+PREPARE REMOVEDIR Contents/Resources/8/88/
+PREPARE REMOVEFILE Contents/Resources/8/87/870/87xtext0
+PREPARE REMOVEFILE Contents/Resources/8/87/870/87xtext1
+PREPARE REMOVEDIR Contents/Resources/8/87/870/
+PREPARE REMOVEFILE Contents/Resources/8/87/871/87xtext0
+PREPARE REMOVEFILE Contents/Resources/8/87/871/87xtext1
+PREPARE REMOVEDIR Contents/Resources/8/87/871/
+PREPARE REMOVEDIR Contents/Resources/8/87/
+PREPARE REMOVEFILE Contents/Resources/8/86/86text0
+PREPARE REMOVEFILE Contents/Resources/8/86/86text1
+PREPARE REMOVEDIR Contents/Resources/8/86/
+PREPARE REMOVEDIR Contents/Resources/8/85/
+PREPARE REMOVEDIR Contents/Resources/8/85/
+PREPARE REMOVEDIR Contents/Resources/8/84/
+PREPARE REMOVEDIR Contents/Resources/8/84/
+PREPARE REMOVEDIR Contents/Resources/8/83/
+PREPARE REMOVEDIR Contents/Resources/8/82/
+PREPARE REMOVEDIR Contents/Resources/8/81/
+PREPARE REMOVEDIR Contents/Resources/8/80/
+PREPARE REMOVEDIR Contents/Resources/8/80/
+PREPARE REMOVEFILE Contents/Resources/7/70/7xtest.exe
+PREPARE REMOVEFILE Contents/Resources/7/70/7xtext0
+PREPARE REMOVEFILE Contents/Resources/7/70/7xtext1
+PREPARE REMOVEDIR Contents/Resources/7/70/
+PREPARE REMOVEFILE Contents/Resources/7/71/7xtest.exe
+PREPARE REMOVEFILE Contents/Resources/7/71/7xtext0
+PREPARE REMOVEFILE Contents/Resources/7/71/7xtext1
+PREPARE REMOVEDIR Contents/Resources/7/71/
+PREPARE REMOVEFILE Contents/Resources/7/7text0
+PREPARE REMOVEFILE Contents/Resources/7/7text1
+PREPARE REMOVEDIR Contents/Resources/7/
+PREPARE REMOVEDIR Contents/Resources/6/
+PREPARE REMOVEFILE Contents/Resources/5/5text1
+PREPARE REMOVEFILE Contents/Resources/5/5text0
+PREPARE REMOVEFILE Contents/Resources/5/5test.exe
+PREPARE REMOVEFILE Contents/Resources/5/5text0
+PREPARE REMOVEFILE Contents/Resources/5/5text1
+PREPARE REMOVEDIR Contents/Resources/5/
+PREPARE REMOVEFILE Contents/Resources/4/4text1
+PREPARE REMOVEFILE Contents/Resources/4/4text0
+PREPARE REMOVEDIR Contents/Resources/4/
+PREPARE REMOVEFILE Contents/Resources/3/3text1
+PREPARE REMOVEFILE Contents/Resources/3/3text0
+EXECUTE REMOVEFILE Contents/Resources/searchplugins/searchpluginstext0
+EXECUTE REMOVEFILE Contents/Resources/searchplugins/searchpluginspng0.png
+EXECUTE REMOVEFILE Contents/Resources/removed-files
+EXECUTE REMOVEFILE Contents/Resources/precomplete
+EXECUTE REMOVEFILE Contents/Resources/2/20/20text0
+EXECUTE REMOVEFILE Contents/Resources/2/20/20png0.png
+EXECUTE REMOVEFILE Contents/Resources/0/0exe0.exe
+EXECUTE REMOVEFILE Contents/Resources/0/00/00text0
+EXECUTE REMOVEFILE Contents/MacOS/exe0.exe
+EXECUTE REMOVEDIR Contents/Resources/searchplugins/
+EXECUTE REMOVEDIR Contents/Resources/defaults/pref/
+EXECUTE REMOVEDIR Contents/Resources/defaults/
+EXECUTE REMOVEDIR Contents/Resources/2/20/
+EXECUTE REMOVEDIR Contents/Resources/2/
+EXECUTE REMOVEDIR Contents/Resources/0/00/
+EXECUTE REMOVEDIR Contents/Resources/0/
+EXECUTE REMOVEDIR Contents/Resources/
+EXECUTE REMOVEDIR Contents/MacOS/
+EXECUTE REMOVEDIR Contents/
+EXECUTE ADD Contents/Resources/searchplugins/searchpluginstext0
+EXECUTE ADD Contents/Resources/searchplugins/searchpluginspng1.png
+EXECUTE ADD Contents/Resources/searchplugins/searchpluginspng0.png
+EXECUTE ADD Contents/Resources/removed-files
+EXECUTE ADD Contents/Resources/precomplete
+EXECUTE ADD Contents/Resources/distribution/extensions/extensions1/extensions1text0
+EXECUTE ADD Contents/Resources/distribution/extensions/extensions1/extensions1png1.png
+EXECUTE ADD Contents/Resources/distribution/extensions/extensions1/extensions1png0.png
+EXECUTE ADD Contents/Resources/distribution/extensions/extensions0/extensions0text0
+EXECUTE ADD Contents/Resources/distribution/extensions/extensions0/extensions0png1.png
+EXECUTE ADD Contents/Resources/distribution/extensions/extensions0/extensions0png0.png
+EXECUTE ADD Contents/Resources/1/10/10text0
+EXECUTE ADD Contents/Resources/0/0exe0.exe
+EXECUTE ADD Contents/Resources/0/00/00text1
+EXECUTE ADD Contents/Resources/0/00/00text0
+EXECUTE ADD Contents/Resources/0/00/00png0.png
+EXECUTE ADD Contents/MacOS/exe0.exe
+EXECUTE REMOVEDIR Contents/Resources/9/99/
+EXECUTE REMOVEDIR Contents/Resources/9/99/
+EXECUTE REMOVEDIR Contents/Resources/9/98/
+EXECUTE REMOVEFILE Contents/Resources/9/97/970/97xtext0
+EXECUTE REMOVEFILE Contents/Resources/9/97/970/97xtext1
+EXECUTE REMOVEDIR Contents/Resources/9/97/970/
+EXECUTE REMOVEFILE Contents/Resources/9/97/971/97xtext0
+EXECUTE REMOVEFILE Contents/Resources/9/97/971/97xtext1
+EXECUTE REMOVEDIR Contents/Resources/9/97/971/
+EXECUTE REMOVEDIR Contents/Resources/9/97/
+EXECUTE REMOVEFILE Contents/Resources/9/96/96text0
+EXECUTE REMOVEFILE Contents/Resources/9/96/96text1
+EXECUTE REMOVEDIR Contents/Resources/9/96/
+EXECUTE REMOVEDIR Contents/Resources/9/95/
+EXECUTE REMOVEDIR Contents/Resources/9/95/
+EXECUTE REMOVEDIR Contents/Resources/9/94/
+EXECUTE REMOVEDIR Contents/Resources/9/94/
+EXECUTE REMOVEDIR Contents/Resources/9/93/
+EXECUTE REMOVEDIR Contents/Resources/9/92/
+EXECUTE REMOVEDIR Contents/Resources/9/91/
+EXECUTE REMOVEDIR Contents/Resources/9/90/
+EXECUTE REMOVEDIR Contents/Resources/9/90/
+EXECUTE REMOVEDIR Contents/Resources/8/89/
+EXECUTE REMOVEDIR Contents/Resources/8/89/
+EXECUTE REMOVEDIR Contents/Resources/8/88/
+EXECUTE REMOVEFILE Contents/Resources/8/87/870/87xtext0
+EXECUTE REMOVEFILE Contents/Resources/8/87/870/87xtext1
+EXECUTE REMOVEDIR Contents/Resources/8/87/870/
+EXECUTE REMOVEFILE Contents/Resources/8/87/871/87xtext0
+EXECUTE REMOVEFILE Contents/Resources/8/87/871/87xtext1
+EXECUTE REMOVEDIR Contents/Resources/8/87/871/
+EXECUTE REMOVEDIR Contents/Resources/8/87/
+EXECUTE REMOVEFILE Contents/Resources/8/86/86text0
+EXECUTE REMOVEFILE Contents/Resources/8/86/86text1
+EXECUTE REMOVEDIR Contents/Resources/8/86/
+EXECUTE REMOVEDIR Contents/Resources/8/85/
+EXECUTE REMOVEDIR Contents/Resources/8/85/
+EXECUTE REMOVEDIR Contents/Resources/8/84/
+EXECUTE REMOVEDIR Contents/Resources/8/84/
+EXECUTE REMOVEDIR Contents/Resources/8/83/
+EXECUTE REMOVEDIR Contents/Resources/8/82/
+EXECUTE REMOVEDIR Contents/Resources/8/81/
+EXECUTE REMOVEDIR Contents/Resources/8/80/
+EXECUTE REMOVEDIR Contents/Resources/8/80/
+EXECUTE REMOVEFILE Contents/Resources/7/70/7xtest.exe
+EXECUTE REMOVEFILE Contents/Resources/7/70/7xtext0
+EXECUTE REMOVEFILE Contents/Resources/7/70/7xtext1
+EXECUTE REMOVEDIR Contents/Resources/7/70/
+EXECUTE REMOVEFILE Contents/Resources/7/71/7xtest.exe
+EXECUTE REMOVEFILE Contents/Resources/7/71/7xtext0
+EXECUTE REMOVEFILE Contents/Resources/7/71/7xtext1
+EXECUTE REMOVEDIR Contents/Resources/7/71/
+EXECUTE REMOVEFILE Contents/Resources/7/7text0
+EXECUTE REMOVEFILE Contents/Resources/7/7text1
+EXECUTE REMOVEDIR Contents/Resources/7/
+EXECUTE REMOVEDIR Contents/Resources/6/
+EXECUTE REMOVEFILE Contents/Resources/5/5text1
+EXECUTE REMOVEFILE Contents/Resources/5/5text0
+EXECUTE REMOVEFILE Contents/Resources/5/5test.exe
+EXECUTE REMOVEFILE Contents/Resources/5/5text0
+file cannot be removed because it does not exist; skipping
+EXECUTE REMOVEFILE Contents/Resources/5/5text1
+file cannot be removed because it does not exist; skipping
+EXECUTE REMOVEDIR Contents/Resources/5/
+EXECUTE REMOVEFILE Contents/Resources/4/4text1
+EXECUTE REMOVEFILE Contents/Resources/4/4text0
+EXECUTE REMOVEDIR Contents/Resources/4/
+EXECUTE REMOVEFILE Contents/Resources/3/3text1
+EXECUTE REMOVEFILE Contents/Resources/3/3text0
+FINISH REMOVEFILE Contents/Resources/searchplugins/searchpluginstext0
+FINISH REMOVEFILE Contents/Resources/searchplugins/searchpluginspng0.png
+FINISH REMOVEFILE Contents/Resources/removed-files
+FINISH REMOVEFILE Contents/Resources/precomplete
+FINISH REMOVEFILE Contents/Resources/2/20/20text0
+FINISH REMOVEFILE Contents/Resources/2/20/20png0.png
+FINISH REMOVEFILE Contents/Resources/0/0exe0.exe
+FINISH REMOVEFILE Contents/Resources/0/00/00text0
+FINISH REMOVEFILE Contents/MacOS/exe0.exe
+FINISH REMOVEDIR Contents/Resources/searchplugins/
+removing directory: Contents/Resources/searchplugins/, rv: 0
+FINISH REMOVEDIR Contents/Resources/defaults/pref/
+removing directory: Contents/Resources/defaults/pref/, rv: 0
+FINISH REMOVEDIR Contents/Resources/defaults/
+removing directory: Contents/Resources/defaults/, rv: 0
+FINISH REMOVEDIR Contents/Resources/2/20/
+FINISH REMOVEDIR Contents/Resources/2/
+FINISH REMOVEDIR Contents/Resources/0/00/
+removing directory: Contents/Resources/0/00/, rv: 0
+FINISH REMOVEDIR Contents/Resources/0/
+removing directory: Contents/Resources/0/, rv: 0
+FINISH REMOVEDIR Contents/Resources/
+removing directory: Contents/Resources/, rv: 0
+FINISH REMOVEDIR Contents/MacOS/
+removing directory: Contents/MacOS/, rv: 0
+FINISH REMOVEDIR Contents/
+removing directory: Contents/, rv: 0
+FINISH ADD Contents/Resources/searchplugins/searchpluginstext0
+FINISH ADD Contents/Resources/searchplugins/searchpluginspng1.png
+FINISH ADD Contents/Resources/searchplugins/searchpluginspng0.png
+FINISH ADD Contents/Resources/removed-files
+FINISH ADD Contents/Resources/precomplete
+FINISH ADD Contents/Resources/distribution/extensions/extensions1/extensions1text0
+FINISH ADD Contents/Resources/distribution/extensions/extensions1/extensions1png1.png
+FINISH ADD Contents/Resources/distribution/extensions/extensions1/extensions1png0.png
+FINISH ADD Contents/Resources/distribution/extensions/extensions0/extensions0text0
+FINISH ADD Contents/Resources/distribution/extensions/extensions0/extensions0png1.png
+FINISH ADD Contents/Resources/distribution/extensions/extensions0/extensions0png0.png
+FINISH ADD Contents/Resources/1/10/10text0
+FINISH ADD Contents/Resources/0/0exe0.exe
+FINISH ADD Contents/Resources/0/00/00text1
+FINISH ADD Contents/Resources/0/00/00text0
+FINISH ADD Contents/Resources/0/00/00png0.png
+FINISH ADD Contents/MacOS/exe0.exe
+FINISH REMOVEDIR Contents/Resources/9/99/
+FINISH REMOVEDIR Contents/Resources/9/99/
+directory no longer exists; skipping
+FINISH REMOVEDIR Contents/Resources/9/98/
+FINISH REMOVEFILE Contents/Resources/9/97/970/97xtext0
+FINISH REMOVEFILE Contents/Resources/9/97/970/97xtext1
+FINISH REMOVEDIR Contents/Resources/9/97/970/
+FINISH REMOVEFILE Contents/Resources/9/97/971/97xtext0
+FINISH REMOVEFILE Contents/Resources/9/97/971/97xtext1
+FINISH REMOVEDIR Contents/Resources/9/97/971/
+FINISH REMOVEDIR Contents/Resources/9/97/
+FINISH REMOVEFILE Contents/Resources/9/96/96text0
+FINISH REMOVEFILE Contents/Resources/9/96/96text1
+FINISH REMOVEDIR Contents/Resources/9/96/
+FINISH REMOVEDIR Contents/Resources/9/95/
+FINISH REMOVEDIR Contents/Resources/9/95/
+directory no longer exists; skipping
+FINISH REMOVEDIR Contents/Resources/9/94/
+FINISH REMOVEDIR Contents/Resources/9/94/
+directory no longer exists; skipping
+FINISH REMOVEDIR Contents/Resources/9/93/
+FINISH REMOVEDIR Contents/Resources/9/92/
+removing directory: Contents/Resources/9/92/, rv: 0
+FINISH REMOVEDIR Contents/Resources/9/91/
+removing directory: Contents/Resources/9/91/, rv: 0
+FINISH REMOVEDIR Contents/Resources/9/90/
+FINISH REMOVEDIR Contents/Resources/9/90/
+directory no longer exists; skipping
+FINISH REMOVEDIR Contents/Resources/8/89/
+FINISH REMOVEDIR Contents/Resources/8/89/
+directory no longer exists; skipping
+FINISH REMOVEDIR Contents/Resources/8/88/
+FINISH REMOVEFILE Contents/Resources/8/87/870/87xtext0
+FINISH REMOVEFILE Contents/Resources/8/87/870/87xtext1
+FINISH REMOVEDIR Contents/Resources/8/87/870/
+FINISH REMOVEFILE Contents/Resources/8/87/871/87xtext0
+FINISH REMOVEFILE Contents/Resources/8/87/871/87xtext1
+FINISH REMOVEDIR Contents/Resources/8/87/871/
+FINISH REMOVEDIR Contents/Resources/8/87/
+FINISH REMOVEFILE Contents/Resources/8/86/86text0
+FINISH REMOVEFILE Contents/Resources/8/86/86text1
+FINISH REMOVEDIR Contents/Resources/8/86/
+FINISH REMOVEDIR Contents/Resources/8/85/
+FINISH REMOVEDIR Contents/Resources/8/85/
+directory no longer exists; skipping
+FINISH REMOVEDIR Contents/Resources/8/84/
+FINISH REMOVEDIR Contents/Resources/8/84/
+directory no longer exists; skipping
+FINISH REMOVEDIR Contents/Resources/8/83/
+FINISH REMOVEDIR Contents/Resources/8/82/
+removing directory: Contents/Resources/8/82/, rv: 0
+FINISH REMOVEDIR Contents/Resources/8/81/
+removing directory: Contents/Resources/8/81/, rv: 0
+FINISH REMOVEDIR Contents/Resources/8/80/
+FINISH REMOVEDIR Contents/Resources/8/80/
+directory no longer exists; skipping
+FINISH REMOVEFILE Contents/Resources/7/70/7xtest.exe
+FINISH REMOVEFILE Contents/Resources/7/70/7xtext0
+FINISH REMOVEFILE Contents/Resources/7/70/7xtext1
+FINISH REMOVEDIR Contents/Resources/7/70/
+FINISH REMOVEFILE Contents/Resources/7/71/7xtest.exe
+FINISH REMOVEFILE Contents/Resources/7/71/7xtext0
+FINISH REMOVEFILE Contents/Resources/7/71/7xtext1
+FINISH REMOVEDIR Contents/Resources/7/71/
+FINISH REMOVEFILE Contents/Resources/7/7text0
+FINISH REMOVEFILE Contents/Resources/7/7text1
+FINISH REMOVEDIR Contents/Resources/7/
+FINISH REMOVEDIR Contents/Resources/6/
+FINISH REMOVEFILE Contents/Resources/5/5text1
+FINISH REMOVEFILE Contents/Resources/5/5text0
+FINISH REMOVEFILE Contents/Resources/5/5test.exe
+FINISH REMOVEDIR Contents/Resources/5/
+FINISH REMOVEFILE Contents/Resources/4/4text1
+FINISH REMOVEFILE Contents/Resources/4/4text0
+FINISH REMOVEDIR Contents/Resources/4/
+FINISH REMOVEFILE Contents/Resources/3/3text1
+FINISH REMOVEFILE Contents/Resources/3/3text0
+succeeded
+calling QuitProgressUI
diff --git a/toolkit/mozapps/update/tests/data/complete_log_success_win b/toolkit/mozapps/update/tests/data/complete_log_success_win
new file mode 100644
index 0000000000..c5a03dc9d6
--- /dev/null
+++ b/toolkit/mozapps/update/tests/data/complete_log_success_win
@@ -0,0 +1,320 @@
+UPDATE TYPE complete
+PREPARE REMOVEFILE searchplugins/searchpluginstext0
+PREPARE REMOVEFILE searchplugins/searchpluginspng0.png
+PREPARE REMOVEFILE removed-files
+PREPARE REMOVEFILE precomplete
+PREPARE REMOVEFILE exe0.exe
+PREPARE REMOVEFILE 2/20/20text0
+PREPARE REMOVEFILE 2/20/20png0.png
+PREPARE REMOVEFILE 0/0exe0.exe
+PREPARE REMOVEFILE 0/00/00text0
+PREPARE REMOVEDIR searchplugins/
+PREPARE REMOVEDIR defaults/pref/
+PREPARE REMOVEDIR defaults/
+PREPARE REMOVEDIR 2/20/
+PREPARE REMOVEDIR 2/
+PREPARE REMOVEDIR 0/00/
+PREPARE REMOVEDIR 0/
+PREPARE ADD searchplugins/searchpluginstext0
+PREPARE ADD searchplugins/searchpluginspng1.png
+PREPARE ADD searchplugins/searchpluginspng0.png
+PREPARE ADD removed-files
+PREPARE ADD precomplete
+PREPARE ADD exe0.exe
+PREPARE ADD distribution/extensions/extensions1/extensions1text0
+PREPARE ADD distribution/extensions/extensions1/extensions1png1.png
+PREPARE ADD distribution/extensions/extensions1/extensions1png0.png
+PREPARE ADD distribution/extensions/extensions0/extensions0text0
+PREPARE ADD distribution/extensions/extensions0/extensions0png1.png
+PREPARE ADD distribution/extensions/extensions0/extensions0png0.png
+PREPARE ADD 1/10/10text0
+PREPARE ADD 0/0exe0.exe
+PREPARE ADD 0/00/00text1
+PREPARE ADD 0/00/00text0
+PREPARE ADD 0/00/00png0.png
+PREPARE REMOVEDIR 9/99/
+PREPARE REMOVEDIR 9/99/
+PREPARE REMOVEDIR 9/98/
+PREPARE REMOVEFILE 9/97/970/97xtext0
+PREPARE REMOVEFILE 9/97/970/97xtext1
+PREPARE REMOVEDIR 9/97/970/
+PREPARE REMOVEFILE 9/97/971/97xtext0
+PREPARE REMOVEFILE 9/97/971/97xtext1
+PREPARE REMOVEDIR 9/97/971/
+PREPARE REMOVEDIR 9/97/
+PREPARE REMOVEFILE 9/96/96text0
+PREPARE REMOVEFILE 9/96/96text1
+PREPARE REMOVEDIR 9/96/
+PREPARE REMOVEDIR 9/95/
+PREPARE REMOVEDIR 9/95/
+PREPARE REMOVEDIR 9/94/
+PREPARE REMOVEDIR 9/94/
+PREPARE REMOVEDIR 9/93/
+PREPARE REMOVEDIR 9/92/
+PREPARE REMOVEDIR 9/91/
+PREPARE REMOVEDIR 9/90/
+PREPARE REMOVEDIR 9/90/
+PREPARE REMOVEDIR 8/89/
+PREPARE REMOVEDIR 8/89/
+PREPARE REMOVEDIR 8/88/
+PREPARE REMOVEFILE 8/87/870/87xtext0
+PREPARE REMOVEFILE 8/87/870/87xtext1
+PREPARE REMOVEDIR 8/87/870/
+PREPARE REMOVEFILE 8/87/871/87xtext0
+PREPARE REMOVEFILE 8/87/871/87xtext1
+PREPARE REMOVEDIR 8/87/871/
+PREPARE REMOVEDIR 8/87/
+PREPARE REMOVEFILE 8/86/86text0
+PREPARE REMOVEFILE 8/86/86text1
+PREPARE REMOVEDIR 8/86/
+PREPARE REMOVEDIR 8/85/
+PREPARE REMOVEDIR 8/85/
+PREPARE REMOVEDIR 8/84/
+PREPARE REMOVEDIR 8/84/
+PREPARE REMOVEDIR 8/83/
+PREPARE REMOVEDIR 8/82/
+PREPARE REMOVEDIR 8/81/
+PREPARE REMOVEDIR 8/80/
+PREPARE REMOVEDIR 8/80/
+PREPARE REMOVEFILE 7/70/7xtest.exe
+PREPARE REMOVEFILE 7/70/7xtext0
+PREPARE REMOVEFILE 7/70/7xtext1
+PREPARE REMOVEDIR 7/70/
+PREPARE REMOVEFILE 7/71/7xtest.exe
+PREPARE REMOVEFILE 7/71/7xtext0
+PREPARE REMOVEFILE 7/71/7xtext1
+PREPARE REMOVEDIR 7/71/
+PREPARE REMOVEFILE 7/7text0
+PREPARE REMOVEFILE 7/7text1
+PREPARE REMOVEDIR 7/
+PREPARE REMOVEDIR 6/
+PREPARE REMOVEFILE 5/5text1
+PREPARE REMOVEFILE 5/5text0
+PREPARE REMOVEFILE 5/5test.exe
+PREPARE REMOVEFILE 5/5text0
+PREPARE REMOVEFILE 5/5text1
+PREPARE REMOVEDIR 5/
+PREPARE REMOVEFILE 4/4text1
+PREPARE REMOVEFILE 4/4text0
+PREPARE REMOVEDIR 4/
+PREPARE REMOVEFILE 3/3text1
+PREPARE REMOVEFILE 3/3text0
+EXECUTE REMOVEFILE searchplugins/searchpluginstext0
+EXECUTE REMOVEFILE searchplugins/searchpluginspng0.png
+EXECUTE REMOVEFILE removed-files
+EXECUTE REMOVEFILE precomplete
+EXECUTE REMOVEFILE exe0.exe
+EXECUTE REMOVEFILE 2/20/20text0
+EXECUTE REMOVEFILE 2/20/20png0.png
+EXECUTE REMOVEFILE 0/0exe0.exe
+EXECUTE REMOVEFILE 0/00/00text0
+EXECUTE REMOVEDIR searchplugins/
+EXECUTE REMOVEDIR defaults/pref/
+EXECUTE REMOVEDIR defaults/
+EXECUTE REMOVEDIR 2/20/
+EXECUTE REMOVEDIR 2/
+EXECUTE REMOVEDIR 0/00/
+EXECUTE REMOVEDIR 0/
+EXECUTE ADD searchplugins/searchpluginstext0
+EXECUTE ADD searchplugins/searchpluginspng1.png
+EXECUTE ADD searchplugins/searchpluginspng0.png
+EXECUTE ADD removed-files
+EXECUTE ADD precomplete
+EXECUTE ADD exe0.exe
+EXECUTE ADD distribution/extensions/extensions1/extensions1text0
+EXECUTE ADD distribution/extensions/extensions1/extensions1png1.png
+EXECUTE ADD distribution/extensions/extensions1/extensions1png0.png
+EXECUTE ADD distribution/extensions/extensions0/extensions0text0
+EXECUTE ADD distribution/extensions/extensions0/extensions0png1.png
+EXECUTE ADD distribution/extensions/extensions0/extensions0png0.png
+EXECUTE ADD 1/10/10text0
+EXECUTE ADD 0/0exe0.exe
+EXECUTE ADD 0/00/00text1
+EXECUTE ADD 0/00/00text0
+EXECUTE ADD 0/00/00png0.png
+EXECUTE REMOVEDIR 9/99/
+EXECUTE REMOVEDIR 9/99/
+EXECUTE REMOVEDIR 9/98/
+EXECUTE REMOVEFILE 9/97/970/97xtext0
+EXECUTE REMOVEFILE 9/97/970/97xtext1
+EXECUTE REMOVEDIR 9/97/970/
+EXECUTE REMOVEFILE 9/97/971/97xtext0
+EXECUTE REMOVEFILE 9/97/971/97xtext1
+EXECUTE REMOVEDIR 9/97/971/
+EXECUTE REMOVEDIR 9/97/
+EXECUTE REMOVEFILE 9/96/96text0
+EXECUTE REMOVEFILE 9/96/96text1
+EXECUTE REMOVEDIR 9/96/
+EXECUTE REMOVEDIR 9/95/
+EXECUTE REMOVEDIR 9/95/
+EXECUTE REMOVEDIR 9/94/
+EXECUTE REMOVEDIR 9/94/
+EXECUTE REMOVEDIR 9/93/
+EXECUTE REMOVEDIR 9/92/
+EXECUTE REMOVEDIR 9/91/
+EXECUTE REMOVEDIR 9/90/
+EXECUTE REMOVEDIR 9/90/
+EXECUTE REMOVEDIR 8/89/
+EXECUTE REMOVEDIR 8/89/
+EXECUTE REMOVEDIR 8/88/
+EXECUTE REMOVEFILE 8/87/870/87xtext0
+EXECUTE REMOVEFILE 8/87/870/87xtext1
+EXECUTE REMOVEDIR 8/87/870/
+EXECUTE REMOVEFILE 8/87/871/87xtext0
+EXECUTE REMOVEFILE 8/87/871/87xtext1
+EXECUTE REMOVEDIR 8/87/871/
+EXECUTE REMOVEDIR 8/87/
+EXECUTE REMOVEFILE 8/86/86text0
+EXECUTE REMOVEFILE 8/86/86text1
+EXECUTE REMOVEDIR 8/86/
+EXECUTE REMOVEDIR 8/85/
+EXECUTE REMOVEDIR 8/85/
+EXECUTE REMOVEDIR 8/84/
+EXECUTE REMOVEDIR 8/84/
+EXECUTE REMOVEDIR 8/83/
+EXECUTE REMOVEDIR 8/82/
+EXECUTE REMOVEDIR 8/81/
+EXECUTE REMOVEDIR 8/80/
+EXECUTE REMOVEDIR 8/80/
+EXECUTE REMOVEFILE 7/70/7xtest.exe
+EXECUTE REMOVEFILE 7/70/7xtext0
+EXECUTE REMOVEFILE 7/70/7xtext1
+EXECUTE REMOVEDIR 7/70/
+EXECUTE REMOVEFILE 7/71/7xtest.exe
+EXECUTE REMOVEFILE 7/71/7xtext0
+EXECUTE REMOVEFILE 7/71/7xtext1
+EXECUTE REMOVEDIR 7/71/
+EXECUTE REMOVEFILE 7/7text0
+EXECUTE REMOVEFILE 7/7text1
+EXECUTE REMOVEDIR 7/
+EXECUTE REMOVEDIR 6/
+EXECUTE REMOVEFILE 5/5text1
+EXECUTE REMOVEFILE 5/5text0
+EXECUTE REMOVEFILE 5/5test.exe
+EXECUTE REMOVEFILE 5/5text0
+file cannot be removed because it does not exist; skipping
+EXECUTE REMOVEFILE 5/5text1
+file cannot be removed because it does not exist; skipping
+EXECUTE REMOVEDIR 5/
+EXECUTE REMOVEFILE 4/4text1
+EXECUTE REMOVEFILE 4/4text0
+EXECUTE REMOVEDIR 4/
+EXECUTE REMOVEFILE 3/3text1
+EXECUTE REMOVEFILE 3/3text0
+FINISH REMOVEFILE searchplugins/searchpluginstext0
+FINISH REMOVEFILE searchplugins/searchpluginspng0.png
+FINISH REMOVEFILE removed-files
+FINISH REMOVEFILE precomplete
+FINISH REMOVEFILE exe0.exe
+FINISH REMOVEFILE 2/20/20text0
+FINISH REMOVEFILE 2/20/20png0.png
+FINISH REMOVEFILE 0/0exe0.exe
+FINISH REMOVEFILE 0/00/00text0
+FINISH REMOVEDIR searchplugins/
+removing directory: searchplugins/, rv: 0
+FINISH REMOVEDIR defaults/pref/
+removing directory: defaults/pref/, rv: 0
+FINISH REMOVEDIR defaults/
+removing directory: defaults/, rv: 0
+FINISH REMOVEDIR 2/20/
+FINISH REMOVEDIR 2/
+FINISH REMOVEDIR 0/00/
+removing directory: 0/00/, rv: 0
+FINISH REMOVEDIR 0/
+removing directory: 0/, rv: 0
+FINISH ADD searchplugins/searchpluginstext0
+FINISH ADD searchplugins/searchpluginspng1.png
+FINISH ADD searchplugins/searchpluginspng0.png
+FINISH ADD removed-files
+FINISH ADD precomplete
+FINISH ADD exe0.exe
+FINISH ADD distribution/extensions/extensions1/extensions1text0
+FINISH ADD distribution/extensions/extensions1/extensions1png1.png
+FINISH ADD distribution/extensions/extensions1/extensions1png0.png
+FINISH ADD distribution/extensions/extensions0/extensions0text0
+FINISH ADD distribution/extensions/extensions0/extensions0png1.png
+FINISH ADD distribution/extensions/extensions0/extensions0png0.png
+FINISH ADD 1/10/10text0
+FINISH ADD 0/0exe0.exe
+FINISH ADD 0/00/00text1
+FINISH ADD 0/00/00text0
+FINISH ADD 0/00/00png0.png
+FINISH REMOVEDIR 9/99/
+FINISH REMOVEDIR 9/99/
+directory no longer exists; skipping
+FINISH REMOVEDIR 9/98/
+FINISH REMOVEFILE 9/97/970/97xtext0
+FINISH REMOVEFILE 9/97/970/97xtext1
+FINISH REMOVEDIR 9/97/970/
+FINISH REMOVEFILE 9/97/971/97xtext0
+FINISH REMOVEFILE 9/97/971/97xtext1
+FINISH REMOVEDIR 9/97/971/
+FINISH REMOVEDIR 9/97/
+FINISH REMOVEFILE 9/96/96text0
+FINISH REMOVEFILE 9/96/96text1
+FINISH REMOVEDIR 9/96/
+FINISH REMOVEDIR 9/95/
+FINISH REMOVEDIR 9/95/
+directory no longer exists; skipping
+FINISH REMOVEDIR 9/94/
+FINISH REMOVEDIR 9/94/
+directory no longer exists; skipping
+FINISH REMOVEDIR 9/93/
+FINISH REMOVEDIR 9/92/
+removing directory: 9/92/, rv: 0
+FINISH REMOVEDIR 9/91/
+removing directory: 9/91/, rv: 0
+FINISH REMOVEDIR 9/90/
+FINISH REMOVEDIR 9/90/
+directory no longer exists; skipping
+FINISH REMOVEDIR 8/89/
+FINISH REMOVEDIR 8/89/
+directory no longer exists; skipping
+FINISH REMOVEDIR 8/88/
+FINISH REMOVEFILE 8/87/870/87xtext0
+FINISH REMOVEFILE 8/87/870/87xtext1
+FINISH REMOVEDIR 8/87/870/
+FINISH REMOVEFILE 8/87/871/87xtext0
+FINISH REMOVEFILE 8/87/871/87xtext1
+FINISH REMOVEDIR 8/87/871/
+FINISH REMOVEDIR 8/87/
+FINISH REMOVEFILE 8/86/86text0
+FINISH REMOVEFILE 8/86/86text1
+FINISH REMOVEDIR 8/86/
+FINISH REMOVEDIR 8/85/
+FINISH REMOVEDIR 8/85/
+directory no longer exists; skipping
+FINISH REMOVEDIR 8/84/
+FINISH REMOVEDIR 8/84/
+directory no longer exists; skipping
+FINISH REMOVEDIR 8/83/
+FINISH REMOVEDIR 8/82/
+removing directory: 8/82/, rv: 0
+FINISH REMOVEDIR 8/81/
+removing directory: 8/81/, rv: 0
+FINISH REMOVEDIR 8/80/
+FINISH REMOVEDIR 8/80/
+directory no longer exists; skipping
+FINISH REMOVEFILE 7/70/7xtest.exe
+FINISH REMOVEFILE 7/70/7xtext0
+FINISH REMOVEFILE 7/70/7xtext1
+FINISH REMOVEDIR 7/70/
+FINISH REMOVEFILE 7/71/7xtest.exe
+FINISH REMOVEFILE 7/71/7xtext0
+FINISH REMOVEFILE 7/71/7xtext1
+FINISH REMOVEDIR 7/71/
+FINISH REMOVEFILE 7/7text0
+FINISH REMOVEFILE 7/7text1
+FINISH REMOVEDIR 7/
+FINISH REMOVEDIR 6/
+FINISH REMOVEFILE 5/5text1
+FINISH REMOVEFILE 5/5text0
+FINISH REMOVEFILE 5/5test.exe
+FINISH REMOVEDIR 5/
+FINISH REMOVEFILE 4/4text1
+FINISH REMOVEFILE 4/4text0
+FINISH REMOVEDIR 4/
+FINISH REMOVEFILE 3/3text1
+FINISH REMOVEFILE 3/3text0
+succeeded
+calling QuitProgressUI
diff --git a/toolkit/mozapps/update/tests/data/complete_mac.mar b/toolkit/mozapps/update/tests/data/complete_mac.mar
new file mode 100644
index 0000000000..c54088610a
--- /dev/null
+++ b/toolkit/mozapps/update/tests/data/complete_mac.mar
Binary files differ
diff --git a/toolkit/mozapps/update/tests/data/complete_precomplete b/toolkit/mozapps/update/tests/data/complete_precomplete
new file mode 100644
index 0000000000..ae7a0013ff
--- /dev/null
+++ b/toolkit/mozapps/update/tests/data/complete_precomplete
@@ -0,0 +1,18 @@
+remove "searchplugins/searchpluginstext0"
+remove "searchplugins/searchpluginspng1.png"
+remove "searchplugins/searchpluginspng0.png"
+remove "removed-files"
+remove "precomplete"
+remove "exe0.exe"
+remove "1/10/10text0"
+remove "0/0exe0.exe"
+remove "0/00/00text1"
+remove "0/00/00text0"
+remove "0/00/00png0.png"
+rmdir "searchplugins/"
+rmdir "defaults/pref/"
+rmdir "defaults/"
+rmdir "1/10/"
+rmdir "1/"
+rmdir "0/00/"
+rmdir "0/"
diff --git a/toolkit/mozapps/update/tests/data/complete_precomplete_mac b/toolkit/mozapps/update/tests/data/complete_precomplete_mac
new file mode 100644
index 0000000000..8d81a36d66
--- /dev/null
+++ b/toolkit/mozapps/update/tests/data/complete_precomplete_mac
@@ -0,0 +1,21 @@
+remove "Contents/Resources/searchplugins/searchpluginstext0"
+remove "Contents/Resources/searchplugins/searchpluginspng1.png"
+remove "Contents/Resources/searchplugins/searchpluginspng0.png"
+remove "Contents/Resources/removed-files"
+remove "Contents/Resources/precomplete"
+remove "Contents/Resources/1/10/10text0"
+remove "Contents/Resources/0/0exe0.exe"
+remove "Contents/Resources/0/00/00text1"
+remove "Contents/Resources/0/00/00text0"
+remove "Contents/Resources/0/00/00png0.png"
+remove "Contents/MacOS/exe0.exe"
+rmdir "Contents/Resources/searchplugins/"
+rmdir "Contents/Resources/defaults/pref/"
+rmdir "Contents/Resources/defaults/"
+rmdir "Contents/Resources/1/10/"
+rmdir "Contents/Resources/1/"
+rmdir "Contents/Resources/0/00/"
+rmdir "Contents/Resources/0/"
+rmdir "Contents/Resources/"
+rmdir "Contents/MacOS/"
+rmdir "Contents/"
diff --git a/toolkit/mozapps/update/tests/data/complete_removed-files b/toolkit/mozapps/update/tests/data/complete_removed-files
new file mode 100644
index 0000000000..e45c43c1f8
--- /dev/null
+++ b/toolkit/mozapps/update/tests/data/complete_removed-files
@@ -0,0 +1,41 @@
+text0
+text1
+3/3text0
+3/3text1
+4/exe0.exe
+4/4text0
+4/4text1
+4/
+5/5text0
+5/5text1
+5/*
+6/
+7/*
+8/80/
+8/81/
+8/82/
+8/83/
+8/84/
+8/85/*
+8/86/*
+8/87/*
+8/88/*
+8/89/*
+8/80/
+8/84/*
+8/85/*
+8/89/
+9/90/
+9/91/
+9/92/
+9/93/
+9/94/
+9/95/*
+9/96/*
+9/97/*
+9/98/*
+9/99/*
+9/90/
+9/94/*
+9/95/*
+9/99/
diff --git a/toolkit/mozapps/update/tests/data/complete_removed-files_mac b/toolkit/mozapps/update/tests/data/complete_removed-files_mac
new file mode 100644
index 0000000000..955dc5b340
--- /dev/null
+++ b/toolkit/mozapps/update/tests/data/complete_removed-files_mac
@@ -0,0 +1,41 @@
+Contents/Resources/text0
+Contents/Resources/text1
+Contents/Resources/3/3text0
+Contents/Resources/3/3text1
+Contents/Resources/4/exe0.exe
+Contents/Resources/4/4text0
+Contents/Resources/4/4text1
+Contents/Resources/4/
+Contents/Resources/5/5text0
+Contents/Resources/5/5text1
+Contents/Resources/5/*
+Contents/Resources/6/
+Contents/Resources/7/*
+Contents/Resources/8/80/
+Contents/Resources/8/81/
+Contents/Resources/8/82/
+Contents/Resources/8/83/
+Contents/Resources/8/84/
+Contents/Resources/8/85/*
+Contents/Resources/8/86/*
+Contents/Resources/8/87/*
+Contents/Resources/8/88/*
+Contents/Resources/8/89/*
+Contents/Resources/8/80/
+Contents/Resources/8/84/*
+Contents/Resources/8/85/*
+Contents/Resources/8/89/
+Contents/Resources/9/90/
+Contents/Resources/9/91/
+Contents/Resources/9/92/
+Contents/Resources/9/93/
+Contents/Resources/9/94/
+Contents/Resources/9/95/*
+Contents/Resources/9/96/*
+Contents/Resources/9/97/*
+Contents/Resources/9/98/*
+Contents/Resources/9/99/*
+Contents/Resources/9/90/
+Contents/Resources/9/94/*
+Contents/Resources/9/95/*
+Contents/Resources/9/99/
diff --git a/toolkit/mozapps/update/tests/data/complete_update_manifest b/toolkit/mozapps/update/tests/data/complete_update_manifest
new file mode 100644
index 0000000000..383a324f63
--- /dev/null
+++ b/toolkit/mozapps/update/tests/data/complete_update_manifest
@@ -0,0 +1,59 @@
+type "complete"
+add "precomplete"
+add "searchplugins/searchpluginstext0"
+add "searchplugins/searchpluginspng1.png"
+add "searchplugins/searchpluginspng0.png"
+add "removed-files"
+add-if "extensions/extensions1" "extensions/extensions1/extensions1text0"
+add-if "extensions/extensions1" "extensions/extensions1/extensions1png1.png"
+add-if "extensions/extensions1" "extensions/extensions1/extensions1png0.png"
+add-if "extensions/extensions0" "extensions/extensions0/extensions0text0"
+add-if "extensions/extensions0" "extensions/extensions0/extensions0png1.png"
+add-if "extensions/extensions0" "extensions/extensions0/extensions0png0.png"
+add "exe0.exe"
+add "1/10/10text0"
+add "0/0exe0.exe"
+add "0/00/00text1"
+add "0/00/00text0"
+add "0/00/00png0.png"
+remove "text1"
+remove "text0"
+rmrfdir "9/99/"
+rmdir "9/99/"
+rmrfdir "9/98/"
+rmrfdir "9/97/"
+rmrfdir "9/96/"
+rmrfdir "9/95/"
+rmrfdir "9/95/"
+rmrfdir "9/94/"
+rmdir "9/94/"
+rmdir "9/93/"
+rmdir "9/92/"
+rmdir "9/91/"
+rmdir "9/90/"
+rmdir "9/90/"
+rmrfdir "8/89/"
+rmdir "8/89/"
+rmrfdir "8/88/"
+rmrfdir "8/87/"
+rmrfdir "8/86/"
+rmrfdir "8/85/"
+rmrfdir "8/85/"
+rmrfdir "8/84/"
+rmdir "8/84/"
+rmdir "8/83/"
+rmdir "8/82/"
+rmdir "8/81/"
+rmdir "8/80/"
+rmdir "8/80/"
+rmrfdir "7/"
+rmdir "6/"
+remove "5/5text1"
+remove "5/5text0"
+rmrfdir "5/"
+remove "4/exe0.exe"
+remove "4/4text1"
+remove "4/4text0"
+rmdir "4/"
+remove "3/3text1"
+remove "3/3text0"
diff --git a/toolkit/mozapps/update/tests/data/old_version.mar b/toolkit/mozapps/update/tests/data/old_version.mar
new file mode 100644
index 0000000000..b48f1d5fa4
--- /dev/null
+++ b/toolkit/mozapps/update/tests/data/old_version.mar
Binary files differ
diff --git a/toolkit/mozapps/update/tests/data/partial.mar b/toolkit/mozapps/update/tests/data/partial.mar
new file mode 100644
index 0000000000..b6b04bbdbf
--- /dev/null
+++ b/toolkit/mozapps/update/tests/data/partial.mar
Binary files differ
diff --git a/toolkit/mozapps/update/tests/data/partial.png b/toolkit/mozapps/update/tests/data/partial.png
new file mode 100644
index 0000000000..9246f586c7
--- /dev/null
+++ b/toolkit/mozapps/update/tests/data/partial.png
Binary files differ
diff --git a/toolkit/mozapps/update/tests/data/partial_log_failure_mac b/toolkit/mozapps/update/tests/data/partial_log_failure_mac
new file mode 100644
index 0000000000..3b2933ebd2
--- /dev/null
+++ b/toolkit/mozapps/update/tests/data/partial_log_failure_mac
@@ -0,0 +1,192 @@
+UPDATE TYPE partial
+PREPARE ADD Contents/Resources/searchplugins/searchpluginstext0
+PREPARE PATCH Contents/Resources/searchplugins/searchpluginspng1.png
+PREPARE PATCH Contents/Resources/searchplugins/searchpluginspng0.png
+PREPARE ADD Contents/Resources/precomplete
+PREPARE ADD Contents/Resources/distribution/extensions/extensions1/extensions1text0
+PREPARE PATCH Contents/Resources/distribution/extensions/extensions1/extensions1png1.png
+PREPARE PATCH Contents/Resources/distribution/extensions/extensions1/extensions1png0.png
+PREPARE ADD Contents/Resources/distribution/extensions/extensions0/extensions0text0
+PREPARE PATCH Contents/Resources/distribution/extensions/extensions0/extensions0png1.png
+PREPARE PATCH Contents/Resources/distribution/extensions/extensions0/extensions0png0.png
+PREPARE PATCH Contents/Resources/0/0exe0.exe
+PREPARE ADD Contents/Resources/0/00/00text0
+PREPARE PATCH Contents/Resources/0/00/00png0.png
+PREPARE PATCH Contents/MacOS/exe0.exe
+PREPARE ADD Contents/Resources/2/20/20text0
+PREPARE ADD Contents/Resources/2/20/20png0.png
+PREPARE ADD Contents/Resources/0/00/00text2
+PREPARE REMOVEFILE Contents/Resources/1/10/10text0
+PREPARE REMOVEFILE Contents/Resources/0/00/00text1
+PREPARE REMOVEDIR Contents/Resources/9/99/
+PREPARE REMOVEDIR Contents/Resources/9/99/
+PREPARE REMOVEDIR Contents/Resources/9/98/
+PREPARE REMOVEFILE Contents/Resources/9/97/970/97xtext0
+PREPARE REMOVEFILE Contents/Resources/9/97/970/97xtext1
+PREPARE REMOVEDIR Contents/Resources/9/97/970/
+PREPARE REMOVEFILE Contents/Resources/9/97/971/97xtext0
+PREPARE REMOVEFILE Contents/Resources/9/97/971/97xtext1
+PREPARE REMOVEDIR Contents/Resources/9/97/971/
+PREPARE REMOVEDIR Contents/Resources/9/97/
+PREPARE REMOVEFILE Contents/Resources/9/96/96text0
+PREPARE REMOVEFILE Contents/Resources/9/96/96text1
+PREPARE REMOVEDIR Contents/Resources/9/96/
+PREPARE REMOVEDIR Contents/Resources/9/95/
+PREPARE REMOVEDIR Contents/Resources/9/95/
+PREPARE REMOVEDIR Contents/Resources/9/94/
+PREPARE REMOVEDIR Contents/Resources/9/94/
+PREPARE REMOVEDIR Contents/Resources/9/93/
+PREPARE REMOVEDIR Contents/Resources/9/92/
+PREPARE REMOVEDIR Contents/Resources/9/91/
+PREPARE REMOVEDIR Contents/Resources/9/90/
+PREPARE REMOVEDIR Contents/Resources/9/90/
+PREPARE REMOVEDIR Contents/Resources/8/89/
+PREPARE REMOVEDIR Contents/Resources/8/89/
+PREPARE REMOVEDIR Contents/Resources/8/88/
+PREPARE REMOVEFILE Contents/Resources/8/87/870/87xtext0
+PREPARE REMOVEFILE Contents/Resources/8/87/870/87xtext1
+PREPARE REMOVEDIR Contents/Resources/8/87/870/
+PREPARE REMOVEFILE Contents/Resources/8/87/871/87xtext0
+PREPARE REMOVEFILE Contents/Resources/8/87/871/87xtext1
+PREPARE REMOVEDIR Contents/Resources/8/87/871/
+PREPARE REMOVEDIR Contents/Resources/8/87/
+PREPARE REMOVEFILE Contents/Resources/8/86/86text0
+PREPARE REMOVEFILE Contents/Resources/8/86/86text1
+PREPARE REMOVEDIR Contents/Resources/8/86/
+PREPARE REMOVEDIR Contents/Resources/8/85/
+PREPARE REMOVEDIR Contents/Resources/8/85/
+PREPARE REMOVEDIR Contents/Resources/8/84/
+PREPARE REMOVEDIR Contents/Resources/8/84/
+PREPARE REMOVEDIR Contents/Resources/8/83/
+PREPARE REMOVEDIR Contents/Resources/8/82/
+PREPARE REMOVEDIR Contents/Resources/8/81/
+PREPARE REMOVEDIR Contents/Resources/8/80/
+PREPARE REMOVEDIR Contents/Resources/8/80/
+PREPARE REMOVEFILE Contents/Resources/7/70/7xtest.exe
+PREPARE REMOVEFILE Contents/Resources/7/70/7xtext0
+PREPARE REMOVEFILE Contents/Resources/7/70/7xtext1
+PREPARE REMOVEDIR Contents/Resources/7/70/
+PREPARE REMOVEFILE Contents/Resources/7/71/7xtest.exe
+PREPARE REMOVEFILE Contents/Resources/7/71/7xtext0
+PREPARE REMOVEFILE Contents/Resources/7/71/7xtext1
+PREPARE REMOVEDIR Contents/Resources/7/71/
+PREPARE REMOVEFILE Contents/Resources/7/7text0
+PREPARE REMOVEFILE Contents/Resources/7/7text1
+PREPARE REMOVEDIR Contents/Resources/7/
+PREPARE REMOVEDIR Contents/Resources/6/
+PREPARE REMOVEFILE Contents/Resources/5/5text1
+PREPARE REMOVEFILE Contents/Resources/5/5text0
+PREPARE REMOVEFILE Contents/Resources/5/5test.exe
+PREPARE REMOVEFILE Contents/Resources/5/5text0
+PREPARE REMOVEFILE Contents/Resources/5/5text1
+PREPARE REMOVEDIR Contents/Resources/5/
+PREPARE REMOVEFILE Contents/Resources/4/4text1
+PREPARE REMOVEFILE Contents/Resources/4/4text0
+PREPARE REMOVEDIR Contents/Resources/4/
+PREPARE REMOVEFILE Contents/Resources/3/3text1
+PREPARE REMOVEFILE Contents/Resources/3/3text0
+PREPARE REMOVEDIR Contents/Resources/1/10/
+PREPARE REMOVEDIR Contents/Resources/1/
+EXECUTE ADD Contents/Resources/searchplugins/searchpluginstext0
+EXECUTE PATCH Contents/Resources/searchplugins/searchpluginspng1.png
+EXECUTE PATCH Contents/Resources/searchplugins/searchpluginspng0.png
+EXECUTE ADD Contents/Resources/precomplete
+EXECUTE ADD Contents/Resources/distribution/extensions/extensions1/extensions1text0
+EXECUTE PATCH Contents/Resources/distribution/extensions/extensions1/extensions1png1.png
+EXECUTE PATCH Contents/Resources/distribution/extensions/extensions1/extensions1png0.png
+EXECUTE ADD Contents/Resources/distribution/extensions/extensions0/extensions0text0
+EXECUTE PATCH Contents/Resources/distribution/extensions/extensions0/extensions0png1.png
+EXECUTE PATCH Contents/Resources/distribution/extensions/extensions0/extensions0png0.png
+EXECUTE PATCH Contents/Resources/0/0exe0.exe
+LoadSourceFile: destination file size 776 does not match expected size 79872
+LoadSourceFile failed
+### execution failed
+FINISH ADD Contents/Resources/searchplugins/searchpluginstext0
+FINISH PATCH Contents/Resources/searchplugins/searchpluginspng1.png
+FINISH PATCH Contents/Resources/searchplugins/searchpluginspng0.png
+FINISH ADD Contents/Resources/precomplete
+FINISH ADD Contents/Resources/distribution/extensions/extensions1/extensions1text0
+backup_restore: backup file doesn't exist: Contents/Resources/distribution/extensions/extensions1/extensions1text0.moz-backup
+FINISH PATCH Contents/Resources/distribution/extensions/extensions1/extensions1png1.png
+FINISH PATCH Contents/Resources/distribution/extensions/extensions1/extensions1png0.png
+FINISH ADD Contents/Resources/distribution/extensions/extensions0/extensions0text0
+FINISH PATCH Contents/Resources/distribution/extensions/extensions0/extensions0png1.png
+FINISH PATCH Contents/Resources/distribution/extensions/extensions0/extensions0png0.png
+FINISH PATCH Contents/Resources/0/0exe0.exe
+backup_restore: backup file doesn't exist: Contents/Resources/0/0exe0.exe.moz-backup
+FINISH ADD Contents/Resources/0/00/00text0
+backup_restore: backup file doesn't exist: Contents/Resources/0/00/00text0.moz-backup
+FINISH PATCH Contents/Resources/0/00/00png0.png
+backup_restore: backup file doesn't exist: Contents/Resources/0/00/00png0.png.moz-backup
+FINISH PATCH Contents/MacOS/exe0.exe
+backup_restore: backup file doesn't exist: Contents/MacOS/exe0.exe.moz-backup
+FINISH ADD Contents/Resources/2/20/20text0
+backup_restore: backup file doesn't exist: Contents/Resources/2/20/20text0.moz-backup
+FINISH ADD Contents/Resources/2/20/20png0.png
+backup_restore: backup file doesn't exist: Contents/Resources/2/20/20png0.png.moz-backup
+FINISH ADD Contents/Resources/0/00/00text2
+backup_restore: backup file doesn't exist: Contents/Resources/0/00/00text2.moz-backup
+FINISH REMOVEFILE Contents/Resources/1/10/10text0
+backup_restore: backup file doesn't exist: Contents/Resources/1/10/10text0.moz-backup
+FINISH REMOVEFILE Contents/Resources/0/00/00text1
+backup_restore: backup file doesn't exist: Contents/Resources/0/00/00text1.moz-backup
+FINISH REMOVEFILE Contents/Resources/9/97/970/97xtext0
+backup_restore: backup file doesn't exist: Contents/Resources/9/97/970/97xtext0.moz-backup
+FINISH REMOVEFILE Contents/Resources/9/97/970/97xtext1
+backup_restore: backup file doesn't exist: Contents/Resources/9/97/970/97xtext1.moz-backup
+FINISH REMOVEFILE Contents/Resources/9/97/971/97xtext0
+backup_restore: backup file doesn't exist: Contents/Resources/9/97/971/97xtext0.moz-backup
+FINISH REMOVEFILE Contents/Resources/9/97/971/97xtext1
+backup_restore: backup file doesn't exist: Contents/Resources/9/97/971/97xtext1.moz-backup
+FINISH REMOVEFILE Contents/Resources/9/96/96text0
+backup_restore: backup file doesn't exist: Contents/Resources/9/96/96text0.moz-backup
+FINISH REMOVEFILE Contents/Resources/9/96/96text1
+backup_restore: backup file doesn't exist: Contents/Resources/9/96/96text1.moz-backup
+FINISH REMOVEFILE Contents/Resources/8/87/870/87xtext0
+backup_restore: backup file doesn't exist: Contents/Resources/8/87/870/87xtext0.moz-backup
+FINISH REMOVEFILE Contents/Resources/8/87/870/87xtext1
+backup_restore: backup file doesn't exist: Contents/Resources/8/87/870/87xtext1.moz-backup
+FINISH REMOVEFILE Contents/Resources/8/87/871/87xtext0
+backup_restore: backup file doesn't exist: Contents/Resources/8/87/871/87xtext0.moz-backup
+FINISH REMOVEFILE Contents/Resources/8/87/871/87xtext1
+backup_restore: backup file doesn't exist: Contents/Resources/8/87/871/87xtext1.moz-backup
+FINISH REMOVEFILE Contents/Resources/8/86/86text0
+backup_restore: backup file doesn't exist: Contents/Resources/8/86/86text0.moz-backup
+FINISH REMOVEFILE Contents/Resources/8/86/86text1
+backup_restore: backup file doesn't exist: Contents/Resources/8/86/86text1.moz-backup
+FINISH REMOVEFILE Contents/Resources/7/70/7xtest.exe
+backup_restore: backup file doesn't exist: Contents/Resources/7/70/7xtest.exe.moz-backup
+FINISH REMOVEFILE Contents/Resources/7/70/7xtext0
+backup_restore: backup file doesn't exist: Contents/Resources/7/70/7xtext0.moz-backup
+FINISH REMOVEFILE Contents/Resources/7/70/7xtext1
+backup_restore: backup file doesn't exist: Contents/Resources/7/70/7xtext1.moz-backup
+FINISH REMOVEFILE Contents/Resources/7/71/7xtest.exe
+backup_restore: backup file doesn't exist: Contents/Resources/7/71/7xtest.exe.moz-backup
+FINISH REMOVEFILE Contents/Resources/7/71/7xtext0
+backup_restore: backup file doesn't exist: Contents/Resources/7/71/7xtext0.moz-backup
+FINISH REMOVEFILE Contents/Resources/7/71/7xtext1
+backup_restore: backup file doesn't exist: Contents/Resources/7/71/7xtext1.moz-backup
+FINISH REMOVEFILE Contents/Resources/7/7text0
+backup_restore: backup file doesn't exist: Contents/Resources/7/7text0.moz-backup
+FINISH REMOVEFILE Contents/Resources/7/7text1
+backup_restore: backup file doesn't exist: Contents/Resources/7/7text1.moz-backup
+FINISH REMOVEFILE Contents/Resources/5/5text1
+backup_restore: backup file doesn't exist: Contents/Resources/5/5text1.moz-backup
+FINISH REMOVEFILE Contents/Resources/5/5text0
+backup_restore: backup file doesn't exist: Contents/Resources/5/5text0.moz-backup
+FINISH REMOVEFILE Contents/Resources/5/5test.exe
+backup_restore: backup file doesn't exist: Contents/Resources/5/5test.exe.moz-backup
+FINISH REMOVEFILE Contents/Resources/5/5text0
+backup_restore: backup file doesn't exist: Contents/Resources/5/5text0.moz-backup
+FINISH REMOVEFILE Contents/Resources/5/5text1
+backup_restore: backup file doesn't exist: Contents/Resources/5/5text1.moz-backup
+FINISH REMOVEFILE Contents/Resources/4/4text1
+backup_restore: backup file doesn't exist: Contents/Resources/4/4text1.moz-backup
+FINISH REMOVEFILE Contents/Resources/4/4text0
+backup_restore: backup file doesn't exist: Contents/Resources/4/4text0.moz-backup
+FINISH REMOVEFILE Contents/Resources/3/3text1
+backup_restore: backup file doesn't exist: Contents/Resources/3/3text1.moz-backup
+FINISH REMOVEFILE Contents/Resources/3/3text0
+backup_restore: backup file doesn't exist: Contents/Resources/3/3text0.moz-backup
+failed: 2
+calling QuitProgressUI
diff --git a/toolkit/mozapps/update/tests/data/partial_log_failure_win b/toolkit/mozapps/update/tests/data/partial_log_failure_win
new file mode 100644
index 0000000000..e3d683dc19
--- /dev/null
+++ b/toolkit/mozapps/update/tests/data/partial_log_failure_win
@@ -0,0 +1,192 @@
+UPDATE TYPE partial
+PREPARE ADD searchplugins/searchpluginstext0
+PREPARE PATCH searchplugins/searchpluginspng1.png
+PREPARE PATCH searchplugins/searchpluginspng0.png
+PREPARE ADD precomplete
+PREPARE PATCH exe0.exe
+PREPARE ADD distribution/extensions/extensions1/extensions1text0
+PREPARE PATCH distribution/extensions/extensions1/extensions1png1.png
+PREPARE PATCH distribution/extensions/extensions1/extensions1png0.png
+PREPARE ADD distribution/extensions/extensions0/extensions0text0
+PREPARE PATCH distribution/extensions/extensions0/extensions0png1.png
+PREPARE PATCH distribution/extensions/extensions0/extensions0png0.png
+PREPARE PATCH 0/0exe0.exe
+PREPARE ADD 0/00/00text0
+PREPARE PATCH 0/00/00png0.png
+PREPARE ADD 2/20/20text0
+PREPARE ADD 2/20/20png0.png
+PREPARE ADD 0/00/00text2
+PREPARE REMOVEFILE 1/10/10text0
+PREPARE REMOVEFILE 0/00/00text1
+PREPARE REMOVEDIR 9/99/
+PREPARE REMOVEDIR 9/99/
+PREPARE REMOVEDIR 9/98/
+PREPARE REMOVEFILE 9/97/970/97xtext0
+PREPARE REMOVEFILE 9/97/970/97xtext1
+PREPARE REMOVEDIR 9/97/970/
+PREPARE REMOVEFILE 9/97/971/97xtext0
+PREPARE REMOVEFILE 9/97/971/97xtext1
+PREPARE REMOVEDIR 9/97/971/
+PREPARE REMOVEDIR 9/97/
+PREPARE REMOVEFILE 9/96/96text0
+PREPARE REMOVEFILE 9/96/96text1
+PREPARE REMOVEDIR 9/96/
+PREPARE REMOVEDIR 9/95/
+PREPARE REMOVEDIR 9/95/
+PREPARE REMOVEDIR 9/94/
+PREPARE REMOVEDIR 9/94/
+PREPARE REMOVEDIR 9/93/
+PREPARE REMOVEDIR 9/92/
+PREPARE REMOVEDIR 9/91/
+PREPARE REMOVEDIR 9/90/
+PREPARE REMOVEDIR 9/90/
+PREPARE REMOVEDIR 8/89/
+PREPARE REMOVEDIR 8/89/
+PREPARE REMOVEDIR 8/88/
+PREPARE REMOVEFILE 8/87/870/87xtext0
+PREPARE REMOVEFILE 8/87/870/87xtext1
+PREPARE REMOVEDIR 8/87/870/
+PREPARE REMOVEFILE 8/87/871/87xtext0
+PREPARE REMOVEFILE 8/87/871/87xtext1
+PREPARE REMOVEDIR 8/87/871/
+PREPARE REMOVEDIR 8/87/
+PREPARE REMOVEFILE 8/86/86text0
+PREPARE REMOVEFILE 8/86/86text1
+PREPARE REMOVEDIR 8/86/
+PREPARE REMOVEDIR 8/85/
+PREPARE REMOVEDIR 8/85/
+PREPARE REMOVEDIR 8/84/
+PREPARE REMOVEDIR 8/84/
+PREPARE REMOVEDIR 8/83/
+PREPARE REMOVEDIR 8/82/
+PREPARE REMOVEDIR 8/81/
+PREPARE REMOVEDIR 8/80/
+PREPARE REMOVEDIR 8/80/
+PREPARE REMOVEFILE 7/70/7xtest.exe
+PREPARE REMOVEFILE 7/70/7xtext0
+PREPARE REMOVEFILE 7/70/7xtext1
+PREPARE REMOVEDIR 7/70/
+PREPARE REMOVEFILE 7/71/7xtest.exe
+PREPARE REMOVEFILE 7/71/7xtext0
+PREPARE REMOVEFILE 7/71/7xtext1
+PREPARE REMOVEDIR 7/71/
+PREPARE REMOVEFILE 7/7text0
+PREPARE REMOVEFILE 7/7text1
+PREPARE REMOVEDIR 7/
+PREPARE REMOVEDIR 6/
+PREPARE REMOVEFILE 5/5text1
+PREPARE REMOVEFILE 5/5text0
+PREPARE REMOVEFILE 5/5test.exe
+PREPARE REMOVEFILE 5/5text0
+PREPARE REMOVEFILE 5/5text1
+PREPARE REMOVEDIR 5/
+PREPARE REMOVEFILE 4/4text1
+PREPARE REMOVEFILE 4/4text0
+PREPARE REMOVEDIR 4/
+PREPARE REMOVEFILE 3/3text1
+PREPARE REMOVEFILE 3/3text0
+PREPARE REMOVEDIR 1/10/
+PREPARE REMOVEDIR 1/
+EXECUTE ADD searchplugins/searchpluginstext0
+EXECUTE PATCH searchplugins/searchpluginspng1.png
+EXECUTE PATCH searchplugins/searchpluginspng0.png
+EXECUTE ADD precomplete
+EXECUTE PATCH exe0.exe
+EXECUTE ADD distribution/extensions/extensions1/extensions1text0
+EXECUTE PATCH distribution/extensions/extensions1/extensions1png1.png
+EXECUTE PATCH distribution/extensions/extensions1/extensions1png0.png
+EXECUTE ADD distribution/extensions/extensions0/extensions0text0
+EXECUTE PATCH distribution/extensions/extensions0/extensions0png1.png
+EXECUTE PATCH distribution/extensions/extensions0/extensions0png0.png
+EXECUTE PATCH 0/0exe0.exe
+LoadSourceFile: destination file size 776 does not match expected size 79872
+LoadSourceFile failed
+### execution failed
+FINISH ADD searchplugins/searchpluginstext0
+FINISH PATCH searchplugins/searchpluginspng1.png
+FINISH PATCH searchplugins/searchpluginspng0.png
+FINISH ADD precomplete
+FINISH PATCH exe0.exe
+FINISH ADD distribution/extensions/extensions1/extensions1text0
+backup_restore: backup file doesn't exist: distribution/extensions/extensions1/extensions1text0.moz-backup
+FINISH PATCH distribution/extensions/extensions1/extensions1png1.png
+FINISH PATCH distribution/extensions/extensions1/extensions1png0.png
+FINISH ADD distribution/extensions/extensions0/extensions0text0
+FINISH PATCH distribution/extensions/extensions0/extensions0png1.png
+FINISH PATCH distribution/extensions/extensions0/extensions0png0.png
+FINISH PATCH 0/0exe0.exe
+backup_restore: backup file doesn't exist: 0/0exe0.exe.moz-backup
+FINISH ADD 0/00/00text0
+backup_restore: backup file doesn't exist: 0/00/00text0.moz-backup
+FINISH PATCH 0/00/00png0.png
+backup_restore: backup file doesn't exist: 0/00/00png0.png.moz-backup
+FINISH ADD 2/20/20text0
+backup_restore: backup file doesn't exist: 2/20/20text0.moz-backup
+FINISH ADD 2/20/20png0.png
+backup_restore: backup file doesn't exist: 2/20/20png0.png.moz-backup
+FINISH ADD 0/00/00text2
+backup_restore: backup file doesn't exist: 0/00/00text2.moz-backup
+FINISH REMOVEFILE 1/10/10text0
+backup_restore: backup file doesn't exist: 1/10/10text0.moz-backup
+FINISH REMOVEFILE 0/00/00text1
+backup_restore: backup file doesn't exist: 0/00/00text1.moz-backup
+FINISH REMOVEFILE 9/97/970/97xtext0
+backup_restore: backup file doesn't exist: 9/97/970/97xtext0.moz-backup
+FINISH REMOVEFILE 9/97/970/97xtext1
+backup_restore: backup file doesn't exist: 9/97/970/97xtext1.moz-backup
+FINISH REMOVEFILE 9/97/971/97xtext0
+backup_restore: backup file doesn't exist: 9/97/971/97xtext0.moz-backup
+FINISH REMOVEFILE 9/97/971/97xtext1
+backup_restore: backup file doesn't exist: 9/97/971/97xtext1.moz-backup
+FINISH REMOVEFILE 9/96/96text0
+backup_restore: backup file doesn't exist: 9/96/96text0.moz-backup
+FINISH REMOVEFILE 9/96/96text1
+backup_restore: backup file doesn't exist: 9/96/96text1.moz-backup
+FINISH REMOVEFILE 8/87/870/87xtext0
+backup_restore: backup file doesn't exist: 8/87/870/87xtext0.moz-backup
+FINISH REMOVEFILE 8/87/870/87xtext1
+backup_restore: backup file doesn't exist: 8/87/870/87xtext1.moz-backup
+FINISH REMOVEFILE 8/87/871/87xtext0
+backup_restore: backup file doesn't exist: 8/87/871/87xtext0.moz-backup
+FINISH REMOVEFILE 8/87/871/87xtext1
+backup_restore: backup file doesn't exist: 8/87/871/87xtext1.moz-backup
+FINISH REMOVEFILE 8/86/86text0
+backup_restore: backup file doesn't exist: 8/86/86text0.moz-backup
+FINISH REMOVEFILE 8/86/86text1
+backup_restore: backup file doesn't exist: 8/86/86text1.moz-backup
+FINISH REMOVEFILE 7/70/7xtest.exe
+backup_restore: backup file doesn't exist: 7/70/7xtest.exe.moz-backup
+FINISH REMOVEFILE 7/70/7xtext0
+backup_restore: backup file doesn't exist: 7/70/7xtext0.moz-backup
+FINISH REMOVEFILE 7/70/7xtext1
+backup_restore: backup file doesn't exist: 7/70/7xtext1.moz-backup
+FINISH REMOVEFILE 7/71/7xtest.exe
+backup_restore: backup file doesn't exist: 7/71/7xtest.exe.moz-backup
+FINISH REMOVEFILE 7/71/7xtext0
+backup_restore: backup file doesn't exist: 7/71/7xtext0.moz-backup
+FINISH REMOVEFILE 7/71/7xtext1
+backup_restore: backup file doesn't exist: 7/71/7xtext1.moz-backup
+FINISH REMOVEFILE 7/7text0
+backup_restore: backup file doesn't exist: 7/7text0.moz-backup
+FINISH REMOVEFILE 7/7text1
+backup_restore: backup file doesn't exist: 7/7text1.moz-backup
+FINISH REMOVEFILE 5/5text1
+backup_restore: backup file doesn't exist: 5/5text1.moz-backup
+FINISH REMOVEFILE 5/5text0
+backup_restore: backup file doesn't exist: 5/5text0.moz-backup
+FINISH REMOVEFILE 5/5test.exe
+backup_restore: backup file doesn't exist: 5/5test.exe.moz-backup
+FINISH REMOVEFILE 5/5text0
+backup_restore: backup file doesn't exist: 5/5text0.moz-backup
+FINISH REMOVEFILE 5/5text1
+backup_restore: backup file doesn't exist: 5/5text1.moz-backup
+FINISH REMOVEFILE 4/4text1
+backup_restore: backup file doesn't exist: 4/4text1.moz-backup
+FINISH REMOVEFILE 4/4text0
+backup_restore: backup file doesn't exist: 4/4text0.moz-backup
+FINISH REMOVEFILE 3/3text1
+backup_restore: backup file doesn't exist: 3/3text1.moz-backup
+FINISH REMOVEFILE 3/3text0
+backup_restore: backup file doesn't exist: 3/3text0.moz-backup
+failed: 2
+calling QuitProgressUI
diff --git a/toolkit/mozapps/update/tests/data/partial_log_success_mac b/toolkit/mozapps/update/tests/data/partial_log_success_mac
new file mode 100644
index 0000000000..fb5272ad2c
--- /dev/null
+++ b/toolkit/mozapps/update/tests/data/partial_log_success_mac
@@ -0,0 +1,279 @@
+UPDATE TYPE partial
+PREPARE ADD Contents/Resources/searchplugins/searchpluginstext0
+PREPARE PATCH Contents/Resources/searchplugins/searchpluginspng1.png
+PREPARE PATCH Contents/Resources/searchplugins/searchpluginspng0.png
+PREPARE ADD Contents/Resources/precomplete
+PREPARE ADD Contents/Resources/distribution/extensions/extensions1/extensions1text0
+PREPARE PATCH Contents/Resources/distribution/extensions/extensions1/extensions1png1.png
+PREPARE PATCH Contents/Resources/distribution/extensions/extensions1/extensions1png0.png
+PREPARE ADD Contents/Resources/distribution/extensions/extensions0/extensions0text0
+PREPARE PATCH Contents/Resources/distribution/extensions/extensions0/extensions0png1.png
+PREPARE PATCH Contents/Resources/distribution/extensions/extensions0/extensions0png0.png
+PREPARE PATCH Contents/Resources/0/0exe0.exe
+PREPARE ADD Contents/Resources/0/00/00text0
+PREPARE PATCH Contents/Resources/0/00/00png0.png
+PREPARE PATCH Contents/MacOS/exe0.exe
+PREPARE ADD Contents/Resources/2/20/20text0
+PREPARE ADD Contents/Resources/2/20/20png0.png
+PREPARE ADD Contents/Resources/0/00/00text2
+PREPARE REMOVEFILE Contents/Resources/1/10/10text0
+PREPARE REMOVEFILE Contents/Resources/0/00/00text1
+PREPARE REMOVEDIR Contents/Resources/9/99/
+PREPARE REMOVEDIR Contents/Resources/9/99/
+PREPARE REMOVEDIR Contents/Resources/9/98/
+PREPARE REMOVEFILE Contents/Resources/9/97/970/97xtext0
+PREPARE REMOVEFILE Contents/Resources/9/97/970/97xtext1
+PREPARE REMOVEDIR Contents/Resources/9/97/970/
+PREPARE REMOVEFILE Contents/Resources/9/97/971/97xtext0
+PREPARE REMOVEFILE Contents/Resources/9/97/971/97xtext1
+PREPARE REMOVEDIR Contents/Resources/9/97/971/
+PREPARE REMOVEDIR Contents/Resources/9/97/
+PREPARE REMOVEFILE Contents/Resources/9/96/96text0
+PREPARE REMOVEFILE Contents/Resources/9/96/96text1
+PREPARE REMOVEDIR Contents/Resources/9/96/
+PREPARE REMOVEDIR Contents/Resources/9/95/
+PREPARE REMOVEDIR Contents/Resources/9/95/
+PREPARE REMOVEDIR Contents/Resources/9/94/
+PREPARE REMOVEDIR Contents/Resources/9/94/
+PREPARE REMOVEDIR Contents/Resources/9/93/
+PREPARE REMOVEDIR Contents/Resources/9/92/
+PREPARE REMOVEDIR Contents/Resources/9/91/
+PREPARE REMOVEDIR Contents/Resources/9/90/
+PREPARE REMOVEDIR Contents/Resources/9/90/
+PREPARE REMOVEDIR Contents/Resources/8/89/
+PREPARE REMOVEDIR Contents/Resources/8/89/
+PREPARE REMOVEDIR Contents/Resources/8/88/
+PREPARE REMOVEFILE Contents/Resources/8/87/870/87xtext0
+PREPARE REMOVEFILE Contents/Resources/8/87/870/87xtext1
+PREPARE REMOVEDIR Contents/Resources/8/87/870/
+PREPARE REMOVEFILE Contents/Resources/8/87/871/87xtext0
+PREPARE REMOVEFILE Contents/Resources/8/87/871/87xtext1
+PREPARE REMOVEDIR Contents/Resources/8/87/871/
+PREPARE REMOVEDIR Contents/Resources/8/87/
+PREPARE REMOVEFILE Contents/Resources/8/86/86text0
+PREPARE REMOVEFILE Contents/Resources/8/86/86text1
+PREPARE REMOVEDIR Contents/Resources/8/86/
+PREPARE REMOVEDIR Contents/Resources/8/85/
+PREPARE REMOVEDIR Contents/Resources/8/85/
+PREPARE REMOVEDIR Contents/Resources/8/84/
+PREPARE REMOVEDIR Contents/Resources/8/84/
+PREPARE REMOVEDIR Contents/Resources/8/83/
+PREPARE REMOVEDIR Contents/Resources/8/82/
+PREPARE REMOVEDIR Contents/Resources/8/81/
+PREPARE REMOVEDIR Contents/Resources/8/80/
+PREPARE REMOVEDIR Contents/Resources/8/80/
+PREPARE REMOVEFILE Contents/Resources/7/70/7xtest.exe
+PREPARE REMOVEFILE Contents/Resources/7/70/7xtext0
+PREPARE REMOVEFILE Contents/Resources/7/70/7xtext1
+PREPARE REMOVEDIR Contents/Resources/7/70/
+PREPARE REMOVEFILE Contents/Resources/7/71/7xtest.exe
+PREPARE REMOVEFILE Contents/Resources/7/71/7xtext0
+PREPARE REMOVEFILE Contents/Resources/7/71/7xtext1
+PREPARE REMOVEDIR Contents/Resources/7/71/
+PREPARE REMOVEFILE Contents/Resources/7/7text0
+PREPARE REMOVEFILE Contents/Resources/7/7text1
+PREPARE REMOVEDIR Contents/Resources/7/
+PREPARE REMOVEDIR Contents/Resources/6/
+PREPARE REMOVEFILE Contents/Resources/5/5text1
+PREPARE REMOVEFILE Contents/Resources/5/5text0
+PREPARE REMOVEFILE Contents/Resources/5/5test.exe
+PREPARE REMOVEFILE Contents/Resources/5/5text0
+PREPARE REMOVEFILE Contents/Resources/5/5text1
+PREPARE REMOVEDIR Contents/Resources/5/
+PREPARE REMOVEFILE Contents/Resources/4/4text1
+PREPARE REMOVEFILE Contents/Resources/4/4text0
+PREPARE REMOVEDIR Contents/Resources/4/
+PREPARE REMOVEFILE Contents/Resources/3/3text1
+PREPARE REMOVEFILE Contents/Resources/3/3text0
+PREPARE REMOVEDIR Contents/Resources/1/10/
+PREPARE REMOVEDIR Contents/Resources/1/
+EXECUTE ADD Contents/Resources/searchplugins/searchpluginstext0
+EXECUTE PATCH Contents/Resources/searchplugins/searchpluginspng1.png
+EXECUTE PATCH Contents/Resources/searchplugins/searchpluginspng0.png
+EXECUTE ADD Contents/Resources/precomplete
+EXECUTE ADD Contents/Resources/distribution/extensions/extensions1/extensions1text0
+EXECUTE PATCH Contents/Resources/distribution/extensions/extensions1/extensions1png1.png
+EXECUTE PATCH Contents/Resources/distribution/extensions/extensions1/extensions1png0.png
+EXECUTE ADD Contents/Resources/distribution/extensions/extensions0/extensions0text0
+EXECUTE PATCH Contents/Resources/distribution/extensions/extensions0/extensions0png1.png
+EXECUTE PATCH Contents/Resources/distribution/extensions/extensions0/extensions0png0.png
+EXECUTE PATCH Contents/Resources/0/0exe0.exe
+EXECUTE ADD Contents/Resources/0/00/00text0
+EXECUTE PATCH Contents/Resources/0/00/00png0.png
+EXECUTE PATCH Contents/MacOS/exe0.exe
+EXECUTE ADD Contents/Resources/2/20/20text0
+EXECUTE ADD Contents/Resources/2/20/20png0.png
+EXECUTE ADD Contents/Resources/0/00/00text2
+EXECUTE REMOVEFILE Contents/Resources/1/10/10text0
+EXECUTE REMOVEFILE Contents/Resources/0/00/00text1
+EXECUTE REMOVEDIR Contents/Resources/9/99/
+EXECUTE REMOVEDIR Contents/Resources/9/99/
+EXECUTE REMOVEDIR Contents/Resources/9/98/
+EXECUTE REMOVEFILE Contents/Resources/9/97/970/97xtext0
+EXECUTE REMOVEFILE Contents/Resources/9/97/970/97xtext1
+EXECUTE REMOVEDIR Contents/Resources/9/97/970/
+EXECUTE REMOVEFILE Contents/Resources/9/97/971/97xtext0
+EXECUTE REMOVEFILE Contents/Resources/9/97/971/97xtext1
+EXECUTE REMOVEDIR Contents/Resources/9/97/971/
+EXECUTE REMOVEDIR Contents/Resources/9/97/
+EXECUTE REMOVEFILE Contents/Resources/9/96/96text0
+EXECUTE REMOVEFILE Contents/Resources/9/96/96text1
+EXECUTE REMOVEDIR Contents/Resources/9/96/
+EXECUTE REMOVEDIR Contents/Resources/9/95/
+EXECUTE REMOVEDIR Contents/Resources/9/95/
+EXECUTE REMOVEDIR Contents/Resources/9/94/
+EXECUTE REMOVEDIR Contents/Resources/9/94/
+EXECUTE REMOVEDIR Contents/Resources/9/93/
+EXECUTE REMOVEDIR Contents/Resources/9/92/
+EXECUTE REMOVEDIR Contents/Resources/9/91/
+EXECUTE REMOVEDIR Contents/Resources/9/90/
+EXECUTE REMOVEDIR Contents/Resources/9/90/
+EXECUTE REMOVEDIR Contents/Resources/8/89/
+EXECUTE REMOVEDIR Contents/Resources/8/89/
+EXECUTE REMOVEDIR Contents/Resources/8/88/
+EXECUTE REMOVEFILE Contents/Resources/8/87/870/87xtext0
+EXECUTE REMOVEFILE Contents/Resources/8/87/870/87xtext1
+EXECUTE REMOVEDIR Contents/Resources/8/87/870/
+EXECUTE REMOVEFILE Contents/Resources/8/87/871/87xtext0
+EXECUTE REMOVEFILE Contents/Resources/8/87/871/87xtext1
+EXECUTE REMOVEDIR Contents/Resources/8/87/871/
+EXECUTE REMOVEDIR Contents/Resources/8/87/
+EXECUTE REMOVEFILE Contents/Resources/8/86/86text0
+EXECUTE REMOVEFILE Contents/Resources/8/86/86text1
+EXECUTE REMOVEDIR Contents/Resources/8/86/
+EXECUTE REMOVEDIR Contents/Resources/8/85/
+EXECUTE REMOVEDIR Contents/Resources/8/85/
+EXECUTE REMOVEDIR Contents/Resources/8/84/
+EXECUTE REMOVEDIR Contents/Resources/8/84/
+EXECUTE REMOVEDIR Contents/Resources/8/83/
+EXECUTE REMOVEDIR Contents/Resources/8/82/
+EXECUTE REMOVEDIR Contents/Resources/8/81/
+EXECUTE REMOVEDIR Contents/Resources/8/80/
+EXECUTE REMOVEDIR Contents/Resources/8/80/
+EXECUTE REMOVEFILE Contents/Resources/7/70/7xtest.exe
+EXECUTE REMOVEFILE Contents/Resources/7/70/7xtext0
+EXECUTE REMOVEFILE Contents/Resources/7/70/7xtext1
+EXECUTE REMOVEDIR Contents/Resources/7/70/
+EXECUTE REMOVEFILE Contents/Resources/7/71/7xtest.exe
+EXECUTE REMOVEFILE Contents/Resources/7/71/7xtext0
+EXECUTE REMOVEFILE Contents/Resources/7/71/7xtext1
+EXECUTE REMOVEDIR Contents/Resources/7/71/
+EXECUTE REMOVEFILE Contents/Resources/7/7text0
+EXECUTE REMOVEFILE Contents/Resources/7/7text1
+EXECUTE REMOVEDIR Contents/Resources/7/
+EXECUTE REMOVEDIR Contents/Resources/6/
+EXECUTE REMOVEFILE Contents/Resources/5/5text1
+EXECUTE REMOVEFILE Contents/Resources/5/5text0
+EXECUTE REMOVEFILE Contents/Resources/5/5test.exe
+EXECUTE REMOVEFILE Contents/Resources/5/5text0
+file cannot be removed because it does not exist; skipping
+EXECUTE REMOVEFILE Contents/Resources/5/5text1
+file cannot be removed because it does not exist; skipping
+EXECUTE REMOVEDIR Contents/Resources/5/
+EXECUTE REMOVEFILE Contents/Resources/4/4text1
+EXECUTE REMOVEFILE Contents/Resources/4/4text0
+EXECUTE REMOVEDIR Contents/Resources/4/
+EXECUTE REMOVEFILE Contents/Resources/3/3text1
+EXECUTE REMOVEFILE Contents/Resources/3/3text0
+EXECUTE REMOVEDIR Contents/Resources/1/10/
+EXECUTE REMOVEDIR Contents/Resources/1/
+FINISH ADD Contents/Resources/searchplugins/searchpluginstext0
+FINISH PATCH Contents/Resources/searchplugins/searchpluginspng1.png
+FINISH PATCH Contents/Resources/searchplugins/searchpluginspng0.png
+FINISH ADD Contents/Resources/precomplete
+FINISH ADD Contents/Resources/distribution/extensions/extensions1/extensions1text0
+FINISH PATCH Contents/Resources/distribution/extensions/extensions1/extensions1png1.png
+FINISH PATCH Contents/Resources/distribution/extensions/extensions1/extensions1png0.png
+FINISH ADD Contents/Resources/distribution/extensions/extensions0/extensions0text0
+FINISH PATCH Contents/Resources/distribution/extensions/extensions0/extensions0png1.png
+FINISH PATCH Contents/Resources/distribution/extensions/extensions0/extensions0png0.png
+FINISH PATCH Contents/Resources/0/0exe0.exe
+FINISH ADD Contents/Resources/0/00/00text0
+FINISH PATCH Contents/Resources/0/00/00png0.png
+FINISH PATCH Contents/MacOS/exe0.exe
+FINISH ADD Contents/Resources/2/20/20text0
+FINISH ADD Contents/Resources/2/20/20png0.png
+FINISH ADD Contents/Resources/0/00/00text2
+FINISH REMOVEFILE Contents/Resources/1/10/10text0
+FINISH REMOVEFILE Contents/Resources/0/00/00text1
+FINISH REMOVEDIR Contents/Resources/9/99/
+FINISH REMOVEDIR Contents/Resources/9/99/
+directory no longer exists; skipping
+FINISH REMOVEDIR Contents/Resources/9/98/
+FINISH REMOVEFILE Contents/Resources/9/97/970/97xtext0
+FINISH REMOVEFILE Contents/Resources/9/97/970/97xtext1
+FINISH REMOVEDIR Contents/Resources/9/97/970/
+FINISH REMOVEFILE Contents/Resources/9/97/971/97xtext0
+FINISH REMOVEFILE Contents/Resources/9/97/971/97xtext1
+FINISH REMOVEDIR Contents/Resources/9/97/971/
+FINISH REMOVEDIR Contents/Resources/9/97/
+FINISH REMOVEFILE Contents/Resources/9/96/96text0
+FINISH REMOVEFILE Contents/Resources/9/96/96text1
+FINISH REMOVEDIR Contents/Resources/9/96/
+FINISH REMOVEDIR Contents/Resources/9/95/
+FINISH REMOVEDIR Contents/Resources/9/95/
+directory no longer exists; skipping
+FINISH REMOVEDIR Contents/Resources/9/94/
+FINISH REMOVEDIR Contents/Resources/9/94/
+directory no longer exists; skipping
+FINISH REMOVEDIR Contents/Resources/9/93/
+FINISH REMOVEDIR Contents/Resources/9/92/
+removing directory: Contents/Resources/9/92/, rv: 0
+FINISH REMOVEDIR Contents/Resources/9/91/
+removing directory: Contents/Resources/9/91/, rv: 0
+FINISH REMOVEDIR Contents/Resources/9/90/
+FINISH REMOVEDIR Contents/Resources/9/90/
+directory no longer exists; skipping
+FINISH REMOVEDIR Contents/Resources/8/89/
+FINISH REMOVEDIR Contents/Resources/8/89/
+directory no longer exists; skipping
+FINISH REMOVEDIR Contents/Resources/8/88/
+FINISH REMOVEFILE Contents/Resources/8/87/870/87xtext0
+FINISH REMOVEFILE Contents/Resources/8/87/870/87xtext1
+FINISH REMOVEDIR Contents/Resources/8/87/870/
+FINISH REMOVEFILE Contents/Resources/8/87/871/87xtext0
+FINISH REMOVEFILE Contents/Resources/8/87/871/87xtext1
+FINISH REMOVEDIR Contents/Resources/8/87/871/
+FINISH REMOVEDIR Contents/Resources/8/87/
+FINISH REMOVEFILE Contents/Resources/8/86/86text0
+FINISH REMOVEFILE Contents/Resources/8/86/86text1
+FINISH REMOVEDIR Contents/Resources/8/86/
+FINISH REMOVEDIR Contents/Resources/8/85/
+FINISH REMOVEDIR Contents/Resources/8/85/
+directory no longer exists; skipping
+FINISH REMOVEDIR Contents/Resources/8/84/
+FINISH REMOVEDIR Contents/Resources/8/84/
+directory no longer exists; skipping
+FINISH REMOVEDIR Contents/Resources/8/83/
+FINISH REMOVEDIR Contents/Resources/8/82/
+removing directory: Contents/Resources/8/82/, rv: 0
+FINISH REMOVEDIR Contents/Resources/8/81/
+removing directory: Contents/Resources/8/81/, rv: 0
+FINISH REMOVEDIR Contents/Resources/8/80/
+FINISH REMOVEDIR Contents/Resources/8/80/
+directory no longer exists; skipping
+FINISH REMOVEFILE Contents/Resources/7/70/7xtest.exe
+FINISH REMOVEFILE Contents/Resources/7/70/7xtext0
+FINISH REMOVEFILE Contents/Resources/7/70/7xtext1
+FINISH REMOVEDIR Contents/Resources/7/70/
+FINISH REMOVEFILE Contents/Resources/7/71/7xtest.exe
+FINISH REMOVEFILE Contents/Resources/7/71/7xtext0
+FINISH REMOVEFILE Contents/Resources/7/71/7xtext1
+FINISH REMOVEDIR Contents/Resources/7/71/
+FINISH REMOVEFILE Contents/Resources/7/7text0
+FINISH REMOVEFILE Contents/Resources/7/7text1
+FINISH REMOVEDIR Contents/Resources/7/
+FINISH REMOVEDIR Contents/Resources/6/
+FINISH REMOVEFILE Contents/Resources/5/5text1
+FINISH REMOVEFILE Contents/Resources/5/5text0
+FINISH REMOVEFILE Contents/Resources/5/5test.exe
+FINISH REMOVEDIR Contents/Resources/5/
+FINISH REMOVEFILE Contents/Resources/4/4text1
+FINISH REMOVEFILE Contents/Resources/4/4text0
+FINISH REMOVEDIR Contents/Resources/4/
+FINISH REMOVEFILE Contents/Resources/3/3text1
+FINISH REMOVEFILE Contents/Resources/3/3text0
+FINISH REMOVEDIR Contents/Resources/1/10/
+FINISH REMOVEDIR Contents/Resources/1/
+succeeded
+calling QuitProgressUI
diff --git a/toolkit/mozapps/update/tests/data/partial_log_success_win b/toolkit/mozapps/update/tests/data/partial_log_success_win
new file mode 100644
index 0000000000..1f5c4b3b49
--- /dev/null
+++ b/toolkit/mozapps/update/tests/data/partial_log_success_win
@@ -0,0 +1,279 @@
+UPDATE TYPE partial
+PREPARE ADD searchplugins/searchpluginstext0
+PREPARE PATCH searchplugins/searchpluginspng1.png
+PREPARE PATCH searchplugins/searchpluginspng0.png
+PREPARE ADD precomplete
+PREPARE PATCH exe0.exe
+PREPARE ADD distribution/extensions/extensions1/extensions1text0
+PREPARE PATCH distribution/extensions/extensions1/extensions1png1.png
+PREPARE PATCH distribution/extensions/extensions1/extensions1png0.png
+PREPARE ADD distribution/extensions/extensions0/extensions0text0
+PREPARE PATCH distribution/extensions/extensions0/extensions0png1.png
+PREPARE PATCH distribution/extensions/extensions0/extensions0png0.png
+PREPARE PATCH 0/0exe0.exe
+PREPARE ADD 0/00/00text0
+PREPARE PATCH 0/00/00png0.png
+PREPARE ADD 2/20/20text0
+PREPARE ADD 2/20/20png0.png
+PREPARE ADD 0/00/00text2
+PREPARE REMOVEFILE 1/10/10text0
+PREPARE REMOVEFILE 0/00/00text1
+PREPARE REMOVEDIR 9/99/
+PREPARE REMOVEDIR 9/99/
+PREPARE REMOVEDIR 9/98/
+PREPARE REMOVEFILE 9/97/970/97xtext0
+PREPARE REMOVEFILE 9/97/970/97xtext1
+PREPARE REMOVEDIR 9/97/970/
+PREPARE REMOVEFILE 9/97/971/97xtext0
+PREPARE REMOVEFILE 9/97/971/97xtext1
+PREPARE REMOVEDIR 9/97/971/
+PREPARE REMOVEDIR 9/97/
+PREPARE REMOVEFILE 9/96/96text0
+PREPARE REMOVEFILE 9/96/96text1
+PREPARE REMOVEDIR 9/96/
+PREPARE REMOVEDIR 9/95/
+PREPARE REMOVEDIR 9/95/
+PREPARE REMOVEDIR 9/94/
+PREPARE REMOVEDIR 9/94/
+PREPARE REMOVEDIR 9/93/
+PREPARE REMOVEDIR 9/92/
+PREPARE REMOVEDIR 9/91/
+PREPARE REMOVEDIR 9/90/
+PREPARE REMOVEDIR 9/90/
+PREPARE REMOVEDIR 8/89/
+PREPARE REMOVEDIR 8/89/
+PREPARE REMOVEDIR 8/88/
+PREPARE REMOVEFILE 8/87/870/87xtext0
+PREPARE REMOVEFILE 8/87/870/87xtext1
+PREPARE REMOVEDIR 8/87/870/
+PREPARE REMOVEFILE 8/87/871/87xtext0
+PREPARE REMOVEFILE 8/87/871/87xtext1
+PREPARE REMOVEDIR 8/87/871/
+PREPARE REMOVEDIR 8/87/
+PREPARE REMOVEFILE 8/86/86text0
+PREPARE REMOVEFILE 8/86/86text1
+PREPARE REMOVEDIR 8/86/
+PREPARE REMOVEDIR 8/85/
+PREPARE REMOVEDIR 8/85/
+PREPARE REMOVEDIR 8/84/
+PREPARE REMOVEDIR 8/84/
+PREPARE REMOVEDIR 8/83/
+PREPARE REMOVEDIR 8/82/
+PREPARE REMOVEDIR 8/81/
+PREPARE REMOVEDIR 8/80/
+PREPARE REMOVEDIR 8/80/
+PREPARE REMOVEFILE 7/70/7xtest.exe
+PREPARE REMOVEFILE 7/70/7xtext0
+PREPARE REMOVEFILE 7/70/7xtext1
+PREPARE REMOVEDIR 7/70/
+PREPARE REMOVEFILE 7/71/7xtest.exe
+PREPARE REMOVEFILE 7/71/7xtext0
+PREPARE REMOVEFILE 7/71/7xtext1
+PREPARE REMOVEDIR 7/71/
+PREPARE REMOVEFILE 7/7text0
+PREPARE REMOVEFILE 7/7text1
+PREPARE REMOVEDIR 7/
+PREPARE REMOVEDIR 6/
+PREPARE REMOVEFILE 5/5text1
+PREPARE REMOVEFILE 5/5text0
+PREPARE REMOVEFILE 5/5test.exe
+PREPARE REMOVEFILE 5/5text0
+PREPARE REMOVEFILE 5/5text1
+PREPARE REMOVEDIR 5/
+PREPARE REMOVEFILE 4/4text1
+PREPARE REMOVEFILE 4/4text0
+PREPARE REMOVEDIR 4/
+PREPARE REMOVEFILE 3/3text1
+PREPARE REMOVEFILE 3/3text0
+PREPARE REMOVEDIR 1/10/
+PREPARE REMOVEDIR 1/
+EXECUTE ADD searchplugins/searchpluginstext0
+EXECUTE PATCH searchplugins/searchpluginspng1.png
+EXECUTE PATCH searchplugins/searchpluginspng0.png
+EXECUTE ADD precomplete
+EXECUTE PATCH exe0.exe
+EXECUTE ADD distribution/extensions/extensions1/extensions1text0
+EXECUTE PATCH distribution/extensions/extensions1/extensions1png1.png
+EXECUTE PATCH distribution/extensions/extensions1/extensions1png0.png
+EXECUTE ADD distribution/extensions/extensions0/extensions0text0
+EXECUTE PATCH distribution/extensions/extensions0/extensions0png1.png
+EXECUTE PATCH distribution/extensions/extensions0/extensions0png0.png
+EXECUTE PATCH 0/0exe0.exe
+EXECUTE ADD 0/00/00text0
+EXECUTE PATCH 0/00/00png0.png
+EXECUTE ADD 2/20/20text0
+EXECUTE ADD 2/20/20png0.png
+EXECUTE ADD 0/00/00text2
+EXECUTE REMOVEFILE 1/10/10text0
+EXECUTE REMOVEFILE 0/00/00text1
+EXECUTE REMOVEDIR 9/99/
+EXECUTE REMOVEDIR 9/99/
+EXECUTE REMOVEDIR 9/98/
+EXECUTE REMOVEFILE 9/97/970/97xtext0
+EXECUTE REMOVEFILE 9/97/970/97xtext1
+EXECUTE REMOVEDIR 9/97/970/
+EXECUTE REMOVEFILE 9/97/971/97xtext0
+EXECUTE REMOVEFILE 9/97/971/97xtext1
+EXECUTE REMOVEDIR 9/97/971/
+EXECUTE REMOVEDIR 9/97/
+EXECUTE REMOVEFILE 9/96/96text0
+EXECUTE REMOVEFILE 9/96/96text1
+EXECUTE REMOVEDIR 9/96/
+EXECUTE REMOVEDIR 9/95/
+EXECUTE REMOVEDIR 9/95/
+EXECUTE REMOVEDIR 9/94/
+EXECUTE REMOVEDIR 9/94/
+EXECUTE REMOVEDIR 9/93/
+EXECUTE REMOVEDIR 9/92/
+EXECUTE REMOVEDIR 9/91/
+EXECUTE REMOVEDIR 9/90/
+EXECUTE REMOVEDIR 9/90/
+EXECUTE REMOVEDIR 8/89/
+EXECUTE REMOVEDIR 8/89/
+EXECUTE REMOVEDIR 8/88/
+EXECUTE REMOVEFILE 8/87/870/87xtext0
+EXECUTE REMOVEFILE 8/87/870/87xtext1
+EXECUTE REMOVEDIR 8/87/870/
+EXECUTE REMOVEFILE 8/87/871/87xtext0
+EXECUTE REMOVEFILE 8/87/871/87xtext1
+EXECUTE REMOVEDIR 8/87/871/
+EXECUTE REMOVEDIR 8/87/
+EXECUTE REMOVEFILE 8/86/86text0
+EXECUTE REMOVEFILE 8/86/86text1
+EXECUTE REMOVEDIR 8/86/
+EXECUTE REMOVEDIR 8/85/
+EXECUTE REMOVEDIR 8/85/
+EXECUTE REMOVEDIR 8/84/
+EXECUTE REMOVEDIR 8/84/
+EXECUTE REMOVEDIR 8/83/
+EXECUTE REMOVEDIR 8/82/
+EXECUTE REMOVEDIR 8/81/
+EXECUTE REMOVEDIR 8/80/
+EXECUTE REMOVEDIR 8/80/
+EXECUTE REMOVEFILE 7/70/7xtest.exe
+EXECUTE REMOVEFILE 7/70/7xtext0
+EXECUTE REMOVEFILE 7/70/7xtext1
+EXECUTE REMOVEDIR 7/70/
+EXECUTE REMOVEFILE 7/71/7xtest.exe
+EXECUTE REMOVEFILE 7/71/7xtext0
+EXECUTE REMOVEFILE 7/71/7xtext1
+EXECUTE REMOVEDIR 7/71/
+EXECUTE REMOVEFILE 7/7text0
+EXECUTE REMOVEFILE 7/7text1
+EXECUTE REMOVEDIR 7/
+EXECUTE REMOVEDIR 6/
+EXECUTE REMOVEFILE 5/5text1
+EXECUTE REMOVEFILE 5/5text0
+EXECUTE REMOVEFILE 5/5test.exe
+EXECUTE REMOVEFILE 5/5text0
+file cannot be removed because it does not exist; skipping
+EXECUTE REMOVEFILE 5/5text1
+file cannot be removed because it does not exist; skipping
+EXECUTE REMOVEDIR 5/
+EXECUTE REMOVEFILE 4/4text1
+EXECUTE REMOVEFILE 4/4text0
+EXECUTE REMOVEDIR 4/
+EXECUTE REMOVEFILE 3/3text1
+EXECUTE REMOVEFILE 3/3text0
+EXECUTE REMOVEDIR 1/10/
+EXECUTE REMOVEDIR 1/
+FINISH ADD searchplugins/searchpluginstext0
+FINISH PATCH searchplugins/searchpluginspng1.png
+FINISH PATCH searchplugins/searchpluginspng0.png
+FINISH ADD precomplete
+FINISH PATCH exe0.exe
+FINISH ADD distribution/extensions/extensions1/extensions1text0
+FINISH PATCH distribution/extensions/extensions1/extensions1png1.png
+FINISH PATCH distribution/extensions/extensions1/extensions1png0.png
+FINISH ADD distribution/extensions/extensions0/extensions0text0
+FINISH PATCH distribution/extensions/extensions0/extensions0png1.png
+FINISH PATCH distribution/extensions/extensions0/extensions0png0.png
+FINISH PATCH 0/0exe0.exe
+FINISH ADD 0/00/00text0
+FINISH PATCH 0/00/00png0.png
+FINISH ADD 2/20/20text0
+FINISH ADD 2/20/20png0.png
+FINISH ADD 0/00/00text2
+FINISH REMOVEFILE 1/10/10text0
+FINISH REMOVEFILE 0/00/00text1
+FINISH REMOVEDIR 9/99/
+FINISH REMOVEDIR 9/99/
+directory no longer exists; skipping
+FINISH REMOVEDIR 9/98/
+FINISH REMOVEFILE 9/97/970/97xtext0
+FINISH REMOVEFILE 9/97/970/97xtext1
+FINISH REMOVEDIR 9/97/970/
+FINISH REMOVEFILE 9/97/971/97xtext0
+FINISH REMOVEFILE 9/97/971/97xtext1
+FINISH REMOVEDIR 9/97/971/
+FINISH REMOVEDIR 9/97/
+FINISH REMOVEFILE 9/96/96text0
+FINISH REMOVEFILE 9/96/96text1
+FINISH REMOVEDIR 9/96/
+FINISH REMOVEDIR 9/95/
+FINISH REMOVEDIR 9/95/
+directory no longer exists; skipping
+FINISH REMOVEDIR 9/94/
+FINISH REMOVEDIR 9/94/
+directory no longer exists; skipping
+FINISH REMOVEDIR 9/93/
+FINISH REMOVEDIR 9/92/
+removing directory: 9/92/, rv: 0
+FINISH REMOVEDIR 9/91/
+removing directory: 9/91/, rv: 0
+FINISH REMOVEDIR 9/90/
+FINISH REMOVEDIR 9/90/
+directory no longer exists; skipping
+FINISH REMOVEDIR 8/89/
+FINISH REMOVEDIR 8/89/
+directory no longer exists; skipping
+FINISH REMOVEDIR 8/88/
+FINISH REMOVEFILE 8/87/870/87xtext0
+FINISH REMOVEFILE 8/87/870/87xtext1
+FINISH REMOVEDIR 8/87/870/
+FINISH REMOVEFILE 8/87/871/87xtext0
+FINISH REMOVEFILE 8/87/871/87xtext1
+FINISH REMOVEDIR 8/87/871/
+FINISH REMOVEDIR 8/87/
+FINISH REMOVEFILE 8/86/86text0
+FINISH REMOVEFILE 8/86/86text1
+FINISH REMOVEDIR 8/86/
+FINISH REMOVEDIR 8/85/
+FINISH REMOVEDIR 8/85/
+directory no longer exists; skipping
+FINISH REMOVEDIR 8/84/
+FINISH REMOVEDIR 8/84/
+directory no longer exists; skipping
+FINISH REMOVEDIR 8/83/
+FINISH REMOVEDIR 8/82/
+removing directory: 8/82/, rv: 0
+FINISH REMOVEDIR 8/81/
+removing directory: 8/81/, rv: 0
+FINISH REMOVEDIR 8/80/
+FINISH REMOVEDIR 8/80/
+directory no longer exists; skipping
+FINISH REMOVEFILE 7/70/7xtest.exe
+FINISH REMOVEFILE 7/70/7xtext0
+FINISH REMOVEFILE 7/70/7xtext1
+FINISH REMOVEDIR 7/70/
+FINISH REMOVEFILE 7/71/7xtest.exe
+FINISH REMOVEFILE 7/71/7xtext0
+FINISH REMOVEFILE 7/71/7xtext1
+FINISH REMOVEDIR 7/71/
+FINISH REMOVEFILE 7/7text0
+FINISH REMOVEFILE 7/7text1
+FINISH REMOVEDIR 7/
+FINISH REMOVEDIR 6/
+FINISH REMOVEFILE 5/5text1
+FINISH REMOVEFILE 5/5text0
+FINISH REMOVEFILE 5/5test.exe
+FINISH REMOVEDIR 5/
+FINISH REMOVEFILE 4/4text1
+FINISH REMOVEFILE 4/4text0
+FINISH REMOVEDIR 4/
+FINISH REMOVEFILE 3/3text1
+FINISH REMOVEFILE 3/3text0
+FINISH REMOVEDIR 1/10/
+FINISH REMOVEDIR 1/
+succeeded
+calling QuitProgressUI
diff --git a/toolkit/mozapps/update/tests/data/partial_mac.mar b/toolkit/mozapps/update/tests/data/partial_mac.mar
new file mode 100644
index 0000000000..bcc04b9939
--- /dev/null
+++ b/toolkit/mozapps/update/tests/data/partial_mac.mar
Binary files differ
diff --git a/toolkit/mozapps/update/tests/data/partial_precomplete b/toolkit/mozapps/update/tests/data/partial_precomplete
new file mode 100644
index 0000000000..3ec201463a
--- /dev/null
+++ b/toolkit/mozapps/update/tests/data/partial_precomplete
@@ -0,0 +1,19 @@
+remove "searchplugins/searchpluginstext0"
+remove "searchplugins/searchpluginspng1.png"
+remove "searchplugins/searchpluginspng0.png"
+remove "removed-files"
+remove "precomplete"
+remove "exe0.exe"
+remove "2/20/20text0"
+remove "2/20/20png0.png"
+remove "0/0exe0.exe"
+remove "0/00/00text2"
+remove "0/00/00text0"
+remove "0/00/00png0.png"
+rmdir "searchplugins/"
+rmdir "defaults/pref/"
+rmdir "defaults/"
+rmdir "2/20/"
+rmdir "2/"
+rmdir "0/00/"
+rmdir "0/"
diff --git a/toolkit/mozapps/update/tests/data/partial_precomplete_mac b/toolkit/mozapps/update/tests/data/partial_precomplete_mac
new file mode 100644
index 0000000000..c65b6e4e38
--- /dev/null
+++ b/toolkit/mozapps/update/tests/data/partial_precomplete_mac
@@ -0,0 +1,22 @@
+remove "Contents/Resources/searchplugins/searchpluginstext0"
+remove "Contents/Resources/searchplugins/searchpluginspng1.png"
+remove "Contents/Resources/searchplugins/searchpluginspng0.png"
+remove "Contents/Resources/removed-files"
+remove "Contents/Resources/precomplete"
+remove "Contents/Resources/2/20/20text0"
+remove "Contents/Resources/2/20/20png0.png"
+remove "Contents/Resources/0/0exe0.exe"
+remove "Contents/Resources/0/00/00text2"
+remove "Contents/Resources/0/00/00text0"
+remove "Contents/Resources/0/00/00png0.png"
+remove "Contents/MacOS/exe0.exe"
+rmdir "Contents/Resources/searchplugins/"
+rmdir "Contents/Resources/defaults/pref/"
+rmdir "Contents/Resources/defaults/"
+rmdir "Contents/Resources/2/20/"
+rmdir "Contents/Resources/2/"
+rmdir "Contents/Resources/0/00/"
+rmdir "Contents/Resources/0/"
+rmdir "Contents/Resources/"
+rmdir "Contents/MacOS/"
+rmdir "Contents/"
diff --git a/toolkit/mozapps/update/tests/data/partial_removed-files b/toolkit/mozapps/update/tests/data/partial_removed-files
new file mode 100644
index 0000000000..881311b82c
--- /dev/null
+++ b/toolkit/mozapps/update/tests/data/partial_removed-files
@@ -0,0 +1,41 @@
+a/b/text0
+a/b/text1
+a/b/3/3text0
+a/b/3/3text1
+a/b/4/4exe0.exe
+a/b/4/4text0
+a/b/4/4text1
+a/b/4/
+a/b/5/5text0
+a/b/5/5text1
+a/b/5/*
+a/b/6/
+a/b/7/*
+a/b/8/80/
+a/b/8/81/
+a/b/8/82/
+a/b/8/83/
+a/b/8/84/
+a/b/8/85/*
+a/b/8/86/*
+a/b/8/87/*
+a/b/8/88/*
+a/b/8/89/*
+a/b/8/80/
+a/b/8/84/*
+a/b/8/85/*
+a/b/8/89/
+a/b/9/90/
+a/b/9/91/
+a/b/9/92/
+a/b/9/93/
+a/b/9/94/
+a/b/9/95/*
+a/b/9/96/*
+a/b/9/97/*
+a/b/9/98/*
+a/b/9/99/*
+a/b/9/90/
+a/b/9/94/*
+a/b/9/95/*
+a/b/9/99/
diff --git a/toolkit/mozapps/update/tests/data/partial_removed-files_mac b/toolkit/mozapps/update/tests/data/partial_removed-files_mac
new file mode 100644
index 0000000000..955dc5b340
--- /dev/null
+++ b/toolkit/mozapps/update/tests/data/partial_removed-files_mac
@@ -0,0 +1,41 @@
+Contents/Resources/text0
+Contents/Resources/text1
+Contents/Resources/3/3text0
+Contents/Resources/3/3text1
+Contents/Resources/4/exe0.exe
+Contents/Resources/4/4text0
+Contents/Resources/4/4text1
+Contents/Resources/4/
+Contents/Resources/5/5text0
+Contents/Resources/5/5text1
+Contents/Resources/5/*
+Contents/Resources/6/
+Contents/Resources/7/*
+Contents/Resources/8/80/
+Contents/Resources/8/81/
+Contents/Resources/8/82/
+Contents/Resources/8/83/
+Contents/Resources/8/84/
+Contents/Resources/8/85/*
+Contents/Resources/8/86/*
+Contents/Resources/8/87/*
+Contents/Resources/8/88/*
+Contents/Resources/8/89/*
+Contents/Resources/8/80/
+Contents/Resources/8/84/*
+Contents/Resources/8/85/*
+Contents/Resources/8/89/
+Contents/Resources/9/90/
+Contents/Resources/9/91/
+Contents/Resources/9/92/
+Contents/Resources/9/93/
+Contents/Resources/9/94/
+Contents/Resources/9/95/*
+Contents/Resources/9/96/*
+Contents/Resources/9/97/*
+Contents/Resources/9/98/*
+Contents/Resources/9/99/*
+Contents/Resources/9/90/
+Contents/Resources/9/94/*
+Contents/Resources/9/95/*
+Contents/Resources/9/99/
diff --git a/toolkit/mozapps/update/tests/data/partial_update_manifest b/toolkit/mozapps/update/tests/data/partial_update_manifest
new file mode 100644
index 0000000000..8d4e60ed25
--- /dev/null
+++ b/toolkit/mozapps/update/tests/data/partial_update_manifest
@@ -0,0 +1,63 @@
+type "partial"
+add "precomplete"
+add "a/b/searchplugins/searchpluginstext0"
+patch-if "a/b/searchplugins/searchpluginspng1.png" "a/b/searchplugins/searchpluginspng1.png.patch" "a/b/searchplugins/searchpluginspng1.png"
+patch-if "a/b/searchplugins/searchpluginspng0.png" "a/b/searchplugins/searchpluginspng0.png.patch" "a/b/searchplugins/searchpluginspng0.png"
+add-if "a/b/extensions/extensions1" "a/b/extensions/extensions1/extensions1text0"
+patch-if "a/b/extensions/extensions1" "a/b/extensions/extensions1/extensions1png1.png.patch" "a/b/extensions/extensions1/extensions1png1.png"
+patch-if "a/b/extensions/extensions1" "a/b/extensions/extensions1/extensions1png0.png.patch" "a/b/extensions/extensions1/extensions1png0.png"
+add-if "a/b/extensions/extensions0" "a/b/extensions/extensions0/extensions0text0"
+patch-if "a/b/extensions/extensions0" "a/b/extensions/extensions0/extensions0png1.png.patch" "a/b/extensions/extensions0/extensions0png1.png"
+patch-if "a/b/extensions/extensions0" "a/b/extensions/extensions0/extensions0png0.png.patch" "a/b/extensions/extensions0/extensions0png0.png"
+patch "a/b/exe0.exe.patch" "a/b/exe0.exe"
+patch "a/b/0/0exe0.exe.patch" "a/b/0/0exe0.exe"
+add "a/b/0/00/00text0"
+patch "a/b/0/00/00png0.png.patch" "a/b/0/00/00png0.png"
+add "a/b/2/20/20text0"
+add "a/b/2/20/20png0.png"
+add "a/b/0/00/00text2"
+remove "a/b/1/10/10text0"
+remove "a/b/0/00/00text1"
+remove "a/b/text1"
+remove "a/b/text0"
+rmrfdir "a/b/9/99/"
+rmdir "a/b/9/99/"
+rmrfdir "a/b/9/98/"
+rmrfdir "a/b/9/97/"
+rmrfdir "a/b/9/96/"
+rmrfdir "a/b/9/95/"
+rmrfdir "a/b/9/95/"
+rmrfdir "a/b/9/94/"
+rmdir "a/b/9/94/"
+rmdir "a/b/9/93/"
+rmdir "a/b/9/92/"
+rmdir "a/b/9/91/"
+rmdir "a/b/9/90/"
+rmdir "a/b/9/90/"
+rmrfdir "a/b/8/89/"
+rmdir "a/b/8/89/"
+rmrfdir "a/b/8/88/"
+rmrfdir "a/b/8/87/"
+rmrfdir "a/b/8/86/"
+rmrfdir "a/b/8/85/"
+rmrfdir "a/b/8/85/"
+rmrfdir "a/b/8/84/"
+rmdir "a/b/8/84/"
+rmdir "a/b/8/83/"
+rmdir "a/b/8/82/"
+rmdir "a/b/8/81/"
+rmdir "a/b/8/80/"
+rmdir "a/b/8/80/"
+rmrfdir "a/b/7/"
+rmdir "a/b/6/"
+remove "a/b/5/5text1"
+remove "a/b/5/5text0"
+rmrfdir "a/b/5/"
+remove "a/b/4/4text1"
+remove "a/b/4/4text0"
+remove "a/b/4/4exe0.exe"
+rmdir "a/b/4/"
+remove "a/b/3/3text1"
+remove "a/b/3/3text0"
+rmdir "a/b/1/10/"
+rmdir "a/b/1/"
diff --git a/toolkit/mozapps/update/tests/data/replace_log_success b/toolkit/mozapps/update/tests/data/replace_log_success
new file mode 100644
index 0000000000..323f1db41e
--- /dev/null
+++ b/toolkit/mozapps/update/tests/data/replace_log_success
@@ -0,0 +1,6 @@
+Performing a replace request
+rename_file: proceeding to rename the directory
+rename_file: proceeding to rename the directory
+Now, remove the tmpDir
+succeeded
+calling QuitProgressUI
diff --git a/toolkit/mozapps/update/tests/data/shared.js b/toolkit/mozapps/update/tests/data/shared.js
new file mode 100644
index 0000000000..bca14c8b89
--- /dev/null
+++ b/toolkit/mozapps/update/tests/data/shared.js
@@ -0,0 +1,928 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* Shared code for xpcshell, mochitests-chrome, and mochitest-browser-chrome. */
+
+// Definitions needed to run eslint on this file.
+/* global AppConstants, DATA_URI_SPEC, LOG_FUNCTION */
+/* global Services, URL_HOST, TestUtils */
+
+const { FileUtils } = ChromeUtils.importESModule(
+ "resource://gre/modules/FileUtils.sys.mjs"
+);
+const { XPCOMUtils } = ChromeUtils.importESModule(
+ "resource://gre/modules/XPCOMUtils.sys.mjs"
+);
+
+ChromeUtils.defineESModuleGetters(this, {
+ UpdateUtils: "resource://gre/modules/UpdateUtils.sys.mjs",
+ ctypes: "resource://gre/modules/ctypes.sys.mjs",
+});
+
+const PREF_APP_UPDATE_AUTO = "app.update.auto";
+const PREF_APP_UPDATE_BACKGROUNDERRORS = "app.update.backgroundErrors";
+const PREF_APP_UPDATE_BACKGROUNDMAXERRORS = "app.update.backgroundMaxErrors";
+const PREF_APP_UPDATE_BADGEWAITTIME = "app.update.badgeWaitTime";
+const PREF_APP_UPDATE_BITS_ENABLED = "app.update.BITS.enabled";
+const PREF_APP_UPDATE_CANCELATIONS = "app.update.cancelations";
+const PREF_APP_UPDATE_CHANNEL = "app.update.channel";
+const PREF_APP_UPDATE_DOWNLOAD_MAXATTEMPTS = "app.update.download.maxAttempts";
+const PREF_APP_UPDATE_DOWNLOAD_ATTEMPTS = "app.update.download.attempts";
+const PREF_APP_UPDATE_DISABLEDFORTESTING = "app.update.disabledForTesting";
+const PREF_APP_UPDATE_INTERVAL = "app.update.interval";
+const PREF_APP_UPDATE_LASTUPDATETIME =
+ "app.update.lastUpdateTime.background-update-timer";
+const PREF_APP_UPDATE_LOG = "app.update.log";
+const PREF_APP_UPDATE_NOTIFYDURINGDOWNLOAD = "app.update.notifyDuringDownload";
+const PREF_APP_UPDATE_PROMPTWAITTIME = "app.update.promptWaitTime";
+const PREF_APP_UPDATE_RETRYTIMEOUT = "app.update.socket.retryTimeout";
+const PREF_APP_UPDATE_SERVICE_ENABLED = "app.update.service.enabled";
+const PREF_APP_UPDATE_SOCKET_MAXERRORS = "app.update.socket.maxErrors";
+const PREF_APP_UPDATE_STAGING_ENABLED = "app.update.staging.enabled";
+const PREF_APP_UPDATE_UNSUPPORTED_URL = "app.update.unsupported.url";
+const PREF_APP_UPDATE_URL_DETAILS = "app.update.url.details";
+const PREF_APP_UPDATE_URL_MANUAL = "app.update.url.manual";
+const PREF_APP_UPDATE_LANGPACK_ENABLED = "app.update.langpack.enabled";
+
+const PREFBRANCH_APP_PARTNER = "app.partner.";
+const PREF_DISTRIBUTION_ID = "distribution.id";
+const PREF_DISTRIBUTION_VERSION = "distribution.version";
+
+const CONFIG_APP_UPDATE_AUTO = "app.update.auto";
+
+const NS_APP_PROFILE_DIR_STARTUP = "ProfDS";
+const NS_APP_USER_PROFILE_50_DIR = "ProfD";
+const NS_GRE_BIN_DIR = "GreBinD";
+const NS_GRE_DIR = "GreD";
+const NS_XPCOM_CURRENT_PROCESS_DIR = "XCurProcD";
+const XRE_EXECUTABLE_FILE = "XREExeF";
+const XRE_OLD_UPDATE_ROOT_DIR = "OldUpdRootD";
+const XRE_UPDATE_ROOT_DIR = "UpdRootD";
+
+const DIR_PATCH = "0";
+const DIR_TOBEDELETED = "tobedeleted";
+const DIR_UPDATES = "updates";
+const DIR_UPDATED =
+ AppConstants.platform == "macosx" ? "Updated.app" : "updated";
+const DIR_DOWNLOADING = "downloading";
+
+const FILE_ACTIVE_UPDATE_XML = "active-update.xml";
+const FILE_ACTIVE_UPDATE_XML_TMP = "active-update.xml.tmp";
+const FILE_APPLICATION_INI = "application.ini";
+const FILE_BACKUP_UPDATE_CONFIG_JSON = "backup-update-config.json";
+const FILE_BACKUP_UPDATE_LOG = "backup-update.log";
+const FILE_BT_RESULT = "bt.result";
+const FILE_LAST_UPDATE_LOG = "last-update.log";
+const FILE_PRECOMPLETE = "precomplete";
+const FILE_PRECOMPLETE_BAK = "precomplete.bak";
+const FILE_UPDATE_CONFIG_JSON = "update-config.json";
+const FILE_UPDATE_LOG = "update.log";
+const FILE_UPDATE_MAR = "update.mar";
+const FILE_UPDATE_SETTINGS_INI = "update-settings.ini";
+const FILE_UPDATE_SETTINGS_INI_BAK = "update-settings.ini.bak";
+const FILE_UPDATE_STATUS = "update.status";
+const FILE_UPDATE_TEST = "update.test";
+const FILE_UPDATE_VERSION = "update.version";
+const FILE_UPDATER_INI = "updater.ini";
+const FILE_UPDATES_XML = "updates.xml";
+const FILE_UPDATES_XML_TMP = "updates.xml.tmp";
+
+const UPDATE_SETTINGS_CONTENTS =
+ "[Settings]\nACCEPTED_MAR_CHANNEL_IDS=xpcshell-test\n";
+const PRECOMPLETE_CONTENTS = 'rmdir "nonexistent_dir/"\n';
+
+const PR_RDWR = 0x04;
+const PR_CREATE_FILE = 0x08;
+const PR_TRUNCATE = 0x20;
+
+var gChannel;
+var gDebugTest = false;
+
+/* import-globals-from sharedUpdateXML.js */
+Services.scriptloader.loadSubScript(DATA_URI_SPEC + "sharedUpdateXML.js", this);
+
+const PERMS_FILE = FileUtils.PERMS_FILE;
+const PERMS_DIRECTORY = FileUtils.PERMS_DIRECTORY;
+
+const MODE_WRONLY = FileUtils.MODE_WRONLY;
+const MODE_CREATE = FileUtils.MODE_CREATE;
+const MODE_APPEND = FileUtils.MODE_APPEND;
+const MODE_TRUNCATE = FileUtils.MODE_TRUNCATE;
+
+const URI_UPDATES_PROPERTIES =
+ "chrome://mozapps/locale/update/updates.properties";
+const gUpdateBundle = Services.strings.createBundle(URI_UPDATES_PROPERTIES);
+
+XPCOMUtils.defineLazyGetter(this, "gAUS", function test_gAUS() {
+ return Cc["@mozilla.org/updates/update-service;1"]
+ .getService(Ci.nsIApplicationUpdateService)
+ .QueryInterface(Ci.nsITimerCallback)
+ .QueryInterface(Ci.nsIObserver);
+});
+
+XPCOMUtils.defineLazyServiceGetter(
+ this,
+ "gUpdateManager",
+ "@mozilla.org/updates/update-manager;1",
+ "nsIUpdateManager"
+);
+
+XPCOMUtils.defineLazyServiceGetter(
+ this,
+ "gUpdateChecker",
+ "@mozilla.org/updates/update-checker;1",
+ "nsIUpdateChecker"
+);
+
+XPCOMUtils.defineLazyGetter(this, "gDefaultPrefBranch", function test_gDPB() {
+ return Services.prefs.getDefaultBranch(null);
+});
+
+XPCOMUtils.defineLazyGetter(this, "gPrefRoot", function test_gPR() {
+ return Services.prefs.getBranch(null);
+});
+
+/**
+ * Waits for the specified topic and (optionally) status.
+ *
+ * @param topic
+ * String representing the topic to wait for.
+ * @param status (optional)
+ * A string representing the status on said topic to wait for.
+ * @return A promise which will resolve the first time an event occurs on the
+ * specified topic, and (optionally) with the specified status.
+ */
+function waitForEvent(topic, status = null) {
+ return new Promise(resolve =>
+ Services.obs.addObserver(
+ {
+ observe(subject, innerTopic, innerStatus) {
+ if (!status || status == innerStatus) {
+ Services.obs.removeObserver(this, topic);
+ resolve(innerStatus);
+ }
+ },
+ },
+ topic
+ )
+ );
+}
+
+/* Triggers post-update processing */
+function testPostUpdateProcessing() {
+ gAUS.observe(null, "test-post-update-processing", "");
+}
+
+/* Initializes the update service stub */
+function initUpdateServiceStub() {
+ Cc["@mozilla.org/updates/update-service-stub;1"].createInstance(
+ Ci.nsISupports
+ );
+}
+
+/**
+ * Reloads the update xml files.
+ *
+ * @param skipFiles (optional)
+ * If true, the update xml files will not be read and the metadata will
+ * be reset. If false (the default), the update xml files will be read
+ * to populate the update metadata.
+ */
+function reloadUpdateManagerData(skipFiles = false) {
+ let observeData = skipFiles ? "skip-files" : "";
+ gUpdateManager
+ .QueryInterface(Ci.nsIObserver)
+ .observe(null, "um-reload-update-data", observeData);
+}
+
+const observer = {
+ observe(aSubject, aTopic, aData) {
+ if (aTopic == "nsPref:changed" && aData == PREF_APP_UPDATE_CHANNEL) {
+ let channel = gDefaultPrefBranch.getCharPref(PREF_APP_UPDATE_CHANNEL);
+ if (channel != gChannel) {
+ debugDump("Changing channel from " + channel + " to " + gChannel);
+ gDefaultPrefBranch.setCharPref(PREF_APP_UPDATE_CHANNEL, gChannel);
+ }
+ }
+ },
+ QueryInterface: ChromeUtils.generateQI(["nsIObserver"]),
+};
+
+/**
+ * Sets the app.update.channel preference.
+ *
+ * @param aChannel
+ * The update channel.
+ */
+function setUpdateChannel(aChannel) {
+ gChannel = aChannel;
+ debugDump(
+ "setting default pref " + PREF_APP_UPDATE_CHANNEL + " to " + gChannel
+ );
+ gDefaultPrefBranch.setCharPref(PREF_APP_UPDATE_CHANNEL, gChannel);
+ gPrefRoot.addObserver(PREF_APP_UPDATE_CHANNEL, observer);
+}
+
+/**
+ * Sets the effective update url.
+ *
+ * @param aURL
+ * The update url. If not specified 'URL_HOST + "/update.xml"' will be
+ * used.
+ */
+function setUpdateURL(aURL) {
+ let url = aURL ? aURL : URL_HOST + "/update.xml";
+ debugDump("setting update URL to " + url);
+
+ // The Update URL is stored in appinfo. We can replace this process's appinfo
+ // directly, but that will affect only this process. Luckily, the update URL
+ // is only ever read from the update process. This means that replacing
+ // Services.appinfo is sufficient and we don't need to worry about registering
+ // a replacement factory or anything like that.
+ let origAppInfo = Services.appinfo;
+ registerCleanupFunction(() => {
+ Services.appinfo = origAppInfo;
+ });
+
+ // Override the appinfo object with an object that exposes all of the same
+ // properties overriding just the updateURL.
+ let mockAppInfo = Object.create(origAppInfo, {
+ updateURL: {
+ configurable: true,
+ enumerable: true,
+ writable: false,
+ value: url,
+ },
+ });
+
+ Services.appinfo = mockAppInfo;
+}
+
+/**
+ * Writes the updates specified to either the active-update.xml or the
+ * updates.xml.
+ *
+ * @param aContent
+ * The updates represented as a string to write to the XML file.
+ * @param isActiveUpdate
+ * If true this will write to the active-update.xml otherwise it will
+ * write to the updates.xml file.
+ */
+function writeUpdatesToXMLFile(aContent, aIsActiveUpdate) {
+ let file = getUpdateDirFile(
+ aIsActiveUpdate ? FILE_ACTIVE_UPDATE_XML : FILE_UPDATES_XML
+ );
+ writeFile(file, aContent);
+}
+
+/**
+ * Writes the current update operation/state to a file in the patch
+ * directory, indicating to the patching system that operations need
+ * to be performed.
+ *
+ * @param aStatus
+ * The status value to write.
+ */
+function writeStatusFile(aStatus) {
+ let file = getUpdateDirFile(FILE_UPDATE_STATUS);
+ writeFile(file, aStatus + "\n");
+}
+
+/**
+ * Writes the current update version to a file in the patch directory,
+ * indicating to the patching system the version of the update.
+ *
+ * @param aVersion
+ * The version value to write.
+ */
+function writeVersionFile(aVersion) {
+ let file = getUpdateDirFile(FILE_UPDATE_VERSION);
+ writeFile(file, aVersion + "\n");
+}
+
+/**
+ * Writes text to a file. This will replace existing text if the file exists
+ * and create the file if it doesn't exist.
+ *
+ * @param aFile
+ * The file to write to. Will be created if it doesn't exist.
+ * @param aText
+ * The text to write to the file. If there is existing text it will be
+ * replaced.
+ */
+function writeFile(aFile, aText) {
+ let fos = Cc["@mozilla.org/network/file-output-stream;1"].createInstance(
+ Ci.nsIFileOutputStream
+ );
+ if (!aFile.exists()) {
+ aFile.create(Ci.nsIFile.NORMAL_FILE_TYPE, PERMS_FILE);
+ }
+ fos.init(aFile, MODE_WRONLY | MODE_CREATE | MODE_TRUNCATE, PERMS_FILE, 0);
+ fos.write(aText, aText.length);
+ fos.close();
+}
+
+/**
+ * Reads the current update operation/state in the status file in the patch
+ * directory including the error code if it is present.
+ *
+ * @return The status value.
+ */
+function readStatusFile() {
+ let file = getUpdateDirFile(FILE_UPDATE_STATUS);
+ if (!file.exists()) {
+ debugDump("update status file does not exists! Path: " + file.path);
+ return STATE_NONE;
+ }
+ return readFile(file).split("\n")[0];
+}
+
+/**
+ * Reads the current update operation/state in the status file in the patch
+ * directory without the error code if it is present.
+ *
+ * @return The state value.
+ */
+function readStatusState() {
+ return readStatusFile().split(": ")[0];
+}
+
+/**
+ * Reads the current update operation/state in the status file in the patch
+ * directory with the error code.
+ *
+ * @return The state value.
+ */
+function readStatusFailedCode() {
+ return readStatusFile().split(": ")[1];
+}
+
+/**
+ * Returns whether or not applying the current update resulted in an error
+ * verifying binary transparency information.
+ *
+ * @return true if there was an error result and false otherwise
+ */
+function updateHasBinaryTransparencyErrorResult() {
+ let file = getUpdateDirFile(FILE_BT_RESULT);
+ return file.exists();
+}
+
+/**
+ * Reads text from a file and returns the string.
+ *
+ * @param aFile
+ * The file to read from.
+ * @return The string of text read from the file.
+ */
+function readFile(aFile) {
+ let fis = Cc["@mozilla.org/network/file-input-stream;1"].createInstance(
+ Ci.nsIFileInputStream
+ );
+ if (!aFile.exists()) {
+ return null;
+ }
+ // Specifying -1 for ioFlags will open the file with the default of PR_RDONLY.
+ // Specifying -1 for perm will open the file with the default of 0.
+ fis.init(aFile, -1, -1, Ci.nsIFileInputStream.CLOSE_ON_EOF);
+ let sis = Cc["@mozilla.org/scriptableinputstream;1"].createInstance(
+ Ci.nsIScriptableInputStream
+ );
+ sis.init(fis);
+ let text = sis.read(sis.available());
+ sis.close();
+ return text;
+}
+
+/* Returns human readable status text from the updates.properties bundle */
+function getStatusText(aErrCode) {
+ return getString("check_error-" + aErrCode);
+}
+
+/* Returns a string from the updates.properties bundle */
+function getString(aName) {
+ try {
+ return gUpdateBundle.GetStringFromName(aName);
+ } catch (e) {}
+ return null;
+}
+
+/**
+ * Gets the file extension for an nsIFile.
+ *
+ * @param aFile
+ * The file to get the file extension for.
+ * @return The file extension.
+ */
+function getFileExtension(aFile) {
+ return Services.io.newFileURI(aFile).QueryInterface(Ci.nsIURL).fileExtension;
+}
+
+/**
+ * Gets the specified update file or directory.
+ *
+ * @param aLogLeafName
+ * The leafName of the file or directory to get.
+ * @param aWhichDir
+ * Since we started having a separate patch directory and downloading
+ * directory, there are now files with the same name that can be in
+ * either directory. This argument is optional and defaults to the
+ * patch directory for historical reasons. But if it is specified as
+ * DIR_DOWNLOADING, this function will provide the version of the file
+ * in the downloading directory. For files that aren't in the patch
+ * directory or the downloading directory, this value is ignored.
+ * @return nsIFile for the file or directory.
+ */
+function getUpdateDirFile(aLeafName, aWhichDir = null) {
+ let file = Services.dirsvc.get(XRE_UPDATE_ROOT_DIR, Ci.nsIFile);
+ switch (aLeafName) {
+ case undefined:
+ return file;
+ case DIR_UPDATES:
+ case FILE_ACTIVE_UPDATE_XML:
+ case FILE_ACTIVE_UPDATE_XML_TMP:
+ case FILE_UPDATE_CONFIG_JSON:
+ case FILE_BACKUP_UPDATE_CONFIG_JSON:
+ case FILE_UPDATE_TEST:
+ case FILE_UPDATES_XML:
+ case FILE_UPDATES_XML_TMP:
+ file.append(aLeafName);
+ return file;
+ case DIR_PATCH:
+ case DIR_DOWNLOADING:
+ case FILE_BACKUP_UPDATE_LOG:
+ case FILE_LAST_UPDATE_LOG:
+ file.append(DIR_UPDATES);
+ file.append(aLeafName);
+ return file;
+ case FILE_BT_RESULT:
+ case FILE_UPDATE_LOG:
+ case FILE_UPDATE_MAR:
+ case FILE_UPDATE_STATUS:
+ case FILE_UPDATE_VERSION:
+ case FILE_UPDATER_INI:
+ file.append(DIR_UPDATES);
+ if (aWhichDir == DIR_DOWNLOADING) {
+ file.append(DIR_DOWNLOADING);
+ } else {
+ file.append(DIR_PATCH);
+ }
+ file.append(aLeafName);
+ return file;
+ }
+
+ throw new Error(
+ "The leafName specified is not handled by this function, " +
+ "leafName: " +
+ aLeafName
+ );
+}
+
+/**
+ * Helper function for getting the nsIFile for a file in the directory where the
+ * update will be staged.
+ *
+ * The files for the update are located two directories below the stage
+ * directory since Mac OS X sets the last modified time for the root directory
+ * to the current time and if the update changes any files in the root directory
+ * then it wouldn't be possible to test (bug 600098).
+ *
+ * @param aRelPath (optional)
+ * The relative path to the file or directory to get from the root of
+ * the stage directory. If not specified the stage directory will be
+ * returned.
+ * @return The nsIFile for the file in the directory where the update will be
+ * staged.
+ */
+function getStageDirFile(aRelPath) {
+ let file;
+ if (AppConstants.platform == "macosx") {
+ file = getUpdateDirFile(DIR_PATCH);
+ } else {
+ file = getGREBinDir();
+ }
+ file.append(DIR_UPDATED);
+ if (aRelPath) {
+ let pathParts = aRelPath.split("/");
+ for (let i = 0; i < pathParts.length; i++) {
+ if (pathParts[i]) {
+ file.append(pathParts[i]);
+ }
+ }
+ }
+ return file;
+}
+
+/**
+ * Removes the update files that typically need to be removed by tests without
+ * removing the directories since removing the directories has caused issues
+ * when running tests with --verify and recursively removes the stage directory.
+ *
+ * @param aRemoveLogFiles
+ * When true the update log files will also be removed. This allows
+ * for the inspection of the log files while troubleshooting tests.
+ */
+function removeUpdateFiles(aRemoveLogFiles) {
+ let files = [
+ [FILE_ACTIVE_UPDATE_XML],
+ [FILE_UPDATES_XML],
+ [FILE_BT_RESULT],
+ [FILE_UPDATE_STATUS],
+ [FILE_UPDATE_VERSION],
+ [FILE_UPDATE_MAR],
+ [FILE_UPDATE_MAR, DIR_DOWNLOADING],
+ [FILE_UPDATER_INI],
+ ];
+
+ if (aRemoveLogFiles) {
+ files = files.concat([
+ [FILE_BACKUP_UPDATE_LOG],
+ [FILE_LAST_UPDATE_LOG],
+ [FILE_UPDATE_LOG],
+ ]);
+ }
+
+ for (let i = 0; i < files.length; i++) {
+ let file = getUpdateDirFile.apply(null, files[i]);
+ try {
+ if (file.exists()) {
+ file.remove(false);
+ }
+ } catch (e) {
+ logTestInfo(
+ "Unable to remove file. Path: " + file.path + ", Exception: " + e
+ );
+ }
+ }
+
+ let stageDir = getStageDirFile();
+ if (stageDir.exists()) {
+ try {
+ removeDirRecursive(stageDir);
+ } catch (e) {
+ logTestInfo(
+ "Unable to remove directory. Path: " +
+ stageDir.path +
+ ", Exception: " +
+ e
+ );
+ }
+ }
+}
+
+/**
+ * Deletes a directory and its children. First it tries nsIFile::Remove(true).
+ * If that fails it will fall back to recursing, setting the appropriate
+ * permissions, and deleting the current entry.
+ *
+ * @param aDir
+ * nsIFile for the directory to be deleted.
+ */
+function removeDirRecursive(aDir) {
+ if (!aDir.exists()) {
+ return;
+ }
+
+ if (!aDir.isDirectory()) {
+ throw new Error("Only a directory can be passed to this funtion!");
+ }
+
+ try {
+ debugDump("attempting to remove directory. Path: " + aDir.path);
+ aDir.remove(true);
+ return;
+ } catch (e) {
+ logTestInfo("non-fatal error removing directory. Exception: " + e);
+ }
+
+ let dirEntries = aDir.directoryEntries;
+ while (dirEntries.hasMoreElements()) {
+ let entry = dirEntries.nextFile;
+
+ if (entry.isDirectory()) {
+ removeDirRecursive(entry);
+ } else {
+ entry.permissions = PERMS_FILE;
+ try {
+ debugDump("attempting to remove file. Path: " + entry.path);
+ entry.remove(false);
+ } catch (e) {
+ logTestInfo("error removing file. Exception: " + e);
+ throw e;
+ }
+ }
+ }
+
+ aDir.permissions = PERMS_DIRECTORY;
+ try {
+ debugDump("attempting to remove directory. Path: " + aDir.path);
+ aDir.remove(true);
+ } catch (e) {
+ logTestInfo("error removing directory. Exception: " + e);
+ throw e;
+ }
+}
+
+/**
+ * Returns the directory for the currently running process. This is used to
+ * clean up after the tests and to locate the active-update.xml and updates.xml
+ * files.
+ *
+ * @return nsIFile for the current process directory.
+ */
+function getCurrentProcessDir() {
+ return Services.dirsvc.get(NS_XPCOM_CURRENT_PROCESS_DIR, Ci.nsIFile);
+}
+
+/**
+ * Returns the Gecko Runtime Engine directory where files other than executable
+ * binaries are located. On Mac OS X this will be <bundle>/Contents/Resources/
+ * and the installation directory on all other platforms.
+ *
+ * @return nsIFile for the Gecko Runtime Engine directory.
+ */
+function getGREDir() {
+ return Services.dirsvc.get(NS_GRE_DIR, Ci.nsIFile);
+}
+
+/**
+ * Returns the Gecko Runtime Engine Binary directory where the executable
+ * binaries are located such as the updater binary (Windows and Linux) or
+ * updater package (Mac OS X). On Mac OS X this will be
+ * <bundle>/Contents/MacOS/ and the installation directory on all other
+ * platforms.
+ *
+ * @return nsIFile for the Gecko Runtime Engine Binary directory.
+ */
+function getGREBinDir() {
+ return Services.dirsvc.get(NS_GRE_BIN_DIR, Ci.nsIFile);
+}
+
+/**
+ * Gets the unique mutex name for the installation.
+ *
+ * @return Global mutex path.
+ * @throws If the function is called on a platform other than Windows.
+ */
+function getPerInstallationMutexName() {
+ if (AppConstants.platform != "win") {
+ throw new Error("Windows only function called by a different platform!");
+ }
+
+ let hasher = Cc["@mozilla.org/security/hash;1"].createInstance(
+ Ci.nsICryptoHash
+ );
+ hasher.init(hasher.SHA1);
+
+ let exeFile = Services.dirsvc.get(XRE_EXECUTABLE_FILE, Ci.nsIFile);
+ let converter = Cc[
+ "@mozilla.org/intl/scriptableunicodeconverter"
+ ].createInstance(Ci.nsIScriptableUnicodeConverter);
+ converter.charset = "UTF-8";
+ let data = converter.convertToByteArray(exeFile.path.toLowerCase());
+
+ hasher.update(data, data.length);
+ return "Global\\MozillaUpdateMutex-" + hasher.finish(true);
+}
+
+/**
+ * Closes a Win32 handle.
+ *
+ * @param aHandle
+ * The handle to close.
+ * @throws If the function is called on a platform other than Windows.
+ */
+function closeHandle(aHandle) {
+ if (AppConstants.platform != "win") {
+ throw new Error("Windows only function called by a different platform!");
+ }
+
+ let lib = ctypes.open("kernel32.dll");
+ let CloseHandle = lib.declare(
+ "CloseHandle",
+ ctypes.winapi_abi,
+ ctypes.int32_t /* success */,
+ ctypes.void_t.ptr
+ ); /* handle */
+ CloseHandle(aHandle);
+ lib.close();
+}
+
+/**
+ * Creates a mutex.
+ *
+ * @param aName
+ * The name for the mutex.
+ * @return The Win32 handle to the mutex.
+ * @throws If the function is called on a platform other than Windows.
+ */
+function createMutex(aName) {
+ if (AppConstants.platform != "win") {
+ throw new Error("Windows only function called by a different platform!");
+ }
+
+ const INITIAL_OWN = 1;
+ const ERROR_ALREADY_EXISTS = 0xb7;
+ let lib = ctypes.open("kernel32.dll");
+ let CreateMutexW = lib.declare(
+ "CreateMutexW",
+ ctypes.winapi_abi,
+ ctypes.void_t.ptr /* return handle */,
+ ctypes.void_t.ptr /* security attributes */,
+ ctypes.int32_t /* initial owner */,
+ ctypes.char16_t.ptr
+ ); /* name */
+
+ let handle = CreateMutexW(null, INITIAL_OWN, aName);
+ lib.close();
+ let alreadyExists = ctypes.winLastError == ERROR_ALREADY_EXISTS;
+ if (handle && !handle.isNull() && alreadyExists) {
+ closeHandle(handle);
+ handle = null;
+ }
+
+ if (handle && handle.isNull()) {
+ handle = null;
+ }
+
+ return handle;
+}
+
+/**
+ * Synchronously writes the value of the app.update.auto setting to the update
+ * configuration file on Windows or to a user preference on other platforms.
+ * When the value passed to this function is null or undefined it will remove
+ * the configuration file on Windows or the user preference on other platforms.
+ *
+ * @param aEnabled
+ * Possible values are true, false, null, and undefined. When true or
+ * false this value will be written for app.update.auto in the update
+ * configuration file on Windows or to the user preference on other
+ * platforms. When null or undefined the update configuration file will
+ * be removed on Windows or the user preference will be removed on other
+ * platforms.
+ */
+function setAppUpdateAutoSync(aEnabled) {
+ if (AppConstants.platform == "win") {
+ let file = getUpdateDirFile(FILE_UPDATE_CONFIG_JSON);
+ if (aEnabled === undefined || aEnabled === null) {
+ if (file.exists()) {
+ file.remove(false);
+ }
+ } else {
+ writeFile(
+ file,
+ '{"' + CONFIG_APP_UPDATE_AUTO + '":' + aEnabled.toString() + "}"
+ );
+ }
+ } else if (aEnabled === undefined || aEnabled === null) {
+ if (Services.prefs.prefHasUserValue(PREF_APP_UPDATE_AUTO)) {
+ Services.prefs.clearUserPref(PREF_APP_UPDATE_AUTO);
+ }
+ } else {
+ Services.prefs.setBoolPref(PREF_APP_UPDATE_AUTO, aEnabled);
+ }
+}
+
+/**
+ * Logs TEST-INFO messages.
+ *
+ * @param aText
+ * The text to log.
+ * @param aCaller (optional)
+ * An optional Components.stack.caller. If not specified
+ * Components.stack.caller will be used.
+ */
+function logTestInfo(aText, aCaller) {
+ let caller = aCaller ? aCaller : Components.stack.caller;
+ let now = new Date();
+ let hh = now.getHours();
+ let mm = now.getMinutes();
+ let ss = now.getSeconds();
+ let ms = now.getMilliseconds();
+ let time =
+ (hh < 10 ? "0" + hh : hh) +
+ ":" +
+ (mm < 10 ? "0" + mm : mm) +
+ ":" +
+ (ss < 10 ? "0" + ss : ss) +
+ ":";
+ if (ms < 10) {
+ time += "00";
+ } else if (ms < 100) {
+ time += "0";
+ }
+ time += ms;
+ let msg =
+ time +
+ " | TEST-INFO | " +
+ caller.filename +
+ " | [" +
+ caller.name +
+ " : " +
+ caller.lineNumber +
+ "] " +
+ aText;
+ LOG_FUNCTION(msg);
+}
+
+/**
+ * Logs TEST-INFO messages when gDebugTest evaluates to true.
+ *
+ * @param aText
+ * The text to log.
+ * @param aCaller (optional)
+ * An optional Components.stack.caller. If not specified
+ * Components.stack.caller will be used.
+ */
+function debugDump(aText, aCaller) {
+ if (gDebugTest) {
+ let caller = aCaller ? aCaller : Components.stack.caller;
+ logTestInfo(aText, caller);
+ }
+}
+
+/**
+ * Creates the continue file used to signal that update staging or the mock http
+ * server should continue. The delay this creates allows the tests to verify the
+ * user interfaces before they auto advance to other phases of an update. The
+ * continue file for staging will be deleted by the test updater and the
+ * continue file for the update check and update download requests will be
+ * deleted by the test http server handler implemented in app_update.sjs. The
+ * test returns a promise so the test can wait on the deletion of the continue
+ * file when necessary. If the continue file still exists at the end of a test
+ * it will be removed to prevent it from affecting tests that run after the test
+ * that created it.
+ *
+ * @param leafName
+ * The leafName of the file to create. This should be one of the
+ * folowing constants that are defined in testConstants.js:
+ * CONTINUE_CHECK
+ * CONTINUE_DOWNLOAD
+ * CONTINUE_STAGING
+ * @return Promise
+ * Resolves when the file is deleted or if the file is not deleted when
+ * the check for the file's existence times out. If the file isn't
+ * deleted before the check for the file's existence times out it will
+ * be deleted when the test ends so it doesn't affect tests that run
+ * after the test that created the continue file.
+ * @throws If the file already exists.
+ */
+async function continueFileHandler(leafName) {
+ // The total time to wait with 300 retries and the default interval of 100 is
+ // approximately 30 seconds.
+ let interval = 100;
+ let retries = 300;
+ let continueFile;
+ if (leafName == CONTINUE_STAGING) {
+ // The total time to wait with 600 retries and an interval of 200 is
+ // approximately 120 seconds.
+ interval = 200;
+ retries = 600;
+ continueFile = getGREBinDir();
+ if (AppConstants.platform == "macosx") {
+ continueFile = continueFile.parent.parent;
+ }
+ continueFile.append(leafName);
+ } else {
+ continueFile = Services.dirsvc.get("CurWorkD", Ci.nsIFile);
+ let continuePath = REL_PATH_DATA + leafName;
+ let continuePathParts = continuePath.split("/");
+ for (let i = 0; i < continuePathParts.length; ++i) {
+ continueFile.append(continuePathParts[i]);
+ }
+ }
+ if (continueFile.exists()) {
+ logTestInfo(
+ "The continue file should not exist, path: " + continueFile.path
+ );
+ continueFile.remove(false);
+ }
+ debugDump("Creating continue file, path: " + continueFile.path);
+ continueFile.create(Ci.nsIFile.NORMAL_FILE_TYPE, PERMS_FILE);
+ // If for whatever reason the continue file hasn't been removed when a test
+ // has finished remove it during cleanup so it doesn't affect tests that run
+ // after the test that created it.
+ registerCleanupFunction(() => {
+ if (continueFile.exists()) {
+ logTestInfo(
+ "Removing continue file during test cleanup, path: " + continueFile.path
+ );
+ continueFile.remove(false);
+ }
+ });
+ return TestUtils.waitForCondition(
+ () => !continueFile.exists(),
+ "Waiting for file to be deleted, path: " + continueFile.path,
+ interval,
+ retries
+ ).catch(e => {
+ logTestInfo(
+ "Continue file was not removed after checking " +
+ retries +
+ " times, path: " +
+ continueFile.path
+ );
+ });
+}
diff --git a/toolkit/mozapps/update/tests/data/sharedUpdateXML.js b/toolkit/mozapps/update/tests/data/sharedUpdateXML.js
new file mode 100644
index 0000000000..acff4aec3f
--- /dev/null
+++ b/toolkit/mozapps/update/tests/data/sharedUpdateXML.js
@@ -0,0 +1,417 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+/**
+ * Shared code for xpcshell, mochitests-chrome, mochitest-browser-chrome, and
+ * SJS server-side scripts for the test http server.
+ */
+
+/**
+ * Helper functions for creating xml strings used by application update tests.
+ */
+
+/* import-globals-from ../browser/testConstants.js */
+
+/* global Services, UpdateUtils, gURLData */
+
+const FILE_SIMPLE_MAR = "simple.mar";
+const SIZE_SIMPLE_MAR = "1419";
+
+const STATE_NONE = "null";
+const STATE_DOWNLOADING = "downloading";
+const STATE_PENDING = "pending";
+const STATE_PENDING_SVC = "pending-service";
+const STATE_PENDING_ELEVATE = "pending-elevate";
+const STATE_APPLYING = "applying";
+const STATE_APPLIED = "applied";
+const STATE_APPLIED_SVC = "applied-service";
+const STATE_SUCCEEDED = "succeeded";
+const STATE_DOWNLOAD_FAILED = "download-failed";
+const STATE_FAILED = "failed";
+
+const LOADSOURCE_ERROR_WRONG_SIZE = 2;
+const CRC_ERROR = 4;
+const READ_ERROR = 6;
+const WRITE_ERROR = 7;
+const MAR_CHANNEL_MISMATCH_ERROR = 22;
+const VERSION_DOWNGRADE_ERROR = 23;
+const UPDATE_SETTINGS_FILE_CHANNEL = 38;
+const SERVICE_COULD_NOT_COPY_UPDATER = 49;
+const SERVICE_INVALID_APPLYTO_DIR_STAGED_ERROR = 52;
+const SERVICE_INVALID_APPLYTO_DIR_ERROR = 54;
+const SERVICE_INVALID_INSTALL_DIR_PATH_ERROR = 55;
+const SERVICE_INVALID_WORKING_DIR_PATH_ERROR = 56;
+const INVALID_APPLYTO_DIR_STAGED_ERROR = 72;
+const INVALID_APPLYTO_DIR_ERROR = 74;
+const INVALID_INSTALL_DIR_PATH_ERROR = 75;
+const INVALID_WORKING_DIR_PATH_ERROR = 76;
+const INVALID_CALLBACK_PATH_ERROR = 77;
+const INVALID_CALLBACK_DIR_ERROR = 78;
+
+// Error codes 80 through 99 are reserved for nsUpdateService.js and are not
+// defined in common/updatererrors.h
+const ERR_OLDER_VERSION_OR_SAME_BUILD = 90;
+const ERR_UPDATE_STATE_NONE = 91;
+const ERR_CHANNEL_CHANGE = 92;
+
+const WRITE_ERROR_BACKGROUND_TASK_SHARING_VIOLATION = 106;
+
+const STATE_FAILED_DELIMETER = ": ";
+
+const STATE_FAILED_LOADSOURCE_ERROR_WRONG_SIZE =
+ STATE_FAILED + STATE_FAILED_DELIMETER + LOADSOURCE_ERROR_WRONG_SIZE;
+const STATE_FAILED_CRC_ERROR =
+ STATE_FAILED + STATE_FAILED_DELIMETER + CRC_ERROR;
+const STATE_FAILED_READ_ERROR =
+ STATE_FAILED + STATE_FAILED_DELIMETER + READ_ERROR;
+const STATE_FAILED_WRITE_ERROR =
+ STATE_FAILED + STATE_FAILED_DELIMETER + WRITE_ERROR;
+const STATE_FAILED_MAR_CHANNEL_MISMATCH_ERROR =
+ STATE_FAILED + STATE_FAILED_DELIMETER + MAR_CHANNEL_MISMATCH_ERROR;
+const STATE_FAILED_VERSION_DOWNGRADE_ERROR =
+ STATE_FAILED + STATE_FAILED_DELIMETER + VERSION_DOWNGRADE_ERROR;
+const STATE_FAILED_UPDATE_SETTINGS_FILE_CHANNEL =
+ STATE_FAILED + STATE_FAILED_DELIMETER + UPDATE_SETTINGS_FILE_CHANNEL;
+const STATE_FAILED_SERVICE_COULD_NOT_COPY_UPDATER =
+ STATE_FAILED + STATE_FAILED_DELIMETER + SERVICE_COULD_NOT_COPY_UPDATER;
+const STATE_FAILED_SERVICE_INVALID_APPLYTO_DIR_STAGED_ERROR =
+ STATE_FAILED +
+ STATE_FAILED_DELIMETER +
+ SERVICE_INVALID_APPLYTO_DIR_STAGED_ERROR;
+const STATE_FAILED_SERVICE_INVALID_APPLYTO_DIR_ERROR =
+ STATE_FAILED + STATE_FAILED_DELIMETER + SERVICE_INVALID_APPLYTO_DIR_ERROR;
+const STATE_FAILED_SERVICE_INVALID_INSTALL_DIR_PATH_ERROR =
+ STATE_FAILED +
+ STATE_FAILED_DELIMETER +
+ SERVICE_INVALID_INSTALL_DIR_PATH_ERROR;
+const STATE_FAILED_SERVICE_INVALID_WORKING_DIR_PATH_ERROR =
+ STATE_FAILED +
+ STATE_FAILED_DELIMETER +
+ SERVICE_INVALID_WORKING_DIR_PATH_ERROR;
+const STATE_FAILED_INVALID_APPLYTO_DIR_STAGED_ERROR =
+ STATE_FAILED + STATE_FAILED_DELIMETER + INVALID_APPLYTO_DIR_STAGED_ERROR;
+const STATE_FAILED_INVALID_APPLYTO_DIR_ERROR =
+ STATE_FAILED + STATE_FAILED_DELIMETER + INVALID_APPLYTO_DIR_ERROR;
+const STATE_FAILED_INVALID_INSTALL_DIR_PATH_ERROR =
+ STATE_FAILED + STATE_FAILED_DELIMETER + INVALID_INSTALL_DIR_PATH_ERROR;
+const STATE_FAILED_INVALID_WORKING_DIR_PATH_ERROR =
+ STATE_FAILED + STATE_FAILED_DELIMETER + INVALID_WORKING_DIR_PATH_ERROR;
+const STATE_FAILED_INVALID_CALLBACK_PATH_ERROR =
+ STATE_FAILED + STATE_FAILED_DELIMETER + INVALID_CALLBACK_PATH_ERROR;
+const STATE_FAILED_INVALID_CALLBACK_DIR_ERROR =
+ STATE_FAILED + STATE_FAILED_DELIMETER + INVALID_CALLBACK_DIR_ERROR;
+const STATE_FAILED_WRITE_ERROR_BACKGROUND_TASK_SHARING_VIOLATION =
+ STATE_FAILED +
+ STATE_FAILED_DELIMETER +
+ WRITE_ERROR_BACKGROUND_TASK_SHARING_VIOLATION;
+
+const DEFAULT_UPDATE_VERSION = "999999.0";
+
+/**
+ * Constructs a string representing a remote update xml file.
+ *
+ * @param aUpdates
+ * The string representing the update elements.
+ * @return The string representing a remote update xml file.
+ */
+function getRemoteUpdatesXMLString(aUpdates) {
+ return '<?xml version="1.0"?><updates>' + aUpdates + "</updates>";
+}
+
+/**
+ * Constructs a string representing an update element for a remote update xml
+ * file. See getUpdateString for parameter information not provided below.
+ *
+ * @param aUpdateProps
+ * An object containing non default test values for an nsIUpdate.
+ * See updateProps names below for possible object names.
+ * @param aPatches
+ * String representing the application update patches.
+ * @return The string representing an update element for an update xml file.
+ */
+function getRemoteUpdateString(aUpdateProps, aPatches) {
+ const updateProps = {
+ appVersion: DEFAULT_UPDATE_VERSION,
+ buildID: "20080811053724",
+ custom1: null,
+ custom2: null,
+ detailsURL: URL_HTTP_UPDATE_SJS + "?uiURL=DETAILS",
+ displayVersion: null,
+ name: "App Update Test",
+ promptWaitTime: null,
+ type: "major",
+ };
+
+ for (let name in aUpdateProps) {
+ updateProps[name] = aUpdateProps[name];
+ }
+
+ // To test that text nodes are handled properly the string returned contains
+ // spaces and newlines.
+ return getUpdateString(updateProps) + ">\n " + aPatches + "\n</update>\n";
+}
+
+/**
+ * Constructs a string representing a patch element for a remote update xml
+ * file. See getPatchString for parameter information not provided below.
+ *
+ * @param aPatchProps (optional)
+ * An object containing non default test values for an nsIUpdatePatch.
+ * See patchProps below for possible object names.
+ * @return The string representing a patch element for a remote update xml file.
+ */
+function getRemotePatchString(aPatchProps) {
+ const patchProps = {
+ type: "complete",
+ _url: null,
+ get url() {
+ if (this._url) {
+ return this._url;
+ }
+ if (gURLData) {
+ return gURLData + FILE_SIMPLE_MAR;
+ }
+ return null;
+ },
+ set url(val) {
+ this._url = val;
+ },
+ custom1: null,
+ custom2: null,
+ size: SIZE_SIMPLE_MAR,
+ };
+
+ for (let name in aPatchProps) {
+ patchProps[name] = aPatchProps[name];
+ }
+
+ return getPatchString(patchProps) + "/>";
+}
+
+/**
+ * Constructs a string representing a local update xml file.
+ *
+ * @param aUpdates
+ * The string representing the update elements.
+ * @return The string representing a local update xml file.
+ */
+function getLocalUpdatesXMLString(aUpdates) {
+ if (!aUpdates || aUpdates == "") {
+ return '<updates xmlns="http://www.mozilla.org/2005/app-update"/>';
+ }
+ return (
+ '<updates xmlns="http://www.mozilla.org/2005/app-update">' +
+ aUpdates +
+ "</updates>"
+ );
+}
+
+/**
+ * Constructs a string representing an update element for a local update xml
+ * file. See getUpdateString for parameter information not provided below.
+ *
+ * @param aUpdateProps
+ * An object containing non default test values for an nsIUpdate.
+ * See updateProps names below for possible object names.
+ * @param aPatches
+ * String representing the application update patches.
+ * @return The string representing an update element for an update xml file.
+ */
+function getLocalUpdateString(aUpdateProps, aPatches) {
+ const updateProps = {
+ _appVersion: null,
+ get appVersion() {
+ if (this._appVersion) {
+ return this._appVersion;
+ }
+ if (Services && Services.appinfo && Services.appinfo.version) {
+ return Services.appinfo.version;
+ }
+ return DEFAULT_UPDATE_VERSION;
+ },
+ set appVersion(val) {
+ this._appVersion = val;
+ },
+ buildID: "20080811053724",
+ channel: UpdateUtils ? UpdateUtils.getUpdateChannel() : "default",
+ custom1: null,
+ custom2: null,
+ detailsURL: URL_HTTP_UPDATE_SJS + "?uiURL=DETAILS",
+ displayVersion: null,
+ foregroundDownload: "true",
+ installDate: "1238441400314",
+ isCompleteUpdate: "true",
+ name: "App Update Test",
+ previousAppVersion: null,
+ promptWaitTime: null,
+ serviceURL: "http://test_service/",
+ statusText: "Install Pending",
+ type: "major",
+ };
+
+ for (let name in aUpdateProps) {
+ updateProps[name] = aUpdateProps[name];
+ }
+
+ let checkInterval = updateProps.checkInterval
+ ? 'checkInterval="' + updateProps.checkInterval + '" '
+ : "";
+ let channel = 'channel="' + updateProps.channel + '" ';
+ let isCompleteUpdate =
+ 'isCompleteUpdate="' + updateProps.isCompleteUpdate + '" ';
+ let foregroundDownload = updateProps.foregroundDownload
+ ? 'foregroundDownload="' + updateProps.foregroundDownload + '" '
+ : "";
+ let installDate = 'installDate="' + updateProps.installDate + '" ';
+ let previousAppVersion = updateProps.previousAppVersion
+ ? 'previousAppVersion="' + updateProps.previousAppVersion + '" '
+ : "";
+ let statusText = updateProps.statusText
+ ? 'statusText="' + updateProps.statusText + '" '
+ : "";
+ let serviceURL = 'serviceURL="' + updateProps.serviceURL + '">';
+
+ return (
+ getUpdateString(updateProps) +
+ " " +
+ checkInterval +
+ channel +
+ isCompleteUpdate +
+ foregroundDownload +
+ installDate +
+ previousAppVersion +
+ statusText +
+ serviceURL +
+ aPatches +
+ "</update>"
+ );
+}
+
+/**
+ * Constructs a string representing a patch element for a local update xml file.
+ * See getPatchString for parameter information not provided below.
+ *
+ * @param aPatchProps (optional)
+ * An object containing non default test values for an nsIUpdatePatch.
+ * See patchProps below for possible object names.
+ * @return The string representing a patch element for a local update xml file.
+ */
+function getLocalPatchString(aPatchProps) {
+ const patchProps = {
+ type: "complete",
+ url: gURLData + FILE_SIMPLE_MAR,
+ size: SIZE_SIMPLE_MAR,
+ custom1: null,
+ custom2: null,
+ selected: "true",
+ state: STATE_SUCCEEDED,
+ };
+
+ for (let name in aPatchProps) {
+ patchProps[name] = aPatchProps[name];
+ }
+
+ let selected = 'selected="' + patchProps.selected + '" ';
+ let state = 'state="' + patchProps.state + '"/>';
+ return getPatchString(patchProps) + " " + selected + state;
+}
+
+/**
+ * Constructs a string representing an update element for a remote update xml
+ * file.
+ *
+ * @param aUpdateProps (optional)
+ * An object containing non default test values for an nsIUpdate.
+ * See the aUpdateProps property names below for possible object names.
+ * @return The string representing an update element for an update xml file.
+ */
+function getUpdateString(aUpdateProps) {
+ let type = 'type="' + aUpdateProps.type + '" ';
+ let name = 'name="' + aUpdateProps.name + '" ';
+ let displayVersion = aUpdateProps.displayVersion
+ ? 'displayVersion="' + aUpdateProps.displayVersion + '" '
+ : "";
+ let appVersion = 'appVersion="' + aUpdateProps.appVersion + '" ';
+ // Not specifying a detailsURL will cause a leak due to bug 470244
+ let detailsURL = 'detailsURL="' + aUpdateProps.detailsURL + '" ';
+ let promptWaitTime = aUpdateProps.promptWaitTime
+ ? 'promptWaitTime="' + aUpdateProps.promptWaitTime + '" '
+ : "";
+ let disableBITS = aUpdateProps.disableBITS
+ ? 'disableBITS="' + aUpdateProps.disableBITS + '" '
+ : "";
+ let disableBackgroundUpdates = aUpdateProps.disableBackgroundUpdates
+ ? 'disableBackgroundUpdates="' +
+ aUpdateProps.disableBackgroundUpdates +
+ '" '
+ : "";
+ let custom1 = aUpdateProps.custom1 ? aUpdateProps.custom1 + " " : "";
+ let custom2 = aUpdateProps.custom2 ? aUpdateProps.custom2 + " " : "";
+ let buildID = 'buildID="' + aUpdateProps.buildID + '"';
+
+ return (
+ "<update " +
+ type +
+ name +
+ displayVersion +
+ appVersion +
+ detailsURL +
+ promptWaitTime +
+ disableBITS +
+ disableBackgroundUpdates +
+ custom1 +
+ custom2 +
+ buildID
+ );
+}
+
+/**
+ * Constructs a string representing a patch element for an update xml file.
+ *
+ * @param aPatchProps (optional)
+ * An object containing non default test values for an nsIUpdatePatch.
+ * See the patchProps property names below for possible object names.
+ * @return The string representing a patch element for an update xml file.
+ */
+function getPatchString(aPatchProps) {
+ let type = 'type="' + aPatchProps.type + '" ';
+ let url = 'URL="' + aPatchProps.url + '" ';
+ let size = 'size="' + aPatchProps.size + '"';
+ let custom1 = aPatchProps.custom1 ? aPatchProps.custom1 + " " : "";
+ let custom2 = aPatchProps.custom2 ? aPatchProps.custom2 + " " : "";
+ return "<patch " + type + url + custom1 + custom2 + size;
+}
+
+/**
+ * Reads the binary contents of a file and returns it as a string.
+ *
+ * @param aFile
+ * The file to read from.
+ * @return The contents of the file as a string.
+ */
+function readFileBytes(aFile) {
+ let fis = Cc["@mozilla.org/network/file-input-stream;1"].createInstance(
+ Ci.nsIFileInputStream
+ );
+ // Specifying -1 for ioFlags will open the file with the default of PR_RDONLY.
+ // Specifying -1 for perm will open the file with the default of 0.
+ fis.init(aFile, -1, -1, Ci.nsIFileInputStream.CLOSE_ON_EOF);
+ let bis = Cc["@mozilla.org/binaryinputstream;1"].createInstance(
+ Ci.nsIBinaryInputStream
+ );
+ bis.setInputStream(fis);
+ let data = [];
+ let count = fis.available();
+ while (count > 0) {
+ let bytes = bis.readByteArray(Math.min(65535, count));
+ data.push(String.fromCharCode.apply(null, bytes));
+ count -= bytes.length;
+ if (!bytes.length) {
+ throw new Error("Nothing read from input stream!");
+ }
+ }
+ data = data.join("");
+ fis.close();
+ return data.toString();
+}
diff --git a/toolkit/mozapps/update/tests/data/simple.mar b/toolkit/mozapps/update/tests/data/simple.mar
new file mode 100644
index 0000000000..fd635b46bd
--- /dev/null
+++ b/toolkit/mozapps/update/tests/data/simple.mar
Binary files differ
diff --git a/toolkit/mozapps/update/tests/data/syncManagerTestChild.js b/toolkit/mozapps/update/tests/data/syncManagerTestChild.js
new file mode 100644
index 0000000000..1f52554e7c
--- /dev/null
+++ b/toolkit/mozapps/update/tests/data/syncManagerTestChild.js
@@ -0,0 +1,55 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+// This is the script that runs in the child xpcshell process for the test
+// unit_aus_update/updateSyncManager.js.
+// The main thing this script does is override the child's directory service
+// so that it ends up with the same fake binary path that the parent test runner
+// has opened its update lock with.
+// This requires that we have already been passed a constant on our command
+// line which contains the relevant fake binary path, which is called:
+/* global customExePath */
+
+print("child process is running");
+
+// This function is copied from xpcshellUtilsAUS.js so that we can have our
+// xpcshell subprocess call it without having to load that whole file, because
+// it turns out that needs a bunch of infrastructure that normally the testing
+// framework would provide, and that also requires a bunch of setup, and it's
+// just not worth all that. This is a cut down version that only includes the
+// directory provider functionality that the subprocess really needs.
+function adjustGeneralPaths() {
+ let dirProvider = {
+ getFile: function AGP_DP_getFile(aProp, aPersistent) {
+ // Set the value of persistent to false so when this directory provider is
+ // unregistered it will revert back to the original provider.
+ aPersistent.value = false;
+ // The sync manager only needs XREExeF, so that's all we provide.
+ if (aProp == "XREExeF") {
+ let file = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile);
+ file.initWithPath(customExePath);
+ return file;
+ }
+ return null;
+ },
+ QueryInterface: ChromeUtils.generateQI(["nsIDirectoryServiceProvider"]),
+ };
+ let ds = Services.dirsvc.QueryInterface(Ci.nsIDirectoryService);
+ ds.QueryInterface(Ci.nsIProperties).undefine("XREExeF");
+ ds.registerProvider(dirProvider);
+
+ // Now that we've overridden the directory provider, the name of the update
+ // lock needs to be changed to match the overridden path.
+ let syncManager = Cc["@mozilla.org/updates/update-sync-manager;1"].getService(
+ Ci.nsIUpdateSyncManager
+ );
+ syncManager.resetLock();
+}
+
+adjustGeneralPaths();
+
+// Wait a few seconds for the parent to do what it needs to do, then exit.
+print("child process should now have the lock; will exit in 5 seconds");
+simulateNoScriptActivity(5);
+print("child process exiting now");
diff --git a/toolkit/mozapps/update/tests/data/xpcshellConstantsPP.js b/toolkit/mozapps/update/tests/data/xpcshellConstantsPP.js
new file mode 100644
index 0000000000..90880d96d9
--- /dev/null
+++ b/toolkit/mozapps/update/tests/data/xpcshellConstantsPP.js
@@ -0,0 +1,29 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#filter substitution
+
+/* Preprocessed constants used by xpcshell tests */
+
+// MOZ_APP_VENDOR is optional.
+#ifdef MOZ_APP_VENDOR
+const MOZ_APP_VENDOR = "@MOZ_APP_VENDOR@";
+#else
+const MOZ_APP_VENDOR = "";
+#endif
+
+// MOZ_APP_BASENAME is not optional for tests.
+const MOZ_APP_BASENAME = "@MOZ_APP_BASENAME@";
+
+#ifdef MOZ_VERIFY_MAR_SIGNATURE
+const MOZ_VERIFY_MAR_SIGNATURE = true;
+#else
+const MOZ_VERIFY_MAR_SIGNATURE = false;
+#endif
+
+#ifdef DISABLE_UPDATER_AUTHENTICODE_CHECK
+ const IS_AUTHENTICODE_CHECK_ENABLED = false;
+#else
+ const IS_AUTHENTICODE_CHECK_ENABLED = true;
+#endif
diff --git a/toolkit/mozapps/update/tests/data/xpcshellUtilsAUS.js b/toolkit/mozapps/update/tests/data/xpcshellUtilsAUS.js
new file mode 100644
index 0000000000..111f0b1e8c
--- /dev/null
+++ b/toolkit/mozapps/update/tests/data/xpcshellUtilsAUS.js
@@ -0,0 +1,4803 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/**
+ * Test log warnings that happen before the test has started
+ * "Couldn't get the user appdata directory. Crash events may not be produced."
+ * in nsExceptionHandler.cpp (possibly bug 619104)
+ *
+ * Test log warnings that happen after the test has finished
+ * "OOPDeinit() without successful OOPInit()" in nsExceptionHandler.cpp
+ * (bug 619104)
+ * "XPCOM objects created/destroyed from static ctor/dtor" in nsTraceRefcnt.cpp
+ * (possibly bug 457479)
+ *
+ * Other warnings printed to the test logs
+ * "site security information will not be persisted" in
+ * nsSiteSecurityService.cpp and the error in nsSystemInfo.cpp preceding this
+ * error are due to not having a profile when running some of the xpcshell
+ * tests. Since most xpcshell tests also log these errors these tests don't
+ * call do_get_profile unless necessary for the test.
+ * "!mMainThread" in nsThreadManager.cpp are due to using timers and it might be
+ * possible to fix some or all of these in the test itself.
+ * "NS_FAILED(rv)" in nsThreadUtils.cpp are due to using timers and it might be
+ * possible to fix some or all of these in the test itself.
+ */
+
+"use strict";
+
+const { AppConstants } = ChromeUtils.importESModule(
+ "resource://gre/modules/AppConstants.sys.mjs"
+);
+const { TestUtils } = ChromeUtils.importESModule(
+ "resource://testing-common/TestUtils.sys.mjs"
+);
+
+ChromeUtils.defineESModuleGetters(this, {
+ MockRegistrar: "resource://testing-common/MockRegistrar.sys.mjs",
+ updateAppInfo: "resource://testing-common/AppInfo.sys.mjs",
+});
+
+const Cm = Components.manager;
+
+/* global MOZ_APP_VENDOR, MOZ_APP_BASENAME */
+/* global MOZ_VERIFY_MAR_SIGNATURE, IS_AUTHENTICODE_CHECK_ENABLED */
+load("../data/xpcshellConstantsPP.js");
+
+const DIR_MACOS = AppConstants.platform == "macosx" ? "Contents/MacOS/" : "";
+const DIR_RESOURCES =
+ AppConstants.platform == "macosx" ? "Contents/Resources/" : "";
+const TEST_FILE_SUFFIX = AppConstants.platform == "macosx" ? "_mac" : "";
+const FILE_COMPLETE_MAR = "complete" + TEST_FILE_SUFFIX + ".mar";
+const FILE_PARTIAL_MAR = "partial" + TEST_FILE_SUFFIX + ".mar";
+const FILE_COMPLETE_PRECOMPLETE = "complete_precomplete" + TEST_FILE_SUFFIX;
+const FILE_PARTIAL_PRECOMPLETE = "partial_precomplete" + TEST_FILE_SUFFIX;
+const FILE_COMPLETE_REMOVEDFILES = "complete_removed-files" + TEST_FILE_SUFFIX;
+const FILE_PARTIAL_REMOVEDFILES = "partial_removed-files" + TEST_FILE_SUFFIX;
+const FILE_UPDATE_IN_PROGRESS_LOCK = "updated.update_in_progress.lock";
+const COMPARE_LOG_SUFFIX = "_" + mozinfo.os;
+const LOG_COMPLETE_SUCCESS = "complete_log_success" + COMPARE_LOG_SUFFIX;
+const LOG_PARTIAL_SUCCESS = "partial_log_success" + COMPARE_LOG_SUFFIX;
+const LOG_PARTIAL_FAILURE = "partial_log_failure" + COMPARE_LOG_SUFFIX;
+const LOG_REPLACE_SUCCESS = "replace_log_success";
+
+const USE_EXECV = AppConstants.platform == "linux";
+
+const URL_HOST = "http://localhost";
+
+const APP_INFO_NAME = "XPCShell";
+const APP_INFO_VENDOR = "Mozilla";
+
+const APP_BIN_SUFFIX =
+ AppConstants.platform == "linux" ? "-bin" : mozinfo.bin_suffix;
+const FILE_APP_BIN = AppConstants.MOZ_APP_NAME + APP_BIN_SUFFIX;
+const FILE_COMPLETE_EXE = "complete.exe";
+const FILE_HELPER_BIN = "TestAUSHelper" + mozinfo.bin_suffix;
+const FILE_MAINTENANCE_SERVICE_BIN = "maintenanceservice.exe";
+const FILE_MAINTENANCE_SERVICE_INSTALLER_BIN =
+ "maintenanceservice_installer.exe";
+const FILE_OLD_VERSION_MAR = "old_version.mar";
+const FILE_PARTIAL_EXE = "partial.exe";
+const FILE_UPDATER_BIN = "updater" + mozinfo.bin_suffix;
+
+const PERFORMING_STAGED_UPDATE = "Performing a staged update";
+const CALL_QUIT = "calling QuitProgressUI";
+const ERR_UPDATE_IN_PROGRESS = "Update already in progress! Exiting";
+const ERR_RENAME_FILE = "rename_file: failed to rename file";
+const ERR_ENSURE_COPY = "ensure_copy: failed to copy the file";
+const ERR_UNABLE_OPEN_DEST = "unable to open destination file";
+const ERR_BACKUP_DISCARD = "backup_discard: unable to remove";
+const ERR_MOVE_DESTDIR_7 = "Moving destDir to tmpDir failed, err: 7";
+const ERR_BACKUP_CREATE_7 = "backup_create failed: 7";
+const ERR_LOADSOURCEFILE_FAILED = "LoadSourceFile failed";
+const ERR_PARENT_PID_PERSISTS =
+ "The parent process didn't exit! Continuing with update.";
+const ERR_BGTASK_EXCLUSIVE =
+ "failed to exclusively open executable file from background task: ";
+
+const LOG_SVC_SUCCESSFUL_LAUNCH = "Process was started... waiting on result.";
+const LOG_SVC_UNSUCCESSFUL_LAUNCH =
+ "The install directory path is not valid for this application.";
+
+// Typical end of a message when calling assert
+const MSG_SHOULD_EQUAL = " should equal the expected value";
+const MSG_SHOULD_EXIST = "the file or directory should exist";
+const MSG_SHOULD_NOT_EXIST = "the file or directory should not exist";
+
+// Time in seconds the helper application should sleep before exiting. The
+// helper can also be made to exit by writing |finish| to its input file.
+const HELPER_SLEEP_TIMEOUT = 180;
+
+// How many of do_timeout calls using FILE_IN_USE_TIMEOUT_MS to wait before the
+// test is aborted.
+const FILE_IN_USE_TIMEOUT_MS = 1000;
+
+const PIPE_TO_NULL =
+ AppConstants.platform == "win" ? ">nul" : "> /dev/null 2>&1";
+
+const LOG_FUNCTION = info;
+
+const gHTTPHandlerPath = "updates.xml";
+
+var gIsServiceTest;
+var gTestID;
+
+// This default value will be overridden when using the http server.
+var gURLData = URL_HOST + "/";
+var gTestserver;
+var gUpdateCheckCount = 0;
+
+var gIncrementalDownloadErrorType;
+
+var gResponseBody;
+
+var gProcess;
+var gAppTimer;
+var gHandle;
+
+var gGREDirOrig;
+var gGREBinDirOrig;
+
+var gPIDPersistProcess;
+
+// Variables are used instead of contants so tests can override these values if
+// necessary.
+var gCallbackBinFile = "callback_app" + mozinfo.bin_suffix;
+var gCallbackArgs = ["./", "callback.log", "Test Arg 2", "Test Arg 3"];
+var gPostUpdateBinFile = "postup_app" + mozinfo.bin_suffix;
+
+var gTimeoutRuns = 0;
+
+// Environment related globals
+var gShouldResetEnv = undefined;
+var gAddedEnvXRENoWindowsCrashDialog = false;
+var gEnvXPCOMDebugBreak;
+var gEnvXPCOMMemLeakLog;
+var gEnvForceServiceFallback = false;
+
+const URL_HTTP_UPDATE_SJS = "http://test_details/";
+const DATA_URI_SPEC = Services.io.newFileURI(do_get_file("", false)).spec;
+
+/* import-globals-from shared.js */
+load("shared.js");
+
+// Set to true to log additional information for debugging. To log additional
+// information for individual tests set gDebugTest to false here and to true in
+// the test's onload function.
+gDebugTest = true;
+
+// Setting gDebugTestLog to true will create log files for the tests in
+// <objdir>/_tests/xpcshell/toolkit/mozapps/update/tests/<testdir>/ except for
+// the service tests since they run sequentially. This can help when debugging
+// failures for the tests that intermittently fail when they run in parallel.
+// Never set gDebugTestLog to true except when running tests locally.
+var gDebugTestLog = false;
+// An empty array for gTestsToLog will log most of the output of all of the
+// update tests except for the service tests. To only log specific tests add the
+// test file name without the file extension to the array below.
+var gTestsToLog = [];
+var gRealDump;
+var gFOS;
+
+var gTestFiles = [];
+var gTestDirs = [];
+
+// Common files for both successful and failed updates.
+var gTestFilesCommon = [
+ {
+ description: "Should never change",
+ fileName: FILE_UPDATE_SETTINGS_INI,
+ relPathDir: DIR_RESOURCES,
+ originalContents: UPDATE_SETTINGS_CONTENTS,
+ compareContents: UPDATE_SETTINGS_CONTENTS,
+ originalFile: null,
+ compareFile: null,
+ originalPerms: 0o767,
+ comparePerms: 0o767,
+ },
+ {
+ description: "Should never change",
+ fileName: "channel-prefs.js",
+ relPathDir: DIR_RESOURCES + "defaults/pref/",
+ originalContents: "ShouldNotBeReplaced\n",
+ compareContents: "ShouldNotBeReplaced\n",
+ originalFile: null,
+ compareFile: null,
+ originalPerms: 0o767,
+ comparePerms: 0o767,
+ },
+];
+
+// Files for a complete successful update. This can be used for a complete
+// failed update by calling setTestFilesAndDirsForFailure.
+var gTestFilesCompleteSuccess = [
+ {
+ description: "Added by update.manifest (add)",
+ fileName: "precomplete",
+ relPathDir: DIR_RESOURCES,
+ originalContents: null,
+ compareContents: null,
+ originalFile: FILE_PARTIAL_PRECOMPLETE,
+ compareFile: FILE_COMPLETE_PRECOMPLETE,
+ originalPerms: 0o666,
+ comparePerms: 0o644,
+ },
+ {
+ description: "Added by update.manifest (add)",
+ fileName: "searchpluginstext0",
+ relPathDir: DIR_RESOURCES + "searchplugins/",
+ originalContents: "ToBeReplacedWithFromComplete\n",
+ compareContents: "FromComplete\n",
+ originalFile: null,
+ compareFile: null,
+ originalPerms: 0o775,
+ comparePerms: 0o644,
+ },
+ {
+ description: "Added by update.manifest (add)",
+ fileName: "searchpluginspng1.png",
+ relPathDir: DIR_RESOURCES + "searchplugins/",
+ originalContents: null,
+ compareContents: null,
+ originalFile: null,
+ compareFile: "complete.png",
+ originalPerms: null,
+ comparePerms: 0o644,
+ },
+ {
+ description: "Added by update.manifest (add)",
+ fileName: "searchpluginspng0.png",
+ relPathDir: DIR_RESOURCES + "searchplugins/",
+ originalContents: null,
+ compareContents: null,
+ originalFile: "partial.png",
+ compareFile: "complete.png",
+ originalPerms: 0o666,
+ comparePerms: 0o644,
+ },
+ {
+ description: "Added by update.manifest (add)",
+ fileName: "removed-files",
+ relPathDir: DIR_RESOURCES,
+ originalContents: null,
+ compareContents: null,
+ originalFile: FILE_PARTIAL_REMOVEDFILES,
+ compareFile: FILE_COMPLETE_REMOVEDFILES,
+ originalPerms: 0o666,
+ comparePerms: 0o644,
+ },
+ {
+ description:
+ "Added by update.manifest if the parent directory exists (add-if)",
+ fileName: "extensions1text0",
+ relPathDir: DIR_RESOURCES + "distribution/extensions/extensions1/",
+ originalContents: null,
+ compareContents: "FromComplete\n",
+ originalFile: null,
+ compareFile: null,
+ originalPerms: null,
+ comparePerms: 0o644,
+ },
+ {
+ description:
+ "Added by update.manifest if the parent directory exists (add-if)",
+ fileName: "extensions1png1.png",
+ relPathDir: DIR_RESOURCES + "distribution/extensions/extensions1/",
+ originalContents: null,
+ compareContents: null,
+ originalFile: "partial.png",
+ compareFile: "complete.png",
+ originalPerms: 0o666,
+ comparePerms: 0o644,
+ },
+ {
+ description:
+ "Added by update.manifest if the parent directory exists (add-if)",
+ fileName: "extensions1png0.png",
+ relPathDir: DIR_RESOURCES + "distribution/extensions/extensions1/",
+ originalContents: null,
+ compareContents: null,
+ originalFile: null,
+ compareFile: "complete.png",
+ originalPerms: null,
+ comparePerms: 0o644,
+ },
+ {
+ description:
+ "Added by update.manifest if the parent directory exists (add-if)",
+ fileName: "extensions0text0",
+ relPathDir: DIR_RESOURCES + "distribution/extensions/extensions0/",
+ originalContents: "ToBeReplacedWithFromComplete\n",
+ compareContents: "FromComplete\n",
+ originalFile: null,
+ compareFile: null,
+ originalPerms: null,
+ comparePerms: 0o644,
+ },
+ {
+ description:
+ "Added by update.manifest if the parent directory exists (add-if)",
+ fileName: "extensions0png1.png",
+ relPathDir: DIR_RESOURCES + "distribution/extensions/extensions0/",
+ originalContents: null,
+ compareContents: null,
+ originalFile: null,
+ compareFile: "complete.png",
+ originalPerms: null,
+ comparePerms: 0o644,
+ },
+ {
+ description:
+ "Added by update.manifest if the parent directory exists (add-if)",
+ fileName: "extensions0png0.png",
+ relPathDir: DIR_RESOURCES + "distribution/extensions/extensions0/",
+ originalContents: null,
+ compareContents: null,
+ originalFile: null,
+ compareFile: "complete.png",
+ originalPerms: null,
+ comparePerms: 0o644,
+ },
+ {
+ description: "Added by update.manifest (add)",
+ fileName: "exe0.exe",
+ relPathDir: DIR_MACOS,
+ originalContents: null,
+ compareContents: null,
+ originalFile: FILE_HELPER_BIN,
+ compareFile: FILE_COMPLETE_EXE,
+ originalPerms: 0o777,
+ comparePerms: 0o755,
+ },
+ {
+ description: "Added by update.manifest (add)",
+ fileName: "10text0",
+ relPathDir: DIR_RESOURCES + "1/10/",
+ originalContents: "ToBeReplacedWithFromComplete\n",
+ compareContents: "FromComplete\n",
+ originalFile: null,
+ compareFile: null,
+ originalPerms: 0o767,
+ comparePerms: 0o644,
+ },
+ {
+ description: "Added by update.manifest (add)",
+ fileName: "0exe0.exe",
+ relPathDir: DIR_RESOURCES + "0/",
+ originalContents: null,
+ compareContents: null,
+ originalFile: FILE_HELPER_BIN,
+ compareFile: FILE_COMPLETE_EXE,
+ originalPerms: 0o777,
+ comparePerms: 0o755,
+ },
+ {
+ description: "Added by update.manifest (add)",
+ fileName: "00text1",
+ relPathDir: DIR_RESOURCES + "0/00/",
+ originalContents: "ToBeReplacedWithFromComplete\n",
+ compareContents: "FromComplete\n",
+ originalFile: null,
+ compareFile: null,
+ originalPerms: 0o677,
+ comparePerms: 0o644,
+ },
+ {
+ description: "Added by update.manifest (add)",
+ fileName: "00text0",
+ relPathDir: DIR_RESOURCES + "0/00/",
+ originalContents: "ToBeReplacedWithFromComplete\n",
+ compareContents: "FromComplete\n",
+ originalFile: null,
+ compareFile: null,
+ originalPerms: 0o775,
+ comparePerms: 0o644,
+ },
+ {
+ description: "Added by update.manifest (add)",
+ fileName: "00png0.png",
+ relPathDir: DIR_RESOURCES + "0/00/",
+ originalContents: null,
+ compareContents: null,
+ originalFile: null,
+ compareFile: "complete.png",
+ originalPerms: 0o776,
+ comparePerms: 0o644,
+ },
+ {
+ description: "Removed by precomplete (remove)",
+ fileName: "20text0",
+ relPathDir: DIR_RESOURCES + "2/20/",
+ originalContents: "ToBeDeleted\n",
+ compareContents: null,
+ originalFile: null,
+ compareFile: null,
+ originalPerms: null,
+ comparePerms: null,
+ },
+ {
+ description: "Removed by precomplete (remove)",
+ fileName: "20png0.png",
+ relPathDir: DIR_RESOURCES + "2/20/",
+ originalContents: "ToBeDeleted\n",
+ compareContents: null,
+ originalFile: null,
+ compareFile: null,
+ originalPerms: null,
+ comparePerms: null,
+ },
+];
+
+// Concatenate the common files to the end of the array.
+gTestFilesCompleteSuccess = gTestFilesCompleteSuccess.concat(gTestFilesCommon);
+
+// Files for a partial successful update. This can be used for a partial failed
+// update by calling setTestFilesAndDirsForFailure.
+var gTestFilesPartialSuccess = [
+ {
+ description: "Added by update.manifest (add)",
+ fileName: "precomplete",
+ relPathDir: DIR_RESOURCES,
+ originalContents: null,
+ compareContents: null,
+ originalFile: FILE_COMPLETE_PRECOMPLETE,
+ compareFile: FILE_PARTIAL_PRECOMPLETE,
+ originalPerms: 0o666,
+ comparePerms: 0o644,
+ },
+ {
+ description: "Added by update.manifest (add)",
+ fileName: "searchpluginstext0",
+ relPathDir: DIR_RESOURCES + "searchplugins/",
+ originalContents: "ToBeReplacedWithFromPartial\n",
+ compareContents: "FromPartial\n",
+ originalFile: null,
+ compareFile: null,
+ originalPerms: 0o775,
+ comparePerms: 0o644,
+ },
+ {
+ description: "Patched by update.manifest if the file exists (patch-if)",
+ fileName: "searchpluginspng1.png",
+ relPathDir: DIR_RESOURCES + "searchplugins/",
+ originalContents: null,
+ compareContents: null,
+ originalFile: "complete.png",
+ compareFile: "partial.png",
+ originalPerms: 0o666,
+ comparePerms: 0o666,
+ },
+ {
+ description: "Patched by update.manifest if the file exists (patch-if)",
+ fileName: "searchpluginspng0.png",
+ relPathDir: DIR_RESOURCES + "searchplugins/",
+ originalContents: null,
+ compareContents: null,
+ originalFile: "complete.png",
+ compareFile: "partial.png",
+ originalPerms: 0o666,
+ comparePerms: 0o666,
+ },
+ {
+ description:
+ "Added by update.manifest if the parent directory exists (add-if)",
+ fileName: "extensions1text0",
+ relPathDir: DIR_RESOURCES + "distribution/extensions/extensions1/",
+ originalContents: null,
+ compareContents: "FromPartial\n",
+ originalFile: null,
+ compareFile: null,
+ originalPerms: null,
+ comparePerms: 0o644,
+ },
+ {
+ description:
+ "Patched by update.manifest if the parent directory exists (patch-if)",
+ fileName: "extensions1png1.png",
+ relPathDir: DIR_RESOURCES + "distribution/extensions/extensions1/",
+ originalContents: null,
+ compareContents: null,
+ originalFile: "complete.png",
+ compareFile: "partial.png",
+ originalPerms: 0o666,
+ comparePerms: 0o666,
+ },
+ {
+ description:
+ "Patched by update.manifest if the parent directory exists (patch-if)",
+ fileName: "extensions1png0.png",
+ relPathDir: DIR_RESOURCES + "distribution/extensions/extensions1/",
+ originalContents: null,
+ compareContents: null,
+ originalFile: "complete.png",
+ compareFile: "partial.png",
+ originalPerms: 0o666,
+ comparePerms: 0o666,
+ },
+ {
+ description:
+ "Added by update.manifest if the parent directory exists (add-if)",
+ fileName: "extensions0text0",
+ relPathDir: DIR_RESOURCES + "distribution/extensions/extensions0/",
+ originalContents: "ToBeReplacedWithFromPartial\n",
+ compareContents: "FromPartial\n",
+ originalFile: null,
+ compareFile: null,
+ originalPerms: 0o644,
+ comparePerms: 0o644,
+ },
+ {
+ description:
+ "Patched by update.manifest if the parent directory exists (patch-if)",
+ fileName: "extensions0png1.png",
+ relPathDir: DIR_RESOURCES + "distribution/extensions/extensions0/",
+ originalContents: null,
+ compareContents: null,
+ originalFile: "complete.png",
+ compareFile: "partial.png",
+ originalPerms: 0o644,
+ comparePerms: 0o644,
+ },
+ {
+ description:
+ "Patched by update.manifest if the parent directory exists (patch-if)",
+ fileName: "extensions0png0.png",
+ relPathDir: DIR_RESOURCES + "distribution/extensions/extensions0/",
+ originalContents: null,
+ compareContents: null,
+ originalFile: "complete.png",
+ compareFile: "partial.png",
+ originalPerms: 0o644,
+ comparePerms: 0o644,
+ },
+ {
+ description: "Patched by update.manifest (patch)",
+ fileName: "exe0.exe",
+ relPathDir: DIR_MACOS,
+ originalContents: null,
+ compareContents: null,
+ originalFile: FILE_COMPLETE_EXE,
+ compareFile: FILE_PARTIAL_EXE,
+ originalPerms: 0o755,
+ comparePerms: 0o755,
+ },
+ {
+ description: "Patched by update.manifest (patch)",
+ fileName: "0exe0.exe",
+ relPathDir: DIR_RESOURCES + "0/",
+ originalContents: null,
+ compareContents: null,
+ originalFile: FILE_COMPLETE_EXE,
+ compareFile: FILE_PARTIAL_EXE,
+ originalPerms: 0o755,
+ comparePerms: 0o755,
+ },
+ {
+ description: "Added by update.manifest (add)",
+ fileName: "00text0",
+ relPathDir: DIR_RESOURCES + "0/00/",
+ originalContents: "ToBeReplacedWithFromPartial\n",
+ compareContents: "FromPartial\n",
+ originalFile: null,
+ compareFile: null,
+ originalPerms: 0o644,
+ comparePerms: 0o644,
+ },
+ {
+ description: "Patched by update.manifest (patch)",
+ fileName: "00png0.png",
+ relPathDir: DIR_RESOURCES + "0/00/",
+ originalContents: null,
+ compareContents: null,
+ originalFile: "complete.png",
+ compareFile: "partial.png",
+ originalPerms: 0o666,
+ comparePerms: 0o666,
+ },
+ {
+ description: "Added by update.manifest (add)",
+ fileName: "20text0",
+ relPathDir: DIR_RESOURCES + "2/20/",
+ originalContents: null,
+ compareContents: "FromPartial\n",
+ originalFile: null,
+ compareFile: null,
+ originalPerms: null,
+ comparePerms: 0o644,
+ },
+ {
+ description: "Added by update.manifest (add)",
+ fileName: "20png0.png",
+ relPathDir: DIR_RESOURCES + "2/20/",
+ originalContents: null,
+ compareContents: null,
+ originalFile: null,
+ compareFile: "partial.png",
+ originalPerms: null,
+ comparePerms: 0o644,
+ },
+ {
+ description: "Added by update.manifest (add)",
+ fileName: "00text2",
+ relPathDir: DIR_RESOURCES + "0/00/",
+ originalContents: null,
+ compareContents: "FromPartial\n",
+ originalFile: null,
+ compareFile: null,
+ originalPerms: null,
+ comparePerms: 0o644,
+ },
+ {
+ description: "Removed by update.manifest (remove)",
+ fileName: "10text0",
+ relPathDir: DIR_RESOURCES + "1/10/",
+ originalContents: "ToBeDeleted\n",
+ compareContents: null,
+ originalFile: null,
+ compareFile: null,
+ originalPerms: null,
+ comparePerms: null,
+ },
+ {
+ description: "Removed by update.manifest (remove)",
+ fileName: "00text1",
+ relPathDir: DIR_RESOURCES + "0/00/",
+ originalContents: "ToBeDeleted\n",
+ compareContents: null,
+ originalFile: null,
+ compareFile: null,
+ originalPerms: null,
+ comparePerms: null,
+ },
+];
+
+// Concatenate the common files to the end of the array.
+gTestFilesPartialSuccess = gTestFilesPartialSuccess.concat(gTestFilesCommon);
+
+var gTestDirsCommon = [
+ {
+ relPathDir: DIR_RESOURCES + "3/",
+ dirRemoved: false,
+ files: ["3text0", "3text1"],
+ filesRemoved: true,
+ },
+ {
+ relPathDir: DIR_RESOURCES + "4/",
+ dirRemoved: true,
+ files: ["4text0", "4text1"],
+ filesRemoved: true,
+ },
+ {
+ relPathDir: DIR_RESOURCES + "5/",
+ dirRemoved: true,
+ files: ["5test.exe", "5text0", "5text1"],
+ filesRemoved: true,
+ },
+ {
+ relPathDir: DIR_RESOURCES + "6/",
+ dirRemoved: true,
+ },
+ {
+ relPathDir: DIR_RESOURCES + "7/",
+ dirRemoved: true,
+ files: ["7text0", "7text1"],
+ subDirs: ["70/", "71/"],
+ subDirFiles: ["7xtest.exe", "7xtext0", "7xtext1"],
+ },
+ {
+ relPathDir: DIR_RESOURCES + "8/",
+ dirRemoved: false,
+ },
+ {
+ relPathDir: DIR_RESOURCES + "8/80/",
+ dirRemoved: true,
+ },
+ {
+ relPathDir: DIR_RESOURCES + "8/81/",
+ dirRemoved: false,
+ files: ["81text0", "81text1"],
+ },
+ {
+ relPathDir: DIR_RESOURCES + "8/82/",
+ dirRemoved: false,
+ subDirs: ["820/", "821/"],
+ },
+ {
+ relPathDir: DIR_RESOURCES + "8/83/",
+ dirRemoved: true,
+ },
+ {
+ relPathDir: DIR_RESOURCES + "8/84/",
+ dirRemoved: true,
+ },
+ {
+ relPathDir: DIR_RESOURCES + "8/85/",
+ dirRemoved: true,
+ },
+ {
+ relPathDir: DIR_RESOURCES + "8/86/",
+ dirRemoved: true,
+ files: ["86text0", "86text1"],
+ },
+ {
+ relPathDir: DIR_RESOURCES + "8/87/",
+ dirRemoved: true,
+ subDirs: ["870/", "871/"],
+ subDirFiles: ["87xtext0", "87xtext1"],
+ },
+ {
+ relPathDir: DIR_RESOURCES + "8/88/",
+ dirRemoved: true,
+ },
+ {
+ relPathDir: DIR_RESOURCES + "8/89/",
+ dirRemoved: true,
+ },
+ {
+ relPathDir: DIR_RESOURCES + "9/90/",
+ dirRemoved: true,
+ },
+ {
+ relPathDir: DIR_RESOURCES + "9/91/",
+ dirRemoved: false,
+ files: ["91text0", "91text1"],
+ },
+ {
+ relPathDir: DIR_RESOURCES + "9/92/",
+ dirRemoved: false,
+ subDirs: ["920/", "921/"],
+ },
+ {
+ relPathDir: DIR_RESOURCES + "9/93/",
+ dirRemoved: true,
+ },
+ {
+ relPathDir: DIR_RESOURCES + "9/94/",
+ dirRemoved: true,
+ },
+ {
+ relPathDir: DIR_RESOURCES + "9/95/",
+ dirRemoved: true,
+ },
+ {
+ relPathDir: DIR_RESOURCES + "9/96/",
+ dirRemoved: true,
+ files: ["96text0", "96text1"],
+ },
+ {
+ relPathDir: DIR_RESOURCES + "9/97/",
+ dirRemoved: true,
+ subDirs: ["970/", "971/"],
+ subDirFiles: ["97xtext0", "97xtext1"],
+ },
+ {
+ relPathDir: DIR_RESOURCES + "9/98/",
+ dirRemoved: true,
+ },
+ {
+ relPathDir: DIR_RESOURCES + "9/99/",
+ dirRemoved: true,
+ },
+ {
+ description:
+ "Silences 'WARNING: Failed to resolve XUL App Dir.' in debug builds",
+ relPathDir: DIR_RESOURCES + "browser",
+ dirRemoved: false,
+ },
+];
+
+// Directories for a complete successful update. This array can be used for a
+// complete failed update by calling setTestFilesAndDirsForFailure.
+var gTestDirsCompleteSuccess = [
+ {
+ description: "Removed by precomplete (rmdir)",
+ relPathDir: DIR_RESOURCES + "2/20/",
+ dirRemoved: true,
+ },
+ {
+ description: "Removed by precomplete (rmdir)",
+ relPathDir: DIR_RESOURCES + "2/",
+ dirRemoved: true,
+ },
+];
+
+// Concatenate the common files to the beginning of the array.
+gTestDirsCompleteSuccess = gTestDirsCommon.concat(gTestDirsCompleteSuccess);
+
+// Directories for a partial successful update. This array can be used for a
+// partial failed update by calling setTestFilesAndDirsForFailure.
+var gTestDirsPartialSuccess = [
+ {
+ description: "Removed by update.manifest (rmdir)",
+ relPathDir: DIR_RESOURCES + "1/10/",
+ dirRemoved: true,
+ },
+ {
+ description: "Removed by update.manifest (rmdir)",
+ relPathDir: DIR_RESOURCES + "1/",
+ dirRemoved: true,
+ },
+];
+
+// Concatenate the common files to the beginning of the array.
+gTestDirsPartialSuccess = gTestDirsCommon.concat(gTestDirsPartialSuccess);
+
+/**
+ * Helper function for setting up the test environment.
+ *
+ * @param aAppUpdateAutoEnabled
+ * See setAppUpdateAutoSync in shared.js for details.
+ * @param aAllowBits
+ * If true, allow update downloads via the Windows BITS service.
+ * If false, this download mechanism will not be used.
+ */
+function setupTestCommon(aAppUpdateAutoEnabled = false, aAllowBits = false) {
+ debugDump("start - general test setup");
+
+ Assert.strictEqual(
+ gTestID,
+ undefined,
+ "gTestID should be 'undefined' (setupTestCommon should " +
+ "only be called once)"
+ );
+
+ let caller = Components.stack.caller;
+ gTestID = caller.filename.toString().split("/").pop().split(".")[0];
+
+ if (gDebugTestLog && !gIsServiceTest) {
+ if (!gTestsToLog.length || gTestsToLog.includes(gTestID)) {
+ let logFile = do_get_file(gTestID + ".log", true);
+ if (!logFile.exists()) {
+ logFile.create(Ci.nsIFile.NORMAL_FILE_TYPE, PERMS_FILE);
+ }
+ gFOS = Cc["@mozilla.org/network/file-output-stream;1"].createInstance(
+ Ci.nsIFileOutputStream
+ );
+ gFOS.init(logFile, MODE_WRONLY | MODE_APPEND, PERMS_FILE, 0);
+
+ gRealDump = dump;
+ dump = dumpOverride;
+ }
+ }
+
+ createAppInfo("xpcshell@tests.mozilla.org", APP_INFO_NAME, "1.0", "2.0");
+
+ if (gIsServiceTest && !shouldRunServiceTest()) {
+ return false;
+ }
+
+ do_test_pending();
+
+ setDefaultPrefs();
+
+ gGREDirOrig = getGREDir();
+ gGREBinDirOrig = getGREBinDir();
+
+ let applyDir = getApplyDirFile().parent;
+
+ // Try to remove the directory used to apply updates and the updates directory
+ // on platforms other than Windows. This is non-fatal for the test since if
+ // this fails a different directory will be used.
+ if (applyDir.exists()) {
+ debugDump("attempting to remove directory. Path: " + applyDir.path);
+ try {
+ removeDirRecursive(applyDir);
+ } catch (e) {
+ logTestInfo(
+ "non-fatal error removing directory. Path: " +
+ applyDir.path +
+ ", Exception: " +
+ e
+ );
+ // When the application doesn't exit properly it can cause the test to
+ // fail again on the second run with an NS_ERROR_FILE_ACCESS_DENIED error
+ // along with no useful information in the test log. To prevent this use
+ // a different directory for the test when it isn't possible to remove the
+ // existing test directory (bug 1294196).
+ gTestID += "_new";
+ logTestInfo(
+ "using a new directory for the test by changing gTestID " +
+ "since there is an existing test directory that can't be " +
+ "removed, gTestID: " +
+ gTestID
+ );
+ }
+ }
+
+ if (AppConstants.platform == "win") {
+ Services.prefs.setBoolPref(
+ PREF_APP_UPDATE_SERVICE_ENABLED,
+ !!gIsServiceTest
+ );
+ }
+
+ if (gIsServiceTest) {
+ let exts = ["id", "log", "status"];
+ for (let i = 0; i < exts.length; ++i) {
+ let file = getSecureOutputFile(exts[i]);
+ if (file.exists()) {
+ try {
+ file.remove(false);
+ } catch (e) {}
+ }
+ }
+ }
+
+ adjustGeneralPaths();
+ createWorldWritableAppUpdateDir();
+
+ // Logged once here instead of in the mock directory provider to lessen test
+ // log spam.
+ debugDump("Updates Directory (UpdRootD) Path: " + getMockUpdRootD().path);
+
+ // This prevents a warning about not being able to find the greprefs.js file
+ // from being logged.
+ let grePrefsFile = getGREDir();
+ if (!grePrefsFile.exists()) {
+ grePrefsFile.create(Ci.nsIFile.DIRECTORY_TYPE, PERMS_DIRECTORY);
+ }
+ grePrefsFile.append("greprefs.js");
+ if (!grePrefsFile.exists()) {
+ grePrefsFile.create(Ci.nsIFile.NORMAL_FILE_TYPE, PERMS_FILE);
+ }
+
+ // The name of the update lock needs to be changed to match the path
+ // overridden in adjustGeneralPaths() above. Wait until now to reset
+ // because the GRE dir now exists, which may cause the "install
+ // path" to be normalized differently now that it can be resolved.
+ debugDump("resetting update lock");
+ let syncManager = Cc["@mozilla.org/updates/update-sync-manager;1"].getService(
+ Ci.nsIUpdateSyncManager
+ );
+ syncManager.resetLock();
+
+ // Remove the updates directory on Windows and Mac OS X which is located
+ // outside of the application directory after the call to adjustGeneralPaths
+ // has set it up. Since the test hasn't ran yet and the directory shouldn't
+ // exist this is non-fatal for the test.
+ if (AppConstants.platform == "win" || AppConstants.platform == "macosx") {
+ let updatesDir = getMockUpdRootD();
+ if (updatesDir.exists()) {
+ debugDump("attempting to remove directory. Path: " + updatesDir.path);
+ try {
+ removeDirRecursive(updatesDir);
+ } catch (e) {
+ logTestInfo(
+ "non-fatal error removing directory. Path: " +
+ updatesDir.path +
+ ", Exception: " +
+ e
+ );
+ }
+ }
+ }
+
+ setAppUpdateAutoSync(aAppUpdateAutoEnabled);
+ Services.prefs.setBoolPref(PREF_APP_UPDATE_BITS_ENABLED, aAllowBits);
+
+ debugDump("finish - general test setup");
+ return true;
+}
+
+/**
+ * Nulls out the most commonly used global vars used by tests to prevent leaks
+ * as needed and attempts to restore the system to its original state.
+ */
+function cleanupTestCommon() {
+ debugDump("start - general test cleanup");
+
+ if (gChannel) {
+ gPrefRoot.removeObserver(PREF_APP_UPDATE_CHANNEL, observer);
+ }
+
+ gTestserver = null;
+
+ if (AppConstants.platform == "macosx" || AppConstants.platform == "linux") {
+ // This will delete the launch script if it exists.
+ getLaunchScript();
+ }
+
+ if (gIsServiceTest) {
+ let exts = ["id", "log", "status"];
+ for (let i = 0; i < exts.length; ++i) {
+ let file = getSecureOutputFile(exts[i]);
+ if (file.exists()) {
+ try {
+ file.remove(false);
+ } catch (e) {}
+ }
+ }
+ }
+
+ if (AppConstants.platform == "win" && MOZ_APP_BASENAME) {
+ let appDir = getApplyDirFile();
+ let vendor = MOZ_APP_VENDOR ? MOZ_APP_VENDOR : "Mozilla";
+ const REG_PATH =
+ "SOFTWARE\\" + vendor + "\\" + MOZ_APP_BASENAME + "\\TaskBarIDs";
+ let key = Cc["@mozilla.org/windows-registry-key;1"].createInstance(
+ Ci.nsIWindowsRegKey
+ );
+ try {
+ key.open(
+ Ci.nsIWindowsRegKey.ROOT_KEY_LOCAL_MACHINE,
+ REG_PATH,
+ Ci.nsIWindowsRegKey.ACCESS_ALL
+ );
+ if (key.hasValue(appDir.path)) {
+ key.removeValue(appDir.path);
+ }
+ } catch (e) {}
+ try {
+ key.open(
+ Ci.nsIWindowsRegKey.ROOT_KEY_CURRENT_USER,
+ REG_PATH,
+ Ci.nsIWindowsRegKey.ACCESS_ALL
+ );
+ if (key.hasValue(appDir.path)) {
+ key.removeValue(appDir.path);
+ }
+ } catch (e) {}
+ }
+
+ // The updates directory is located outside of the application directory and
+ // needs to be removed on Windows and Mac OS X.
+ if (AppConstants.platform == "win" || AppConstants.platform == "macosx") {
+ let updatesDir = getMockUpdRootD();
+ // Try to remove the directory used to apply updates. Since the test has
+ // already finished this is non-fatal for the test.
+ if (updatesDir.exists()) {
+ debugDump("attempting to remove directory. Path: " + updatesDir.path);
+ try {
+ removeDirRecursive(updatesDir);
+ } catch (e) {
+ logTestInfo(
+ "non-fatal error removing directory. Path: " +
+ updatesDir.path +
+ ", Exception: " +
+ e
+ );
+ }
+ if (AppConstants.platform == "macosx") {
+ let updatesRootDir = gUpdatesRootDir.clone();
+ while (updatesRootDir.path != updatesDir.path) {
+ if (updatesDir.exists()) {
+ debugDump(
+ "attempting to remove directory. Path: " + updatesDir.path
+ );
+ try {
+ // Try to remove the directory without the recursive flag set
+ // since the top level directory has already had its contents
+ // removed and the parent directory might still be used by a
+ // different test.
+ updatesDir.remove(false);
+ } catch (e) {
+ logTestInfo(
+ "non-fatal error removing directory. Path: " +
+ updatesDir.path +
+ ", Exception: " +
+ e
+ );
+ if (e == Cr.NS_ERROR_FILE_DIR_NOT_EMPTY) {
+ break;
+ }
+ }
+ }
+ updatesDir = updatesDir.parent;
+ }
+ }
+ }
+ }
+
+ let applyDir = getApplyDirFile().parent;
+
+ // Try to remove the directory used to apply updates. Since the test has
+ // already finished this is non-fatal for the test.
+ if (applyDir.exists()) {
+ debugDump("attempting to remove directory. Path: " + applyDir.path);
+ try {
+ removeDirRecursive(applyDir);
+ } catch (e) {
+ logTestInfo(
+ "non-fatal error removing directory. Path: " +
+ applyDir.path +
+ ", Exception: " +
+ e
+ );
+ }
+ }
+
+ resetEnvironment();
+ Services.prefs.clearUserPref(PREF_APP_UPDATE_BITS_ENABLED);
+
+ debugDump("finish - general test cleanup");
+
+ if (gRealDump) {
+ dump = gRealDump;
+ gRealDump = null;
+ }
+
+ if (gFOS) {
+ gFOS.close();
+ }
+}
+
+/**
+ * Helper function to store the log output of calls to dump in a variable so the
+ * values can be written to a file for a parallel run of a test and printed to
+ * the log file when the test runs synchronously.
+ */
+function dumpOverride(aText) {
+ gFOS.write(aText, aText.length);
+ gRealDump(aText);
+}
+
+/**
+ * Helper function that calls do_test_finished that tracks whether a parallel
+ * run of a test passed when it runs synchronously so the log output can be
+ * inspected.
+ */
+function doTestFinish() {
+ if (gDebugTest) {
+ // This prevents do_print errors from being printed by the xpcshell test
+ // harness due to nsUpdateService.js logging to the console when the
+ // app.update.log preference is true.
+ Services.prefs.setBoolPref(PREF_APP_UPDATE_LOG, false);
+ gAUS.observe(null, "nsPref:changed", PREF_APP_UPDATE_LOG);
+ }
+
+ reloadUpdateManagerData(true);
+
+ // Call app update's observe method passing quit-application to test that the
+ // shutdown of app update runs without throwing or leaking. The observer
+ // method is used directly instead of calling notifyObservers so components
+ // outside of the scope of this test don't assert and thereby cause app update
+ // tests to fail.
+ gAUS.observe(null, "quit-application", "");
+
+ executeSoon(do_test_finished);
+}
+
+/**
+ * Sets the most commonly used preferences used by tests
+ */
+function setDefaultPrefs() {
+ Services.prefs.setBoolPref(PREF_APP_UPDATE_DISABLEDFORTESTING, false);
+ if (gDebugTest) {
+ // Enable Update logging
+ Services.prefs.setBoolPref(PREF_APP_UPDATE_LOG, true);
+ } else {
+ // Some apps set this preference to true by default
+ Services.prefs.setBoolPref(PREF_APP_UPDATE_LOG, false);
+ }
+}
+
+/**
+ * Helper function for updater binary tests that sets the appropriate values
+ * to check for update failures.
+ */
+function setTestFilesAndDirsForFailure() {
+ gTestFiles.forEach(function STFADFF_Files(aTestFile) {
+ aTestFile.compareContents = aTestFile.originalContents;
+ aTestFile.compareFile = aTestFile.originalFile;
+ aTestFile.comparePerms = aTestFile.originalPerms;
+ });
+
+ gTestDirs.forEach(function STFADFF_Dirs(aTestDir) {
+ aTestDir.dirRemoved = false;
+ if (aTestDir.filesRemoved) {
+ aTestDir.filesRemoved = false;
+ }
+ });
+}
+
+/**
+ * Helper function for updater binary tests that prevents the distribution
+ * directory files from being created.
+ */
+function preventDistributionFiles() {
+ gTestFiles = gTestFiles.filter(function (aTestFile) {
+ return !aTestFile.relPathDir.includes("distribution/");
+ });
+
+ gTestDirs = gTestDirs.filter(function (aTestDir) {
+ return !aTestDir.relPathDir.includes("distribution/");
+ });
+}
+
+/**
+ * On Mac OS X this sets the last modified time for the app bundle directory to
+ * a date in the past to test that the last modified time is updated when an
+ * update has been successfully applied (bug 600098).
+ */
+function setAppBundleModTime() {
+ if (AppConstants.platform != "macosx") {
+ return;
+ }
+ let now = Date.now();
+ let yesterday = now - 1000 * 60 * 60 * 24;
+ let applyToDir = getApplyDirFile();
+ applyToDir.lastModifiedTime = yesterday;
+}
+
+/**
+ * On Mac OS X this checks that the last modified time for the app bundle
+ * directory has been updated when an update has been successfully applied
+ * (bug 600098).
+ */
+function checkAppBundleModTime() {
+ if (AppConstants.platform != "macosx") {
+ return;
+ }
+ // All we care about is that the last modified time has changed so that Mac OS
+ // X Launch Services invalidates its cache so the test allows up to one minute
+ // difference in the last modified time.
+ const MAC_MAX_TIME_DIFFERENCE = 60000;
+ let now = Date.now();
+ let applyToDir = getApplyDirFile();
+ let timeDiff = Math.abs(applyToDir.lastModifiedTime - now);
+ Assert.ok(
+ timeDiff < MAC_MAX_TIME_DIFFERENCE,
+ "the last modified time on the apply to directory should " +
+ "change after a successful update"
+ );
+}
+
+/**
+ * Performs Update Manager checks to verify that the update metadata is correct
+ * and that it is the same after the update xml files are reloaded.
+ *
+ * @param aStatusFileState
+ * The expected state of the status file.
+ * @param aHasActiveUpdate
+ * Should there be an active update.
+ * @param aUpdateStatusState
+ * The expected update's status state.
+ * @param aUpdateErrCode
+ * The expected update's error code.
+ * @param aUpdateCount
+ * The update history's update count.
+ */
+function checkUpdateManager(
+ aStatusFileState,
+ aHasActiveUpdate,
+ aUpdateStatusState,
+ aUpdateErrCode,
+ aUpdateCount
+) {
+ let activeUpdate =
+ aUpdateStatusState == STATE_DOWNLOADING
+ ? gUpdateManager.downloadingUpdate
+ : gUpdateManager.readyUpdate;
+ Assert.equal(
+ readStatusState(),
+ aStatusFileState,
+ "the status file state" + MSG_SHOULD_EQUAL
+ );
+ let msgTags = [" after startup ", " after a file reload "];
+ for (let i = 0; i < msgTags.length; ++i) {
+ logTestInfo(
+ "checking Update Manager updates" + msgTags[i] + "is performed"
+ );
+ if (aHasActiveUpdate) {
+ Assert.ok(
+ !!activeUpdate,
+ msgTags[i] + "the active update should be defined"
+ );
+ } else {
+ Assert.ok(
+ !activeUpdate,
+ msgTags[i] + "the active update should not be defined"
+ );
+ }
+ Assert.equal(
+ gUpdateManager.getUpdateCount(),
+ aUpdateCount,
+ msgTags[i] + "the update manager updateCount attribute" + MSG_SHOULD_EQUAL
+ );
+ if (aUpdateCount > 0) {
+ let update = gUpdateManager.getUpdateAt(0);
+ Assert.equal(
+ update.state,
+ aUpdateStatusState,
+ msgTags[i] + "the first update state" + MSG_SHOULD_EQUAL
+ );
+ Assert.equal(
+ update.errorCode,
+ aUpdateErrCode,
+ msgTags[i] + "the first update errorCode" + MSG_SHOULD_EQUAL
+ );
+ }
+ if (i != msgTags.length - 1) {
+ reloadUpdateManagerData();
+ }
+ }
+}
+
+/**
+ * Waits until the update files exist or not based on the parameters specified
+ * when calling this function or the default values if the parameters are not
+ * specified. This is necessary due to the update xml files being written
+ * asynchronously by nsIUpdateManager.
+ *
+ * @param aActiveUpdateExists (optional)
+ * Whether the active-update.xml file should exist (default is false).
+ * @param aUpdatesExists (optional)
+ * Whether the updates.xml file should exist (default is true).
+ */
+async function waitForUpdateXMLFiles(
+ aActiveUpdateExists = false,
+ aUpdatesExists = true
+) {
+ function areFilesStabilized() {
+ let file = getUpdateDirFile(FILE_ACTIVE_UPDATE_XML_TMP);
+ if (file.exists()) {
+ debugDump("file exists, Path: " + file.path);
+ return false;
+ }
+ file = getUpdateDirFile(FILE_UPDATES_XML_TMP);
+ if (file.exists()) {
+ debugDump("file exists, Path: " + file.path);
+ return false;
+ }
+ file = getUpdateDirFile(FILE_ACTIVE_UPDATE_XML);
+ if (file.exists() != aActiveUpdateExists) {
+ debugDump(
+ "file exists should equal: " +
+ aActiveUpdateExists +
+ ", Path: " +
+ file.path
+ );
+ return false;
+ }
+ file = getUpdateDirFile(FILE_UPDATES_XML);
+ if (file.exists() != aUpdatesExists) {
+ debugDump(
+ "file exists should equal: " +
+ aActiveUpdateExists +
+ ", Path: " +
+ file.path
+ );
+ return false;
+ }
+ return true;
+ }
+
+ await TestUtils.waitForCondition(
+ () => areFilesStabilized(),
+ "Waiting for update xml files to stabilize"
+ );
+}
+
+/**
+ * On Mac OS X and Windows this checks if the post update '.running' file exists
+ * to determine if the post update binary was launched.
+ *
+ * @param aShouldExist
+ * Whether the post update '.running' file should exist.
+ */
+function checkPostUpdateRunningFile(aShouldExist) {
+ if (AppConstants.platform == "linux") {
+ return;
+ }
+ let postUpdateRunningFile = getPostUpdateFile(".running");
+ if (aShouldExist) {
+ Assert.ok(
+ postUpdateRunningFile.exists(),
+ MSG_SHOULD_EXIST + getMsgPath(postUpdateRunningFile.path)
+ );
+ } else {
+ Assert.ok(
+ !postUpdateRunningFile.exists(),
+ MSG_SHOULD_NOT_EXIST + getMsgPath(postUpdateRunningFile.path)
+ );
+ }
+}
+
+/**
+ * Initializes the most commonly used settings and creates an instance of the
+ * update service stub.
+ */
+function standardInit() {
+ // Initialize the update service stub component
+ initUpdateServiceStub();
+}
+
+/**
+ * Helper function for getting the application version from the application.ini
+ * file. This will look in both the GRE and the application directories for the
+ * application.ini file.
+ *
+ * @return The version string from the application.ini file.
+ */
+function getAppVersion() {
+ // Read the application.ini and use its application version.
+ let iniFile = gGREDirOrig.clone();
+ iniFile.append(FILE_APPLICATION_INI);
+ if (!iniFile.exists()) {
+ iniFile = gGREBinDirOrig.clone();
+ iniFile.append(FILE_APPLICATION_INI);
+ }
+ Assert.ok(iniFile.exists(), MSG_SHOULD_EXIST + getMsgPath(iniFile.path));
+ let iniParser = Cc["@mozilla.org/xpcom/ini-parser-factory;1"]
+ .getService(Ci.nsIINIParserFactory)
+ .createINIParser(iniFile);
+ return iniParser.getString("App", "Version");
+}
+
+/**
+ * Helper function for getting the path to the directory where the
+ * application binary is located (e.g. <test_file_leafname>/dir.app/).
+ *
+ * Note: The dir.app subdirectory under <test_file_leafname> is needed for
+ * platforms other than Mac OS X so the tests can run in parallel due to
+ * update staging creating a lock file named moz_update_in_progress.lock in
+ * the parent directory of the installation directory.
+ * Note: For service tests with IS_AUTHENTICODE_CHECK_ENABLED we use an absolute
+ * path inside Program Files because the service itself will refuse to
+ * update an installation not located in Program Files.
+ *
+ * @return The path to the directory where application binary is located.
+ */
+function getApplyDirPath() {
+ if (gIsServiceTest && IS_AUTHENTICODE_CHECK_ENABLED) {
+ let dir = getMaintSvcDir();
+ dir.append(gTestID);
+ dir.append("dir.app");
+ return dir.path;
+ }
+ return gTestID + "/dir.app/";
+}
+
+/**
+ * Helper function for getting the nsIFile for a file in the directory where the
+ * update will be applied.
+ *
+ * The files for the update are located two directories below the apply to
+ * directory since Mac OS X sets the last modified time for the root directory
+ * to the current time and if the update changes any files in the root directory
+ * then it wouldn't be possible to test (bug 600098).
+ *
+ * @param aRelPath (optional)
+ * The relative path to the file or directory to get from the root of
+ * the test's directory. If not specified the test's directory will be
+ * returned.
+ * @return The nsIFile for the file in the directory where the update will be
+ * applied.
+ */
+function getApplyDirFile(aRelPath) {
+ // do_get_file only supports relative paths, but under these conditions we
+ // need to use an absolute path in Program Files instead.
+ if (gIsServiceTest && IS_AUTHENTICODE_CHECK_ENABLED) {
+ let file = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile);
+ file.initWithPath(getApplyDirPath());
+ if (aRelPath) {
+ if (aRelPath == "..") {
+ file = file.parent;
+ } else {
+ aRelPath = aRelPath.replace(/\//g, "\\");
+ file.appendRelativePath(aRelPath);
+ }
+ }
+ return file;
+ }
+ let relpath = getApplyDirPath() + (aRelPath ? aRelPath : "");
+ return do_get_file(relpath, true);
+}
+
+/**
+ * Helper function for getting the relative path to the directory where the
+ * test data files are located.
+ *
+ * @return The relative path to the directory where the test data files are
+ * located.
+ */
+function getTestDirPath() {
+ return "../data/";
+}
+
+/**
+ * Helper function for getting the nsIFile for a file in the test data
+ * directory.
+ *
+ * @param aRelPath (optional)
+ * The relative path to the file or directory to get from the root of
+ * the test's data directory. If not specified the test's data
+ * directory will be returned.
+ * @param aAllowNonExists (optional)
+ * Whether or not to throw an error if the path exists.
+ * If not specified, then false is used.
+ * @return The nsIFile for the file in the test data directory.
+ * @throws If the file or directory does not exist.
+ */
+function getTestDirFile(aRelPath, aAllowNonExists) {
+ let relpath = getTestDirPath() + (aRelPath ? aRelPath : "");
+ return do_get_file(relpath, !!aAllowNonExists);
+}
+
+/**
+ * Helper function for getting the nsIFile for the maintenance service
+ * directory on Windows.
+ *
+ * @return The nsIFile for the maintenance service directory.
+ * @throws If called from a platform other than Windows.
+ */
+function getMaintSvcDir() {
+ if (AppConstants.platform != "win") {
+ do_throw("Windows only function called by a different platform!");
+ }
+
+ const CSIDL_PROGRAM_FILES = 0x26;
+ const CSIDL_PROGRAM_FILESX86 = 0x2a;
+ // This will return an empty string on our Win XP build systems.
+ let maintSvcDir = getSpecialFolderDir(CSIDL_PROGRAM_FILESX86);
+ if (maintSvcDir) {
+ maintSvcDir.append("Mozilla Maintenance Service");
+ debugDump(
+ "using CSIDL_PROGRAM_FILESX86 - maintenance service install " +
+ "directory path: " +
+ maintSvcDir.path
+ );
+ }
+ if (!maintSvcDir || !maintSvcDir.exists()) {
+ maintSvcDir = getSpecialFolderDir(CSIDL_PROGRAM_FILES);
+ if (maintSvcDir) {
+ maintSvcDir.append("Mozilla Maintenance Service");
+ debugDump(
+ "using CSIDL_PROGRAM_FILES - maintenance service install " +
+ "directory path: " +
+ maintSvcDir.path
+ );
+ }
+ }
+ if (!maintSvcDir) {
+ do_throw("Unable to find the maintenance service install directory");
+ }
+
+ return maintSvcDir;
+}
+
+/**
+ * Reads the current update operation/state in the status file in the secure
+ * update log directory.
+ *
+ * @return The status value.
+ */
+function readSecureStatusFile() {
+ let file = getSecureOutputFile("status");
+ if (!file.exists()) {
+ debugDump("update status file does not exist, path: " + file.path);
+ return STATE_NONE;
+ }
+ return readFile(file).split("\n")[0];
+}
+
+/**
+ * Get an nsIFile for a file in the secure update log directory. The file name
+ * is always the value of gTestID and the file extension is specified by the
+ * aFileExt parameter.
+ *
+ * @param aFileExt
+ * The file extension.
+ * @return The nsIFile of the secure update file.
+ */
+function getSecureOutputFile(aFileExt) {
+ let file = getMaintSvcDir();
+ file.append("UpdateLogs");
+ file.append(gTestID + "." + aFileExt);
+ return file;
+}
+
+/**
+ * Get the nsIFile for a Windows special folder determined by the CSIDL
+ * passed.
+ *
+ * @param aCSIDL
+ * The CSIDL for the Windows special folder.
+ * @return The nsIFile for the Windows special folder.
+ * @throws If called from a platform other than Windows.
+ */
+function getSpecialFolderDir(aCSIDL) {
+ if (AppConstants.platform != "win") {
+ do_throw("Windows only function called by a different platform!");
+ }
+
+ let lib = ctypes.open("shell32");
+ let SHGetSpecialFolderPath = lib.declare(
+ "SHGetSpecialFolderPathW",
+ ctypes.winapi_abi,
+ ctypes.bool /* bool(return) */,
+ ctypes.int32_t /* HWND hwndOwner */,
+ ctypes.char16_t.ptr /* LPTSTR lpszPath */,
+ ctypes.int32_t /* int csidl */,
+ ctypes.bool /* BOOL fCreate */
+ );
+
+ let aryPath = ctypes.char16_t.array()(260);
+ let rv = SHGetSpecialFolderPath(0, aryPath, aCSIDL, false);
+ if (!rv) {
+ do_throw(
+ "SHGetSpecialFolderPath failed to retrieve " +
+ aCSIDL +
+ " with Win32 error " +
+ ctypes.winLastError
+ );
+ }
+ lib.close();
+
+ let path = aryPath.readString(); // Convert the c-string to js-string
+ if (!path) {
+ return null;
+ }
+ debugDump("SHGetSpecialFolderPath returned path: " + path);
+ let dir = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile);
+ dir.initWithPath(path);
+ return dir;
+}
+
+XPCOMUtils.defineLazyGetter(this, "gInstallDirPathHash", function test_gIDPH() {
+ if (AppConstants.platform != "win") {
+ do_throw("Windows only function called by a different platform!");
+ }
+
+ if (!MOZ_APP_BASENAME) {
+ return null;
+ }
+
+ let vendor = MOZ_APP_VENDOR ? MOZ_APP_VENDOR : "Mozilla";
+ let appDir = getApplyDirFile();
+
+ const REG_PATH =
+ "SOFTWARE\\" + vendor + "\\" + MOZ_APP_BASENAME + "\\TaskBarIDs";
+ let regKey = Cc["@mozilla.org/windows-registry-key;1"].createInstance(
+ Ci.nsIWindowsRegKey
+ );
+ try {
+ regKey.open(
+ Ci.nsIWindowsRegKey.ROOT_KEY_LOCAL_MACHINE,
+ REG_PATH,
+ Ci.nsIWindowsRegKey.ACCESS_ALL
+ );
+ regKey.writeStringValue(appDir.path, gTestID);
+ return gTestID;
+ } catch (e) {}
+
+ try {
+ regKey.create(
+ Ci.nsIWindowsRegKey.ROOT_KEY_CURRENT_USER,
+ REG_PATH,
+ Ci.nsIWindowsRegKey.ACCESS_ALL
+ );
+ regKey.writeStringValue(appDir.path, gTestID);
+ return gTestID;
+ } catch (e) {
+ logTestInfo(
+ "failed to create registry value. Registry Path: " +
+ REG_PATH +
+ ", Value Name: " +
+ appDir.path +
+ ", Value Data: " +
+ gTestID +
+ ", Exception " +
+ e
+ );
+ do_throw(
+ "Unable to write HKLM or HKCU TaskBarIDs registry value, key path: " +
+ REG_PATH
+ );
+ }
+ return null;
+});
+
+XPCOMUtils.defineLazyGetter(this, "gLocalAppDataDir", function test_gLADD() {
+ if (AppConstants.platform != "win") {
+ do_throw("Windows only function called by a different platform!");
+ }
+
+ const CSIDL_LOCAL_APPDATA = 0x1c;
+ return getSpecialFolderDir(CSIDL_LOCAL_APPDATA);
+});
+
+XPCOMUtils.defineLazyGetter(this, "gCommonAppDataDir", function test_gCDD() {
+ if (AppConstants.platform != "win") {
+ do_throw("Windows only function called by a different platform!");
+ }
+
+ const CSIDL_COMMON_APPDATA = 0x0023;
+ return getSpecialFolderDir(CSIDL_COMMON_APPDATA);
+});
+
+XPCOMUtils.defineLazyGetter(this, "gProgFilesDir", function test_gPFD() {
+ if (AppConstants.platform != "win") {
+ do_throw("Windows only function called by a different platform!");
+ }
+
+ const CSIDL_PROGRAM_FILES = 0x26;
+ return getSpecialFolderDir(CSIDL_PROGRAM_FILES);
+});
+
+/**
+ * Helper function for getting the update root directory used by the tests. This
+ * returns the same directory as returned by nsXREDirProvider::GetUpdateRootDir
+ * in nsXREDirProvider.cpp so an application will be able to find the update
+ * when running a test that launches the application.
+ *
+ * The aGetOldLocation argument performs the same function that the argument
+ * with the same name in nsXREDirProvider::GetUpdateRootDir performs. If true,
+ * the old (pre-migration) update directory is returned.
+ */
+function getMockUpdRootD(aGetOldLocation = false) {
+ if (AppConstants.platform == "win") {
+ return getMockUpdRootDWin(aGetOldLocation);
+ }
+
+ if (AppConstants.platform == "macosx") {
+ return getMockUpdRootDMac();
+ }
+
+ return getApplyDirFile(DIR_MACOS);
+}
+
+/**
+ * Helper function for getting the update root directory used by the tests. This
+ * returns the same directory as returned by nsXREDirProvider::GetUpdateRootDir
+ * in nsXREDirProvider.cpp so an application will be able to find the update
+ * when running a test that launches the application.
+ *
+ * @throws If called from a platform other than Windows.
+ */
+function getMockUpdRootDWin(aGetOldLocation) {
+ if (AppConstants.platform != "win") {
+ do_throw("Windows only function called by a different platform!");
+ }
+
+ let relPathUpdates = "";
+ let dataDirectory = gCommonAppDataDir.clone();
+ if (aGetOldLocation) {
+ relPathUpdates += "Mozilla";
+ } else {
+ relPathUpdates += "Mozilla-1de4eec8-1241-4177-a864-e594e8d1fb38";
+ }
+
+ relPathUpdates += "\\" + DIR_UPDATES + "\\" + gInstallDirPathHash;
+ let updatesDir = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile);
+ updatesDir.initWithPath(dataDirectory.path + "\\" + relPathUpdates);
+ return updatesDir;
+}
+
+function createWorldWritableAppUpdateDir() {
+ // This function is only necessary in Windows
+ if (AppConstants.platform == "win") {
+ let installDir = Services.dirsvc.get(
+ XRE_EXECUTABLE_FILE,
+ Ci.nsIFile
+ ).parent;
+ let exitValue = runTestHelperSync(["create-update-dir", installDir.path]);
+ Assert.equal(exitValue, 0, "The helper process exit value should be 0");
+ }
+}
+
+XPCOMUtils.defineLazyGetter(this, "gUpdatesRootDir", function test_gURD() {
+ if (AppConstants.platform != "macosx") {
+ do_throw("Mac OS X only function called by a different platform!");
+ }
+
+ let dir = Services.dirsvc.get("ULibDir", Ci.nsIFile);
+ dir.append("Caches");
+ if (MOZ_APP_VENDOR || MOZ_APP_BASENAME) {
+ dir.append(MOZ_APP_VENDOR ? MOZ_APP_VENDOR : MOZ_APP_BASENAME);
+ } else {
+ dir.append("Mozilla");
+ }
+ dir.append(DIR_UPDATES);
+ return dir;
+});
+
+/**
+ * Helper function for getting the update root directory used by the tests. This
+ * returns the same directory as returned by nsXREDirProvider::GetUpdateRootDir
+ * in nsXREDirProvider.cpp so an application will be able to find the update
+ * when running a test that launches the application.
+ */
+function getMockUpdRootDMac() {
+ if (AppConstants.platform != "macosx") {
+ do_throw("Mac OS X only function called by a different platform!");
+ }
+
+ let appDir = Services.dirsvc.get(XRE_EXECUTABLE_FILE, Ci.nsIFile).parent
+ .parent.parent;
+ let appDirPath = appDir.path;
+ appDirPath = appDirPath.substr(0, appDirPath.length - 4);
+
+ let pathUpdates = gUpdatesRootDir.path + appDirPath;
+ let updatesDir = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile);
+ updatesDir.initWithPath(pathUpdates);
+ return updatesDir;
+}
+
+/**
+ * Creates an update in progress lock file in the specified directory on
+ * Windows.
+ *
+ * @param aDir
+ * The nsIFile for the directory where the lock file should be created.
+ * @throws If called from a platform other than Windows.
+ */
+function createUpdateInProgressLockFile(aDir) {
+ if (AppConstants.platform != "win") {
+ do_throw("Windows only function called by a different platform!");
+ }
+
+ let file = aDir.clone();
+ file.append(FILE_UPDATE_IN_PROGRESS_LOCK);
+ file.create(file.NORMAL_FILE_TYPE, 0o444);
+ file.QueryInterface(Ci.nsILocalFileWin);
+ file.readOnly = true;
+ Assert.ok(file.exists(), MSG_SHOULD_EXIST + getMsgPath(file.path));
+ Assert.ok(!file.isWritable(), "the lock file should not be writeable");
+}
+
+/**
+ * Removes an update in progress lock file in the specified directory on
+ * Windows.
+ *
+ * @param aDir
+ * The nsIFile for the directory where the lock file is located.
+ * @throws If called from a platform other than Windows.
+ */
+function removeUpdateInProgressLockFile(aDir) {
+ if (AppConstants.platform != "win") {
+ do_throw("Windows only function called by a different platform!");
+ }
+
+ let file = aDir.clone();
+ file.append(FILE_UPDATE_IN_PROGRESS_LOCK);
+ file.QueryInterface(Ci.nsILocalFileWin);
+ file.readOnly = false;
+ file.remove(false);
+ Assert.ok(!file.exists(), MSG_SHOULD_NOT_EXIST + getMsgPath(file.path));
+}
+
+/**
+ * Copies the test updater to the GRE binary directory and returns the nsIFile
+ * for the copied test updater.
+ *
+ * @return nsIFIle for the copied test updater.
+ */
+function copyTestUpdaterToBinDir() {
+ let updaterLeafName =
+ AppConstants.platform == "macosx" ? "updater.app" : FILE_UPDATER_BIN;
+ let testUpdater = getTestDirFile(updaterLeafName);
+ let updater = getGREBinDir();
+ updater.append(updaterLeafName);
+ if (!updater.exists()) {
+ testUpdater.copyToFollowingLinks(updater.parent, updaterLeafName);
+ }
+ if (AppConstants.platform == "macosx") {
+ updater.append("Contents");
+ updater.append("MacOS");
+ updater.append("org.mozilla.updater");
+ }
+ return updater;
+}
+
+/**
+ * Logs the contents of an update log and for maintenance service tests this
+ * will log the contents of the latest maintenanceservice.log.
+ *
+ * @param aLogLeafName
+ * The leaf name of the update log.
+ */
+function logUpdateLog(aLogLeafName) {
+ let updateLog = getUpdateDirFile(aLogLeafName);
+ if (updateLog.exists()) {
+ // xpcshell tests won't display the entire contents so log each line.
+ let updateLogContents = readFileBytes(updateLog).replace(/\r\n/g, "\n");
+ updateLogContents = removeTimeStamps(updateLogContents);
+ updateLogContents = replaceLogPaths(updateLogContents);
+ let aryLogContents = updateLogContents.split("\n");
+ logTestInfo("contents of " + updateLog.path + ":");
+ aryLogContents.forEach(function LU_ULC_FE(aLine) {
+ logTestInfo(aLine);
+ });
+ } else {
+ logTestInfo("update log doesn't exist, path: " + updateLog.path);
+ }
+
+ if (gIsServiceTest) {
+ let secureStatus = readSecureStatusFile();
+ logTestInfo("secure update status: " + secureStatus);
+
+ updateLog = getSecureOutputFile("log");
+ if (updateLog.exists()) {
+ // xpcshell tests won't display the entire contents so log each line.
+ let updateLogContents = readFileBytes(updateLog).replace(/\r\n/g, "\n");
+ updateLogContents = removeTimeStamps(updateLogContents);
+ updateLogContents = replaceLogPaths(updateLogContents);
+ let aryLogContents = updateLogContents.split("\n");
+ logTestInfo("contents of " + updateLog.path + ":");
+ aryLogContents.forEach(function LU_SULC_FE(aLine) {
+ logTestInfo(aLine);
+ });
+ } else {
+ logTestInfo("secure update log doesn't exist, path: " + updateLog.path);
+ }
+
+ let serviceLog = getMaintSvcDir();
+ serviceLog.append("logs");
+ serviceLog.append("maintenanceservice.log");
+ if (serviceLog.exists()) {
+ // xpcshell tests won't display the entire contents so log each line.
+ let serviceLogContents = readFileBytes(serviceLog).replace(/\r\n/g, "\n");
+ serviceLogContents = replaceLogPaths(serviceLogContents);
+ let aryLogContents = serviceLogContents.split("\n");
+ logTestInfo("contents of " + serviceLog.path + ":");
+ aryLogContents.forEach(function LU_MSLC_FE(aLine) {
+ logTestInfo(aLine);
+ });
+ } else {
+ logTestInfo(
+ "maintenance service log doesn't exist, path: " + serviceLog.path
+ );
+ }
+ }
+}
+
+/**
+ * Gets the maintenance service log contents.
+ */
+function readServiceLogFile() {
+ let file = getMaintSvcDir();
+ file.append("logs");
+ file.append("maintenanceservice.log");
+ return readFile(file);
+}
+
+/**
+ * Launches the updater binary to apply an update for updater tests.
+ *
+ * @param aExpectedStatus
+ * The expected value of update.status when the update finishes. For
+ * service tests passing STATE_PENDING or STATE_APPLIED will change the
+ * value to STATE_PENDING_SVC and STATE_APPLIED_SVC respectively.
+ * @param aSwitchApp
+ * If true the update should switch the application with an updated
+ * staged application and if false the update should be applied to the
+ * installed application.
+ * @param aExpectedExitValue
+ * The expected exit value from the updater binary for non-service
+ * tests.
+ * @param aCheckSvcLog
+ * Whether the service log should be checked for service tests.
+ * @param aPatchDirPath (optional)
+ * When specified the patch directory path to use for invalid argument
+ * tests otherwise the normal path will be used.
+ * @param aInstallDirPath (optional)
+ * When specified the install directory path to use for invalid
+ * argument tests otherwise the normal path will be used.
+ * @param aApplyToDirPath (optional)
+ * When specified the apply to / working directory path to use for
+ * invalid argument tests otherwise the normal path will be used.
+ * @param aCallbackPath (optional)
+ * When specified the callback path to use for invalid argument tests
+ * otherwise the normal path will be used.
+ */
+function runUpdate(
+ aExpectedStatus,
+ aSwitchApp,
+ aExpectedExitValue,
+ aCheckSvcLog,
+ aPatchDirPath,
+ aInstallDirPath,
+ aApplyToDirPath,
+ aCallbackPath
+) {
+ let isInvalidArgTest =
+ !!aPatchDirPath ||
+ !!aInstallDirPath ||
+ !!aApplyToDirPath ||
+ !!aCallbackPath;
+
+ let svcOriginalLog;
+ if (gIsServiceTest) {
+ copyFileToTestAppDir(FILE_MAINTENANCE_SERVICE_BIN, false);
+ copyFileToTestAppDir(FILE_MAINTENANCE_SERVICE_INSTALLER_BIN, false);
+ if (aCheckSvcLog) {
+ svcOriginalLog = readServiceLogFile();
+ }
+ }
+
+ let pid = 0;
+ if (gPIDPersistProcess) {
+ pid = gPIDPersistProcess.pid;
+ Services.env.set("MOZ_TEST_SHORTER_WAIT_PID", "1");
+ }
+
+ let updateBin = copyTestUpdaterToBinDir();
+ Assert.ok(updateBin.exists(), MSG_SHOULD_EXIST + getMsgPath(updateBin.path));
+
+ let updatesDirPath = aPatchDirPath || getUpdateDirFile(DIR_PATCH).path;
+ let installDirPath = aInstallDirPath || getApplyDirFile().path;
+ let applyToDirPath = aApplyToDirPath || getApplyDirFile().path;
+ let stageDirPath = aApplyToDirPath || getStageDirFile().path;
+
+ let callbackApp = getApplyDirFile(DIR_RESOURCES + gCallbackBinFile);
+ Assert.ok(
+ callbackApp.exists(),
+ MSG_SHOULD_EXIST + ", path: " + callbackApp.path
+ );
+ callbackApp.permissions = PERMS_DIRECTORY;
+
+ setAppBundleModTime();
+
+ let args = [updatesDirPath, installDirPath];
+ if (aSwitchApp) {
+ args[2] = stageDirPath;
+ args[3] = pid + "/replace";
+ } else {
+ args[2] = applyToDirPath;
+ args[3] = pid;
+ }
+
+ let launchBin = gIsServiceTest && isInvalidArgTest ? callbackApp : updateBin;
+
+ if (!isInvalidArgTest) {
+ args = args.concat([callbackApp.parent.path, callbackApp.path]);
+ args = args.concat(gCallbackArgs);
+ } else if (gIsServiceTest) {
+ args = ["launch-service", updateBin.path].concat(args);
+ } else if (aCallbackPath) {
+ args = args.concat([callbackApp.parent.path, aCallbackPath]);
+ }
+
+ debugDump("launching the program: " + launchBin.path + " " + args.join(" "));
+
+ if (aSwitchApp && !isInvalidArgTest) {
+ // We want to set the env vars again
+ gShouldResetEnv = undefined;
+ }
+
+ setEnvironment();
+
+ let process = Cc["@mozilla.org/process/util;1"].createInstance(Ci.nsIProcess);
+ process.init(launchBin);
+ process.run(true, args, args.length);
+
+ resetEnvironment();
+
+ if (gPIDPersistProcess) {
+ Services.env.set("MOZ_TEST_SHORTER_WAIT_PID", "");
+ }
+
+ let status = readStatusFile();
+ if (
+ (!gIsServiceTest && process.exitValue != aExpectedExitValue) ||
+ (status != aExpectedStatus && !gIsServiceTest && !isInvalidArgTest)
+ ) {
+ if (process.exitValue != aExpectedExitValue) {
+ logTestInfo(
+ "updater exited with unexpected value! Got: " +
+ process.exitValue +
+ ", Expected: " +
+ aExpectedExitValue
+ );
+ }
+ if (status != aExpectedStatus) {
+ logTestInfo(
+ "update status is not the expected status! Got: " +
+ status +
+ ", Expected: " +
+ aExpectedStatus
+ );
+ }
+ logUpdateLog(FILE_LAST_UPDATE_LOG);
+ }
+
+ if (gIsServiceTest && isInvalidArgTest) {
+ let secureStatus = readSecureStatusFile();
+ if (secureStatus != STATE_NONE) {
+ status = secureStatus;
+ }
+ }
+
+ if (!gIsServiceTest) {
+ Assert.equal(
+ process.exitValue,
+ aExpectedExitValue,
+ "the process exit value" + MSG_SHOULD_EQUAL
+ );
+ }
+
+ if (status != aExpectedStatus) {
+ logUpdateLog(FILE_UPDATE_LOG);
+ }
+ Assert.equal(status, aExpectedStatus, "the update status" + MSG_SHOULD_EQUAL);
+
+ Assert.ok(
+ !updateHasBinaryTransparencyErrorResult(),
+ "binary transparency is not being processed for now"
+ );
+
+ if (gIsServiceTest && aCheckSvcLog) {
+ let contents = readServiceLogFile();
+ Assert.notEqual(
+ contents,
+ svcOriginalLog,
+ "the contents of the maintenanceservice.log should not " +
+ "be the same as the original contents"
+ );
+ if (gEnvForceServiceFallback) {
+ // If we are forcing the service to fail and fall back to update without
+ // the service, the service log should reflect that we failed in that way.
+ Assert.ok(
+ contents.includes(LOG_SVC_UNSUCCESSFUL_LAUNCH),
+ "the contents of the maintenanceservice.log should " +
+ "contain the unsuccessful launch string"
+ );
+ } else if (!isInvalidArgTest) {
+ Assert.notEqual(
+ contents.indexOf(LOG_SVC_SUCCESSFUL_LAUNCH),
+ -1,
+ "the contents of the maintenanceservice.log should " +
+ "contain the successful launch string"
+ );
+ }
+ }
+}
+
+/**
+ * Launches the helper binary synchronously with the specified arguments for
+ * updater tests.
+ *
+ * @param aArgs
+ * The arguments to pass to the helper binary.
+ * @return the process exit value returned by the helper binary.
+ */
+function runTestHelperSync(aArgs) {
+ let helperBin = getTestDirFile(FILE_HELPER_BIN);
+ let process = Cc["@mozilla.org/process/util;1"].createInstance(Ci.nsIProcess);
+ process.init(helperBin);
+ debugDump("Running " + helperBin.path + " " + aArgs.join(" "));
+ process.run(true, aArgs, aArgs.length);
+ return process.exitValue;
+}
+
+/**
+ * Creates a symlink for updater tests.
+ */
+function createSymlink() {
+ let args = [
+ "setup-symlink",
+ "moz-foo",
+ "moz-bar",
+ "target",
+ getApplyDirFile().path + "/" + DIR_RESOURCES + "link",
+ ];
+ let exitValue = runTestHelperSync(args);
+ Assert.equal(exitValue, 0, "the helper process exit value should be 0");
+ let file = getApplyDirFile(DIR_RESOURCES + "link");
+ Assert.ok(file.exists(), MSG_SHOULD_EXIST + ", path: " + file.path);
+ file.permissions = 0o666;
+ args = [
+ "setup-symlink",
+ "moz-foo2",
+ "moz-bar2",
+ "target2",
+ getApplyDirFile().path + "/" + DIR_RESOURCES + "link2",
+ "change-perm",
+ ];
+ exitValue = runTestHelperSync(args);
+ Assert.equal(exitValue, 0, "the helper process exit value should be 0");
+}
+
+/**
+ * Removes a symlink for updater tests.
+ */
+function removeSymlink() {
+ let args = [
+ "remove-symlink",
+ "moz-foo",
+ "moz-bar",
+ "target",
+ getApplyDirFile().path + "/" + DIR_RESOURCES + "link",
+ ];
+ let exitValue = runTestHelperSync(args);
+ Assert.equal(exitValue, 0, "the helper process exit value should be 0");
+ args = [
+ "remove-symlink",
+ "moz-foo2",
+ "moz-bar2",
+ "target2",
+ getApplyDirFile().path + "/" + DIR_RESOURCES + "link2",
+ ];
+ exitValue = runTestHelperSync(args);
+ Assert.equal(exitValue, 0, "the helper process exit value should be 0");
+}
+
+/**
+ * Checks a symlink for updater tests.
+ */
+function checkSymlink() {
+ let args = [
+ "check-symlink",
+ getApplyDirFile().path + "/" + DIR_RESOURCES + "link",
+ ];
+ let exitValue = runTestHelperSync(args);
+ Assert.equal(exitValue, 0, "the helper process exit value should be 0");
+}
+
+/**
+ * Sets the active update and related information for updater tests.
+ */
+function setupActiveUpdate() {
+ let pendingState = gIsServiceTest ? STATE_PENDING_SVC : STATE_PENDING;
+ let patchProps = { state: pendingState };
+ let patches = getLocalPatchString(patchProps);
+ let updates = getLocalUpdateString({}, patches);
+ writeUpdatesToXMLFile(getLocalUpdatesXMLString(updates), true);
+ writeVersionFile(DEFAULT_UPDATE_VERSION);
+ writeStatusFile(pendingState);
+ reloadUpdateManagerData();
+ Assert.ok(!!gUpdateManager.readyUpdate, "the ready update should be defined");
+}
+
+/**
+ * Stages an update using nsIUpdateProcessor:processUpdate for updater tests.
+ *
+ * @param aStateAfterStage
+ * The expected update state after the update has been staged.
+ * @param aCheckSvcLog
+ * Whether the service log should be checked for service tests.
+ * @param aUpdateRemoved (optional)
+ * Whether the update is removed after staging. This can happen when
+ * a staging failure occurs.
+ */
+async function stageUpdate(
+ aStateAfterStage,
+ aCheckSvcLog,
+ aUpdateRemoved = false
+) {
+ debugDump("start - attempting to stage update");
+
+ let svcLogOriginalContents;
+ if (gIsServiceTest && aCheckSvcLog) {
+ svcLogOriginalContents = readServiceLogFile();
+ }
+
+ setAppBundleModTime();
+ setEnvironment();
+ try {
+ // Stage the update.
+ Cc["@mozilla.org/updates/update-processor;1"]
+ .createInstance(Ci.nsIUpdateProcessor)
+ .processUpdate();
+ } catch (e) {
+ Assert.ok(
+ false,
+ "error thrown while calling processUpdate, Exception: " + e
+ );
+ }
+ await waitForEvent("update-staged", aStateAfterStage);
+ resetEnvironment();
+
+ if (AppConstants.platform == "win") {
+ if (gIsServiceTest) {
+ waitForServiceStop(false);
+ } else {
+ let updater = getApplyDirFile(FILE_UPDATER_BIN);
+ await TestUtils.waitForCondition(
+ () => !isFileInUse(updater),
+ "Waiting for the file tp not be in use, Path: " + updater.path
+ );
+ }
+ }
+
+ if (!aUpdateRemoved) {
+ Assert.equal(
+ readStatusState(),
+ aStateAfterStage,
+ "the status file state" + MSG_SHOULD_EQUAL
+ );
+
+ Assert.equal(
+ gUpdateManager.readyUpdate.state,
+ aStateAfterStage,
+ "the update state" + MSG_SHOULD_EQUAL
+ );
+ }
+
+ let log = getUpdateDirFile(FILE_LAST_UPDATE_LOG);
+ Assert.ok(log.exists(), MSG_SHOULD_EXIST + getMsgPath(log.path));
+
+ log = getUpdateDirFile(FILE_UPDATE_LOG);
+ Assert.ok(!log.exists(), MSG_SHOULD_NOT_EXIST + getMsgPath(log.path));
+
+ log = getUpdateDirFile(FILE_BACKUP_UPDATE_LOG);
+ Assert.ok(!log.exists(), MSG_SHOULD_NOT_EXIST + getMsgPath(log.path));
+
+ let stageDir = getStageDirFile();
+ if (
+ aStateAfterStage == STATE_APPLIED ||
+ aStateAfterStage == STATE_APPLIED_SVC
+ ) {
+ Assert.ok(stageDir.exists(), MSG_SHOULD_EXIST + getMsgPath(stageDir.path));
+ } else {
+ Assert.ok(
+ !stageDir.exists(),
+ MSG_SHOULD_NOT_EXIST + getMsgPath(stageDir.path)
+ );
+ }
+
+ if (gIsServiceTest && aCheckSvcLog) {
+ let contents = readServiceLogFile();
+ Assert.notEqual(
+ contents,
+ svcLogOriginalContents,
+ "the contents of the maintenanceservice.log should not " +
+ "be the same as the original contents"
+ );
+ Assert.notEqual(
+ contents.indexOf(LOG_SVC_SUCCESSFUL_LAUNCH),
+ -1,
+ "the contents of the maintenanceservice.log should " +
+ "contain the successful launch string"
+ );
+ }
+
+ debugDump("finish - attempting to stage update");
+}
+
+/**
+ * Helper function to check whether the maintenance service updater tests should
+ * run. See bug 711660 for more details.
+ *
+ * @return true if the test should run and false if it shouldn't.
+ * @throws If called from a platform other than Windows.
+ */
+function shouldRunServiceTest() {
+ if (AppConstants.platform != "win") {
+ do_throw("Windows only function called by a different platform!");
+ }
+
+ let binDir = getGREBinDir();
+ let updaterBin = binDir.clone();
+ updaterBin.append(FILE_UPDATER_BIN);
+ Assert.ok(
+ updaterBin.exists(),
+ MSG_SHOULD_EXIST + ", leafName: " + updaterBin.leafName
+ );
+
+ let updaterBinPath = updaterBin.path;
+ if (/ /.test(updaterBinPath)) {
+ updaterBinPath = '"' + updaterBinPath + '"';
+ }
+
+ let isBinSigned = isBinarySigned(updaterBinPath);
+
+ const REG_PATH =
+ "SOFTWARE\\Mozilla\\MaintenanceService\\" +
+ "3932ecacee736d366d6436db0f55bce4";
+ let key = Cc["@mozilla.org/windows-registry-key;1"].createInstance(
+ Ci.nsIWindowsRegKey
+ );
+ try {
+ key.open(
+ Ci.nsIWindowsRegKey.ROOT_KEY_LOCAL_MACHINE,
+ REG_PATH,
+ Ci.nsIWindowsRegKey.ACCESS_READ | key.WOW64_64
+ );
+ } catch (e) {
+ // The build system could sign the files and not have the test registry key
+ // in which case we should fail the test if the updater binary is signed so
+ // the build system can be fixed by adding the registry key.
+ if (IS_AUTHENTICODE_CHECK_ENABLED) {
+ Assert.ok(
+ !isBinSigned,
+ "the updater.exe binary should not be signed when the test " +
+ "registry key doesn't exist (if it is, build system " +
+ "configuration bug?)"
+ );
+ }
+
+ logTestInfo(
+ "this test can only run on the buildbot build system at this time"
+ );
+ return false;
+ }
+
+ // Check to make sure the service is installed
+ let args = ["wait-for-service-stop", "MozillaMaintenance", "10"];
+ let exitValue = runTestHelperSync(args);
+ Assert.notEqual(
+ exitValue,
+ 0xee,
+ "the maintenance service should be " +
+ "installed (if not, build system configuration bug?)"
+ );
+
+ if (IS_AUTHENTICODE_CHECK_ENABLED) {
+ // The test registry key exists and IS_AUTHENTICODE_CHECK_ENABLED is true
+ // so the binaries should be signed. To run the test locally
+ // DISABLE_UPDATER_AUTHENTICODE_CHECK can be defined.
+ Assert.ok(
+ isBinSigned,
+ "the updater.exe binary should be signed (if not, build system " +
+ "configuration bug?)"
+ );
+ }
+
+ // In case the machine is running an old maintenance service or if it
+ // is not installed, and permissions exist to install it. Then install
+ // the newer bin that we have since all of the other checks passed.
+ return attemptServiceInstall();
+}
+
+/**
+ * Helper function to check whether the a binary is signed.
+ *
+ * @param aBinPath
+ * The path to the file to check if it is signed.
+ * @return true if the file is signed and false if it isn't.
+ */
+function isBinarySigned(aBinPath) {
+ let args = ["check-signature", aBinPath];
+ let exitValue = runTestHelperSync(args);
+ if (exitValue != 0) {
+ logTestInfo(
+ "binary is not signed. " +
+ FILE_HELPER_BIN +
+ " returned " +
+ exitValue +
+ " for file " +
+ aBinPath
+ );
+ return false;
+ }
+ return true;
+}
+
+/**
+ * Helper function for setting up the application files required to launch the
+ * application for the updater tests by either copying or creating symlinks to
+ * the files.
+ *
+ * @param options.requiresOmnijar when true, copy or symlink omnijars as well.
+ * This may be required to launch the updated application and have non-trivial
+ * functionality available.
+ */
+function setupAppFiles({ requiresOmnijar = false } = {}) {
+ debugDump(
+ "start - copying or creating symlinks to application files " +
+ "for the test"
+ );
+
+ let destDir = getApplyDirFile();
+ if (!destDir.exists()) {
+ try {
+ destDir.create(Ci.nsIFile.DIRECTORY_TYPE, PERMS_DIRECTORY);
+ } catch (e) {
+ logTestInfo(
+ "unable to create directory! Path: " +
+ destDir.path +
+ ", Exception: " +
+ e
+ );
+ do_throw(e);
+ }
+ }
+
+ // Required files for the application or the test that aren't listed in the
+ // dependentlibs.list file.
+ let appFiles = [
+ { relPath: FILE_APP_BIN, inGreDir: false },
+ { relPath: FILE_APPLICATION_INI, inGreDir: true },
+ { relPath: "dependentlibs.list", inGreDir: true },
+ ];
+
+ if (requiresOmnijar) {
+ appFiles.push({ relPath: AppConstants.OMNIJAR_NAME, inGreDir: true });
+
+ if (AppConstants.MOZ_BUILD_APP == "browser") {
+ // Only Firefox uses an app-specific omnijar.
+ appFiles.push({
+ relPath: "browser/" + AppConstants.OMNIJAR_NAME,
+ inGreDir: true,
+ });
+ }
+ }
+
+ // On Linux the updater.png must also be copied and libsoftokn3.so must be
+ // symlinked or copied.
+ if (AppConstants.platform == "linux") {
+ appFiles.push(
+ { relPath: "icons/updater.png", inGreDir: true },
+ { relPath: "libsoftokn3.so", inGreDir: true }
+ );
+ }
+
+ // Read the dependent libs file leafnames from the dependentlibs.list file
+ // into the array.
+ let deplibsFile = gGREDirOrig.clone();
+ deplibsFile.append("dependentlibs.list");
+ let fis = Cc["@mozilla.org/network/file-input-stream;1"].createInstance(
+ Ci.nsIFileInputStream
+ );
+ fis.init(deplibsFile, 0x01, 0o444, Ci.nsIFileInputStream.CLOSE_ON_EOF);
+ fis.QueryInterface(Ci.nsILineInputStream);
+
+ let hasMore;
+ let line = {};
+ do {
+ hasMore = fis.readLine(line);
+ appFiles.push({ relPath: line.value, inGreDir: false });
+ } while (hasMore);
+
+ fis.close();
+
+ appFiles.forEach(function CMAF_FLN_FE(aAppFile) {
+ copyFileToTestAppDir(aAppFile.relPath, aAppFile.inGreDir);
+ });
+
+ copyTestUpdaterToBinDir();
+
+ debugDump(
+ "finish - copying or creating symlinks to application files " +
+ "for the test"
+ );
+}
+
+/**
+ * Copies the specified files from the dist/bin directory into the test's
+ * application directory.
+ *
+ * @param aFileRelPath
+ * The relative path to the source and the destination of the file to
+ * copy.
+ * @param aInGreDir
+ * Whether the file is located in the GRE directory which is
+ * <bundle>/Contents/Resources on Mac OS X and is the installation
+ * directory on all other platforms. If false the file must be in the
+ * GRE Binary directory which is <bundle>/Contents/MacOS on Mac OS X
+ * and is the installation directory on on all other platforms.
+ */
+function copyFileToTestAppDir(aFileRelPath, aInGreDir) {
+ // gGREDirOrig and gGREBinDirOrig must always be cloned when changing its
+ // properties
+ let srcFile = aInGreDir ? gGREDirOrig.clone() : gGREBinDirOrig.clone();
+ let destFile = aInGreDir ? getGREDir() : getGREBinDir();
+ let fileRelPath = aFileRelPath;
+ let pathParts = fileRelPath.split("/");
+ for (let i = 0; i < pathParts.length; i++) {
+ if (pathParts[i]) {
+ srcFile.append(pathParts[i]);
+ destFile.append(pathParts[i]);
+ }
+ }
+
+ if (AppConstants.platform == "macosx" && !srcFile.exists()) {
+ debugDump(
+ "unable to copy file since it doesn't exist! Checking if " +
+ fileRelPath +
+ ".app exists. Path: " +
+ srcFile.path
+ );
+ // gGREDirOrig and gGREBinDirOrig must always be cloned when changing its
+ // properties
+ srcFile = aInGreDir ? gGREDirOrig.clone() : gGREBinDirOrig.clone();
+ destFile = aInGreDir ? getGREDir() : getGREBinDir();
+ for (let i = 0; i < pathParts.length; i++) {
+ if (pathParts[i]) {
+ srcFile.append(
+ pathParts[i] + (pathParts.length - 1 == i ? ".app" : "")
+ );
+ destFile.append(
+ pathParts[i] + (pathParts.length - 1 == i ? ".app" : "")
+ );
+ }
+ }
+ fileRelPath = fileRelPath + ".app";
+ }
+ Assert.ok(
+ srcFile.exists(),
+ MSG_SHOULD_EXIST + ", leafName: " + srcFile.leafName
+ );
+
+ // Symlink libraries. Note that the XUL library on Mac OS X doesn't have a
+ // file extension and shouldSymlink will always be false on Windows.
+ let shouldSymlink =
+ pathParts[pathParts.length - 1] == "XUL" ||
+ fileRelPath.substr(fileRelPath.length - 3) == ".so" ||
+ fileRelPath.substr(fileRelPath.length - 6) == ".dylib";
+ if (!shouldSymlink) {
+ if (!destFile.exists()) {
+ try {
+ srcFile.copyToFollowingLinks(destFile.parent, destFile.leafName);
+ } catch (e) {
+ // Just in case it is partially copied
+ if (destFile.exists()) {
+ try {
+ destFile.remove(true);
+ } catch (ex) {
+ logTestInfo(
+ "unable to remove file that failed to copy! Path: " +
+ destFile.path +
+ ", Exception: " +
+ ex
+ );
+ }
+ }
+ do_throw(
+ "Unable to copy file! Path: " + srcFile.path + ", Exception: " + e
+ );
+ }
+ }
+ } else {
+ try {
+ if (destFile.exists()) {
+ destFile.remove(false);
+ }
+ let ln = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile);
+ ln.initWithPath("/bin/ln");
+ let process = Cc["@mozilla.org/process/util;1"].createInstance(
+ Ci.nsIProcess
+ );
+ process.init(ln);
+ let args = ["-s", srcFile.path, destFile.path];
+ process.run(true, args, args.length);
+ Assert.ok(
+ destFile.isSymlink(),
+ destFile.leafName + " should be a symlink"
+ );
+ } catch (e) {
+ do_throw(
+ "Unable to create symlink for file! Path: " +
+ srcFile.path +
+ ", Exception: " +
+ e
+ );
+ }
+ }
+}
+
+/**
+ * Attempts to upgrade the maintenance service if permissions are allowed.
+ * This is useful for XP where we have permission to upgrade in case an
+ * older service installer exists. Also if the user manually installed into
+ * a unprivileged location.
+ *
+ * @return true if the installed service is from this build. If the installed
+ * service is not from this build the test will fail instead of
+ * returning false.
+ * @throws If called from a platform other than Windows.
+ */
+function attemptServiceInstall() {
+ if (AppConstants.platform != "win") {
+ do_throw("Windows only function called by a different platform!");
+ }
+
+ let maintSvcDir = getMaintSvcDir();
+ Assert.ok(
+ maintSvcDir.exists(),
+ MSG_SHOULD_EXIST + ", leafName: " + maintSvcDir.leafName
+ );
+ let oldMaintSvcBin = maintSvcDir.clone();
+ oldMaintSvcBin.append(FILE_MAINTENANCE_SERVICE_BIN);
+ Assert.ok(
+ oldMaintSvcBin.exists(),
+ MSG_SHOULD_EXIST + ", leafName: " + oldMaintSvcBin.leafName
+ );
+ let buildMaintSvcBin = getGREBinDir();
+ buildMaintSvcBin.append(FILE_MAINTENANCE_SERVICE_BIN);
+ if (readFileBytes(oldMaintSvcBin) == readFileBytes(buildMaintSvcBin)) {
+ debugDump(
+ "installed maintenance service binary is the same as the " +
+ "build's maintenance service binary"
+ );
+ return true;
+ }
+ let backupMaintSvcBin = maintSvcDir.clone();
+ backupMaintSvcBin.append(FILE_MAINTENANCE_SERVICE_BIN + ".backup");
+ try {
+ if (backupMaintSvcBin.exists()) {
+ backupMaintSvcBin.remove(false);
+ }
+ oldMaintSvcBin.moveTo(
+ maintSvcDir,
+ FILE_MAINTENANCE_SERVICE_BIN + ".backup"
+ );
+ buildMaintSvcBin.copyTo(maintSvcDir, FILE_MAINTENANCE_SERVICE_BIN);
+ backupMaintSvcBin.remove(false);
+ } catch (e) {
+ // Restore the original file in case the moveTo was successful.
+ if (backupMaintSvcBin.exists()) {
+ oldMaintSvcBin = maintSvcDir.clone();
+ oldMaintSvcBin.append(FILE_MAINTENANCE_SERVICE_BIN);
+ if (!oldMaintSvcBin.exists()) {
+ backupMaintSvcBin.moveTo(maintSvcDir, FILE_MAINTENANCE_SERVICE_BIN);
+ }
+ }
+ Assert.ok(
+ false,
+ "should be able copy the test maintenance service to " +
+ "the maintenance service directory (if not, build system " +
+ "configuration bug?), path: " +
+ maintSvcDir.path
+ );
+ }
+
+ return true;
+}
+
+/**
+ * Waits for the applications that are launched by the maintenance service to
+ * stop.
+ *
+ * @throws If called from a platform other than Windows.
+ */
+function waitServiceApps() {
+ if (AppConstants.platform != "win") {
+ do_throw("Windows only function called by a different platform!");
+ }
+
+ // maintenanceservice_installer.exe is started async during updates.
+ waitForApplicationStop("maintenanceservice_installer.exe");
+ // maintenanceservice_tmp.exe is started async from the service installer.
+ waitForApplicationStop("maintenanceservice_tmp.exe");
+ // In case the SCM thinks the service is stopped, but process still exists.
+ waitForApplicationStop("maintenanceservice.exe");
+}
+
+/**
+ * Waits for the maintenance service to stop.
+ *
+ * @throws If called from a platform other than Windows.
+ */
+function waitForServiceStop(aFailTest) {
+ if (AppConstants.platform != "win") {
+ do_throw("Windows only function called by a different platform!");
+ }
+
+ waitServiceApps();
+ debugDump("waiting for the maintenance service to stop if necessary");
+ // Use the helper bin to ensure the service is stopped. If not stopped, then
+ // wait for the service to stop (at most 120 seconds).
+ let args = ["wait-for-service-stop", "MozillaMaintenance", "120"];
+ let exitValue = runTestHelperSync(args);
+ Assert.notEqual(exitValue, 0xee, "the maintenance service should exist");
+ if (exitValue != 0) {
+ if (aFailTest) {
+ Assert.ok(
+ false,
+ "the maintenance service should stop, process exit " +
+ "value: " +
+ exitValue
+ );
+ }
+ logTestInfo(
+ "maintenance service did not stop which may cause test " +
+ "failures later, process exit value: " +
+ exitValue
+ );
+ } else {
+ debugDump("service stopped");
+ }
+ waitServiceApps();
+}
+
+/**
+ * Waits for the specified application to stop.
+ *
+ * @param aApplication
+ * The application binary name to wait until it has stopped.
+ * @throws If called from a platform other than Windows.
+ */
+function waitForApplicationStop(aApplication) {
+ if (AppConstants.platform != "win") {
+ do_throw("Windows only function called by a different platform!");
+ }
+
+ debugDump("waiting for " + aApplication + " to stop if necessary");
+ // Use the helper bin to ensure the application is stopped. If not stopped,
+ // then wait for it to stop (at most 120 seconds).
+ let args = ["wait-for-application-exit", aApplication, "120"];
+ let exitValue = runTestHelperSync(args);
+ Assert.equal(
+ exitValue,
+ 0,
+ "the process should have stopped, process name: " + aApplication
+ );
+}
+
+/**
+ * Gets the platform specific shell binary that is launched using nsIProcess and
+ * in turn launches a binary used for the test (e.g. application, updater,
+ * etc.). A shell is used so debug console output can be redirected to a file so
+ * it doesn't end up in the test log.
+ *
+ * @return nsIFile for the shell binary to launch using nsIProcess.
+ */
+function getLaunchBin() {
+ let launchBin;
+ if (AppConstants.platform == "win") {
+ launchBin = Services.dirsvc.get("WinD", Ci.nsIFile);
+ launchBin.append("System32");
+ launchBin.append("cmd.exe");
+ } else {
+ launchBin = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile);
+ launchBin.initWithPath("/bin/sh");
+ }
+ Assert.ok(launchBin.exists(), MSG_SHOULD_EXIST + getMsgPath(launchBin.path));
+
+ return launchBin;
+}
+
+/**
+ * Locks a Windows directory.
+ *
+ * @param aDirPath
+ * The test file object that describes the file to make in use.
+ * @throws If called from a platform other than Windows.
+ */
+function lockDirectory(aDirPath) {
+ if (AppConstants.platform != "win") {
+ do_throw("Windows only function called by a different platform!");
+ }
+
+ debugDump("start - locking installation directory");
+ const LPCWSTR = ctypes.char16_t.ptr;
+ const DWORD = ctypes.uint32_t;
+ const LPVOID = ctypes.voidptr_t;
+ const GENERIC_READ = 0x80000000;
+ const FILE_SHARE_READ = 1;
+ const FILE_SHARE_WRITE = 2;
+ const OPEN_EXISTING = 3;
+ const FILE_FLAG_BACKUP_SEMANTICS = 0x02000000;
+ const INVALID_HANDLE_VALUE = LPVOID(0xffffffff);
+ let kernel32 = ctypes.open("kernel32");
+ let CreateFile = kernel32.declare(
+ "CreateFileW",
+ ctypes.winapi_abi,
+ LPVOID,
+ LPCWSTR,
+ DWORD,
+ DWORD,
+ LPVOID,
+ DWORD,
+ DWORD,
+ LPVOID
+ );
+ gHandle = CreateFile(
+ aDirPath,
+ GENERIC_READ,
+ FILE_SHARE_READ | FILE_SHARE_WRITE,
+ LPVOID(0),
+ OPEN_EXISTING,
+ FILE_FLAG_BACKUP_SEMANTICS,
+ LPVOID(0)
+ );
+ Assert.notEqual(
+ gHandle.toString(),
+ INVALID_HANDLE_VALUE.toString(),
+ "the handle should not equal INVALID_HANDLE_VALUE"
+ );
+ kernel32.close();
+ debugDump("finish - locking installation directory");
+}
+
+/**
+ * Launches the test helper binary to make it in use for updater tests.
+ *
+ * @param aRelPath
+ * The relative path in the apply to directory for the helper binary.
+ * @param aCopyTestHelper
+ * Whether to copy the test helper binary to the relative path in the
+ * apply to directory.
+ */
+async function runHelperFileInUse(aRelPath, aCopyTestHelper) {
+ debugDump("aRelPath: " + aRelPath);
+ // Launch an existing file so it is in use during the update.
+ let helperBin = getTestDirFile(FILE_HELPER_BIN);
+ let fileInUseBin = getApplyDirFile(aRelPath);
+ if (aCopyTestHelper) {
+ if (fileInUseBin.exists()) {
+ fileInUseBin.remove(false);
+ }
+ helperBin.copyTo(fileInUseBin.parent, fileInUseBin.leafName);
+ }
+ fileInUseBin.permissions = PERMS_DIRECTORY;
+ let args = [
+ getApplyDirPath() + DIR_RESOURCES,
+ "input",
+ "output",
+ "-s",
+ HELPER_SLEEP_TIMEOUT,
+ ];
+ let fileInUseProcess = Cc["@mozilla.org/process/util;1"].createInstance(
+ Ci.nsIProcess
+ );
+ fileInUseProcess.init(fileInUseBin);
+ fileInUseProcess.run(false, args, args.length);
+
+ await waitForHelperSleep();
+}
+
+/**
+ * Launches the test helper binary to provide a pid that is in use for updater
+ * tests.
+ *
+ * @param aRelPath
+ * The relative path in the apply to directory for the helper binary.
+ * @param aCopyTestHelper
+ * Whether to copy the test helper binary to the relative path in the
+ * apply to directory.
+ */
+async function runHelperPIDPersists(aRelPath, aCopyTestHelper) {
+ debugDump("aRelPath: " + aRelPath);
+ // Launch an existing file so it is in use during the update.
+ let helperBin = getTestDirFile(FILE_HELPER_BIN);
+ let pidPersistsBin = getApplyDirFile(aRelPath);
+ if (aCopyTestHelper) {
+ if (pidPersistsBin.exists()) {
+ pidPersistsBin.remove(false);
+ }
+ helperBin.copyTo(pidPersistsBin.parent, pidPersistsBin.leafName);
+ }
+ pidPersistsBin.permissions = PERMS_DIRECTORY;
+ let args = [
+ getApplyDirPath() + DIR_RESOURCES,
+ "input",
+ "output",
+ "-s",
+ HELPER_SLEEP_TIMEOUT,
+ ];
+ gPIDPersistProcess = Cc["@mozilla.org/process/util;1"].createInstance(
+ Ci.nsIProcess
+ );
+ gPIDPersistProcess.init(pidPersistsBin);
+ gPIDPersistProcess.run(false, args, args.length);
+
+ await waitForHelperSleep();
+ await TestUtils.waitForCondition(
+ () => !!gPIDPersistProcess.pid,
+ "Waiting for the process pid"
+ );
+}
+
+/**
+ * Launches the test helper binary and locks a file specified on the command
+ * line for updater tests.
+ *
+ * @param aTestFile
+ * The test file object that describes the file to lock.
+ */
+async function runHelperLockFile(aTestFile) {
+ // Exclusively lock an existing file so it is in use during the update.
+ let helperBin = getTestDirFile(FILE_HELPER_BIN);
+ let helperDestDir = getApplyDirFile(DIR_RESOURCES);
+ helperBin.copyTo(helperDestDir, FILE_HELPER_BIN);
+ helperBin = getApplyDirFile(DIR_RESOURCES + FILE_HELPER_BIN);
+ // Strip off the first two directories so the path has to be from the helper's
+ // working directory.
+ let lockFileRelPath = aTestFile.relPathDir.split("/");
+ if (AppConstants.platform == "macosx") {
+ lockFileRelPath = lockFileRelPath.slice(2);
+ }
+ lockFileRelPath = lockFileRelPath.join("/") + "/" + aTestFile.fileName;
+ let args = [
+ getApplyDirPath() + DIR_RESOURCES,
+ "input",
+ "output",
+ "-s",
+ HELPER_SLEEP_TIMEOUT,
+ lockFileRelPath,
+ ];
+ let helperProcess = Cc["@mozilla.org/process/util;1"].createInstance(
+ Ci.nsIProcess
+ );
+ helperProcess.init(helperBin);
+ helperProcess.run(false, args, args.length);
+
+ await waitForHelperSleep();
+}
+
+/**
+ * Helper function that waits until the helper has completed its operations.
+ */
+async function waitForHelperSleep() {
+ // Give the lock file process time to lock the file before updating otherwise
+ // this test can fail intermittently on Windows debug builds.
+ let file = getApplyDirFile(DIR_RESOURCES + "output");
+ await TestUtils.waitForCondition(
+ () => file.exists(),
+ "Waiting for file to exist, path: " + file.path
+ );
+
+ let expectedContents = "sleeping\n";
+ await TestUtils.waitForCondition(
+ () => readFile(file) == expectedContents,
+ "Waiting for expected file contents: " + expectedContents
+ );
+
+ await TestUtils.waitForCondition(() => {
+ try {
+ file.remove(false);
+ } catch (e) {
+ debugDump(
+ "failed to remove file. Path: " + file.path + ", Exception: " + e
+ );
+ }
+ return !file.exists();
+ }, "Waiting for file to be removed, Path: " + file.path);
+}
+
+/**
+ * Helper function to tell the helper to finish and exit its sleep state.
+ */
+async function waitForHelperExit() {
+ let file = getApplyDirFile(DIR_RESOURCES + "input");
+ writeFile(file, "finish\n");
+
+ // Give the lock file process time to lock the file before updating otherwise
+ // this test can fail intermittently on Windows debug builds.
+ file = getApplyDirFile(DIR_RESOURCES + "output");
+ await TestUtils.waitForCondition(
+ () => file.exists(),
+ "Waiting for file to exist, Path: " + file.path
+ );
+
+ let expectedContents = "finished\n";
+ await TestUtils.waitForCondition(
+ () => readFile(file) == expectedContents,
+ "Waiting for expected file contents: " + expectedContents
+ );
+
+ // Give the lock file process time to unlock the file before deleting the
+ // input and output files.
+ await TestUtils.waitForCondition(() => {
+ try {
+ file.remove(false);
+ } catch (e) {
+ debugDump(
+ "failed to remove file. Path: " + file.path + ", Exception: " + e
+ );
+ }
+ return !file.exists();
+ }, "Waiting for file to be removed, Path: " + file.path);
+
+ file = getApplyDirFile(DIR_RESOURCES + "input");
+ await TestUtils.waitForCondition(() => {
+ try {
+ file.remove(false);
+ } catch (e) {
+ debugDump(
+ "failed to remove file. Path: " + file.path + ", Exception: " + e
+ );
+ }
+ return !file.exists();
+ }, "Waiting for file to be removed, Path: " + file.path);
+}
+
+/**
+ * Helper function for updater binary tests that creates the files and
+ * directories used by the test.
+ *
+ * @param aMarFile
+ * The mar file for the update test.
+ * @param aPostUpdateAsync
+ * When null the updater.ini is not created otherwise this parameter
+ * is passed to createUpdaterINI.
+ * @param aPostUpdateExeRelPathPrefix
+ * When aPostUpdateAsync null this value is ignored otherwise it is
+ * passed to createUpdaterINI.
+ * @param aSetupActiveUpdate
+ * Whether to setup the active update.
+ *
+ * @param options.requiresOmnijar
+ * When true, copy or symlink omnijars as well. This may be required
+ * to launch the updated application and have non-trivial functionality
+ * available.
+ */
+async function setupUpdaterTest(
+ aMarFile,
+ aPostUpdateAsync,
+ aPostUpdateExeRelPathPrefix = "",
+ aSetupActiveUpdate = true,
+ { requiresOmnijar = false } = {}
+) {
+ debugDump("start - updater test setup");
+ let updatesPatchDir = getUpdateDirFile(DIR_PATCH);
+ if (!updatesPatchDir.exists()) {
+ updatesPatchDir.create(Ci.nsIFile.DIRECTORY_TYPE, PERMS_DIRECTORY);
+ }
+ // Copy the mar that will be applied
+ let mar = getTestDirFile(aMarFile);
+ mar.copyToFollowingLinks(updatesPatchDir, FILE_UPDATE_MAR);
+
+ let helperBin = getTestDirFile(FILE_HELPER_BIN);
+ helperBin.permissions = PERMS_DIRECTORY;
+ let afterApplyBinDir = getApplyDirFile(DIR_RESOURCES);
+ helperBin.copyToFollowingLinks(afterApplyBinDir, gCallbackBinFile);
+ helperBin.copyToFollowingLinks(afterApplyBinDir, gPostUpdateBinFile);
+
+ gTestFiles.forEach(function SUT_TF_FE(aTestFile) {
+ debugDump("start - setup test file: " + aTestFile.fileName);
+ if (aTestFile.originalFile || aTestFile.originalContents) {
+ let testDir = getApplyDirFile(aTestFile.relPathDir);
+ // Somehow these create calls are failing with FILE_ALREADY_EXISTS even
+ // after checking .exists() first, so we just catch the exception.
+ try {
+ testDir.create(Ci.nsIFile.DIRECTORY_TYPE, PERMS_DIRECTORY);
+ } catch (e) {
+ if (e.result != Cr.NS_ERROR_FILE_ALREADY_EXISTS) {
+ throw e;
+ }
+ }
+
+ let testFile;
+ if (aTestFile.originalFile) {
+ testFile = getTestDirFile(aTestFile.originalFile);
+ testFile.copyToFollowingLinks(testDir, aTestFile.fileName);
+ testFile = getApplyDirFile(aTestFile.relPathDir + aTestFile.fileName);
+ Assert.ok(
+ testFile.exists(),
+ MSG_SHOULD_EXIST + ", path: " + testFile.path
+ );
+ } else {
+ testFile = getApplyDirFile(aTestFile.relPathDir + aTestFile.fileName);
+ writeFile(testFile, aTestFile.originalContents);
+ }
+
+ // Skip these tests on Windows since chmod doesn't really set permissions
+ // on Windows.
+ if (AppConstants.platform != "win" && aTestFile.originalPerms) {
+ testFile.permissions = aTestFile.originalPerms;
+ // Store the actual permissions on the file for reference later after
+ // setting the permissions.
+ if (!aTestFile.comparePerms) {
+ aTestFile.comparePerms = testFile.permissions;
+ }
+ }
+ }
+ debugDump("finish - setup test file: " + aTestFile.fileName);
+ });
+
+ // Add the test directory that will be updated for a successful update or left
+ // in the initial state for a failed update.
+ gTestDirs.forEach(function SUT_TD_FE(aTestDir) {
+ debugDump("start - setup test directory: " + aTestDir.relPathDir);
+ let testDir = getApplyDirFile(aTestDir.relPathDir);
+ // Somehow these create calls are failing with FILE_ALREADY_EXISTS even
+ // after checking .exists() first, so we just catch the exception.
+ try {
+ testDir.create(Ci.nsIFile.DIRECTORY_TYPE, PERMS_DIRECTORY);
+ } catch (e) {
+ if (e.result != Cr.NS_ERROR_FILE_ALREADY_EXISTS) {
+ throw e;
+ }
+ }
+
+ if (aTestDir.files) {
+ aTestDir.files.forEach(function SUT_TD_F_FE(aTestFile) {
+ let testFile = getApplyDirFile(aTestDir.relPathDir + aTestFile);
+ if (!testFile.exists()) {
+ testFile.create(Ci.nsIFile.NORMAL_FILE_TYPE, PERMS_FILE);
+ }
+ });
+ }
+
+ if (aTestDir.subDirs) {
+ aTestDir.subDirs.forEach(function SUT_TD_SD_FE(aSubDir) {
+ let testSubDir = getApplyDirFile(aTestDir.relPathDir + aSubDir);
+ if (!testSubDir.exists()) {
+ testSubDir.create(Ci.nsIFile.DIRECTORY_TYPE, PERMS_DIRECTORY);
+ }
+
+ if (aTestDir.subDirFiles) {
+ aTestDir.subDirFiles.forEach(function SUT_TD_SDF_FE(aTestFile) {
+ let testFile = getApplyDirFile(
+ aTestDir.relPathDir + aSubDir + aTestFile
+ );
+ if (!testFile.exists()) {
+ testFile.create(Ci.nsIFile.NORMAL_FILE_TYPE, PERMS_FILE);
+ }
+ });
+ }
+ });
+ }
+ debugDump("finish - setup test directory: " + aTestDir.relPathDir);
+ });
+
+ if (aSetupActiveUpdate) {
+ setupActiveUpdate();
+ }
+
+ if (aPostUpdateAsync !== null) {
+ createUpdaterINI(aPostUpdateAsync, aPostUpdateExeRelPathPrefix);
+ }
+
+ await TestUtils.waitForCondition(() => {
+ try {
+ setupAppFiles({ requiresOmnijar });
+ return true;
+ } catch (e) {
+ logTestInfo("exception when calling setupAppFiles, Exception: " + e);
+ }
+ return false;
+ }, "Waiting to setup app files");
+
+ debugDump("finish - updater test setup");
+}
+
+/**
+ * Helper function for updater binary tests that creates the updater.ini
+ * file.
+ *
+ * @param aIsExeAsync
+ * True or undefined if the post update process should be async. If
+ * undefined ExeAsync will not be added to the updater.ini file in
+ * order to test the default launch behavior which is async.
+ * @param aExeRelPathPrefix
+ * A string to prefix the ExeRelPath values in the updater.ini.
+ */
+function createUpdaterINI(aIsExeAsync, aExeRelPathPrefix) {
+ let exeArg = "ExeArg=post-update-async\n";
+ let exeAsync = "";
+ if (aIsExeAsync !== undefined) {
+ if (aIsExeAsync) {
+ exeAsync = "ExeAsync=true\n";
+ } else {
+ exeArg = "ExeArg=post-update-sync\n";
+ exeAsync = "ExeAsync=false\n";
+ }
+ }
+
+ if (AppConstants.platform == "win" && aExeRelPathPrefix) {
+ aExeRelPathPrefix = aExeRelPathPrefix.replace("/", "\\");
+ }
+
+ let exeRelPathMac =
+ "ExeRelPath=" +
+ aExeRelPathPrefix +
+ DIR_RESOURCES +
+ gPostUpdateBinFile +
+ "\n";
+ let exeRelPathWin =
+ "ExeRelPath=" + aExeRelPathPrefix + gPostUpdateBinFile + "\n";
+ let updaterIniContents =
+ "[Strings]\n" +
+ "Title=Update Test\n" +
+ "Info=Running update test " +
+ gTestID +
+ "\n\n" +
+ "[PostUpdateMac]\n" +
+ exeRelPathMac +
+ exeArg +
+ exeAsync +
+ "\n" +
+ "[PostUpdateWin]\n" +
+ exeRelPathWin +
+ exeArg +
+ exeAsync;
+ let updaterIni = getApplyDirFile(DIR_RESOURCES + FILE_UPDATER_INI);
+ writeFile(updaterIni, updaterIniContents);
+}
+
+/**
+ * Gets the message log path used for assert checks to lessen the length printed
+ * to the log file.
+ *
+ * @param aPath
+ * The path to shorten for the log file.
+ * @return the message including the shortened path for the log file.
+ */
+function getMsgPath(aPath) {
+ return ", path: " + replaceLogPaths(aPath);
+}
+
+/**
+ * Helper function that replaces the common part of paths in the update log's
+ * contents with <test_dir_path> for paths to the the test directory and
+ * <update_dir_path> for paths to the update directory. This is needed since
+ * Assert.equal will truncate what it prints to the xpcshell log file.
+ *
+ * @param aLogContents
+ * The update log file's contents.
+ * @return the log contents with the paths replaced.
+ */
+function replaceLogPaths(aLogContents) {
+ let logContents = aLogContents;
+ // Remove the majority of the path up to the test directory. This is needed
+ // since Assert.equal won't print long strings to the test logs.
+ let testDirPath = getApplyDirFile().parent.path;
+ if (AppConstants.platform == "win") {
+ // Replace \\ with \\\\ so the regexp works.
+ testDirPath = testDirPath.replace(/\\/g, "\\\\");
+ }
+ logContents = logContents.replace(
+ new RegExp(testDirPath, "g"),
+ "<test_dir_path>/" + gTestID
+ );
+ let updatesDirPath = getMockUpdRootD().path;
+ if (AppConstants.platform == "win") {
+ // Replace \\ with \\\\ so the regexp works.
+ updatesDirPath = updatesDirPath.replace(/\\/g, "\\\\");
+ }
+ logContents = logContents.replace(
+ new RegExp(updatesDirPath, "g"),
+ "<update_dir_path>/" + gTestID
+ );
+ if (AppConstants.platform == "win") {
+ // Replace \ with /
+ logContents = logContents.replace(/\\/g, "/");
+ }
+ return logContents;
+}
+
+/**
+ * Helper function that removes the timestamps in the update log
+ *
+ * @param aLogContents
+ * The update log file's contents.
+ * @return the log contents without timestamps
+ */
+function removeTimeStamps(aLogContents) {
+ return aLogContents.replace(
+ /^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}[+-]\d{4}: /gm,
+ ""
+ );
+}
+
+/**
+ * Helper function for updater binary tests for verifying the contents of the
+ * update log after a successful update.
+ *
+ * @param aCompareLogFile
+ * The log file to compare the update log with.
+ * @param aStaged
+ * If the update log file is for a staged update.
+ * @param aReplace
+ * If the update log file is for a replace update.
+ * @param aExcludeDistDir
+ * Removes lines containing the distribution directory from the log
+ * file to compare the update log with.
+ */
+function checkUpdateLogContents(
+ aCompareLogFile,
+ aStaged = false,
+ aReplace = false,
+ aExcludeDistDir = false
+) {
+ if (AppConstants.platform == "macosx" || AppConstants.platform == "linux") {
+ // The order that files are returned when enumerating the file system on
+ // Linux and Mac is not deterministic so skip checking the logs.
+ return;
+ }
+
+ let updateLog = getUpdateDirFile(FILE_LAST_UPDATE_LOG);
+ let updateLogContents = readFileBytes(updateLog);
+
+ // Remove leading timestamps
+ updateLogContents = removeTimeStamps(updateLogContents);
+
+ // The channel-prefs.js is defined in gTestFilesCommon which will always be
+ // located to the end of gTestFiles when it is present.
+ if (
+ gTestFiles.length > 1 &&
+ gTestFiles[gTestFiles.length - 1].fileName == "channel-prefs.js" &&
+ !gTestFiles[gTestFiles.length - 1].originalContents
+ ) {
+ updateLogContents = updateLogContents.replace(/.*defaults\/.*/g, "");
+ }
+
+ if (
+ gTestFiles.length > 2 &&
+ gTestFiles[gTestFiles.length - 2].fileName == FILE_UPDATE_SETTINGS_INI &&
+ !gTestFiles[gTestFiles.length - 2].originalContents
+ ) {
+ updateLogContents = updateLogContents.replace(
+ /.*update-settings.ini.*/g,
+ ""
+ );
+ }
+
+ // Skip the source/destination lines since they contain absolute paths.
+ // These could be changed to relative paths using <test_dir_path> and
+ // <update_dir_path>
+ updateLogContents = updateLogContents.replace(/PATCH DIRECTORY.*/g, "");
+ updateLogContents = updateLogContents.replace(
+ /INSTALLATION DIRECTORY.*/g,
+ ""
+ );
+ updateLogContents = updateLogContents.replace(/WORKING DIRECTORY.*/g, "");
+ // Skip lines that log failed attempts to open the callback executable.
+ updateLogContents = updateLogContents.replace(
+ /NS_main: callback app file .*/g,
+ ""
+ );
+ // Remove carriage returns.
+ updateLogContents = updateLogContents.replace(/\r/g, "");
+
+ if (AppConstants.platform == "win") {
+ // The FindFile results when enumerating the filesystem on Windows is not
+ // determistic so the results matching the following need to be fixed.
+ let re = new RegExp(
+ // eslint-disable-next-line no-control-regex
+ "([^\n]* 7/7text1[^\n]*)\n([^\n]* 7/7text0[^\n]*)\n",
+ "g"
+ );
+ updateLogContents = updateLogContents.replace(re, "$2\n$1\n");
+ }
+
+ if (aReplace) {
+ // Remove the lines which contain absolute paths
+ updateLogContents = updateLogContents.replace(/^Begin moving.*$/gm, "");
+ updateLogContents = updateLogContents.replace(
+ /^ensure_remove: failed to remove file: .*$/gm,
+ ""
+ );
+ updateLogContents = updateLogContents.replace(
+ /^ensure_remove_recursive: unable to remove directory: .*$/gm,
+ ""
+ );
+ updateLogContents = updateLogContents.replace(
+ /^Removing tmpDir failed, err: -1$/gm,
+ ""
+ );
+ updateLogContents = updateLogContents.replace(
+ /^remove_recursive_on_reboot: .*$/gm,
+ ""
+ );
+ // Replace requests will retry renaming the installation directory 10 times
+ // when there are files still in use. The following will remove the
+ // additional entries from the log file when this happens so the log check
+ // passes.
+ let re = new RegExp(
+ ERR_RENAME_FILE +
+ "[^\n]*\n" +
+ "PerformReplaceRequest: destDir rename[^\n]*\n" +
+ "rename_file: proceeding to rename the directory\n",
+ "g"
+ );
+ updateLogContents = updateLogContents.replace(re, "");
+ }
+
+ // Replace error codes since they are different on each platform.
+ updateLogContents = updateLogContents.replace(/, err:.*\n/g, "\n");
+ // Replace to make the log parsing happy.
+ updateLogContents = updateLogContents.replace(/non-fatal error /g, "");
+ // Remove consecutive newlines
+ updateLogContents = updateLogContents.replace(/\n+/g, "\n");
+ // Remove leading and trailing newlines
+ updateLogContents = updateLogContents.replace(/^\n|\n$/g, "");
+ // Replace the log paths with <test_dir_path> and <update_dir_path>
+ updateLogContents = replaceLogPaths(updateLogContents);
+
+ let compareLogContents = "";
+ if (aCompareLogFile) {
+ compareLogContents = readFileBytes(getTestDirFile(aCompareLogFile));
+ }
+
+ if (aStaged) {
+ compareLogContents = PERFORMING_STAGED_UPDATE + "\n" + compareLogContents;
+ }
+
+ // Remove leading timestamps
+ compareLogContents = removeTimeStamps(compareLogContents);
+
+ // The channel-prefs.js is defined in gTestFilesCommon which will always be
+ // located to the end of gTestFiles.
+ if (
+ gTestFiles.length > 1 &&
+ gTestFiles[gTestFiles.length - 1].fileName == "channel-prefs.js" &&
+ !gTestFiles[gTestFiles.length - 1].originalContents
+ ) {
+ compareLogContents = compareLogContents.replace(/.*defaults\/.*/g, "");
+ }
+
+ if (
+ gTestFiles.length > 2 &&
+ gTestFiles[gTestFiles.length - 2].fileName == FILE_UPDATE_SETTINGS_INI &&
+ !gTestFiles[gTestFiles.length - 2].originalContents
+ ) {
+ compareLogContents = compareLogContents.replace(
+ /.*update-settings.ini.*/g,
+ ""
+ );
+ }
+
+ if (aExcludeDistDir) {
+ compareLogContents = compareLogContents.replace(/.*distribution\/.*/g, "");
+ }
+
+ // Remove leading and trailing newlines
+ compareLogContents = compareLogContents.replace(/\n+/g, "\n");
+ // Remove leading and trailing newlines
+ compareLogContents = compareLogContents.replace(/^\n|\n$/g, "");
+
+ // Don't write the contents of the file to the log to reduce log spam
+ // unless there is a failure.
+ if (compareLogContents == updateLogContents) {
+ Assert.ok(true, "the update log contents" + MSG_SHOULD_EQUAL);
+ } else {
+ logTestInfo("the update log contents are not correct");
+ logUpdateLog(FILE_LAST_UPDATE_LOG);
+ let aryLog = updateLogContents.split("\n");
+ let aryCompare = compareLogContents.split("\n");
+ // Pushing an empty string to both arrays makes it so either array's length
+ // can be used in the for loop below without going out of bounds.
+ aryLog.push("");
+ aryCompare.push("");
+ // xpcshell tests won't display the entire contents so log the first
+ // incorrect line.
+ for (let i = 0; i < aryLog.length; ++i) {
+ if (aryLog[i] != aryCompare[i]) {
+ logTestInfo(
+ "the first incorrect line is line #" +
+ i +
+ " and the " +
+ "value is: '" +
+ aryLog[i] +
+ "'"
+ );
+ Assert.equal(
+ aryLog[i],
+ aryCompare[i],
+ "the update log contents" + MSG_SHOULD_EQUAL
+ );
+ }
+ }
+ // This should never happen!
+ do_throw("Unable to find incorrect update log contents!");
+ }
+}
+
+/**
+ * Helper function to check if the update log contains a string.
+ *
+ * @param aCheckString
+ * The string to check if the update log contains.
+ */
+function checkUpdateLogContains(aCheckString) {
+ let updateLog = getUpdateDirFile(FILE_LAST_UPDATE_LOG);
+ let updateLogContents = readFileBytes(updateLog).replace(/\r\n/g, "\n");
+ updateLogContents = removeTimeStamps(updateLogContents);
+ updateLogContents = replaceLogPaths(updateLogContents);
+ Assert.notEqual(
+ updateLogContents.indexOf(aCheckString),
+ -1,
+ "the update log '" +
+ updateLog +
+ "' contents should contain value: '" +
+ aCheckString +
+ "'"
+ );
+}
+
+/**
+ * Helper function for updater binary tests for verifying the state of files and
+ * directories after a successful update.
+ *
+ * @param aGetFileFunc
+ * The function used to get the files in the directory to be checked.
+ * @param aStageDirExists
+ * If true the staging directory will be tested for existence and if
+ * false the staging directory will be tested for non-existence.
+ * @param aToBeDeletedDirExists
+ * On Windows, if true the tobedeleted directory will be tested for
+ * existence and if false the tobedeleted directory will be tested for
+ * non-existence. On all othere platforms it will be tested for
+ * non-existence.
+ */
+function checkFilesAfterUpdateSuccess(
+ aGetFileFunc,
+ aStageDirExists = false,
+ aToBeDeletedDirExists = false
+) {
+ debugDump("testing contents of files after a successful update");
+ gTestFiles.forEach(function CFAUS_TF_FE(aTestFile) {
+ let testFile = aGetFileFunc(
+ aTestFile.relPathDir + aTestFile.fileName,
+ true
+ );
+ debugDump("testing file: " + testFile.path);
+ if (aTestFile.compareFile || aTestFile.compareContents) {
+ Assert.ok(
+ testFile.exists(),
+ MSG_SHOULD_EXIST + getMsgPath(testFile.path)
+ );
+
+ // Skip these tests on Windows since chmod doesn't really set permissions
+ // on Windows.
+ if (AppConstants.platform != "win" && aTestFile.comparePerms) {
+ // Check if the permssions as set in the complete mar file are correct.
+ Assert.equal(
+ "0o" + (testFile.permissions & 0xfff).toString(8),
+ "0o" + (aTestFile.comparePerms & 0xfff).toString(8),
+ "the file permissions" + MSG_SHOULD_EQUAL
+ );
+ }
+
+ let fileContents1 = readFileBytes(testFile);
+ let fileContents2 = aTestFile.compareFile
+ ? readFileBytes(getTestDirFile(aTestFile.compareFile))
+ : aTestFile.compareContents;
+ // Don't write the contents of the file to the log to reduce log spam
+ // unless there is a failure.
+ if (fileContents1 == fileContents2) {
+ Assert.ok(true, "the file contents" + MSG_SHOULD_EQUAL);
+ } else {
+ Assert.equal(
+ fileContents1,
+ fileContents2,
+ "the file contents" + MSG_SHOULD_EQUAL
+ );
+ }
+ } else {
+ Assert.ok(
+ !testFile.exists(),
+ MSG_SHOULD_NOT_EXIST + getMsgPath(testFile.path)
+ );
+ }
+ });
+
+ debugDump(
+ "testing operations specified in removed-files were performed " +
+ "after a successful update"
+ );
+ gTestDirs.forEach(function CFAUS_TD_FE(aTestDir) {
+ let testDir = aGetFileFunc(aTestDir.relPathDir, true);
+ debugDump("testing directory: " + testDir.path);
+ if (aTestDir.dirRemoved) {
+ Assert.ok(
+ !testDir.exists(),
+ MSG_SHOULD_NOT_EXIST + getMsgPath(testDir.path)
+ );
+ } else {
+ Assert.ok(testDir.exists(), MSG_SHOULD_EXIST + getMsgPath(testDir.path));
+
+ if (aTestDir.files) {
+ aTestDir.files.forEach(function CFAUS_TD_F_FE(aTestFile) {
+ let testFile = aGetFileFunc(aTestDir.relPathDir + aTestFile, true);
+ if (aTestDir.filesRemoved) {
+ Assert.ok(
+ !testFile.exists(),
+ MSG_SHOULD_NOT_EXIST + getMsgPath(testFile.path)
+ );
+ } else {
+ Assert.ok(
+ testFile.exists(),
+ MSG_SHOULD_EXIST + getMsgPath(testFile.path)
+ );
+ }
+ });
+ }
+
+ if (aTestDir.subDirs) {
+ aTestDir.subDirs.forEach(function CFAUS_TD_SD_FE(aSubDir) {
+ let testSubDir = aGetFileFunc(aTestDir.relPathDir + aSubDir, true);
+ Assert.ok(
+ testSubDir.exists(),
+ MSG_SHOULD_EXIST + getMsgPath(testSubDir.path)
+ );
+ if (aTestDir.subDirFiles) {
+ aTestDir.subDirFiles.forEach(function CFAUS_TD_SDF_FE(aTestFile) {
+ let testFile = aGetFileFunc(
+ aTestDir.relPathDir + aSubDir + aTestFile,
+ true
+ );
+ Assert.ok(
+ testFile.exists(),
+ MSG_SHOULD_EXIST + getMsgPath(testFile.path)
+ );
+ });
+ }
+ });
+ }
+ }
+ });
+
+ checkFilesAfterUpdateCommon(aStageDirExists, aToBeDeletedDirExists);
+}
+
+/**
+ * Helper function for updater binary tests for verifying the state of files and
+ * directories after a failed update.
+ *
+ * @param aGetFileFunc
+ * The function used to get the files in the directory to be checked.
+ * @param aStageDirExists
+ * If true the staging directory will be tested for existence and if
+ * false the staging directory will be tested for non-existence.
+ * @param aToBeDeletedDirExists
+ * On Windows, if true the tobedeleted directory will be tested for
+ * existence and if false the tobedeleted directory will be tested for
+ * non-existence. On all othere platforms it will be tested for
+ * non-existence.
+ */
+function checkFilesAfterUpdateFailure(
+ aGetFileFunc,
+ aStageDirExists = false,
+ aToBeDeletedDirExists = false
+) {
+ debugDump("testing contents of files after a failed update");
+ gTestFiles.forEach(function CFAUF_TF_FE(aTestFile) {
+ let testFile = aGetFileFunc(
+ aTestFile.relPathDir + aTestFile.fileName,
+ true
+ );
+ debugDump("testing file: " + testFile.path);
+ if (aTestFile.compareFile || aTestFile.compareContents) {
+ Assert.ok(
+ testFile.exists(),
+ MSG_SHOULD_EXIST + getMsgPath(testFile.path)
+ );
+
+ // Skip these tests on Windows since chmod doesn't really set permissions
+ // on Windows.
+ if (AppConstants.platform != "win" && aTestFile.comparePerms) {
+ // Check the original permssions are retained on the file.
+ Assert.equal(
+ testFile.permissions & 0xfff,
+ aTestFile.comparePerms & 0xfff,
+ "the file permissions" + MSG_SHOULD_EQUAL
+ );
+ }
+
+ let fileContents1 = readFileBytes(testFile);
+ let fileContents2 = aTestFile.compareFile
+ ? readFileBytes(getTestDirFile(aTestFile.compareFile))
+ : aTestFile.compareContents;
+ // Don't write the contents of the file to the log to reduce log spam
+ // unless there is a failure.
+ if (fileContents1 == fileContents2) {
+ Assert.ok(true, "the file contents" + MSG_SHOULD_EQUAL);
+ } else {
+ Assert.equal(
+ fileContents1,
+ fileContents2,
+ "the file contents" + MSG_SHOULD_EQUAL
+ );
+ }
+ } else {
+ Assert.ok(
+ !testFile.exists(),
+ MSG_SHOULD_NOT_EXIST + getMsgPath(testFile.path)
+ );
+ }
+ });
+
+ debugDump(
+ "testing operations specified in removed-files were not " +
+ "performed after a failed update"
+ );
+ gTestDirs.forEach(function CFAUF_TD_FE(aTestDir) {
+ let testDir = aGetFileFunc(aTestDir.relPathDir, true);
+ Assert.ok(testDir.exists(), MSG_SHOULD_EXIST + getMsgPath(testDir.path));
+
+ if (aTestDir.files) {
+ aTestDir.files.forEach(function CFAUS_TD_F_FE(aTestFile) {
+ let testFile = aGetFileFunc(aTestDir.relPathDir + aTestFile, true);
+ Assert.ok(
+ testFile.exists(),
+ MSG_SHOULD_EXIST + getMsgPath(testFile.path)
+ );
+ });
+ }
+
+ if (aTestDir.subDirs) {
+ aTestDir.subDirs.forEach(function CFAUS_TD_SD_FE(aSubDir) {
+ let testSubDir = aGetFileFunc(aTestDir.relPathDir + aSubDir, true);
+ Assert.ok(
+ testSubDir.exists(),
+ MSG_SHOULD_EXIST + getMsgPath(testSubDir.path)
+ );
+ if (aTestDir.subDirFiles) {
+ aTestDir.subDirFiles.forEach(function CFAUS_TD_SDF_FE(aTestFile) {
+ let testFile = aGetFileFunc(
+ aTestDir.relPathDir + aSubDir + aTestFile,
+ true
+ );
+ Assert.ok(
+ testFile.exists(),
+ MSG_SHOULD_EXIST + getMsgPath(testFile.path)
+ );
+ });
+ }
+ });
+ }
+ });
+
+ checkFilesAfterUpdateCommon(aStageDirExists, aToBeDeletedDirExists);
+}
+
+/**
+ * Helper function for updater binary tests for verifying the state of common
+ * files and directories after a successful or failed update.
+ *
+ * @param aStageDirExists
+ * If true the staging directory will be tested for existence and if
+ * false the staging directory will be tested for non-existence.
+ * @param aToBeDeletedDirExists
+ * On Windows, if true the tobedeleted directory will be tested for
+ * existence and if false the tobedeleted directory will be tested for
+ * non-existence. On all othere platforms it will be tested for
+ * non-existence.
+ */
+function checkFilesAfterUpdateCommon(aStageDirExists, aToBeDeletedDirExists) {
+ debugDump("testing extra directories");
+ let stageDir = getStageDirFile();
+ if (aStageDirExists) {
+ Assert.ok(stageDir.exists(), MSG_SHOULD_EXIST + getMsgPath(stageDir.path));
+ } else {
+ Assert.ok(
+ !stageDir.exists(),
+ MSG_SHOULD_NOT_EXIST + getMsgPath(stageDir.path)
+ );
+ }
+
+ let toBeDeletedDirExists =
+ AppConstants.platform == "win" ? aToBeDeletedDirExists : false;
+ let toBeDeletedDir = getApplyDirFile(DIR_TOBEDELETED);
+ if (toBeDeletedDirExists) {
+ Assert.ok(
+ toBeDeletedDir.exists(),
+ MSG_SHOULD_EXIST + getMsgPath(toBeDeletedDir.path)
+ );
+ } else {
+ Assert.ok(
+ !toBeDeletedDir.exists(),
+ MSG_SHOULD_NOT_EXIST + getMsgPath(toBeDeletedDir.path)
+ );
+ }
+
+ let updatingDir = getApplyDirFile("updating");
+ Assert.ok(
+ !updatingDir.exists(),
+ MSG_SHOULD_NOT_EXIST + getMsgPath(updatingDir.path)
+ );
+
+ if (stageDir.exists()) {
+ updatingDir = stageDir.clone();
+ updatingDir.append("updating");
+ Assert.ok(
+ !updatingDir.exists(),
+ MSG_SHOULD_NOT_EXIST + getMsgPath(updatingDir.path)
+ );
+ }
+
+ debugDump(
+ "testing backup files should not be left behind in the " +
+ "application directory"
+ );
+ let applyToDir = getApplyDirFile();
+ checkFilesInDirRecursive(applyToDir, checkForBackupFiles);
+
+ if (stageDir.exists()) {
+ debugDump(
+ "testing backup files should not be left behind in the " +
+ "staging directory"
+ );
+ checkFilesInDirRecursive(stageDir, checkForBackupFiles);
+ }
+}
+
+/**
+ * Helper function for updater binary tests for verifying the contents of the
+ * updater callback application log which should contain the arguments passed to
+ * the callback application.
+ *
+ * @param appLaunchLog (optional)
+ * The application log nsIFile to verify. Defaults to the second
+ * parameter passed to the callback executable (in the apply directory).
+ */
+function checkCallbackLog(
+ appLaunchLog = getApplyDirFile(DIR_RESOURCES + gCallbackArgs[1])
+) {
+ if (!appLaunchLog.exists()) {
+ // Uses do_timeout instead of do_execute_soon to lessen log spew.
+ do_timeout(FILE_IN_USE_TIMEOUT_MS, checkCallbackLog);
+ return;
+ }
+
+ let expectedLogContents = gCallbackArgs.join("\n") + "\n";
+ let logContents = readFile(appLaunchLog);
+ // It is possible for the log file contents check to occur before the log file
+ // contents are completely written so wait until the contents are the expected
+ // value. If the contents are never the expected value then the test will
+ // fail by timing out after gTimeoutRuns is greater than MAX_TIMEOUT_RUNS or
+ // the test harness times out the test.
+ const MAX_TIMEOUT_RUNS = 20000;
+ if (logContents != expectedLogContents) {
+ gTimeoutRuns++;
+ if (gTimeoutRuns > MAX_TIMEOUT_RUNS) {
+ logTestInfo("callback log contents are not correct");
+ // This file doesn't contain full paths so there is no need to call
+ // replaceLogPaths.
+ let aryLog = logContents.split("\n");
+ let aryCompare = expectedLogContents.split("\n");
+ // Pushing an empty string to both arrays makes it so either array's length
+ // can be used in the for loop below without going out of bounds.
+ aryLog.push("");
+ aryCompare.push("");
+ // xpcshell tests won't display the entire contents so log the incorrect
+ // line.
+ for (let i = 0; i < aryLog.length; ++i) {
+ if (aryLog[i] != aryCompare[i]) {
+ logTestInfo(
+ "the first incorrect line in the callback log is: " + aryLog[i]
+ );
+ Assert.equal(
+ aryLog[i],
+ aryCompare[i],
+ "the callback log contents" + MSG_SHOULD_EQUAL
+ );
+ }
+ }
+ // This should never happen!
+ do_throw("Unable to find incorrect callback log contents!");
+ }
+ // Uses do_timeout instead of do_execute_soon to lessen log spew.
+ do_timeout(FILE_IN_USE_TIMEOUT_MS, checkCallbackLog);
+ return;
+ }
+ Assert.ok(true, "the callback log contents" + MSG_SHOULD_EQUAL);
+
+ waitForFilesInUse();
+}
+
+/**
+ * Helper function for updater binary tests for getting the log and running
+ * files created by the test helper binary file when called with the post-update
+ * command line argument.
+ *
+ * @param aSuffix
+ * The string to append to the post update test helper binary path.
+ */
+function getPostUpdateFile(aSuffix) {
+ return getApplyDirFile(DIR_RESOURCES + gPostUpdateBinFile + aSuffix);
+}
+
+/**
+ * Checks the contents of the updater post update binary log. When completed
+ * checkPostUpdateAppLogFinished will be called.
+ */
+async function checkPostUpdateAppLog() {
+ // Only Mac OS X and Windows support post update.
+ if (AppConstants.platform == "macosx" || AppConstants.platform == "win") {
+ let file = getPostUpdateFile(".log");
+ await TestUtils.waitForCondition(
+ () => file.exists(),
+ "Waiting for file to exist, path: " + file.path
+ );
+
+ let expectedContents = "post-update\n";
+ await TestUtils.waitForCondition(
+ () => readFile(file) == expectedContents,
+ "Waiting for expected file contents: " + expectedContents
+ );
+ }
+}
+
+/**
+ * Helper function to check if a file is in use on Windows by making a copy of
+ * a file and attempting to delete the original file. If the deletion is
+ * successful the copy of the original file is renamed to the original file's
+ * name and if the deletion is not successful the copy of the original file is
+ * deleted.
+ *
+ * @param aFile
+ * An nsIFile for the file to be checked if it is in use.
+ * @return true if the file can't be deleted and false otherwise.
+ * @throws If called from a platform other than Windows.
+ */
+function isFileInUse(aFile) {
+ if (AppConstants.platform != "win") {
+ do_throw("Windows only function called by a different platform!");
+ }
+
+ if (!aFile.exists()) {
+ debugDump("file does not exist, path: " + aFile.path);
+ return false;
+ }
+
+ let fileBak = aFile.parent;
+ fileBak.append(aFile.leafName + ".bak");
+ try {
+ if (fileBak.exists()) {
+ fileBak.remove(false);
+ }
+ aFile.copyTo(aFile.parent, fileBak.leafName);
+ aFile.remove(false);
+ fileBak.moveTo(aFile.parent, aFile.leafName);
+ debugDump("file is not in use, path: " + aFile.path);
+ return false;
+ } catch (e) {
+ debugDump("file in use, path: " + aFile.path + ", Exception: " + e);
+ try {
+ if (fileBak.exists()) {
+ fileBak.remove(false);
+ }
+ } catch (ex) {
+ logTestInfo(
+ "unable to remove backup file, path: " +
+ fileBak.path +
+ ", Exception: " +
+ ex
+ );
+ }
+ }
+ return true;
+}
+
+/**
+ * Waits until files that are in use that break tests are no longer in use and
+ * then calls doTestFinish to end the test.
+ */
+function waitForFilesInUse() {
+ if (AppConstants.platform == "win") {
+ let fileNames = [
+ FILE_APP_BIN,
+ FILE_UPDATER_BIN,
+ FILE_MAINTENANCE_SERVICE_INSTALLER_BIN,
+ ];
+ for (let i = 0; i < fileNames.length; ++i) {
+ let file = getApplyDirFile(fileNames[i]);
+ if (isFileInUse(file)) {
+ do_timeout(FILE_IN_USE_TIMEOUT_MS, waitForFilesInUse);
+ return;
+ }
+ }
+ }
+
+ debugDump("calling doTestFinish");
+ doTestFinish();
+}
+
+/**
+ * Helper function for updater binary tests for verifying there are no update
+ * backup files left behind after an update.
+ *
+ * @param aFile
+ * An nsIFile to check if it has moz-backup for its extension.
+ */
+function checkForBackupFiles(aFile) {
+ Assert.notEqual(
+ getFileExtension(aFile),
+ "moz-backup",
+ "the file's extension should not equal moz-backup" + getMsgPath(aFile.path)
+ );
+}
+
+/**
+ * Helper function for updater binary tests for recursively enumerating a
+ * directory and calling a callback function with the file as a parameter for
+ * each file found.
+ *
+ * @param aDir
+ * A nsIFile for the directory to be deleted
+ * @param aCallback
+ * A callback function that will be called with the file as a
+ * parameter for each file found.
+ */
+function checkFilesInDirRecursive(aDir, aCallback) {
+ if (!aDir.exists()) {
+ do_throw("Directory must exist!");
+ }
+
+ let dirEntries = aDir.directoryEntries;
+ while (dirEntries.hasMoreElements()) {
+ let entry = dirEntries.nextFile;
+
+ if (entry.exists()) {
+ if (entry.isDirectory()) {
+ checkFilesInDirRecursive(entry, aCallback);
+ } else {
+ aCallback(entry);
+ }
+ }
+ }
+}
+
+/**
+ * Waits for an update check request to complete and asserts that the results
+ * are as-expected.
+ *
+ * @param aSuccess
+ * Whether the update check succeeds or not. If aSuccess is true then
+ * the check should succeed and if aSuccess is false then the check
+ * should fail.
+ * @param aExpectedValues
+ * An object with common values to check.
+ * @return A promise which will resolve with the nsIUpdateCheckResult object
+ * once the update check is complete.
+ */
+async function waitForUpdateCheck(aSuccess, aExpectedValues = {}) {
+ let check = gUpdateChecker.checkForUpdates(gUpdateChecker.FOREGROUND_CHECK);
+ let result = await check.result;
+ Assert.ok(result.checksAllowed, "We should be able to check for updates");
+ Assert.equal(
+ result.succeeded,
+ aSuccess,
+ "the update check should " + (aSuccess ? "succeed" : "error")
+ );
+ if (aExpectedValues.updateCount) {
+ Assert.equal(
+ aExpectedValues.updateCount,
+ result.updates.length,
+ "the update count" + MSG_SHOULD_EQUAL
+ );
+ }
+ if (aExpectedValues.url) {
+ Assert.equal(
+ aExpectedValues.url,
+ result.request.channel.originalURI.spec,
+ "the url" + MSG_SHOULD_EQUAL
+ );
+ }
+ return result;
+}
+
+/**
+ * Downloads an update and waits for the download onStopRequest.
+ *
+ * @param aUpdates
+ * An array of updates to select from to download an update.
+ * @param aUpdateCount
+ * The number of updates in the aUpdates array.
+ * @param aExpectedStatus
+ * The download onStopRequest expected status.
+ * @return A promise which will resolve the first time the update download
+ * onStopRequest occurs and returns the arguments from onStopRequest.
+ */
+async function waitForUpdateDownload(aUpdates, aExpectedStatus) {
+ let bestUpdate = gAUS.selectUpdate(aUpdates);
+ let success = await gAUS.downloadUpdate(bestUpdate, false);
+ if (!success) {
+ do_throw("nsIApplicationUpdateService:downloadUpdate returned " + success);
+ }
+ return new Promise(resolve =>
+ gAUS.addDownloadListener({
+ onStartRequest: aRequest => {},
+ onProgress: (aRequest, aContext, aProgress, aMaxProgress) => {},
+ onStatus: (aRequest, aStatus, aStatusText) => {},
+ onStopRequest(request, status) {
+ gAUS.removeDownloadListener(this);
+ Assert.equal(
+ aExpectedStatus,
+ status,
+ "the download status" + MSG_SHOULD_EQUAL
+ );
+ resolve(request, status);
+ },
+ QueryInterface: ChromeUtils.generateQI([
+ "nsIRequestObserver",
+ "nsIProgressEventSink",
+ ]),
+ })
+ );
+}
+
+/**
+ * Helper for starting the http server used by the tests
+ */
+function start_httpserver() {
+ let dir = getTestDirFile();
+ debugDump("http server directory path: " + dir.path);
+
+ if (!dir.isDirectory()) {
+ do_throw(
+ "A file instead of a directory was specified for HttpServer " +
+ "registerDirectory! Path: " +
+ dir.path
+ );
+ }
+
+ let { HttpServer } = ChromeUtils.import("resource://testing-common/httpd.js");
+ gTestserver = new HttpServer();
+ gTestserver.registerDirectory("/", dir);
+ gTestserver.registerPathHandler("/" + gHTTPHandlerPath, pathHandler);
+ gTestserver.start(-1);
+ let testserverPort = gTestserver.identity.primaryPort;
+ // eslint-disable-next-line no-global-assign
+ gURLData = URL_HOST + ":" + testserverPort + "/";
+ debugDump("http server port = " + testserverPort);
+}
+
+/**
+ * Custom path handler for the http server
+ *
+ * @param aMetadata
+ * The http metadata for the request.
+ * @param aResponse
+ * The http response for the request.
+ */
+function pathHandler(aMetadata, aResponse) {
+ gUpdateCheckCount += 1;
+ aResponse.setHeader("Content-Type", "text/xml", false);
+ aResponse.setStatusLine(aMetadata.httpVersion, 200, "OK");
+ aResponse.bodyOutputStream.write(gResponseBody, gResponseBody.length);
+}
+
+/**
+ * Helper for stopping the http server used by the tests
+ *
+ * @param aCallback
+ * The callback to call after stopping the http server.
+ */
+function stop_httpserver(aCallback) {
+ Assert.ok(!!aCallback, "the aCallback parameter should be defined");
+ gTestserver.stop(aCallback);
+}
+
+/**
+ * Creates an nsIXULAppInfo
+ *
+ * @param aID
+ * The ID of the test application
+ * @param aName
+ * A name for the test application
+ * @param aVersion
+ * The version of the application
+ * @param aPlatformVersion
+ * The gecko version of the application
+ */
+function createAppInfo(aID, aName, aVersion, aPlatformVersion) {
+ updateAppInfo({
+ vendor: APP_INFO_VENDOR,
+ name: aName,
+ ID: aID,
+ version: aVersion,
+ appBuildID: "2007010101",
+ platformVersion: aPlatformVersion,
+ platformBuildID: "2007010101",
+ inSafeMode: false,
+ logConsoleErrors: true,
+ OS: "XPCShell",
+ XPCOMABI: "noarch-spidermonkey",
+ });
+}
+
+/**
+ * Returns the platform specific arguments used by nsIProcess when launching
+ * the application.
+ *
+ * @param aExtraArgs (optional)
+ * An array of extra arguments to append to the default arguments.
+ * @return an array of arguments to be passed to nsIProcess.
+ *
+ * Note: a shell is necessary to pipe the application's console output which
+ * would otherwise pollute the xpcshell log.
+ *
+ * Command line arguments used when launching the application:
+ * -no-remote prevents shell integration from being affected by an existing
+ * application process.
+ * -test-process-updates makes the application exit after being relaunched by
+ * the updater.
+ * the platform specific string defined by PIPE_TO_NULL to output both stdout
+ * and stderr to null. This is needed to prevent output from the application
+ * from ending up in the xpchsell log.
+ */
+function getProcessArgs(aExtraArgs) {
+ if (!aExtraArgs) {
+ aExtraArgs = [];
+ }
+
+ let appBin = getApplyDirFile(DIR_MACOS + FILE_APP_BIN);
+ Assert.ok(appBin.exists(), MSG_SHOULD_EXIST + ", path: " + appBin.path);
+ let appBinPath = appBin.path;
+
+ // The profile must be specified for the tests that launch the application to
+ // run locally when the profiles.ini and installs.ini files already exist.
+ // We can't use getApplyDirFile to find the profile path because on Windows
+ // for service tests that would place the profile inside Program Files, and
+ // this test script has permission to write in Program Files, but the
+ // application may drop those permissions. So for Windows service tests we
+ // override that path with the per-test temp directory that xpcshell provides,
+ // which should be user writable.
+ let profileDir = appBin.parent.parent;
+ if (gIsServiceTest && IS_AUTHENTICODE_CHECK_ENABLED) {
+ profileDir = do_get_tempdir();
+ }
+ profileDir.append("profile");
+ let profilePath = profileDir.path;
+
+ let args;
+ if (AppConstants.platform == "macosx" || AppConstants.platform == "linux") {
+ let launchScript = getLaunchScript();
+ // Precreate the script with executable permissions
+ launchScript.create(Ci.nsIFile.NORMAL_FILE_TYPE, PERMS_DIRECTORY);
+
+ let scriptContents = "#! /bin/sh\n";
+ scriptContents += "export XRE_PROFILE_PATH=" + profilePath + "\n";
+ scriptContents +=
+ appBinPath +
+ " -no-remote -test-process-updates " +
+ aExtraArgs.join(" ") +
+ " " +
+ PIPE_TO_NULL;
+ writeFile(launchScript, scriptContents);
+ debugDump(
+ "created " + launchScript.path + " containing:\n" + scriptContents
+ );
+ args = [launchScript.path];
+ } else {
+ args = [
+ "/D",
+ "/Q",
+ "/C",
+ appBinPath,
+ "-profile",
+ profilePath,
+ "-no-remote",
+ "-test-process-updates",
+ "-wait-for-browser",
+ ]
+ .concat(aExtraArgs)
+ .concat([PIPE_TO_NULL]);
+ }
+ return args;
+}
+
+/**
+ * Gets a file path for the application to dump its arguments into. This is used
+ * to verify that a callback application is launched.
+ *
+ * @return the file for the application to dump its arguments into.
+ */
+function getAppArgsLogPath() {
+ let appArgsLog = do_get_file("/" + gTestID + "_app_args_log", true);
+ if (appArgsLog.exists()) {
+ appArgsLog.remove(false);
+ }
+ let appArgsLogPath = appArgsLog.path;
+ if (/ /.test(appArgsLogPath)) {
+ appArgsLogPath = '"' + appArgsLogPath + '"';
+ }
+ return appArgsLogPath;
+}
+
+/**
+ * Gets the nsIFile reference for the shell script to launch the application. If
+ * the file exists it will be removed by this function.
+ *
+ * @return the nsIFile for the shell script to launch the application.
+ */
+function getLaunchScript() {
+ let launchScript = do_get_file("/" + gTestID + "_launch.sh", true);
+ if (launchScript.exists()) {
+ launchScript.remove(false);
+ }
+ return launchScript;
+}
+
+/**
+ * Makes GreD, XREExeF, and UpdRootD point to unique file system locations so
+ * xpcshell tests can run in parallel and to keep the environment clean.
+ */
+function adjustGeneralPaths() {
+ let dirProvider = {
+ getFile: function AGP_DP_getFile(aProp, aPersistent) {
+ // Set the value of persistent to false so when this directory provider is
+ // unregistered it will revert back to the original provider.
+ aPersistent.value = false;
+ switch (aProp) {
+ case NS_GRE_DIR:
+ return getApplyDirFile(DIR_RESOURCES);
+ case NS_GRE_BIN_DIR:
+ return getApplyDirFile(DIR_MACOS);
+ case XRE_EXECUTABLE_FILE:
+ return getApplyDirFile(DIR_MACOS + FILE_APP_BIN);
+ case XRE_UPDATE_ROOT_DIR:
+ return getMockUpdRootD();
+ case XRE_OLD_UPDATE_ROOT_DIR:
+ return getMockUpdRootD(true);
+ }
+ return null;
+ },
+ QueryInterface: ChromeUtils.generateQI(["nsIDirectoryServiceProvider"]),
+ };
+ let ds = Services.dirsvc.QueryInterface(Ci.nsIDirectoryService);
+ ds.QueryInterface(Ci.nsIProperties).undefine(NS_GRE_DIR);
+ ds.QueryInterface(Ci.nsIProperties).undefine(NS_GRE_BIN_DIR);
+ ds.QueryInterface(Ci.nsIProperties).undefine(XRE_EXECUTABLE_FILE);
+ ds.registerProvider(dirProvider);
+ registerCleanupFunction(function AGP_cleanup() {
+ debugDump("start - unregistering directory provider");
+
+ if (gAppTimer) {
+ debugDump("start - cancel app timer");
+ gAppTimer.cancel();
+ gAppTimer = null;
+ debugDump("finish - cancel app timer");
+ }
+
+ if (gProcess && gProcess.isRunning) {
+ debugDump("start - kill process");
+ try {
+ gProcess.kill();
+ } catch (e) {
+ debugDump("kill process failed, Exception: " + e);
+ }
+ gProcess = null;
+ debugDump("finish - kill process");
+ }
+
+ if (gPIDPersistProcess && gPIDPersistProcess.isRunning) {
+ debugDump("start - kill pid persist process");
+ try {
+ gPIDPersistProcess.kill();
+ } catch (e) {
+ debugDump("kill pid persist process failed, Exception: " + e);
+ }
+ gPIDPersistProcess = null;
+ debugDump("finish - kill pid persist process");
+ }
+
+ if (gHandle) {
+ try {
+ debugDump("start - closing handle");
+ let kernel32 = ctypes.open("kernel32");
+ let CloseHandle = kernel32.declare(
+ "CloseHandle",
+ ctypes.winapi_abi,
+ ctypes.bool /* return*/,
+ ctypes.voidptr_t /* handle*/
+ );
+ if (!CloseHandle(gHandle)) {
+ debugDump("call to CloseHandle failed");
+ }
+ kernel32.close();
+ gHandle = null;
+ debugDump("finish - closing handle");
+ } catch (e) {
+ debugDump("call to CloseHandle failed, Exception: " + e);
+ }
+ }
+
+ ds.unregisterProvider(dirProvider);
+ cleanupTestCommon();
+
+ // Now that our provider is unregistered, reset the lock a second time so
+ // that we know the lock we're interested in gets released (xpcshell
+ // doesn't always run a proper XPCOM shutdown sequence, which is where that
+ // would normally be happening).
+ let syncManager = Cc[
+ "@mozilla.org/updates/update-sync-manager;1"
+ ].getService(Ci.nsIUpdateSyncManager);
+ syncManager.resetLock();
+
+ debugDump("finish - unregistering directory provider");
+ });
+}
+
+/**
+ * The timer callback to kill the process if it takes too long.
+ */
+const gAppTimerCallback = {
+ notify: function TC_notify(aTimer) {
+ gAppTimer = null;
+ if (gProcess.isRunning) {
+ logTestInfo("attempting to kill process");
+ gProcess.kill();
+ }
+ Assert.ok(false, "launch application timer expired");
+ },
+ QueryInterface: ChromeUtils.generateQI(["nsITimerCallback"]),
+};
+
+/**
+ * Launches an application to apply an update.
+ *
+ * @param aExpectedStatus
+ * The expected value of update.status when the update finishes.
+ */
+async function runUpdateUsingApp(aExpectedStatus) {
+ debugDump("start - launching application to apply update");
+
+ // The maximum number of milliseconds the process that is launched can run
+ // before the test will try to kill it.
+ const APP_TIMER_TIMEOUT = 120000;
+ let launchBin = getLaunchBin();
+ let args = getProcessArgs();
+ debugDump("launching " + launchBin.path + " " + args.join(" "));
+
+ gProcess = Cc["@mozilla.org/process/util;1"].createInstance(Ci.nsIProcess);
+ gProcess.init(launchBin);
+
+ gAppTimer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
+ gAppTimer.initWithCallback(
+ gAppTimerCallback,
+ APP_TIMER_TIMEOUT,
+ Ci.nsITimer.TYPE_ONE_SHOT
+ );
+
+ setEnvironment();
+
+ debugDump("launching application");
+ gProcess.run(true, args, args.length);
+ debugDump("launched application exited");
+
+ resetEnvironment();
+
+ if (AppConstants.platform == "win") {
+ waitForApplicationStop(FILE_UPDATER_BIN);
+ }
+
+ let file = getUpdateDirFile(FILE_UPDATE_STATUS);
+ await TestUtils.waitForCondition(
+ () => file.exists(),
+ "Waiting for file to exist, path: " + file.path
+ );
+
+ await TestUtils.waitForCondition(
+ () => readStatusFile() == aExpectedStatus,
+ "Waiting for expected status file contents: " + aExpectedStatus
+ ).catch(e => {
+ // Instead of throwing let the check below fail the test so the status
+ // file's contents are logged.
+ logTestInfo(e);
+ });
+ Assert.equal(
+ readStatusFile(),
+ aExpectedStatus,
+ "the status file state" + MSG_SHOULD_EQUAL
+ );
+
+ // Don't check for an update log when the code in nsUpdateDriver.cpp skips
+ // updating.
+ if (
+ aExpectedStatus != STATE_PENDING &&
+ aExpectedStatus != STATE_PENDING_SVC &&
+ aExpectedStatus != STATE_APPLIED &&
+ aExpectedStatus != STATE_APPLIED_SVC
+ ) {
+ // Don't proceed until the update log has been created.
+ file = getUpdateDirFile(FILE_UPDATE_LOG);
+ await TestUtils.waitForCondition(
+ () => file.exists(),
+ "Waiting for file to exist, path: " + file.path
+ );
+ }
+
+ debugDump("finish - launching application to apply update");
+}
+
+/* This Mock incremental downloader is used to verify that connection interrupts
+ * work correctly in updater code. The implementation of the mock incremental
+ * downloader is very simple, it simply copies the file to the destination
+ * location.
+ */
+function initMockIncrementalDownload() {
+ const INC_CONTRACT_ID = "@mozilla.org/network/incremental-download;1";
+ let incrementalDownloadCID = MockRegistrar.register(
+ INC_CONTRACT_ID,
+ IncrementalDownload
+ );
+ registerCleanupFunction(() => {
+ MockRegistrar.unregister(incrementalDownloadCID);
+ });
+}
+
+function IncrementalDownload() {
+ this.wrappedJSObject = this;
+}
+
+IncrementalDownload.prototype = {
+ /* nsIIncrementalDownload */
+ init(uri, file, chunkSize, intervalInSeconds) {
+ this._destination = file;
+ this._URI = uri;
+ this._finalURI = uri;
+ },
+
+ start(observer, ctxt) {
+ // Do the actual operation async to give a chance for observers to add
+ // themselves.
+ Services.tm.dispatchToMainThread(() => {
+ this._observer = observer.QueryInterface(Ci.nsIRequestObserver);
+ this._ctxt = ctxt;
+ this._observer.onStartRequest(this);
+ let mar = getTestDirFile(FILE_SIMPLE_MAR);
+ mar.copyTo(this._destination.parent, this._destination.leafName);
+ let status = Cr.NS_OK;
+ switch (gIncrementalDownloadErrorType++) {
+ case 0:
+ status = Cr.NS_ERROR_NET_RESET;
+ break;
+ case 1:
+ status = Cr.NS_ERROR_CONNECTION_REFUSED;
+ break;
+ case 2:
+ status = Cr.NS_ERROR_NET_RESET;
+ break;
+ case 3:
+ status = Cr.NS_OK;
+ break;
+ case 4:
+ status = Cr.NS_ERROR_OFFLINE;
+ // After we report offline, we want to eventually show offline
+ // status being changed to online.
+ Services.tm.dispatchToMainThread(function () {
+ Services.obs.notifyObservers(
+ gAUS,
+ "network:offline-status-changed",
+ "online"
+ );
+ });
+ break;
+ }
+ this._observer.onStopRequest(this, status);
+ });
+ },
+
+ get URI() {
+ return this._URI;
+ },
+
+ get currentSize() {
+ throw Components.Exception("", Cr.NS_ERROR_NOT_IMPLEMENTED);
+ },
+
+ get destination() {
+ return this._destination;
+ },
+
+ get finalURI() {
+ return this._finalURI;
+ },
+
+ get totalSize() {
+ throw Components.Exception("", Cr.NS_ERROR_NOT_IMPLEMENTED);
+ },
+
+ /* nsIRequest */
+ cancel(aStatus) {
+ throw Components.Exception("", Cr.NS_ERROR_NOT_IMPLEMENTED);
+ },
+ suspend() {
+ throw Components.Exception("", Cr.NS_ERROR_NOT_IMPLEMENTED);
+ },
+ isPending() {
+ throw Components.Exception("", Cr.NS_ERROR_NOT_IMPLEMENTED);
+ },
+ _loadFlags: 0,
+ get loadFlags() {
+ return this._loadFlags;
+ },
+ set loadFlags(val) {
+ this._loadFlags = val;
+ },
+
+ _loadGroup: null,
+ get loadGroup() {
+ return this._loadGroup;
+ },
+ set loadGroup(val) {
+ this._loadGroup = val;
+ },
+
+ _name: "",
+ get name() {
+ return this._name;
+ },
+
+ _status: 0,
+ get status() {
+ return this._status;
+ },
+ QueryInterface: ChromeUtils.generateQI(["nsIIncrementalDownload"]),
+};
+
+/**
+ * Sets the environment that will be used by the application process when it is
+ * launched.
+ */
+function setEnvironment() {
+ if (AppConstants.platform == "win") {
+ // The tests use nsIProcess to launch the updater and it is simpler to just
+ // set an environment variable and have the test updater set the current
+ // working directory than it is to set the current working directory in the
+ // test itself.
+ Services.env.set("CURWORKDIRPATH", getApplyDirFile().path);
+ }
+
+ // Prevent setting the environment more than once.
+ if (gShouldResetEnv !== undefined) {
+ return;
+ }
+
+ gShouldResetEnv = true;
+
+ if (
+ AppConstants.platform == "win" &&
+ !Services.env.exists("XRE_NO_WINDOWS_CRASH_DIALOG")
+ ) {
+ gAddedEnvXRENoWindowsCrashDialog = true;
+ debugDump(
+ "setting the XRE_NO_WINDOWS_CRASH_DIALOG environment " +
+ "variable to 1... previously it didn't exist"
+ );
+ Services.env.set("XRE_NO_WINDOWS_CRASH_DIALOG", "1");
+ }
+
+ if (Services.env.exists("XPCOM_MEM_LEAK_LOG")) {
+ gEnvXPCOMMemLeakLog = Services.env.get("XPCOM_MEM_LEAK_LOG");
+ debugDump(
+ "removing the XPCOM_MEM_LEAK_LOG environment variable... " +
+ "previous value " +
+ gEnvXPCOMMemLeakLog
+ );
+ Services.env.set("XPCOM_MEM_LEAK_LOG", "");
+ }
+
+ if (Services.env.exists("XPCOM_DEBUG_BREAK")) {
+ gEnvXPCOMDebugBreak = Services.env.get("XPCOM_DEBUG_BREAK");
+ debugDump(
+ "setting the XPCOM_DEBUG_BREAK environment variable to " +
+ "warn... previous value " +
+ gEnvXPCOMDebugBreak
+ );
+ } else {
+ debugDump(
+ "setting the XPCOM_DEBUG_BREAK environment variable to " +
+ "warn... previously it didn't exist"
+ );
+ }
+
+ Services.env.set("XPCOM_DEBUG_BREAK", "warn");
+
+ if (gEnvForceServiceFallback) {
+ // This env variable forces the updater to use the service in an invalid
+ // way, so that it has to fall back to updating without the service.
+ debugDump("setting MOZ_FORCE_SERVICE_FALLBACK environment variable to 1");
+ Services.env.set("MOZ_FORCE_SERVICE_FALLBACK", "1");
+ } else if (gIsServiceTest) {
+ debugDump("setting MOZ_NO_SERVICE_FALLBACK environment variable to 1");
+ Services.env.set("MOZ_NO_SERVICE_FALLBACK", "1");
+ }
+}
+
+/**
+ * Sets the environment back to the original values after launching the
+ * application.
+ */
+function resetEnvironment() {
+ // Prevent resetting the environment more than once.
+ if (gShouldResetEnv !== true) {
+ return;
+ }
+
+ gShouldResetEnv = false;
+
+ if (gEnvXPCOMMemLeakLog) {
+ debugDump(
+ "setting the XPCOM_MEM_LEAK_LOG environment variable back to " +
+ gEnvXPCOMMemLeakLog
+ );
+ Services.env.set("XPCOM_MEM_LEAK_LOG", gEnvXPCOMMemLeakLog);
+ }
+
+ if (gEnvXPCOMDebugBreak) {
+ debugDump(
+ "setting the XPCOM_DEBUG_BREAK environment variable back to " +
+ gEnvXPCOMDebugBreak
+ );
+ Services.env.set("XPCOM_DEBUG_BREAK", gEnvXPCOMDebugBreak);
+ } else if (Services.env.exists("XPCOM_DEBUG_BREAK")) {
+ debugDump("clearing the XPCOM_DEBUG_BREAK environment variable");
+ Services.env.set("XPCOM_DEBUG_BREAK", "");
+ }
+
+ if (AppConstants.platform == "win" && gAddedEnvXRENoWindowsCrashDialog) {
+ debugDump("removing the XRE_NO_WINDOWS_CRASH_DIALOG environment variable");
+ Services.env.set("XRE_NO_WINDOWS_CRASH_DIALOG", "");
+ }
+
+ if (gEnvForceServiceFallback) {
+ debugDump("removing MOZ_FORCE_SERVICE_FALLBACK environment variable");
+ Services.env.set("MOZ_FORCE_SERVICE_FALLBACK", "");
+ } else if (gIsServiceTest) {
+ debugDump("removing MOZ_NO_SERVICE_FALLBACK environment variable");
+ Services.env.set("MOZ_NO_SERVICE_FALLBACK", "");
+ }
+}
diff --git a/toolkit/mozapps/update/tests/diff_base_service.bash b/toolkit/mozapps/update/tests/diff_base_service.bash
new file mode 100644
index 0000000000..bf2338666d
--- /dev/null
+++ b/toolkit/mozapps/update/tests/diff_base_service.bash
@@ -0,0 +1,116 @@
+#!/usr/bin/env bash
+
+# Compare the common files in unit_base_updater and unit_service_updater.
+
+while read LINE1 && read LINE2 && read BLANK; do
+ diff -U 3 "unit_base_updater/$LINE1" "unit_service_updater/$LINE2"
+done <<END
+invalidArgInstallDirPathTooLongFailure.js
+invalidArgInstallDirPathTooLongFailureSvc.js
+
+invalidArgInstallDirPathTraversalFailure.js
+invalidArgInstallDirPathTraversalFailureSvc.js
+
+invalidArgInstallWorkingDirPathNotSameFailure_win.js
+invalidArgInstallWorkingDirPathNotSameFailureSvc_win.js
+
+invalidArgPatchDirPathTraversalFailure.js
+invalidArgPatchDirPathTraversalFailureSvc.js
+
+invalidArgStageDirNotInInstallDirFailure_win.js
+invalidArgStageDirNotInInstallDirFailureSvc_win.js
+
+invalidArgWorkingDirPathLocalUNCFailure_win.js
+invalidArgWorkingDirPathLocalUNCFailureSvc_win.js
+
+invalidArgWorkingDirPathRelativeFailure.js
+invalidArgWorkingDirPathRelativeFailureSvc.js
+
+marAppApplyDirLockedStageFailure_win.js
+marAppApplyDirLockedStageFailureSvc_win.js
+
+marAppApplyUpdateAppBinInUseStageSuccess_win.js
+marAppApplyUpdateAppBinInUseStageSuccessSvc_win.js
+
+marAppApplyUpdateStageSuccess.js
+marAppApplyUpdateStageSuccessSvc.js
+
+marAppApplyUpdateSuccess.js
+marAppApplyUpdateSuccessSvc.js
+
+marAppInUseBackgroundTaskFailure_win.js
+marAppInUseBackgroundTaskFailureSvc_win.js
+
+marAppInUseStageFailureComplete_win.js
+marAppInUseStageFailureCompleteSvc_win.js
+
+marAppInUseSuccessComplete.js
+marAppInUseSuccessCompleteSvc.js
+
+marCallbackAppStageSuccessComplete_win.js
+marCallbackAppStageSuccessCompleteSvc_win.js
+
+marCallbackAppStageSuccessPartial_win.js
+marCallbackAppStageSuccessPartialSvc_win.js
+
+marCallbackAppSuccessComplete_win.js
+marCallbackAppSuccessCompleteSvc_win.js
+
+marCallbackAppSuccessPartial_win.js
+marCallbackAppSuccessPartialSvc_win.js
+
+marFailurePartial.js
+marFailurePartialSvc.js
+
+marFileInUseStageFailureComplete_win.js
+marFileInUseStageFailureCompleteSvc_win.js
+
+marFileInUseStageFailurePartial_win.js
+marFileInUseStageFailurePartialSvc_win.js
+
+marFileInUseSuccessComplete_win.js
+marFileInUseSuccessCompleteSvc_win.js
+
+marFileInUseSuccessPartial_win.js
+marFileInUseSuccessPartialSvc_win.js
+
+marFileLockedFailureComplete_win.js
+marFileLockedFailureCompleteSvc_win.js
+
+marFileLockedFailurePartial_win.js
+marFileLockedFailurePartialSvc_win.js
+
+marFileLockedStageFailureComplete_win.js
+marFileLockedStageFailureCompleteSvc_win.js
+
+marFileLockedStageFailurePartial_win.js
+marFileLockedStageFailurePartialSvc_win.js
+
+marRMRFDirFileInUseStageFailureComplete_win.js
+marRMRFDirFileInUseStageFailureCompleteSvc_win.js
+
+marRMRFDirFileInUseStageFailurePartial_win.js
+marRMRFDirFileInUseStageFailurePartialSvc_win.js
+
+marRMRFDirFileInUseSuccessComplete_win.js
+marRMRFDirFileInUseSuccessCompleteSvc_win.js
+
+marRMRFDirFileInUseSuccessPartial_win.js
+marRMRFDirFileInUseSuccessPartialSvc_win.js
+
+marStageFailurePartial.js
+marStageFailurePartialSvc.js
+
+marStageSuccessComplete.js
+marStageSuccessCompleteSvc.js
+
+marStageSuccessPartial.js
+marStageSuccessPartialSvc.js
+
+marSuccessComplete.js
+marSuccessCompleteSvc.js
+
+marSuccessPartial.js
+marSuccessPartialSvc.js
+
+END
diff --git a/toolkit/mozapps/update/tests/marionette/marionette.ini b/toolkit/mozapps/update/tests/marionette/marionette.ini
new file mode 100644
index 0000000000..4bb7f3a640
--- /dev/null
+++ b/toolkit/mozapps/update/tests/marionette/marionette.ini
@@ -0,0 +1,3 @@
+[test_no_window_update_restart.py]
+run-if = os == "mac"
+reason = Test of a mac only feature
diff --git a/toolkit/mozapps/update/tests/marionette/test_no_window_update_restart.py b/toolkit/mozapps/update/tests/marionette/test_no_window_update_restart.py
new file mode 100644
index 0000000000..245efc774d
--- /dev/null
+++ b/toolkit/mozapps/update/tests/marionette/test_no_window_update_restart.py
@@ -0,0 +1,255 @@
+# Any copyright is dedicated to the Public Domain.
+# http://creativecommons.org/publicdomain/zero/1.0/
+
+# This test is not written in a very modular way because update tests are generally not done with
+# Marionette, so this is sort of a one-off. We can't successfully just load all the actual setup
+# code that the normal update tests use, so this test basically just copies the bits that it needs.
+# The reason that this is a Marionette test at all is that, even if we stub out the quit/restart
+# call, we need no windows to be open to test the relevant functionality, but xpcshell doesn't do
+# windows at all and mochitest has a test runner window that Firefox recognizes, but mustn't close
+# during testing.
+
+from marionette_driver import Wait, errors
+from marionette_harness import MarionetteTestCase
+
+
+class TestNoWindowUpdateRestart(MarionetteTestCase):
+ def setUp(self):
+ super(TestNoWindowUpdateRestart, self).setUp()
+
+ self.marionette.delete_session()
+ self.marionette.start_session({"moz:windowless": True})
+ # See Bug 1777956
+ window = self.marionette.window_handles[0]
+ self.marionette.switch_to_window(window)
+
+ # Every part of this test ought to run in the chrome context.
+ self.marionette.set_context(self.marionette.CONTEXT_CHROME)
+ self.setUpBrowser()
+ self.origDisabledForTesting = self.marionette.get_pref(
+ "app.update.disabledForTesting"
+ )
+ self.resetUpdate()
+
+ def setUpBrowser(self):
+ self.origAppUpdateAuto = self.marionette.execute_async_script(
+ """
+ let [resolve] = arguments;
+
+ (async () => {
+ Services.prefs.setIntPref("app.update.download.attempts", 0);
+ Services.prefs.setIntPref("app.update.download.maxAttempts", 0);
+ Services.prefs.setBoolPref("app.update.staging.enabled", false);
+ Services.prefs.setBoolPref("app.update.noWindowAutoRestart.enabled", true);
+ Services.prefs.setIntPref("app.update.noWindowAutoRestart.delayMs", 1000);
+ Services.prefs.clearUserPref("testing.no_window_update_restart.silent_restart_env");
+
+ let { UpdateUtils } = ChromeUtils.importESModule(
+ "resource://gre/modules/UpdateUtils.sys.mjs"
+ );
+ let origAppUpdateAuto = await UpdateUtils.getAppUpdateAutoEnabled();
+ await UpdateUtils.setAppUpdateAutoEnabled(true);
+
+ // Prevent the update sync manager from thinking there are two instances running
+ let exePath = Services.dirsvc.get("XREExeF", Ci.nsIFile);
+ let dirProvider = {
+ getFile: function AGP_DP_getFile(aProp, aPersistent) {
+ aPersistent.value = false;
+ switch (aProp) {
+ case "XREExeF":
+ exePath.append("browser-test");
+ return exePath;
+ }
+ return null;
+ },
+ QueryInterface: ChromeUtils.generateQI(["nsIDirectoryServiceProvider"]),
+ };
+ let ds = Services.dirsvc.QueryInterface(Ci.nsIDirectoryService);
+ ds.QueryInterface(Ci.nsIProperties).undefine("XREExeF");
+ ds.registerProvider(dirProvider);
+ let gSyncManager = Cc["@mozilla.org/updates/update-sync-manager;1"].getService(
+ Ci.nsIUpdateSyncManager
+ );
+ gSyncManager.resetLock();
+ ds.unregisterProvider(dirProvider);
+
+ return origAppUpdateAuto;
+ })().then(resolve);
+ """
+ )
+
+ def tearDown(self):
+ self.tearDownBrowser()
+ self.resetUpdate()
+
+ # Reset context to the default.
+ self.marionette.set_context(self.marionette.CONTEXT_CONTENT)
+
+ super(TestNoWindowUpdateRestart, self).tearDown()
+
+ def tearDownBrowser(self):
+ self.marionette.execute_async_script(
+ """
+ let [origAppUpdateAuto, origDisabledForTesting, resolve] = arguments;
+ (async () => {
+ Services.prefs.setBoolPref("app.update.disabledForTesting", origDisabledForTesting);
+ Services.prefs.clearUserPref("app.update.download.attempts");
+ Services.prefs.clearUserPref("app.update.download.maxAttempts");
+ Services.prefs.clearUserPref("app.update.staging.enabled");
+ Services.prefs.clearUserPref("app.update.noWindowAutoRestart.enabled");
+ Services.prefs.clearUserPref("app.update.noWindowAutoRestart.delayMs");
+ Services.prefs.clearUserPref("testing.no_window_update_restart.silent_restart_env");
+
+ let { UpdateUtils } = ChromeUtils.importESModule(
+ "resource://gre/modules/UpdateUtils.sys.mjs"
+ );
+ await UpdateUtils.setAppUpdateAutoEnabled(origAppUpdateAuto);
+ })().then(resolve);
+ """,
+ script_args=(self.origAppUpdateAuto, self.origDisabledForTesting),
+ )
+
+ def test_update_on_last_window_close(self):
+ # By preparing an update and closing all windows, we activate the No
+ # Window Update Restart feature (see Bug 1720742) which causes Firefox
+ # to restart to install updates.
+ self.marionette.restart(
+ callback=self.prepare_update_and_close_all_windows, in_app=True
+ )
+
+ # Firefox should come back without any windows (i.e. silently).
+ with self.assertRaises(errors.TimeoutException):
+ wait = Wait(
+ self.marionette,
+ ignored_exceptions=errors.NoSuchWindowException,
+ timeout=5,
+ )
+ wait.until(lambda _: self.marionette.window_handles)
+
+ # Reset the browser and active WebDriver session
+ self.marionette.restart(in_app=True)
+ self.marionette.delete_session()
+ self.marionette.start_session()
+ self.marionette.set_context(self.marionette.CONTEXT_CHROME)
+
+ quit_flags_correct = self.marionette.get_pref(
+ "testing.no_window_update_restart.silent_restart_env"
+ )
+ self.assertTrue(quit_flags_correct)
+
+ # Normally, the update status file would have been removed at this point by Post Update
+ # Processing. But restarting resets app.update.disabledForTesting, which causes that to be
+ # skipped, allowing us to look at the update status file directly.
+ update_status_path = self.marionette.execute_script(
+ """
+ let statusFile = FileUtils.getDir("UpdRootD", ["updates", "0"], true);
+ statusFile.append("update.status");
+ return statusFile.path;
+ """
+ )
+ with open(update_status_path, "r") as f:
+ # If Firefox was built with "--enable-unverified-updates" (or presumably if we tested
+ # with an actual, signed update), the update should succeed. Otherwise, it will fail
+ # with CERT_VERIFY_ERROR (error code 19). Unfortunately, there is no good way to tell
+ # which of those situations we are in. Luckily, it doesn't matter, because we aren't
+ # trying to test whether the update applied successfully, just whether the
+ # "No Window Update Restart" feature works.
+ self.assertIn(f.read().strip(), ["succeeded", "failed: 19"])
+
+ def resetUpdate(self):
+ self.marionette.execute_script(
+ """
+ let UM = Cc["@mozilla.org/updates/update-manager;1"].getService(Ci.nsIUpdateManager);
+ UM.QueryInterface(Ci.nsIObserver).observe(null, "um-reload-update-data", "skip-files");
+
+ let { UpdateListener } = ChromeUtils.import("resource://gre/modules/UpdateListener.jsm");
+ UpdateListener.reset();
+
+ let { AppMenuNotifications } = ChromeUtils.importESModule(
+ "resource://gre/modules/AppMenuNotifications.sys.mjs"
+ );
+ AppMenuNotifications.removeNotification(/.*/);
+
+ // Remove old update files so that they don't interfere with tests.
+ let rootUpdateDir = Services.dirsvc.get("UpdRootD", Ci.nsIFile);
+ let updateDir = rootUpdateDir.clone();
+ updateDir.append("updates");
+ let patchDir = updateDir.clone();
+ patchDir.append("0");
+
+ let filesToRemove = [];
+ let addFileToRemove = (dir, filename) => {
+ let file = dir.clone();
+ file.append(filename);
+ filesToRemove.push(file);
+ };
+
+ addFileToRemove(rootUpdateDir, "active-update.xml");
+ addFileToRemove(rootUpdateDir, "updates.xml");
+ addFileToRemove(patchDir, "bt.result");
+ addFileToRemove(patchDir, "update.status");
+ addFileToRemove(patchDir, "update.version");
+ addFileToRemove(patchDir, "update.mar");
+ addFileToRemove(patchDir, "updater.ini");
+ addFileToRemove(updateDir, "backup-update.log");
+ addFileToRemove(updateDir, "last-update.log");
+ addFileToRemove(patchDir, "update.log");
+
+ for (const file of filesToRemove) {
+ try {
+ if (file.exists()) {
+ file.remove(false);
+ }
+ } catch (e) {
+ console.warn("Unable to remove file. Path: '" + file.path + "', Exception: " + e);
+ }
+ }
+ """
+ )
+
+ def prepare_update_and_close_all_windows(self):
+ self.marionette.execute_async_script(
+ """
+ let [updateURLString, resolve] = arguments;
+
+ (async () => {
+ let updateDownloadedPromise = new Promise(innerResolve => {
+ Services.obs.addObserver(function callback() {
+ Services.obs.removeObserver(callback, "update-downloaded");
+ innerResolve();
+ }, "update-downloaded");
+ });
+
+ // Set the update URL to the one that was passed in.
+ let mockAppInfo = Object.create(Services.appinfo, {
+ updateURL: {
+ configurable: true,
+ enumerable: true,
+ writable: false,
+ value: updateURLString,
+ },
+ });
+ Services.appinfo = mockAppInfo;
+
+ // We aren't going to flip this until after the URL is set because the test fails
+ // if we hit the real update server.
+ Services.prefs.setBoolPref("app.update.disabledForTesting", false);
+
+ let aus = Cc["@mozilla.org/updates/update-service;1"]
+ .getService(Ci.nsIApplicationUpdateService);
+ aus.checkForBackgroundUpdates();
+
+ await updateDownloadedPromise;
+
+ Services.obs.addObserver((aSubject, aTopic, aData) => {
+ let silent_restart = Services.env.get("MOZ_APP_SILENT_START") == 1 && Services.env.get("MOZ_APP_RESTART") == 1;
+ Services.prefs.setBoolPref("testing.no_window_update_restart.silent_restart_env", silent_restart);
+ }, "quit-application-granted");
+
+ for (const win of Services.wm.getEnumerator("navigator:browser")) {
+ win.close();
+ }
+ })().then(resolve);
+ """,
+ script_args=(self.marionette.absolute_url("update.xml"),),
+ )
diff --git a/toolkit/mozapps/update/tests/moz.build b/toolkit/mozapps/update/tests/moz.build
new file mode 100644
index 0000000000..d0adf99c84
--- /dev/null
+++ b/toolkit/mozapps/update/tests/moz.build
@@ -0,0 +1,118 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+FINAL_TARGET = "_tests/xpcshell/toolkit/mozapps/update/tests/data"
+
+if not CONFIG["MOZ_SUITE"]:
+ BROWSER_CHROME_MANIFESTS += [
+ "browser/browser.ini",
+ "browser/manual_app_update_only/browser.ini",
+ ]
+ if CONFIG["MOZ_BITS_DOWNLOAD"]:
+ BROWSER_CHROME_MANIFESTS += ["browser/browser.bits.ini"]
+
+XPCSHELL_TESTS_MANIFESTS += [
+ "unit_aus_update/xpcshell.ini",
+ "unit_base_updater/xpcshell.ini",
+]
+
+if CONFIG["MOZ_MAINTENANCE_SERVICE"]:
+ XPCSHELL_TESTS_MANIFESTS += ["unit_service_updater/xpcshell.ini"]
+
+if CONFIG["MOZ_BUILD_APP"] == "browser" and CONFIG["MOZ_UPDATE_AGENT"]:
+ XPCSHELL_TESTS_MANIFESTS += ["unit_background_update/xpcshell.ini"]
+
+SimplePrograms(
+ [
+ "TestAUSHelper",
+ "TestAUSReadStrings",
+ ]
+)
+
+LOCAL_INCLUDES += [
+ "/toolkit/mozapps/update",
+ "/toolkit/mozapps/update/common",
+]
+
+if CONFIG["OS_ARCH"] == "WINNT":
+ OS_LIBS += [
+ "shlwapi",
+ "user32",
+ "uuid",
+ ]
+
+USE_LIBS += [
+ "updatecommon",
+]
+
+for var in ("MOZ_APP_VENDOR", "MOZ_APP_BASENAME"):
+ DEFINES[var] = CONFIG[var]
+
+DEFINES["NS_NO_XPCOM"] = True
+
+DisableStlWrapping()
+
+if CONFIG["MOZ_MAINTENANCE_SERVICE"]:
+ DEFINES["MOZ_MAINTENANCE_SERVICE"] = CONFIG["MOZ_MAINTENANCE_SERVICE"]
+
+if CONFIG["DISABLE_UPDATER_AUTHENTICODE_CHECK"]:
+ DEFINES["DISABLE_UPDATER_AUTHENTICODE_CHECK"] = True
+
+if CONFIG["CC_TYPE"] == "clang-cl":
+ WIN32_EXE_LDFLAGS += ["-ENTRY:wmainCRTStartup"]
+
+if CONFIG["OS_ARCH"] == "WINNT":
+ DEFINES["UNICODE"] = True
+ DEFINES["_UNICODE"] = True
+ USE_STATIC_LIBS = True
+ if CONFIG["CC_TYPE"] in ("clang", "gcc"):
+ WIN32_EXE_LDFLAGS += ["-municode"]
+
+TEST_HARNESS_FILES.testing.mochitest.browser.toolkit.mozapps.update.tests.browser += [
+ "data/simple.mar",
+]
+
+FINAL_TARGET_FILES += [
+ "data/complete.exe",
+ "data/complete.mar",
+ "data/complete.png",
+ "data/complete_log_success_mac",
+ "data/complete_log_success_win",
+ "data/complete_mac.mar",
+ "data/complete_precomplete",
+ "data/complete_precomplete_mac",
+ "data/complete_removed-files",
+ "data/complete_removed-files_mac",
+ "data/complete_update_manifest",
+ "data/old_version.mar",
+ "data/partial.exe",
+ "data/partial.mar",
+ "data/partial.png",
+ "data/partial_log_failure_mac",
+ "data/partial_log_failure_win",
+ "data/partial_log_success_mac",
+ "data/partial_log_success_win",
+ "data/partial_mac.mar",
+ "data/partial_precomplete",
+ "data/partial_precomplete_mac",
+ "data/partial_removed-files",
+ "data/partial_removed-files_mac",
+ "data/partial_update_manifest",
+ "data/replace_log_success",
+ "data/simple.mar",
+ "data/syncManagerTestChild.js",
+ "TestAUSReadStrings1.ini",
+ "TestAUSReadStrings2.ini",
+ "TestAUSReadStrings3.ini",
+ "TestAUSReadStrings4.ini",
+]
+
+FINAL_TARGET_PP_FILES += [
+ "data/xpcshellConstantsPP.js",
+]
+
+with Files("browser/browser_telemetry_updatePing_*_ready.js"):
+ BUG_COMPONENT = ("Toolkit", "Telemetry")
diff --git a/toolkit/mozapps/update/tests/unit_aus_update/ausReadStrings.js b/toolkit/mozapps/update/tests/unit_aus_update/ausReadStrings.js
new file mode 100644
index 0000000000..ed331f5ebc
--- /dev/null
+++ b/toolkit/mozapps/update/tests/unit_aus_update/ausReadStrings.js
@@ -0,0 +1,33 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+const BIN_DIR =
+ AppConstants.platform == "win" ? "test_bug473417-ó" : "test_bug473417";
+const BIN_EXE = "TestAUSReadStrings" + mozinfo.bin_suffix;
+const tempdir = do_get_tempdir();
+
+function run_test() {
+ let workdir = tempdir.clone();
+ workdir.append(BIN_DIR);
+
+ let paths = [
+ BIN_EXE,
+ "TestAUSReadStrings1.ini",
+ "TestAUSReadStrings2.ini",
+ "TestAUSReadStrings3.ini",
+ "TestAUSReadStrings4.ini",
+ ];
+ for (let i = 0; i < paths.length; i++) {
+ let file = do_get_file("../data/" + paths[i]);
+ file.copyTo(workdir, null);
+ }
+
+ let readStrings = workdir.clone();
+ readStrings.append(BIN_EXE);
+
+ let process = Cc["@mozilla.org/process/util;1"].createInstance(Ci.nsIProcess);
+ process.init(readStrings);
+ process.run(true, [], 0);
+ Assert.equal(process.exitValue, 0);
+}
diff --git a/toolkit/mozapps/update/tests/unit_aus_update/backgroundUpdateTaskInternalUpdater.js b/toolkit/mozapps/update/tests/unit_aus_update/backgroundUpdateTaskInternalUpdater.js
new file mode 100644
index 0000000000..329b8edbea
--- /dev/null
+++ b/toolkit/mozapps/update/tests/unit_aus_update/backgroundUpdateTaskInternalUpdater.js
@@ -0,0 +1,85 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+"use strict";
+
+/**
+ * This test ensures that we don't resume an update download with the internal
+ * downloader when we are running background updates. Normally, the background
+ * update task won't even run if we can't use BITS. But it is possible for us to
+ * fall back from BITS to the internal downloader. Background update should
+ * prevent this fallback and just abort.
+ *
+ * But interactive Firefox allows that fallback. And once the internal
+ * download has started, the background update task must leave that download
+ * untouched and allow it to finish.
+ */
+
+var TEST_MAR_CONTENTS = "Arbitrary MAR contents";
+
+add_task(async function setup() {
+ setupTestCommon();
+ start_httpserver();
+ setUpdateURL(gURLData + gHTTPHandlerPath);
+ setUpdateChannel("test_channel");
+
+ // Pretend that this is a background task.
+ const bts = Cc["@mozilla.org/backgroundtasks;1"].getService(
+ Ci.nsIBackgroundTasks
+ );
+ bts.overrideBackgroundTaskNameForTesting("test-task");
+
+ // No need for cleanup needed for changing update files. These will be cleaned
+ // up by removeUpdateFiles.
+ const downloadingMarFile = getUpdateDirFile(FILE_UPDATE_MAR, DIR_DOWNLOADING);
+ await IOUtils.writeUTF8(downloadingMarFile.path, TEST_MAR_CONTENTS);
+
+ writeStatusFile(STATE_DOWNLOADING);
+
+ let patchProps = {
+ state: STATE_DOWNLOADING,
+ bitsResult: Cr.NS_ERROR_FAILURE,
+ };
+ let patches = getLocalPatchString(patchProps);
+ let updateProps = { appVersion: "1.0" };
+ let updates = getLocalUpdateString(updateProps, patches);
+ writeUpdatesToXMLFile(getLocalUpdatesXMLString(updates), true);
+});
+
+add_task(async function backgroundUpdate() {
+ let patches = getRemotePatchString({});
+ let updateString = getRemoteUpdateString({}, patches);
+ gResponseBody = getRemoteUpdatesXMLString(updateString);
+
+ let { updates } = await waitForUpdateCheck(true);
+ let bestUpdate = gAUS.selectUpdate(updates);
+ let success = await gAUS.downloadUpdate(bestUpdate, false);
+ Assert.equal(
+ success,
+ false,
+ "We should not attempt to download an update in the background when an " +
+ "internal update download is already in progress."
+ );
+ Assert.equal(
+ readStatusFile(),
+ STATE_DOWNLOADING,
+ "Background update during an internally downloading update should not " +
+ "change update status"
+ );
+ const downloadingMarFile = getUpdateDirFile(FILE_UPDATE_MAR, DIR_DOWNLOADING);
+ Assert.ok(
+ await IOUtils.exists(downloadingMarFile.path),
+ "Downloading MAR should still exist"
+ );
+ Assert.equal(
+ await IOUtils.readUTF8(downloadingMarFile.path),
+ TEST_MAR_CONTENTS,
+ "Downloading MAR should not have been modified"
+ );
+});
+
+add_task(async function finish() {
+ stop_httpserver(doTestFinish);
+});
diff --git a/toolkit/mozapps/update/tests/unit_aus_update/canCheckForAndCanApplyUpdates.js b/toolkit/mozapps/update/tests/unit_aus_update/canCheckForAndCanApplyUpdates.js
new file mode 100644
index 0000000000..b3dcb72d14
--- /dev/null
+++ b/toolkit/mozapps/update/tests/unit_aus_update/canCheckForAndCanApplyUpdates.js
@@ -0,0 +1,62 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+function run_test() {
+ setupTestCommon();
+
+ // Verify write access to the custom app dir
+ debugDump("testing write access to the application directory");
+ let testFile = getCurrentProcessDir();
+ testFile.append("update_write_access_test");
+ testFile.create(Ci.nsIFile.NORMAL_FILE_TYPE, PERMS_FILE);
+ Assert.ok(testFile.exists(), MSG_SHOULD_EXIST);
+ testFile.remove(false);
+ Assert.ok(!testFile.exists(), MSG_SHOULD_NOT_EXIST);
+
+ if (AppConstants.platform == "win") {
+ // Create a mutex to prevent being able to check for or apply updates.
+ debugDump("attempting to create mutex");
+ let handle = createMutex(getPerInstallationMutexName());
+ Assert.ok(!!handle, "the update mutex should have been created");
+
+ // Check if available updates cannot be checked for when there is a mutex
+ // for this installation.
+ Assert.ok(
+ !gAUS.canCheckForUpdates,
+ "should not be able to check for " +
+ "updates when there is an update mutex"
+ );
+
+ // Check if updates cannot be applied when there is a mutex for this
+ // installation.
+ Assert.ok(
+ !gAUS.canApplyUpdates,
+ "should not be able to apply updates when there is an update mutex"
+ );
+
+ debugDump("destroying mutex");
+ closeHandle(handle);
+ }
+
+ // Check if available updates can be checked for
+ Assert.ok(gAUS.canCheckForUpdates, "should be able to check for updates");
+ // Check if updates can be applied
+ Assert.ok(gAUS.canApplyUpdates, "should be able to apply updates");
+
+ if (AppConstants.platform == "win") {
+ // Attempt to create a mutex when application update has already created one
+ // with the same name.
+ debugDump("attempting to create mutex");
+ let handle = createMutex(getPerInstallationMutexName());
+
+ Assert.ok(
+ !handle,
+ "should not be able to create the update mutex when " +
+ "the application has created the update mutex"
+ );
+ }
+
+ doTestFinish();
+}
diff --git a/toolkit/mozapps/update/tests/unit_aus_update/cleanupDownloadingForDifferentChannel.js b/toolkit/mozapps/update/tests/unit_aus_update/cleanupDownloadingForDifferentChannel.js
new file mode 100644
index 0000000000..33df63e7e7
--- /dev/null
+++ b/toolkit/mozapps/update/tests/unit_aus_update/cleanupDownloadingForDifferentChannel.js
@@ -0,0 +1,60 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+async function run_test() {
+ setupTestCommon();
+
+ debugDump(
+ "testing removal of an active update for a channel that is not " +
+ "valid due to switching channels (Bug 486275)."
+ );
+
+ let patchProps = { state: STATE_DOWNLOADING };
+ let patches = getLocalPatchString(patchProps);
+ let updateProps = { appVersion: "1.0" };
+ let updates = getLocalUpdateString(updateProps, patches);
+ writeUpdatesToXMLFile(getLocalUpdatesXMLString(updates), true);
+ writeStatusFile(STATE_DOWNLOADING);
+
+ setUpdateChannel("original_channel");
+
+ standardInit();
+
+ Assert.ok(
+ !gUpdateManager.downloadingUpdate,
+ "there should not be a downloading update"
+ );
+ Assert.ok(!gUpdateManager.readyUpdate, "there should not be a ready update");
+ Assert.equal(
+ gUpdateManager.getUpdateCount(),
+ 1,
+ "the update manager update count" + MSG_SHOULD_EQUAL
+ );
+ let update = gUpdateManager.getUpdateAt(0);
+ Assert.equal(
+ update.state,
+ STATE_FAILED,
+ "the first update state" + MSG_SHOULD_EQUAL
+ );
+ Assert.equal(
+ update.errorCode,
+ ERR_CHANNEL_CHANGE,
+ "the first update errorCode" + MSG_SHOULD_EQUAL
+ );
+ Assert.equal(
+ update.statusText,
+ getString("statusFailed"),
+ "the first update statusText " + MSG_SHOULD_EQUAL
+ );
+ await waitForUpdateXMLFiles();
+
+ let dir = getUpdateDirFile(DIR_PATCH);
+ Assert.ok(dir.exists(), MSG_SHOULD_EXIST);
+
+ let statusFile = getUpdateDirFile(FILE_UPDATE_STATUS);
+ Assert.ok(!statusFile.exists(), MSG_SHOULD_NOT_EXIST);
+
+ doTestFinish();
+}
diff --git a/toolkit/mozapps/update/tests/unit_aus_update/cleanupDownloadingForOlderAppVersion.js b/toolkit/mozapps/update/tests/unit_aus_update/cleanupDownloadingForOlderAppVersion.js
new file mode 100644
index 0000000000..3c9f7d1c2e
--- /dev/null
+++ b/toolkit/mozapps/update/tests/unit_aus_update/cleanupDownloadingForOlderAppVersion.js
@@ -0,0 +1,58 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+async function run_test() {
+ setupTestCommon();
+
+ debugDump(
+ "testing cleanup of an update download in progress for an " +
+ "older version of the application on startup (Bug 485624)"
+ );
+
+ let patchProps = { state: STATE_DOWNLOADING };
+ let patches = getLocalPatchString(patchProps);
+ let updateProps = { appVersion: "0.9" };
+ let updates = getLocalUpdateString(updateProps, patches);
+ writeUpdatesToXMLFile(getLocalUpdatesXMLString(updates), true);
+ writeStatusFile(STATE_DOWNLOADING);
+
+ standardInit();
+
+ Assert.ok(
+ !gUpdateManager.downloadingUpdate,
+ "there should not be a downloading update"
+ );
+ Assert.ok(!gUpdateManager.readyUpdate, "there should not be a ready update");
+ Assert.equal(
+ gUpdateManager.getUpdateCount(),
+ 1,
+ "the update manager update count" + MSG_SHOULD_EQUAL
+ );
+ let update = gUpdateManager.getUpdateAt(0);
+ Assert.equal(
+ update.state,
+ STATE_FAILED,
+ "the first update state" + MSG_SHOULD_EQUAL
+ );
+ Assert.equal(
+ update.errorCode,
+ ERR_OLDER_VERSION_OR_SAME_BUILD,
+ "the first update errorCode" + MSG_SHOULD_EQUAL
+ );
+ Assert.equal(
+ update.statusText,
+ getString("statusFailed"),
+ "the first update statusText " + MSG_SHOULD_EQUAL
+ );
+ await waitForUpdateXMLFiles();
+
+ let dir = getUpdateDirFile(DIR_PATCH);
+ Assert.ok(dir.exists(), MSG_SHOULD_EXIST);
+
+ let statusFile = getUpdateDirFile(FILE_UPDATE_STATUS);
+ Assert.ok(!statusFile.exists(), MSG_SHOULD_NOT_EXIST);
+
+ doTestFinish();
+}
diff --git a/toolkit/mozapps/update/tests/unit_aus_update/cleanupDownloadingForSameVersionAndBuildID.js b/toolkit/mozapps/update/tests/unit_aus_update/cleanupDownloadingForSameVersionAndBuildID.js
new file mode 100644
index 0000000000..0eb1b6c22e
--- /dev/null
+++ b/toolkit/mozapps/update/tests/unit_aus_update/cleanupDownloadingForSameVersionAndBuildID.js
@@ -0,0 +1,59 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+async function run_test() {
+ setupTestCommon();
+
+ debugDump(
+ "testing removal of an update download in progress for the " +
+ "same version of the application with the same application " +
+ "build id on startup (Bug 536547)"
+ );
+
+ let patchProps = { state: STATE_DOWNLOADING };
+ let patches = getLocalPatchString(patchProps);
+ let updateProps = { appVersion: "1.0", buildID: "2007010101" };
+ let updates = getLocalUpdateString(updateProps, patches);
+ writeUpdatesToXMLFile(getLocalUpdatesXMLString(updates), true);
+ writeStatusFile(STATE_DOWNLOADING);
+
+ standardInit();
+
+ Assert.ok(
+ !gUpdateManager.downloadingUpdate,
+ "there should not be a downloading update"
+ );
+ Assert.ok(!gUpdateManager.readyUpdate, "there should not be a ready update");
+ Assert.equal(
+ gUpdateManager.getUpdateCount(),
+ 1,
+ "the update manager update count" + MSG_SHOULD_EQUAL
+ );
+ let update = gUpdateManager.getUpdateAt(0);
+ Assert.equal(
+ update.state,
+ STATE_FAILED,
+ "the first update state" + MSG_SHOULD_EQUAL
+ );
+ Assert.equal(
+ update.errorCode,
+ ERR_OLDER_VERSION_OR_SAME_BUILD,
+ "the first update errorCode" + MSG_SHOULD_EQUAL
+ );
+ Assert.equal(
+ update.statusText,
+ getString("statusFailed"),
+ "the first update statusText " + MSG_SHOULD_EQUAL
+ );
+ await waitForUpdateXMLFiles();
+
+ let dir = getUpdateDirFile(DIR_PATCH);
+ Assert.ok(dir.exists(), MSG_SHOULD_EXIST);
+
+ let statusFile = getUpdateDirFile(FILE_UPDATE_STATUS);
+ Assert.ok(!statusFile.exists(), MSG_SHOULD_NOT_EXIST);
+
+ doTestFinish();
+}
diff --git a/toolkit/mozapps/update/tests/unit_aus_update/cleanupDownloadingIncorrectStatus.js b/toolkit/mozapps/update/tests/unit_aus_update/cleanupDownloadingIncorrectStatus.js
new file mode 100644
index 0000000000..8468ca453c
--- /dev/null
+++ b/toolkit/mozapps/update/tests/unit_aus_update/cleanupDownloadingIncorrectStatus.js
@@ -0,0 +1,57 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+async function run_test() {
+ setupTestCommon();
+
+ debugDump(
+ "testing update cleanup when reading the status file returns " +
+ "STATUS_NONE and the update xml has an update with " +
+ "STATE_DOWNLOADING (Bug 539717)."
+ );
+
+ let patchProps = { state: STATE_DOWNLOADING };
+ let patches = getLocalPatchString(patchProps);
+ let updates = getLocalUpdateString({}, patches);
+ writeUpdatesToXMLFile(getLocalUpdatesXMLString(updates), true);
+ writeStatusFile(STATE_NONE);
+
+ standardInit();
+
+ Assert.ok(
+ !gUpdateManager.downloadingUpdate,
+ "there should not be a downloading update"
+ );
+ Assert.ok(!gUpdateManager.readyUpdate, "there should not be a ready update");
+ Assert.equal(
+ gUpdateManager.getUpdateCount(),
+ 1,
+ "the update manager update count" + MSG_SHOULD_EQUAL
+ );
+ let update = gUpdateManager.getUpdateAt(0);
+ Assert.equal(
+ update.state,
+ STATE_FAILED,
+ "the first update state" + MSG_SHOULD_EQUAL
+ );
+ Assert.equal(
+ update.errorCode,
+ ERR_UPDATE_STATE_NONE,
+ "the first update errorCode" + MSG_SHOULD_EQUAL
+ );
+ Assert.equal(
+ update.statusText,
+ getString("statusFailed"),
+ "the first update statusText " + MSG_SHOULD_EQUAL
+ );
+ await waitForUpdateXMLFiles();
+
+ let dir = getUpdateDirFile(DIR_PATCH);
+ Assert.ok(dir.exists(), MSG_SHOULD_EXIST);
+
+ let statusFile = getUpdateDirFile(FILE_UPDATE_STATUS);
+ Assert.ok(!statusFile.exists(), MSG_SHOULD_NOT_EXIST);
+
+ doTestFinish();
+}
diff --git a/toolkit/mozapps/update/tests/unit_aus_update/cleanupPendingVersionFileIncorrectStatus.js b/toolkit/mozapps/update/tests/unit_aus_update/cleanupPendingVersionFileIncorrectStatus.js
new file mode 100644
index 0000000000..274f029150
--- /dev/null
+++ b/toolkit/mozapps/update/tests/unit_aus_update/cleanupPendingVersionFileIncorrectStatus.js
@@ -0,0 +1,57 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+async function run_test() {
+ setupTestCommon();
+
+ debugDump(
+ "testing update cleanup when reading the status file returns " +
+ "STATUS_NONE, the version file is for a newer version, and the " +
+ "update xml has an update with STATE_PENDING (Bug 601701)."
+ );
+
+ let patchProps = { state: STATE_PENDING };
+ let patches = getLocalPatchString(patchProps);
+ let updates = getLocalUpdateString({}, patches);
+ writeUpdatesToXMLFile(getLocalUpdatesXMLString(updates), true);
+ writeVersionFile("99.9");
+
+ // Check that there are no active updates first so the updates directory is
+ // cleaned up by the UpdateManager before the remaining tests.
+ Assert.ok(
+ !gUpdateManager.downloadingUpdate,
+ "there should not be a downloading update"
+ );
+ Assert.ok(!gUpdateManager.readyUpdate, "there should not be a ready update");
+ Assert.equal(
+ gUpdateManager.getUpdateCount(),
+ 1,
+ "the update manager update count" + MSG_SHOULD_EQUAL
+ );
+ let update = gUpdateManager.getUpdateAt(0);
+ Assert.equal(
+ update.state,
+ STATE_FAILED,
+ "the first update state" + MSG_SHOULD_EQUAL
+ );
+ Assert.equal(
+ update.errorCode,
+ ERR_UPDATE_STATE_NONE,
+ "the first update errorCode" + MSG_SHOULD_EQUAL
+ );
+ Assert.equal(
+ update.statusText,
+ getString("statusFailed"),
+ "the first update statusText " + MSG_SHOULD_EQUAL
+ );
+ await waitForUpdateXMLFiles();
+
+ let dir = getUpdateDirFile(DIR_PATCH);
+ Assert.ok(dir.exists(), MSG_SHOULD_EXIST);
+
+ let versionFile = getUpdateDirFile(FILE_UPDATE_VERSION);
+ Assert.ok(!versionFile.exists(), MSG_SHOULD_NOT_EXIST);
+
+ doTestFinish();
+}
diff --git a/toolkit/mozapps/update/tests/unit_aus_update/cleanupSuccessLogMove.js b/toolkit/mozapps/update/tests/unit_aus_update/cleanupSuccessLogMove.js
new file mode 100644
index 0000000000..3a36693dca
--- /dev/null
+++ b/toolkit/mozapps/update/tests/unit_aus_update/cleanupSuccessLogMove.js
@@ -0,0 +1,61 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+async function run_test() {
+ setupTestCommon();
+
+ debugDump("testing that the update.log is moved after a successful update");
+
+ Services.prefs.setIntPref(PREF_APP_UPDATE_CANCELATIONS, 5);
+
+ let patchProps = { state: STATE_PENDING };
+ let patches = getLocalPatchString(patchProps);
+ let updates = getLocalUpdateString({}, patches);
+ writeUpdatesToXMLFile(getLocalUpdatesXMLString(updates), true);
+ writeStatusFile(STATE_SUCCEEDED);
+
+ let log = getUpdateDirFile(FILE_UPDATE_LOG);
+ writeFile(log, "Last Update Log");
+
+ standardInit();
+
+ Assert.ok(
+ !gUpdateManager.downloadingUpdate,
+ "there should not be a downloading update"
+ );
+ Assert.ok(!gUpdateManager.readyUpdate, "there should not be a ready update");
+ Assert.equal(
+ gUpdateManager.getUpdateCount(),
+ 1,
+ "the update manager update count" + MSG_SHOULD_EQUAL
+ );
+ await waitForUpdateXMLFiles();
+
+ let cancelations = Services.prefs.getIntPref(PREF_APP_UPDATE_CANCELATIONS, 0);
+ Assert.equal(
+ cancelations,
+ 0,
+ "the " + PREF_APP_UPDATE_CANCELATIONS + " preference " + MSG_SHOULD_EQUAL
+ );
+
+ log = getUpdateDirFile(FILE_UPDATE_LOG);
+ Assert.ok(!log.exists(), MSG_SHOULD_NOT_EXIST);
+
+ log = getUpdateDirFile(FILE_LAST_UPDATE_LOG);
+ Assert.ok(log.exists(), MSG_SHOULD_EXIST);
+ Assert.equal(
+ readFile(log),
+ "Last Update Log",
+ "the last update log contents" + MSG_SHOULD_EQUAL
+ );
+
+ log = getUpdateDirFile(FILE_BACKUP_UPDATE_LOG);
+ Assert.ok(!log.exists(), MSG_SHOULD_NOT_EXIST);
+
+ let dir = getUpdateDirFile(DIR_PATCH);
+ Assert.ok(dir.exists(), MSG_SHOULD_EXIST);
+
+ doTestFinish();
+}
diff --git a/toolkit/mozapps/update/tests/unit_aus_update/cleanupSuccessLogsFIFO.js b/toolkit/mozapps/update/tests/unit_aus_update/cleanupSuccessLogsFIFO.js
new file mode 100644
index 0000000000..9caceccc99
--- /dev/null
+++ b/toolkit/mozapps/update/tests/unit_aus_update/cleanupSuccessLogsFIFO.js
@@ -0,0 +1,63 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+async function run_test() {
+ setupTestCommon();
+
+ debugDump("testing update logs are first in first out deleted");
+
+ let patchProps = { state: STATE_PENDING };
+ let patches = getLocalPatchString(patchProps);
+ let updates = getLocalUpdateString({}, patches);
+ writeUpdatesToXMLFile(getLocalUpdatesXMLString(updates), true);
+ writeStatusFile(STATE_SUCCEEDED);
+
+ let log = getUpdateDirFile(FILE_LAST_UPDATE_LOG);
+ writeFile(log, "Backup Update Log");
+
+ log = getUpdateDirFile(FILE_BACKUP_UPDATE_LOG);
+ writeFile(log, "To Be Deleted Backup Update Log");
+
+ log = getUpdateDirFile(FILE_UPDATE_LOG);
+ writeFile(log, "Last Update Log");
+
+ standardInit();
+
+ Assert.ok(
+ !gUpdateManager.downloadingUpdate,
+ "there should not be a downloading update"
+ );
+ Assert.ok(!gUpdateManager.readyUpdate, "there should not be a ready update");
+ Assert.equal(
+ gUpdateManager.getUpdateCount(),
+ 1,
+ "the update manager update count" + MSG_SHOULD_EQUAL
+ );
+ await waitForUpdateXMLFiles();
+
+ log = getUpdateDirFile(FILE_UPDATE_LOG);
+ Assert.ok(!log.exists(), MSG_SHOULD_NOT_EXIST);
+
+ log = getUpdateDirFile(FILE_LAST_UPDATE_LOG);
+ Assert.ok(log.exists(), MSG_SHOULD_EXIST);
+ Assert.equal(
+ readFile(log),
+ "Last Update Log",
+ "the last update log contents" + MSG_SHOULD_EQUAL
+ );
+
+ log = getUpdateDirFile(FILE_BACKUP_UPDATE_LOG);
+ Assert.ok(log.exists(), MSG_SHOULD_EXIST);
+ Assert.equal(
+ readFile(log),
+ "Backup Update Log",
+ "the backup update log contents" + MSG_SHOULD_EQUAL
+ );
+
+ let dir = getUpdateDirFile(DIR_PATCH);
+ Assert.ok(dir.exists(), MSG_SHOULD_EXIST);
+
+ doTestFinish();
+}
diff --git a/toolkit/mozapps/update/tests/unit_aus_update/disableBackgroundUpdatesBackgroundTask.js b/toolkit/mozapps/update/tests/unit_aus_update/disableBackgroundUpdatesBackgroundTask.js
new file mode 100644
index 0000000000..4dcf559563
--- /dev/null
+++ b/toolkit/mozapps/update/tests/unit_aus_update/disableBackgroundUpdatesBackgroundTask.js
@@ -0,0 +1,48 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+"use strict";
+
+/**
+ * This test verifies that when Balrog advertises that an update should not
+ * be downloaded in the background, it is not.
+ */
+
+function setup() {
+ setupTestCommon();
+ start_httpserver();
+ setUpdateURL(gURLData + gHTTPHandlerPath);
+ setUpdateChannel("test_channel");
+
+ // Pretend that this is a background task.
+ const bts = Cc["@mozilla.org/backgroundtasks;1"].getService(
+ Ci.nsIBackgroundTasks
+ );
+ bts.overrideBackgroundTaskNameForTesting("test-task");
+}
+setup();
+
+add_task(async function disableBackgroundUpdatesBackgroundTask() {
+ let patches = getRemotePatchString({});
+ let updateString = getRemoteUpdateString(
+ { disableBackgroundUpdates: "true" },
+ patches
+ );
+ gResponseBody = getRemoteUpdatesXMLString(updateString);
+
+ let { updates } = await waitForUpdateCheck(true);
+ let bestUpdate = gAUS.selectUpdate(updates);
+ let success = await gAUS.downloadUpdate(bestUpdate, false);
+ Assert.equal(
+ success,
+ false,
+ "Update should not download when disableBackgroundUpdates is specified " +
+ "and we are in background task mode."
+ );
+});
+
+add_task(async function finish() {
+ stop_httpserver(doTestFinish);
+});
diff --git a/toolkit/mozapps/update/tests/unit_aus_update/disableBackgroundUpdatesNonBackgroundTask.js b/toolkit/mozapps/update/tests/unit_aus_update/disableBackgroundUpdatesNonBackgroundTask.js
new file mode 100644
index 0000000000..2f4eec25ff
--- /dev/null
+++ b/toolkit/mozapps/update/tests/unit_aus_update/disableBackgroundUpdatesNonBackgroundTask.js
@@ -0,0 +1,41 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+"use strict";
+
+/**
+ * This test verifies that when Balrog advertises that an update should not
+ * be downloaded in the background, but we are not running in the background,
+ * the advertisement does not have any effect.
+ */
+
+function setup() {
+ setupTestCommon();
+ start_httpserver();
+ setUpdateURL(gURLData + gHTTPHandlerPath);
+ setUpdateChannel("test_channel");
+}
+setup();
+
+add_task(async function disableBackgroundUpdatesBackgroundTask() {
+ let patches = getRemotePatchString({});
+ let updateString = getRemoteUpdateString(
+ { disableBackgroundUpdates: "true" },
+ patches
+ );
+ gResponseBody = getRemoteUpdatesXMLString(updateString);
+
+ let { updates } = await waitForUpdateCheck(true);
+
+ initMockIncrementalDownload();
+ gIncrementalDownloadErrorType = 3;
+
+ // This will assert that the download completes successfully.
+ await waitForUpdateDownload(updates, Cr.NS_OK);
+});
+
+add_task(async function finish() {
+ stop_httpserver(doTestFinish);
+});
diff --git a/toolkit/mozapps/update/tests/unit_aus_update/downloadInterruptedNoRecovery.js b/toolkit/mozapps/update/tests/unit_aus_update/downloadInterruptedNoRecovery.js
new file mode 100644
index 0000000000..15dd39ce0a
--- /dev/null
+++ b/toolkit/mozapps/update/tests/unit_aus_update/downloadInterruptedNoRecovery.js
@@ -0,0 +1,23 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+async function run_test() {
+ setupTestCommon();
+ debugDump("testing mar download with interrupted recovery count exceeded");
+ Services.prefs.setBoolPref(PREF_APP_UPDATE_STAGING_ENABLED, false);
+ start_httpserver();
+ setUpdateURL(gURLData + gHTTPHandlerPath);
+ initMockIncrementalDownload();
+ gIncrementalDownloadErrorType = 0;
+ Services.prefs.setIntPref(PREF_APP_UPDATE_SOCKET_MAXERRORS, 2);
+ Services.prefs.setIntPref(PREF_APP_UPDATE_RETRYTIMEOUT, 0);
+ let patches = getRemotePatchString({});
+ let updates = getRemoteUpdateString({}, patches);
+ gResponseBody = getRemoteUpdatesXMLString(updates);
+ await waitForUpdateCheck(true, { updateCount: 1 }).then(async aArgs => {
+ await waitForUpdateDownload(aArgs.updates, Cr.NS_ERROR_NET_RESET);
+ });
+ stop_httpserver(doTestFinish);
+}
diff --git a/toolkit/mozapps/update/tests/unit_aus_update/downloadInterruptedOffline.js b/toolkit/mozapps/update/tests/unit_aus_update/downloadInterruptedOffline.js
new file mode 100644
index 0000000000..2c54e058d2
--- /dev/null
+++ b/toolkit/mozapps/update/tests/unit_aus_update/downloadInterruptedOffline.js
@@ -0,0 +1,21 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+async function run_test() {
+ setupTestCommon();
+ debugDump("testing mar download when offline");
+ Services.prefs.setBoolPref(PREF_APP_UPDATE_STAGING_ENABLED, false);
+ start_httpserver();
+ setUpdateURL(gURLData + gHTTPHandlerPath);
+ initMockIncrementalDownload();
+ gIncrementalDownloadErrorType = 4;
+ let patches = getRemotePatchString({});
+ let updates = getRemoteUpdateString({}, patches);
+ gResponseBody = getRemoteUpdatesXMLString(updates);
+ await waitForUpdateCheck(true, { updateCount: 1 }).then(async aArgs => {
+ await waitForUpdateDownload(aArgs.updates, Cr.NS_OK);
+ });
+ stop_httpserver(doTestFinish);
+}
diff --git a/toolkit/mozapps/update/tests/unit_aus_update/downloadInterruptedRecovery.js b/toolkit/mozapps/update/tests/unit_aus_update/downloadInterruptedRecovery.js
new file mode 100644
index 0000000000..1ed40f9a2a
--- /dev/null
+++ b/toolkit/mozapps/update/tests/unit_aus_update/downloadInterruptedRecovery.js
@@ -0,0 +1,26 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+async function run_test() {
+ setupTestCommon();
+ debugDump("testing mar mar download interrupted recovery");
+ // This test assumes speculative connections enabled.
+ Services.prefs.setIntPref("network.http.speculative-parallel-limit", 6);
+ registerCleanupFunction(() => {
+ Services.prefs.clearUserPref("network.http.speculative-parallel-limit");
+ });
+ Services.prefs.setBoolPref(PREF_APP_UPDATE_STAGING_ENABLED, false);
+ start_httpserver();
+ setUpdateURL(gURLData + gHTTPHandlerPath);
+ initMockIncrementalDownload();
+ gIncrementalDownloadErrorType = 0;
+ let patches = getRemotePatchString({});
+ let updates = getRemoteUpdateString({}, patches);
+ gResponseBody = getRemoteUpdatesXMLString(updates);
+ await waitForUpdateCheck(true, { updateCount: 1 }).then(async aArgs => {
+ await waitForUpdateDownload(aArgs.updates, Cr.NS_OK);
+ });
+ stop_httpserver(doTestFinish);
+}
diff --git a/toolkit/mozapps/update/tests/unit_aus_update/downloadResumeForSameAppVersion.js b/toolkit/mozapps/update/tests/unit_aus_update/downloadResumeForSameAppVersion.js
new file mode 100644
index 0000000000..8a386513f3
--- /dev/null
+++ b/toolkit/mozapps/update/tests/unit_aus_update/downloadResumeForSameAppVersion.js
@@ -0,0 +1,38 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+async function run_test() {
+ setupTestCommon();
+
+ debugDump(
+ "testing resuming an update download in progress for the same " +
+ "version of the application on startup (Bug 485624)"
+ );
+
+ let patchProps = { state: STATE_DOWNLOADING };
+ let patches = getLocalPatchString(patchProps);
+ let updateProps = { appVersion: "1.0" };
+ let updates = getLocalUpdateString(updateProps, patches);
+ writeUpdatesToXMLFile(getLocalUpdatesXMLString(updates), true);
+ writeStatusFile(STATE_DOWNLOADING);
+
+ standardInit();
+
+ Assert.equal(
+ gUpdateManager.getUpdateCount(),
+ 0,
+ "the update manager updateCount attribute" + MSG_SHOULD_EQUAL
+ );
+ Assert.equal(
+ gUpdateManager.downloadingUpdate.state,
+ STATE_DOWNLOADING,
+ "the update manager activeUpdate state attribute" + MSG_SHOULD_EQUAL
+ );
+
+ // Cancel the download early to prevent it writing the update xml files during
+ // shutdown.
+ await gAUS.stopDownload();
+ executeSoon(doTestFinish);
+}
diff --git a/toolkit/mozapps/update/tests/unit_aus_update/ensureExperimentToRolloutTransitionPerformed.js b/toolkit/mozapps/update/tests/unit_aus_update/ensureExperimentToRolloutTransitionPerformed.js
new file mode 100644
index 0000000000..86bc75a7d6
--- /dev/null
+++ b/toolkit/mozapps/update/tests/unit_aus_update/ensureExperimentToRolloutTransitionPerformed.js
@@ -0,0 +1,111 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+ChromeUtils.defineESModuleGetters(this, {
+ BackgroundUpdate: "resource://gre/modules/BackgroundUpdate.sys.mjs",
+});
+
+const transitionPerformedPref = "app.update.background.rolledout";
+const backgroundUpdateEnabledPref = "app.update.background.enabled";
+const defaultPrefValue =
+ UpdateUtils.PER_INSTALLATION_PREFS[backgroundUpdateEnabledPref].defaultValue;
+
+async function testTransition(options) {
+ Services.prefs.clearUserPref(transitionPerformedPref);
+ await UpdateUtils.writeUpdateConfigSetting(
+ backgroundUpdateEnabledPref,
+ options.initialDefaultValue,
+ { setDefaultOnly: true }
+ );
+ await UpdateUtils.writeUpdateConfigSetting(
+ backgroundUpdateEnabledPref,
+ options.initialUserValue
+ );
+ BackgroundUpdate.ensureExperimentToRolloutTransitionPerformed();
+ Assert.equal(
+ await UpdateUtils.readUpdateConfigSetting(backgroundUpdateEnabledPref),
+ options.expectedPostTransitionValue,
+ "Post transition option value does not match the expected value"
+ );
+
+ // Make sure that we only do the transition once.
+
+ // If we change the default value, then change the user value to the same
+ // thing, we will end up with only a default value and no saved user value.
+ // This allows us to ensure that we read the default value back out, if it is
+ // changed.
+ await UpdateUtils.writeUpdateConfigSetting(
+ backgroundUpdateEnabledPref,
+ !defaultPrefValue,
+ { setDefaultOnly: true }
+ );
+ await UpdateUtils.writeUpdateConfigSetting(
+ backgroundUpdateEnabledPref,
+ !defaultPrefValue
+ );
+ BackgroundUpdate.ensureExperimentToRolloutTransitionPerformed();
+ Assert.equal(
+ await UpdateUtils.readUpdateConfigSetting(backgroundUpdateEnabledPref),
+ !defaultPrefValue,
+ "Transition should not change the pref value if it already ran"
+ );
+}
+
+async function run_test() {
+ setupTestCommon(null);
+ standardInit();
+ // The setup functions we use for update testing typically allow for update.
+ // But we are just testing preferences here. We don't want anything to
+ // actually attempt to update. Also, because we are messing with the pref
+ // system itself in this test, we want to make sure to use a pref outside of
+ // that system to disable update.
+ Services.prefs.setBoolPref(PREF_APP_UPDATE_DISABLEDFORTESTING, true);
+
+ const originalBackgroundUpdateEnabled =
+ await UpdateUtils.readUpdateConfigSetting(backgroundUpdateEnabledPref);
+
+ registerCleanupFunction(async () => {
+ Services.prefs.clearUserPref(transitionPerformedPref);
+ await UpdateUtils.writeUpdateConfigSetting(
+ backgroundUpdateEnabledPref,
+ originalBackgroundUpdateEnabled
+ );
+ await UpdateUtils.writeUpdateConfigSetting(
+ backgroundUpdateEnabledPref,
+ defaultPrefValue,
+ { setDefaultOnly: true }
+ );
+ });
+
+ await testTransition({
+ initialDefaultValue: true,
+ initialUserValue: true,
+ expectedPostTransitionValue: true,
+ });
+
+ // Make sure we don't interfere with a user's choice to turn the feature off.
+ await testTransition({
+ initialDefaultValue: true,
+ initialUserValue: false,
+ expectedPostTransitionValue: false,
+ });
+
+ // In this case, there effectively is no user value since the user value
+ // equals the default value. So the effective value should change after
+ // the transition switches the default.
+ await testTransition({
+ initialDefaultValue: false,
+ initialUserValue: false,
+ expectedPostTransitionValue: true,
+ });
+
+ await testTransition({
+ initialDefaultValue: false,
+ initialUserValue: true,
+ expectedPostTransitionValue: true,
+ });
+
+ doTestFinish();
+}
diff --git a/toolkit/mozapps/update/tests/unit_aus_update/head_update.js b/toolkit/mozapps/update/tests/unit_aus_update/head_update.js
new file mode 100644
index 0000000000..3cfde06015
--- /dev/null
+++ b/toolkit/mozapps/update/tests/unit_aus_update/head_update.js
@@ -0,0 +1,8 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+const IS_SERVICE_TEST = false;
+
+/* import-globals-from ../data/xpcshellUtilsAUS.js */
+load("xpcshellUtilsAUS.js");
diff --git a/toolkit/mozapps/update/tests/unit_aus_update/languagePackUpdates.js b/toolkit/mozapps/update/tests/unit_aus_update/languagePackUpdates.js
new file mode 100644
index 0000000000..5c67c59fe0
--- /dev/null
+++ b/toolkit/mozapps/update/tests/unit_aus_update/languagePackUpdates.js
@@ -0,0 +1,291 @@
+const { AddonTestUtils } = ChromeUtils.importESModule(
+ "resource://testing-common/AddonTestUtils.sys.mjs"
+);
+const { getAppInfo } = ChromeUtils.importESModule(
+ "resource://testing-common/AppInfo.sys.mjs"
+);
+const { XPIInstall } = ChromeUtils.import(
+ "resource://gre/modules/addons/XPIInstall.jsm"
+);
+const { PromiseUtils } = ChromeUtils.importESModule(
+ "resource://gre/modules/PromiseUtils.sys.mjs"
+);
+const { setTimeout } = ChromeUtils.importESModule(
+ "resource://gre/modules/Timer.sys.mjs"
+);
+const { TelemetryTestUtils } = ChromeUtils.importESModule(
+ "resource://testing-common/TelemetryTestUtils.sys.mjs"
+);
+
+AddonTestUtils.init(this);
+setupTestCommon();
+AddonTestUtils.appInfo = getAppInfo();
+start_httpserver();
+setUpdateURL(gURLData + gHTTPHandlerPath);
+setUpdateChannel("test_channel");
+Services.prefs.setBoolPref(PREF_APP_UPDATE_LANGPACK_ENABLED, true);
+
+/**
+ * Checks for updates and waits for the update to download.
+ */
+async function downloadUpdate() {
+ let patches = getRemotePatchString({});
+ let updateString = getRemoteUpdateString({}, patches);
+ gResponseBody = getRemoteUpdatesXMLString(updateString);
+
+ let { updates } = await waitForUpdateCheck(true);
+
+ initMockIncrementalDownload();
+ gIncrementalDownloadErrorType = 3;
+
+ await waitForUpdateDownload(updates, Cr.NS_OK);
+}
+
+/**
+ * Returns a promise that will resolve when the add-ons manager attempts to
+ * stage langpack updates. The returned object contains the appVersion and
+ * platformVersion parameters as well as resolve and reject functions to
+ * complete the mocked langpack update.
+ */
+function mockLangpackUpdate() {
+ let stagingCall = PromiseUtils.defer();
+ XPIInstall.stageLangpacksForAppUpdate = (appVersion, platformVersion) => {
+ let result = PromiseUtils.defer();
+ stagingCall.resolve({
+ appVersion,
+ platformVersion,
+ resolve: result.resolve,
+ reject: result.reject,
+ });
+
+ return result.promise;
+ };
+
+ return stagingCall.promise;
+}
+
+add_setup(async function () {
+ // Thunderbird doesn't have one or more of the probes used in this test.
+ // Ensure the data is collected anyway.
+ Services.prefs.setBoolPref(
+ "toolkit.telemetry.testing.overrideProductsCheck",
+ true
+ );
+
+ await AddonTestUtils.promiseStartupManager();
+});
+
+add_task(async function testLangpackUpdateSuccess() {
+ let histogram = TelemetryTestUtils.getAndClearHistogram(
+ "UPDATE_LANGPACK_OVERTIME"
+ );
+
+ let updateDownloadNotified = false;
+ let notified = waitForEvent("update-downloaded").then(
+ () => (updateDownloadNotified = true)
+ );
+
+ let stagingCall = mockLangpackUpdate();
+
+ await downloadUpdate();
+
+ // We have to wait for UpdateService's onStopRequest to run far enough that
+ // the notification will have been sent if the language pack update completed.
+ await TestUtils.waitForCondition(() => readStatusFile() == "pending");
+
+ Assert.ok(
+ !updateDownloadNotified,
+ "Should not have seen the notification yet."
+ );
+
+ let { appVersion, platformVersion, resolve } = await stagingCall;
+ Assert.equal(
+ appVersion,
+ DEFAULT_UPDATE_VERSION,
+ "Should see the right app version"
+ );
+ Assert.equal(
+ platformVersion,
+ DEFAULT_UPDATE_VERSION,
+ "Should see the right platform version"
+ );
+
+ resolve();
+
+ await notified;
+
+ // Because we resolved the lang pack call after the download completed a value
+ // should have been recorded in telemetry.
+ let snapshot = histogram.snapshot();
+ Assert.ok(
+ !Object.values(snapshot.values).every(val => val == 0),
+ "Should have recorded a time"
+ );
+
+ // Reload the update manager so that we can download the same update again
+ reloadUpdateManagerData(true);
+});
+
+add_task(async function testLangpackUpdateFails() {
+ let updateDownloadNotified = false;
+ let notified = waitForEvent("update-downloaded").then(
+ () => (updateDownloadNotified = true)
+ );
+
+ let stagingCall = mockLangpackUpdate();
+
+ await downloadUpdate();
+
+ // We have to wait for UpdateService's onStopRequest to run far enough that
+ // the notification will have been sent if the language pack update completed.
+ await TestUtils.waitForCondition(() => readStatusFile() == "pending");
+
+ Assert.ok(
+ !updateDownloadNotified,
+ "Should not have seen the notification yet."
+ );
+
+ let { appVersion, platformVersion, reject } = await stagingCall;
+ Assert.equal(
+ appVersion,
+ DEFAULT_UPDATE_VERSION,
+ "Should see the right app version"
+ );
+ Assert.equal(
+ platformVersion,
+ DEFAULT_UPDATE_VERSION,
+ "Should see the right platform version"
+ );
+
+ reject();
+
+ await notified;
+
+ // Reload the update manager so that we can download the same update again
+ reloadUpdateManagerData(true);
+});
+
+add_task(async function testLangpackStaged() {
+ let updateStagedNotified = false;
+ let notified = waitForEvent("update-staged").then(
+ () => (updateStagedNotified = true)
+ );
+
+ let stagingCall = mockLangpackUpdate();
+
+ Services.prefs.setBoolPref(PREF_APP_UPDATE_STAGING_ENABLED, true);
+ copyTestUpdaterToBinDir();
+
+ let greDir = getGREDir();
+ let updateSettingsIni = greDir.clone();
+ updateSettingsIni.append(FILE_UPDATE_SETTINGS_INI);
+ writeFile(updateSettingsIni, UPDATE_SETTINGS_CONTENTS);
+
+ await downloadUpdate();
+
+ // We have to wait for the update to be applied and then check that the
+ // notification hasn't been sent.
+ await TestUtils.waitForCondition(() => readStatusFile() == "applied");
+
+ Assert.ok(
+ !updateStagedNotified,
+ "Should not have seen the notification yet."
+ );
+
+ let { appVersion, platformVersion, resolve } = await stagingCall;
+ Assert.equal(
+ appVersion,
+ DEFAULT_UPDATE_VERSION,
+ "Should see the right app version"
+ );
+ Assert.equal(
+ platformVersion,
+ DEFAULT_UPDATE_VERSION,
+ "Should see the right platform version"
+ );
+
+ resolve();
+
+ await notified;
+
+ // Reload the update manager so that we can download the same update again
+ reloadUpdateManagerData(true);
+});
+
+add_task(async function testRedownload() {
+ // When the download of a partial mar fails the same downloader is re-used to
+ // download the complete mar. We should only call the add-ons manager to stage
+ // language packs once in this case.
+ Services.prefs.setBoolPref(PREF_APP_UPDATE_STAGING_ENABLED, false);
+ let histogram = TelemetryTestUtils.getAndClearHistogram(
+ "UPDATE_LANGPACK_OVERTIME"
+ );
+
+ let partialPatch = getRemotePatchString({
+ type: "partial",
+ url: gURLData + "missing.mar",
+ size: 28,
+ });
+ let completePatch = getRemotePatchString({});
+ let updateString = getRemoteUpdateString({}, partialPatch + completePatch);
+ gResponseBody = getRemoteUpdatesXMLString(updateString);
+
+ let { updates } = await waitForUpdateCheck(true);
+
+ initMockIncrementalDownload();
+ gIncrementalDownloadErrorType = 3;
+
+ let stageCount = 0;
+ XPIInstall.stageLangpacksForAppUpdate = () => {
+ stageCount++;
+ return Promise.resolve();
+ };
+
+ let downloadCount = 0;
+ let listener = {
+ onStartRequest: aRequest => {},
+ onProgress: (aRequest, aContext, aProgress, aMaxProgress) => {},
+ onStatus: (aRequest, aStatus, aStatusText) => {},
+ onStopRequest: (request, status) => {
+ Assert.equal(
+ status,
+ downloadCount ? 0 : Cr.NS_ERROR_CORRUPTED_CONTENT,
+ "Should have seen the right status."
+ );
+ downloadCount++;
+
+ // Keep the same status.
+ gIncrementalDownloadErrorType = 3;
+ },
+ QueryInterface: ChromeUtils.generateQI([
+ "nsIRequestObserver",
+ "nsIProgressEventSink",
+ ]),
+ };
+ gAUS.addDownloadListener(listener);
+
+ let bestUpdate = gAUS.selectUpdate(updates);
+ await gAUS.downloadUpdate(bestUpdate, false);
+
+ await waitForEvent("update-downloaded");
+
+ gAUS.removeDownloadListener(listener);
+
+ Assert.equal(downloadCount, 2, "Should have seen two downloads");
+ Assert.equal(stageCount, 1, "Should have only tried to stage langpacks once");
+
+ // Because we resolved the lang pack call before the download completed a value
+ // should not have been recorded in telemetry.
+ let snapshot = histogram.snapshot();
+ Assert.ok(
+ Object.values(snapshot.values).every(val => val == 0),
+ "Should have recorded a time"
+ );
+
+ // Reload the update manager so that we can download the same update again
+ reloadUpdateManagerData(true);
+});
+
+add_task(async function finish() {
+ stop_httpserver(doTestFinish);
+});
diff --git a/toolkit/mozapps/update/tests/unit_aus_update/multiUpdate.js b/toolkit/mozapps/update/tests/unit_aus_update/multiUpdate.js
new file mode 100644
index 0000000000..73a262e527
--- /dev/null
+++ b/toolkit/mozapps/update/tests/unit_aus_update/multiUpdate.js
@@ -0,0 +1,396 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+/**
+ * This tests the multiple update downloads per Firefox session feature.
+ *
+ * This test does some unusual things, compared to the other files in this
+ * directory. We want to start the updates with aus.checkForBackgroundUpdates()
+ * to ensure that we test the whole flow. Other tests start update with things
+ * like aus.downloadUpdate(), but that bypasses some of the exact checks that we
+ * are trying to test as part of the multiupdate flow.
+ *
+ * In order to accomplish all this, we will be using app_update.sjs to serve
+ * updates XMLs and MARs. Outside of this test, this is really only done
+ * by browser-chrome mochitests (in ../browser). So we have to do some weird
+ * things to make it work properly in an xpcshell test. Things like
+ * defining URL_HTTP_UPDATE_SJS in testConstants.js so that it can be read by
+ * app_update.sjs in order to provide the correct download URL for MARs, but
+ * not reading that file here, because URL_HTTP_UPDATE_SJS is already defined
+ * (as something else) in xpcshellUtilsAUS.js.
+ */
+
+let { HttpServer } = ChromeUtils.import("resource://testing-common/httpd.js");
+
+// These are from testConstants.js, which cannot be loaded by this file, because
+// some values are already defined at this point. However, we need these some
+// other values to be defined because continueFileHandler in shared.js expects
+// them to be.
+const REL_PATH_DATA = "";
+// This should be URL_HOST, but that conflicts with an existing constant.
+const APP_UPDATE_SJS_HOST = "http://127.0.0.1:8888";
+const URL_PATH_UPDATE_XML = "/" + REL_PATH_DATA + "app_update.sjs";
+// This should be URL_HTTP_UPDATE_SJS, but that conflicts with an existing
+// constant.
+const APP_UPDATE_SJS_URL = APP_UPDATE_SJS_HOST + URL_PATH_UPDATE_XML;
+const CONTINUE_CHECK = "continueCheck";
+const CONTINUE_DOWNLOAD = "continueDownload";
+const CONTINUE_STAGING = "continueStaging";
+
+const FIRST_UPDATE_VERSION = "999998.0";
+const SECOND_UPDATE_VERSION = "999999.0";
+
+/**
+ * Downloads an update via aus.checkForBackgroundUpdates()
+ * Function returns only after the update has been downloaded.
+ *
+ * The provided callback will be invoked once during the update download,
+ * specifically when onStartRequest is fired.
+ *
+ * If automatic update downloads are turned off (appUpdateAuto is false), then
+ * we listen for the update-available notification and then roughly simulate
+ * accepting the prompt by calling:
+ * AppUpdateService.downloadUpdate(update, true);
+ * This is what is normally called when the user accepts the update-available
+ * prompt.
+ */
+async function downloadUpdate(appUpdateAuto, onDownloadStartCallback) {
+ let downloadFinishedPromise = waitForEvent("update-downloaded");
+ let updateAvailablePromise;
+ if (!appUpdateAuto) {
+ updateAvailablePromise = new Promise(resolve => {
+ let observer = (subject, topic, status) => {
+ Services.obs.removeObserver(observer, "update-available");
+ subject.QueryInterface(Ci.nsIUpdate);
+ resolve({ update: subject, status });
+ };
+ Services.obs.addObserver(observer, "update-available");
+ });
+ }
+ let waitToStartPromise = new Promise(resolve => {
+ let listener = {
+ onStartRequest: aRequest => {
+ gAUS.removeDownloadListener(listener);
+ onDownloadStartCallback();
+ resolve();
+ },
+ onProgress: (aRequest, aContext, aProgress, aMaxProgress) => {},
+ onStatus: (aRequest, aStatus, aStatusText) => {},
+ onStopRequest(request, status) {},
+ QueryInterface: ChromeUtils.generateQI([
+ "nsIRequestObserver",
+ "nsIProgressEventSink",
+ ]),
+ };
+ gAUS.addDownloadListener(listener);
+ });
+
+ let updateCheckStarted = gAUS.checkForBackgroundUpdates();
+ Assert.ok(updateCheckStarted, "Update check should have started");
+
+ if (!appUpdateAuto) {
+ let { update, status } = await updateAvailablePromise;
+ Assert.equal(
+ status,
+ "show-prompt",
+ "Should attempt to show the update-available prompt"
+ );
+ // Simulate accepting the update-available prompt
+ await gAUS.downloadUpdate(update, true);
+ }
+
+ await continueFileHandler(CONTINUE_DOWNLOAD);
+ await waitToStartPromise;
+ await downloadFinishedPromise;
+ // Wait an extra tick after the download has finished. If we try to check for
+ // another update exactly when "update-downloaded" fires,
+ // Downloader:onStopRequest won't have finished yet, which it normally would
+ // have.
+ await TestUtils.waitForTick();
+}
+
+/**
+ * This is like downloadUpdate. The difference is that downloadUpdate assumes
+ * that an update actually will be downloaded. This function instead verifies
+ * that we the update wasn't downloaded.
+ *
+ * downloadUpdate(), above, uses aus.checkForBackgroundUpdates() to download
+ * updates to verify that background updates actually will check for subsequent
+ * updates, but this function will use some slightly different mechanisms. We
+ * can call aus.downloadUpdate() and check its return value to see if it started
+ * a download. But this doesn't properly check that the update-available
+ * notification isn't shown. So we will do an additional check where we follow
+ * the normal flow a bit more closely by forwarding the results that we got from
+ * checkForUpdates() to aus.onCheckComplete() and make sure that the update
+ * prompt isn't shown.
+ */
+async function testUpdateDoesNotDownload() {
+ let check = gUpdateChecker.checkForUpdates(gUpdateChecker.BACKGROUND_CHECK);
+ let result = await check.result;
+ Assert.ok(result.checksAllowed, "Should be able to check for updates");
+ Assert.ok(result.succeeded, "Update check should have succeeded");
+
+ Assert.equal(
+ result.updates.length,
+ 1,
+ "Should have gotten 1 update in update check"
+ );
+ let update = result.updates[0];
+
+ let downloadStarted = await gAUS.downloadUpdate(update, true);
+ Assert.equal(
+ downloadStarted,
+ false,
+ "Expected that we would not start downloading an update"
+ );
+
+ let updateAvailableObserved = false;
+ let observer = (subject, topic, status) => {
+ updateAvailableObserved = true;
+ };
+ Services.obs.addObserver(observer, "update-available");
+ await gAUS.onCheckComplete(result);
+ Services.obs.removeObserver(observer, "update-available");
+ Assert.equal(
+ updateAvailableObserved,
+ false,
+ "update-available notification should not fire if we aren't going to " +
+ "download the update."
+ );
+}
+
+function testUpdateCheckDoesNotStart() {
+ let updateCheckStarted = gAUS.checkForBackgroundUpdates();
+ Assert.equal(
+ updateCheckStarted,
+ false,
+ "Update check should not have started"
+ );
+}
+
+function prepareToDownloadVersion(version, onlyCompleteMar = false) {
+ let updateUrl = `${APP_UPDATE_SJS_URL}?useSlowDownloadMar=1&appVersion=${version}`;
+ if (onlyCompleteMar) {
+ updateUrl += "&completePatchOnly=1";
+ }
+ setUpdateURL(updateUrl);
+}
+
+function startUpdateServer() {
+ let httpServer = new HttpServer();
+ httpServer.registerContentType("sjs", "sjs");
+ httpServer.registerDirectory("/", do_get_cwd());
+ httpServer.start(8888);
+ registerCleanupFunction(async function cleanup_httpServer() {
+ await new Promise(resolve => {
+ httpServer.stop(resolve);
+ });
+ });
+}
+
+async function multi_update_test(appUpdateAuto) {
+ await UpdateUtils.setAppUpdateAutoEnabled(appUpdateAuto);
+
+ prepareToDownloadVersion(FIRST_UPDATE_VERSION);
+
+ await downloadUpdate(appUpdateAuto, () => {
+ Assert.ok(
+ !gUpdateManager.readyUpdate,
+ "There should not be a ready update yet"
+ );
+ Assert.ok(
+ !!gUpdateManager.downloadingUpdate,
+ "First update download should be in downloadingUpdate"
+ );
+ Assert.equal(
+ gUpdateManager.downloadingUpdate.state,
+ STATE_DOWNLOADING,
+ "downloadingUpdate should be downloading"
+ );
+ Assert.equal(
+ readStatusFile(),
+ STATE_DOWNLOADING,
+ "Updater state should be downloading"
+ );
+ });
+
+ Assert.ok(
+ !gUpdateManager.downloadingUpdate,
+ "First update download should no longer be in downloadingUpdate"
+ );
+ Assert.ok(
+ !!gUpdateManager.readyUpdate,
+ "First update download should be in readyUpdate"
+ );
+ Assert.equal(
+ gUpdateManager.readyUpdate.state,
+ STATE_PENDING,
+ "readyUpdate should be pending"
+ );
+ Assert.equal(
+ gUpdateManager.readyUpdate.appVersion,
+ FIRST_UPDATE_VERSION,
+ "readyUpdate version should be match the version of the first update"
+ );
+ Assert.equal(
+ readStatusFile(),
+ STATE_PENDING,
+ "Updater state should be pending"
+ );
+
+ let existingUpdate = gUpdateManager.readyUpdate;
+ await testUpdateDoesNotDownload();
+
+ Assert.equal(
+ gUpdateManager.readyUpdate,
+ existingUpdate,
+ "readyUpdate should not have changed when no newer update is available"
+ );
+ Assert.equal(
+ gUpdateManager.readyUpdate.state,
+ STATE_PENDING,
+ "readyUpdate should still be pending"
+ );
+ Assert.equal(
+ gUpdateManager.readyUpdate.appVersion,
+ FIRST_UPDATE_VERSION,
+ "readyUpdate version should be match the version of the first update"
+ );
+ Assert.equal(
+ readStatusFile(),
+ STATE_PENDING,
+ "Updater state should still be pending"
+ );
+
+ // With only a complete update available, we should not download the newer
+ // update when we already have an update ready.
+ prepareToDownloadVersion(SECOND_UPDATE_VERSION, true);
+ await testUpdateDoesNotDownload();
+
+ Assert.equal(
+ gUpdateManager.readyUpdate,
+ existingUpdate,
+ "readyUpdate should not have changed when no newer partial update is available"
+ );
+ Assert.equal(
+ gUpdateManager.readyUpdate.state,
+ STATE_PENDING,
+ "readyUpdate should still be pending"
+ );
+ Assert.equal(
+ gUpdateManager.readyUpdate.appVersion,
+ FIRST_UPDATE_VERSION,
+ "readyUpdate version should be match the version of the first update"
+ );
+ Assert.equal(
+ readStatusFile(),
+ STATE_PENDING,
+ "Updater state should still be pending"
+ );
+
+ prepareToDownloadVersion(SECOND_UPDATE_VERSION);
+
+ await downloadUpdate(appUpdateAuto, () => {
+ Assert.ok(
+ !!gUpdateManager.downloadingUpdate,
+ "Second update download should be in downloadingUpdate"
+ );
+ Assert.equal(
+ gUpdateManager.downloadingUpdate.state,
+ STATE_DOWNLOADING,
+ "downloadingUpdate should be downloading"
+ );
+ Assert.ok(
+ !!gUpdateManager.readyUpdate,
+ "First update download should still be in readyUpdate"
+ );
+ Assert.equal(
+ gUpdateManager.readyUpdate.state,
+ STATE_PENDING,
+ "readyUpdate should still be pending"
+ );
+ Assert.equal(
+ gUpdateManager.readyUpdate.appVersion,
+ FIRST_UPDATE_VERSION,
+ "readyUpdate version should be match the version of the first update"
+ );
+ Assert.equal(
+ readStatusFile(),
+ STATE_PENDING,
+ "Updater state should match the readyUpdate's state"
+ );
+ });
+
+ Assert.ok(
+ !gUpdateManager.downloadingUpdate,
+ "Second update download should no longer be in downloadingUpdate"
+ );
+ Assert.ok(
+ !!gUpdateManager.readyUpdate,
+ "Second update download should be in readyUpdate"
+ );
+ Assert.equal(
+ gUpdateManager.readyUpdate.state,
+ STATE_PENDING,
+ "readyUpdate should be pending"
+ );
+ Assert.equal(
+ gUpdateManager.readyUpdate.appVersion,
+ SECOND_UPDATE_VERSION,
+ "readyUpdate version should be match the version of the second update"
+ );
+ Assert.equal(
+ readStatusFile(),
+ STATE_PENDING,
+ "Updater state should be pending"
+ );
+
+ // Reset the updater to its initial state to test that the complete/partial
+ // MAR behavior is correct
+ reloadUpdateManagerData(true);
+
+ // Second parameter forces a complete MAR download.
+ prepareToDownloadVersion(FIRST_UPDATE_VERSION, true);
+
+ await downloadUpdate(appUpdateAuto, () => {
+ Assert.equal(
+ gUpdateManager.downloadingUpdate.selectedPatch.type,
+ "complete",
+ "First update download should be a complete patch"
+ );
+ });
+
+ Assert.equal(
+ gUpdateManager.readyUpdate.selectedPatch.type,
+ "complete",
+ "First update download should be a complete patch"
+ );
+
+ // Even a newer partial update should not be downloaded at this point.
+ prepareToDownloadVersion(SECOND_UPDATE_VERSION);
+ testUpdateCheckDoesNotStart();
+}
+
+add_task(async function all_multi_update_tests() {
+ setupTestCommon(true);
+ startUpdateServer();
+
+ Services.prefs.setBoolPref(PREF_APP_UPDATE_DISABLEDFORTESTING, false);
+ Services.prefs.setBoolPref(PREF_APP_UPDATE_STAGING_ENABLED, false);
+
+ let origAppUpdateAutoVal = await UpdateUtils.getAppUpdateAutoEnabled();
+ registerCleanupFunction(async () => {
+ await UpdateUtils.setAppUpdateAutoEnabled(origAppUpdateAutoVal);
+ });
+
+ await multi_update_test(true);
+
+ // Reset the update system so we can start again from scratch.
+ reloadUpdateManagerData(true);
+
+ await multi_update_test(false);
+
+ doTestFinish();
+});
diff --git a/toolkit/mozapps/update/tests/unit_aus_update/onlyDownloadUpdatesThisSession.js b/toolkit/mozapps/update/tests/unit_aus_update/onlyDownloadUpdatesThisSession.js
new file mode 100644
index 0000000000..ab08ac854f
--- /dev/null
+++ b/toolkit/mozapps/update/tests/unit_aus_update/onlyDownloadUpdatesThisSession.js
@@ -0,0 +1,69 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+"use strict";
+
+function setup() {
+ setupTestCommon();
+ start_httpserver();
+ setUpdateURL(gURLData + gHTTPHandlerPath);
+ setUpdateChannel("test_channel");
+}
+setup();
+
+/**
+ * Checks for updates and makes sure that the update process does not proceed
+ * beyond the downloading stage.
+ */
+async function downloadUpdate() {
+ let patches = getRemotePatchString({});
+ let updateString = getRemoteUpdateString({}, patches);
+ gResponseBody = getRemoteUpdatesXMLString(updateString);
+
+ let { updates } = await waitForUpdateCheck(true);
+
+ initMockIncrementalDownload();
+ gIncrementalDownloadErrorType = 3;
+
+ let downloadRestrictionHitPromise = new Promise(resolve => {
+ let downloadRestrictionHitListener = (subject, topic) => {
+ Services.obs.removeObserver(downloadRestrictionHitListener, topic);
+ resolve();
+ };
+ Services.obs.addObserver(
+ downloadRestrictionHitListener,
+ "update-download-restriction-hit"
+ );
+ });
+
+ let bestUpdate = gAUS.selectUpdate(updates);
+ let success = await gAUS.downloadUpdate(bestUpdate, false);
+ Assert.ok(success, "Update download should have started");
+ return downloadRestrictionHitPromise;
+}
+
+add_task(async function onlyDownloadUpdatesThisSession() {
+ gAUS.onlyDownloadUpdatesThisSession = true;
+
+ await downloadUpdate();
+
+ Assert.ok(
+ !gUpdateManager.readyUpdate,
+ "There should not be a ready update. The update should still be downloading"
+ );
+ Assert.ok(
+ !!gUpdateManager.downloadingUpdate,
+ "A downloading update should exist"
+ );
+ Assert.equal(
+ gUpdateManager.downloadingUpdate.state,
+ STATE_DOWNLOADING,
+ "The downloading update should still be in the downloading state"
+ );
+});
+
+add_task(async function finish() {
+ stop_httpserver(doTestFinish);
+});
diff --git a/toolkit/mozapps/update/tests/unit_aus_update/perInstallationPrefs.js b/toolkit/mozapps/update/tests/unit_aus_update/perInstallationPrefs.js
new file mode 100644
index 0000000000..4a4a8ff2ae
--- /dev/null
+++ b/toolkit/mozapps/update/tests/unit_aus_update/perInstallationPrefs.js
@@ -0,0 +1,238 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+let gPolicyFunctionResult;
+
+async function testSetup() {
+ // The setup functions we use for update testing typically allow for update.
+ // But we are just testing preferences here. We don't want anything to
+ // actually attempt to update. Also, because we are messing with the pref
+ // system itself in this test, we want to make sure to use a pref outside of
+ // that system to disable update.
+ Services.prefs.setBoolPref(PREF_APP_UPDATE_DISABLEDFORTESTING, true);
+
+ // We run these tests whether per-installation prefs are supported or not,
+ // because this API needs to work in both cases.
+ logTestInfo(
+ "PER_INSTALLATION_PREFS_SUPPORTED = " +
+ UpdateUtils.PER_INSTALLATION_PREFS_SUPPORTED.toString()
+ );
+
+ // Save the original state so we can restore it after the test.
+ const originalPerInstallationPrefs = UpdateUtils.PER_INSTALLATION_PREFS;
+ let configFile = getUpdateDirFile(FILE_UPDATE_CONFIG_JSON);
+ try {
+ configFile.moveTo(null, FILE_BACKUP_UPDATE_CONFIG_JSON);
+ } catch (e) {}
+
+ // Currently, features exist for per-installation prefs that are not used by
+ // any actual pref. We added them because we intend to use these features in
+ // the future. Thus, we will override the normally defined per-installation
+ // prefs and provide our own, for the purposes of testing.
+ UpdateUtils.PER_INSTALLATION_PREFS = {
+ "test.pref.boolean": {
+ type: UpdateUtils.PER_INSTALLATION_PREF_TYPE_BOOL,
+ defaultValue: true,
+ observerTopic: "test-pref-change-observer-boolean",
+ },
+ "test.pref.integer": {
+ type: UpdateUtils.PER_INSTALLATION_PREF_TYPE_INT,
+ defaultValue: 1234,
+ observerTopic: "test-pref-change-observer-integer",
+ },
+ "test.pref.string": {
+ type: UpdateUtils.PER_INSTALLATION_PREF_TYPE_ASCII_STRING,
+ defaultValue: "<default>",
+ observerTopic: "test-pref-change-observer-string",
+ },
+ "test.pref.policy": {
+ type: UpdateUtils.PER_INSTALLATION_PREF_TYPE_INT,
+ defaultValue: 1234,
+ observerTopic: "test-pref-change-observer-policy",
+ policyFn: () => gPolicyFunctionResult,
+ },
+ };
+ // We need to re-initialize the pref system with these new prefs
+ UpdateUtils.initPerInstallPrefs();
+
+ registerCleanupFunction(() => {
+ if (UpdateUtils.PER_INSTALLATION_PREFS_SUPPORTED) {
+ let testConfigFile = getUpdateDirFile(FILE_UPDATE_CONFIG_JSON);
+ let backupConfigFile = getUpdateDirFile(FILE_BACKUP_UPDATE_CONFIG_JSON);
+ try {
+ testConfigFile.remove(false);
+ backupConfigFile.moveTo(null, FILE_UPDATE_CONFIG_JSON);
+ } catch (ex) {}
+ } else {
+ for (const prefName in UpdateUtils.PER_INSTALLATION_PREFS) {
+ Services.prefs.clearUserPref(prefName);
+ }
+ }
+
+ UpdateUtils.PER_INSTALLATION_PREFS = originalPerInstallationPrefs;
+ UpdateUtils.initPerInstallPrefs();
+ });
+}
+
+let gObserverSeenCount = 0;
+let gExpectedObserverData;
+function observerCallback(subject, topic, data) {
+ gObserverSeenCount += 1;
+ Assert.equal(
+ data,
+ gExpectedObserverData,
+ `Expected observer to have data: "${gExpectedObserverData}". ` +
+ `It actually has data: "${data}"`
+ );
+}
+
+async function changeAndVerifyPref(
+ prefName,
+ expectedInitialValue,
+ newValue,
+ setterShouldThrow = false
+) {
+ let initialValue = await UpdateUtils.readUpdateConfigSetting(prefName);
+ Assert.strictEqual(
+ initialValue,
+ expectedInitialValue,
+ `Expected pref '${prefName}' to have an initial value of ` +
+ `${JSON.stringify(expectedInitialValue)}. Its actual initial value is ` +
+ `${JSON.stringify(initialValue)}`
+ );
+
+ let expectedObserverCount = 1;
+ if (initialValue == newValue || setterShouldThrow) {
+ expectedObserverCount = 0;
+ }
+
+ let observerTopic =
+ UpdateUtils.PER_INSTALLATION_PREFS[prefName].observerTopic;
+ gObserverSeenCount = 0;
+ gExpectedObserverData = newValue.toString();
+ Services.obs.addObserver(observerCallback, observerTopic);
+
+ let returned;
+ let exceptionThrown;
+ try {
+ returned = await UpdateUtils.writeUpdateConfigSetting(prefName, newValue);
+ } catch (e) {
+ exceptionThrown = e;
+ }
+ if (setterShouldThrow) {
+ Assert.ok(!!exceptionThrown, "Expected an exception to be thrown");
+ } else {
+ Assert.ok(
+ !exceptionThrown,
+ `Unexpected exception thrown by writeUpdateConfigSetting: ` +
+ `${exceptionThrown}`
+ );
+ }
+
+ if (!exceptionThrown) {
+ Assert.strictEqual(
+ returned,
+ newValue,
+ `Expected writeUpdateConfigSetting to return ` +
+ `${JSON.stringify(newValue)}. It actually returned ` +
+ `${JSON.stringify(returned)}`
+ );
+ }
+
+ let readValue = await UpdateUtils.readUpdateConfigSetting(prefName);
+ let expectedReadValue = exceptionThrown ? expectedInitialValue : newValue;
+ Assert.strictEqual(
+ readValue,
+ expectedReadValue,
+ `Expected pref '${prefName}' to be ${JSON.stringify(expectedReadValue)}.` +
+ ` It was actually ${JSON.stringify(readValue)}.`
+ );
+
+ Assert.equal(
+ gObserverSeenCount,
+ expectedObserverCount,
+ `Expected to see observer fire ${expectedObserverCount} times. It ` +
+ `actually fired ${gObserverSeenCount} times.`
+ );
+ Services.obs.removeObserver(observerCallback, observerTopic);
+}
+
+async function run_test() {
+ setupTestCommon(null);
+ standardInit();
+ await testSetup();
+
+ logTestInfo("Testing boolean pref and its observer");
+ let pref = "test.pref.boolean";
+ let defaultValue = UpdateUtils.PER_INSTALLATION_PREFS[pref].defaultValue;
+ await changeAndVerifyPref(pref, defaultValue, defaultValue);
+ await changeAndVerifyPref(pref, defaultValue, !defaultValue);
+ await changeAndVerifyPref(pref, !defaultValue, !defaultValue);
+ await changeAndVerifyPref(pref, !defaultValue, defaultValue);
+ await changeAndVerifyPref(pref, defaultValue, defaultValue);
+ await changeAndVerifyPref(pref, defaultValue, "true", true);
+ await changeAndVerifyPref(pref, defaultValue, 1, true);
+
+ logTestInfo("Testing string pref and its observer");
+ pref = "test.pref.string";
+ defaultValue = UpdateUtils.PER_INSTALLATION_PREFS[pref].defaultValue;
+ await changeAndVerifyPref(pref, defaultValue, defaultValue);
+ await changeAndVerifyPref(pref, defaultValue, defaultValue + "1");
+ await changeAndVerifyPref(pref, defaultValue + "1", "");
+ await changeAndVerifyPref(pref, "", 1, true);
+ await changeAndVerifyPref(pref, "", true, true);
+
+ logTestInfo("Testing integer pref and its observer");
+ pref = "test.pref.integer";
+ defaultValue = UpdateUtils.PER_INSTALLATION_PREFS[pref].defaultValue;
+ await changeAndVerifyPref(pref, defaultValue, defaultValue);
+ await changeAndVerifyPref(pref, defaultValue, defaultValue + 1);
+ await changeAndVerifyPref(pref, defaultValue + 1, 0);
+ await changeAndVerifyPref(pref, 0, "1", true);
+ await changeAndVerifyPref(pref, 0, true, true);
+
+ // Testing that the default pref branch works the same way that the default
+ // branch works for our per-profile prefs.
+ logTestInfo("Testing default branch behavior");
+ pref = "test.pref.integer";
+ let originalDefault = UpdateUtils.PER_INSTALLATION_PREFS[pref].defaultValue;
+ // Make sure the value is the default value, then change the default value
+ // and check that the effective value changes.
+ await UpdateUtils.writeUpdateConfigSetting(pref, originalDefault, {
+ setDefaultOnly: true,
+ });
+ await UpdateUtils.writeUpdateConfigSetting(pref, originalDefault);
+ await UpdateUtils.writeUpdateConfigSetting(pref, originalDefault + 1, {
+ setDefaultOnly: true,
+ });
+ Assert.strictEqual(
+ await UpdateUtils.readUpdateConfigSetting(pref),
+ originalDefault + 1,
+ `Expected that changing the default of a pref with no user value should ` +
+ `change the effective value`
+ );
+ // Now make the user value different from the default value and ensure that
+ // changing the default value does not affect the effective value
+ await UpdateUtils.writeUpdateConfigSetting(pref, originalDefault + 10);
+ await UpdateUtils.writeUpdateConfigSetting(pref, originalDefault + 20, {
+ setDefaultOnly: true,
+ });
+ Assert.strictEqual(
+ await UpdateUtils.readUpdateConfigSetting(pref),
+ originalDefault + 10,
+ `Expected that changing the default of a pref with a user value should ` +
+ `NOT change the effective value`
+ );
+
+ logTestInfo("Testing policy behavior");
+ pref = "test.pref.policy";
+ defaultValue = UpdateUtils.PER_INSTALLATION_PREFS[pref].defaultValue;
+ gPolicyFunctionResult = null;
+ await changeAndVerifyPref(pref, defaultValue, defaultValue + 1);
+ gPolicyFunctionResult = defaultValue + 10;
+ await changeAndVerifyPref(pref, gPolicyFunctionResult, 0, true);
+
+ doTestFinish();
+}
diff --git a/toolkit/mozapps/update/tests/unit_aus_update/remoteUpdateXML.js b/toolkit/mozapps/update/tests/unit_aus_update/remoteUpdateXML.js
new file mode 100644
index 0000000000..94081a01a5
--- /dev/null
+++ b/toolkit/mozapps/update/tests/unit_aus_update/remoteUpdateXML.js
@@ -0,0 +1,327 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+async function run_test() {
+ setupTestCommon();
+ debugDump("testing remote update xml attributes");
+ start_httpserver();
+ setUpdateURL(gURLData + gHTTPHandlerPath);
+ setUpdateChannel("test_channel");
+
+ debugDump("testing update xml not available");
+ await waitForUpdateCheck(false).then(aArgs => {
+ Assert.equal(
+ aArgs.updates[0].errorCode,
+ 1500,
+ "the update errorCode" + MSG_SHOULD_EQUAL
+ );
+ });
+
+ debugDump(
+ "testing one update available, the update's property values and " +
+ "the property values of the update's patches"
+ );
+ let patchProps = {
+ type: "complete",
+ url: "http://complete/",
+ size: "9856459",
+ };
+ let patches = getRemotePatchString(patchProps);
+ patchProps = { type: "partial", url: "http://partial/", size: "1316138" };
+ patches += getRemotePatchString(patchProps);
+ let updateProps = {
+ type: "minor",
+ name: "Minor Test",
+ displayVersion: "version 2.1a1pre",
+ appVersion: "2.1a1pre",
+ buildID: "20080811053724",
+ detailsURL: "http://details/",
+ promptWaitTime: "345600",
+ custom1: 'custom1_attr="custom1 value"',
+ custom2: 'custom2_attr="custom2 value"',
+ };
+ let updates = getRemoteUpdateString(updateProps, patches);
+ gResponseBody = getRemoteUpdatesXMLString(updates);
+ await waitForUpdateCheck(true, { updateCount: 1 }).then(aArgs => {
+ // XXXrstrong - not specifying a detailsURL will cause a leak due to
+ // bug 470244 and until this is fixed this will not test the value for
+ // detailsURL when it isn't specified in the update xml.
+
+ let bestUpdate = gAUS
+ .selectUpdate(aArgs.updates)
+ .QueryInterface(Ci.nsIWritablePropertyBag);
+ Assert.equal(
+ bestUpdate.type,
+ "minor",
+ "the update type attribute" + MSG_SHOULD_EQUAL
+ );
+ Assert.equal(
+ bestUpdate.name,
+ "Minor Test",
+ "the update name attribute" + MSG_SHOULD_EQUAL
+ );
+ Assert.equal(
+ bestUpdate.displayVersion,
+ "version 2.1a1pre",
+ "the update displayVersion attribute" + MSG_SHOULD_EQUAL
+ );
+ Assert.equal(
+ bestUpdate.appVersion,
+ "2.1a1pre",
+ "the update appVersion attribute" + MSG_SHOULD_EQUAL
+ );
+ Assert.equal(
+ bestUpdate.buildID,
+ "20080811053724",
+ "the update buildID attribute" + MSG_SHOULD_EQUAL
+ );
+ Assert.equal(
+ bestUpdate.detailsURL,
+ "http://details/",
+ "the update detailsURL attribute" + MSG_SHOULD_EQUAL
+ );
+ Assert.equal(
+ bestUpdate.promptWaitTime,
+ "345600",
+ "the update promptWaitTime attribute" + MSG_SHOULD_EQUAL
+ );
+ Assert.equal(
+ bestUpdate.serviceURL,
+ gURLData + gHTTPHandlerPath + "?force=1",
+ "the update serviceURL attribute" + MSG_SHOULD_EQUAL
+ );
+ Assert.equal(
+ bestUpdate.channel,
+ "test_channel",
+ "the update channel attribute" + MSG_SHOULD_EQUAL
+ );
+ Assert.ok(
+ !bestUpdate.isCompleteUpdate,
+ "the update isCompleteUpdate attribute" + MSG_SHOULD_EQUAL
+ );
+ Assert.ok(
+ !bestUpdate.isSecurityUpdate,
+ "the update isSecurityUpdate attribute" + MSG_SHOULD_EQUAL
+ );
+ // Check that installDate is within 10 seconds of the current date.
+ Assert.ok(
+ Date.now() - bestUpdate.installDate < 10000,
+ "the update installDate attribute should be within 10 seconds " +
+ "of the current time"
+ );
+ Assert.ok(
+ !bestUpdate.statusText,
+ "the update statusText attribute" + MSG_SHOULD_EQUAL
+ );
+ // nsIUpdate:state returns an empty string when no action has been performed
+ // on an available update
+ Assert.equal(
+ bestUpdate.state,
+ "",
+ "the update state attribute" + MSG_SHOULD_EQUAL
+ );
+ Assert.equal(
+ bestUpdate.errorCode,
+ 0,
+ "the update errorCode attribute" + MSG_SHOULD_EQUAL
+ );
+ Assert.equal(
+ bestUpdate.patchCount,
+ 2,
+ "the update patchCount attribute" + MSG_SHOULD_EQUAL
+ );
+ // XXX TODO - test nsIUpdate:serialize
+
+ Assert.equal(
+ bestUpdate.getProperty("custom1_attr"),
+ "custom1 value",
+ "the update custom1_attr property" + MSG_SHOULD_EQUAL
+ );
+ Assert.equal(
+ bestUpdate.getProperty("custom2_attr"),
+ "custom2 value",
+ "the update custom2_attr property" + MSG_SHOULD_EQUAL
+ );
+
+ let patch = bestUpdate.getPatchAt(0);
+ Assert.equal(
+ patch.type,
+ "complete",
+ "the update patch type attribute" + MSG_SHOULD_EQUAL
+ );
+ Assert.equal(
+ patch.URL,
+ "http://complete/",
+ "the update patch URL attribute" + MSG_SHOULD_EQUAL
+ );
+ Assert.equal(
+ patch.size,
+ 9856459,
+ "the update patch size attribute" + MSG_SHOULD_EQUAL
+ );
+ // The value for patch.state can be the string 'null' as a valid value. This
+ // is confusing if it returns null which is an invalid value since the test
+ // failure output will show a failure for null == null. To lessen the
+ // confusion first check that the typeof for patch.state is string.
+ Assert.equal(
+ typeof patch.state,
+ "string",
+ "the update patch state typeof value should equal |string|"
+ );
+ Assert.equal(
+ patch.state,
+ STATE_NONE,
+ "the update patch state attribute" + MSG_SHOULD_EQUAL
+ );
+ Assert.ok(
+ !patch.selected,
+ "the update patch selected attribute" + MSG_SHOULD_EQUAL
+ );
+ // XXX TODO - test nsIUpdatePatch:serialize
+
+ patch = bestUpdate.getPatchAt(1);
+ Assert.equal(
+ patch.type,
+ "partial",
+ "the update patch type attribute" + MSG_SHOULD_EQUAL
+ );
+ Assert.equal(
+ patch.URL,
+ "http://partial/",
+ "the update patch URL attribute" + MSG_SHOULD_EQUAL
+ );
+ Assert.equal(
+ patch.size,
+ 1316138,
+ "the update patch size attribute" + MSG_SHOULD_EQUAL
+ );
+ Assert.equal(
+ patch.state,
+ STATE_NONE,
+ "the update patch state attribute" + MSG_SHOULD_EQUAL
+ );
+ Assert.ok(
+ !patch.selected,
+ "the update patch selected attribute" + MSG_SHOULD_EQUAL
+ );
+ // XXX TODO - test nsIUpdatePatch:serialize
+ });
+
+ debugDump(
+ "testing an empty update xml that returns a root node name of " +
+ "parsererror"
+ );
+ gResponseBody = "<parsererror/>";
+ await waitForUpdateCheck(false).then(aArgs => {
+ Assert.equal(
+ aArgs.updates[0].errorCode,
+ 1200,
+ "the update errorCode" + MSG_SHOULD_EQUAL
+ );
+ });
+
+ debugDump("testing no updates available");
+ gResponseBody = getRemoteUpdatesXMLString("");
+ await waitForUpdateCheck(true, { updateCount: 0 });
+
+ debugDump("testing one update available with two patches");
+ patches = getRemotePatchString({});
+ patchProps = { type: "partial" };
+ patches += getRemotePatchString(patchProps);
+ updates = getRemoteUpdateString({}, patches);
+ gResponseBody = getRemoteUpdatesXMLString(updates);
+ await waitForUpdateCheck(true, { updateCount: 1 });
+
+ debugDump("testing three updates available each with two patches");
+ patches = getRemotePatchString({});
+ patchProps = { type: "partial" };
+ patches += getRemotePatchString(patchProps);
+ updates = getRemoteUpdateString({}, patches);
+ updates += updates + updates;
+ gResponseBody = getRemoteUpdatesXMLString(updates);
+ await waitForUpdateCheck(true, { updateCount: 3 });
+
+ debugDump(
+ "testing one update with complete and partial patches with size " +
+ "0 specified in the update xml"
+ );
+ patchProps = { size: "0" };
+ patches = getRemotePatchString(patchProps);
+ patchProps = { type: "partial", size: "0" };
+ patches += getRemotePatchString(patchProps);
+ updates = getRemoteUpdateString({}, patches);
+ gResponseBody = getRemoteUpdatesXMLString(updates);
+ await waitForUpdateCheck(true).then(aArgs => {
+ Assert.equal(
+ aArgs.updates.length,
+ 0,
+ "the update count" + MSG_SHOULD_EQUAL
+ );
+ });
+
+ debugDump(
+ "testing one update with complete patch with size 0 specified in " +
+ "the update xml"
+ );
+ patchProps = { size: "0" };
+ patches = getRemotePatchString(patchProps);
+ updates = getRemoteUpdateString({}, patches);
+ gResponseBody = getRemoteUpdatesXMLString(updates);
+ await waitForUpdateCheck(true, { updateCount: 0 });
+
+ debugDump(
+ "testing one update with partial patch with size 0 specified in " +
+ "the update xml"
+ );
+ patchProps = { type: "partial", size: "0" };
+ patches = getRemotePatchString(patchProps);
+ updates = getRemoteUpdateString({}, patches);
+ gResponseBody = getRemoteUpdatesXMLString(updates);
+ await waitForUpdateCheck(true, { updateCount: 0 });
+
+ debugDump(
+ "testing that updates for older versions of the application " +
+ "aren't selected"
+ );
+ patches = getRemotePatchString({});
+ patchProps = { type: "partial" };
+ patches += getRemotePatchString(patchProps);
+ updateProps = { appVersion: "1.0pre" };
+ updates = getRemoteUpdateString(updateProps, patches);
+ updateProps = { appVersion: "1.0a" };
+ updates += getRemoteUpdateString(updateProps, patches);
+ gResponseBody = getRemoteUpdatesXMLString(updates);
+ await waitForUpdateCheck(true, { updateCount: 2 }).then(aArgs => {
+ let bestUpdate = gAUS.selectUpdate(aArgs.updates);
+ Assert.ok(!bestUpdate, "there shouldn't be an update available");
+ });
+
+ debugDump(
+ "testing that updates for the current version of the application " +
+ "are selected"
+ );
+ patches = getRemotePatchString({});
+ patchProps = { type: "partial" };
+ patches += getRemotePatchString(patchProps);
+ updateProps = { appVersion: "1.0" };
+ updates = getRemoteUpdateString(updateProps, patches);
+ gResponseBody = getRemoteUpdatesXMLString(updates);
+ await waitForUpdateCheck(true, { updateCount: 1 }).then(aArgs => {
+ let bestUpdate = gAUS.selectUpdate(aArgs.updates);
+ Assert.ok(!!bestUpdate, "there should be one update available");
+ Assert.equal(
+ bestUpdate.appVersion,
+ "1.0",
+ "the update appVersion attribute" + MSG_SHOULD_EQUAL
+ );
+ Assert.equal(
+ bestUpdate.displayVersion,
+ "1.0",
+ "the update displayVersion attribute" + MSG_SHOULD_EQUAL
+ );
+ });
+
+ stop_httpserver(doTestFinish);
+}
diff --git a/toolkit/mozapps/update/tests/unit_aus_update/testConstants.js b/toolkit/mozapps/update/tests/unit_aus_update/testConstants.js
new file mode 100644
index 0000000000..ace6134a38
--- /dev/null
+++ b/toolkit/mozapps/update/tests/unit_aus_update/testConstants.js
@@ -0,0 +1,8 @@
+/* eslint-disable no-unused-vars */
+const REL_PATH_DATA = "";
+const URL_HOST = "http://127.0.0.1:8888";
+const URL_PATH_UPDATE_XML = "/" + REL_PATH_DATA + "app_update.sjs";
+const URL_HTTP_UPDATE_SJS = URL_HOST + URL_PATH_UPDATE_XML;
+const CONTINUE_CHECK = "continueCheck";
+const CONTINUE_DOWNLOAD = "continueDownload";
+const CONTINUE_STAGING = "continueStaging";
diff --git a/toolkit/mozapps/update/tests/unit_aus_update/updateAutoPrefMigrate.js b/toolkit/mozapps/update/tests/unit_aus_update/updateAutoPrefMigrate.js
new file mode 100644
index 0000000000..8f62b9458b
--- /dev/null
+++ b/toolkit/mozapps/update/tests/unit_aus_update/updateAutoPrefMigrate.js
@@ -0,0 +1,74 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+async function verifyPref(expectedValue) {
+ let configValue = await UpdateUtils.getAppUpdateAutoEnabled();
+ Assert.equal(
+ configValue,
+ expectedValue,
+ "Value returned by getAppUpdateAutoEnabled should have " +
+ "matched the expected value"
+ );
+}
+
+async function run_test() {
+ setupTestCommon(null);
+ standardInit();
+
+ let configFile = getUpdateDirFile(FILE_UPDATE_CONFIG_JSON);
+
+ // Test that, if there is no value to migrate, the default value is set.
+ Services.prefs.setBoolPref("app.update.auto.migrated", false);
+ Services.prefs.clearUserPref("app.update.auto");
+ Assert.ok(!configFile.exists(), "Config file should not exist yet");
+ await verifyPref(
+ UpdateUtils.PER_INSTALLATION_PREFS["app.update.auto"].defaultValue
+ );
+
+ debugDump("about to remove config file");
+ configFile.remove(false);
+
+ // Test migration of a |false| value
+ Services.prefs.setBoolPref("app.update.auto.migrated", false);
+ Services.prefs.setBoolPref("app.update.auto", false);
+ Assert.ok(!configFile.exists(), "Config file should have been removed");
+ await verifyPref(false);
+
+ // Test that migration doesn't happen twice
+ Services.prefs.setBoolPref("app.update.auto", true);
+ await verifyPref(false);
+
+ // If the file is deleted after migration, the default value should be
+ // returned, regardless of the pref value.
+ debugDump("about to remove config file");
+ configFile.remove(false);
+ Assert.ok(!configFile.exists(), "Config file should have been removed");
+ let configValue = await UpdateUtils.getAppUpdateAutoEnabled();
+ Assert.equal(
+ configValue,
+ true,
+ "getAppUpdateAutoEnabled should have returned the default value (true)"
+ );
+
+ // Setting a new value should cause the value to get written out again
+ await UpdateUtils.setAppUpdateAutoEnabled(false);
+ await verifyPref(false);
+
+ // Test migration of a |true| value
+ Services.prefs.setBoolPref("app.update.auto.migrated", false);
+ Services.prefs.setBoolPref("app.update.auto", true);
+ configFile.remove(false);
+ Assert.ok(
+ !configFile.exists(),
+ "App update config file should have been removed"
+ );
+ await verifyPref(true);
+
+ // Test that setting app.update.auto without migrating also works
+ await UpdateUtils.setAppUpdateAutoEnabled(false);
+ await verifyPref(false);
+
+ doTestFinish();
+}
diff --git a/toolkit/mozapps/update/tests/unit_aus_update/updateCheckCombine.js b/toolkit/mozapps/update/tests/unit_aus_update/updateCheckCombine.js
new file mode 100644
index 0000000000..1b2c9ef78c
--- /dev/null
+++ b/toolkit/mozapps/update/tests/unit_aus_update/updateCheckCombine.js
@@ -0,0 +1,38 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+"use strict";
+
+/**
+ * This test checks that multiple foreground update checks are combined into
+ * a single web request.
+ */
+
+add_task(async function setup() {
+ setupTestCommon();
+ start_httpserver();
+ setUpdateURL(gURLData + gHTTPHandlerPath);
+ setUpdateChannel("test_channel");
+
+ let patches = getRemotePatchString({});
+ let updateString = getRemoteUpdateString({}, patches);
+ gResponseBody = getRemoteUpdatesXMLString(updateString);
+});
+
+add_task(async function testUpdateCheckCombine() {
+ gUpdateCheckCount = 0;
+ let check1 = gUpdateChecker.checkForUpdates(gUpdateChecker.FOREGROUND_CHECK);
+ let check2 = gUpdateChecker.checkForUpdates(gUpdateChecker.FOREGROUND_CHECK);
+
+ let result1 = await check1.result;
+ let result2 = await check2.result;
+ Assert.ok(result1.succeeded, "Check 1 should have succeeded");
+ Assert.ok(result2.succeeded, "Check 2 should have succeeded");
+ Assert.equal(gUpdateCheckCount, 1, "Should only have made a single request");
+});
+
+add_task(async function finish() {
+ stop_httpserver(doTestFinish);
+});
diff --git a/toolkit/mozapps/update/tests/unit_aus_update/updateDirectoryMigrate.js b/toolkit/mozapps/update/tests/unit_aus_update/updateDirectoryMigrate.js
new file mode 100644
index 0000000000..6c566d76fc
--- /dev/null
+++ b/toolkit/mozapps/update/tests/unit_aus_update/updateDirectoryMigrate.js
@@ -0,0 +1,246 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+/**
+ * Gets the root directory for the old (unmigrated) updates directory.
+ *
+ * @return nsIFile for the updates root directory.
+ */
+function getOldUpdatesRootDir() {
+ return Services.dirsvc.get(XRE_OLD_UPDATE_ROOT_DIR, Ci.nsIFile);
+}
+
+/**
+ * Gets the old (unmigrated) updates directory.
+ *
+ * @return nsIFile for the updates directory.
+ */
+function getOldUpdatesDir() {
+ let dir = getOldUpdatesRootDir();
+ dir.append(DIR_UPDATES);
+ return dir;
+}
+
+/**
+ * Gets the directory for update patches in the old (unmigrated) updates
+ * directory.
+ *
+ * @return nsIFile for the updates directory.
+ */
+function getOldUpdatesPatchDir() {
+ let dir = getOldUpdatesDir();
+ dir.append(DIR_PATCH);
+ return dir;
+}
+
+/**
+ * Returns either the active or regular update database XML file in the old
+ * (unmigrated) updates directory
+ *
+ * @param isActiveUpdate
+ * If true this will return the active-update.xml otherwise it will
+ * return the updates.xml file.
+ */
+function getOldUpdatesXMLFile(aIsActiveUpdate) {
+ let file = getOldUpdatesRootDir();
+ file.append(aIsActiveUpdate ? FILE_ACTIVE_UPDATE_XML : FILE_UPDATES_XML);
+ return file;
+}
+
+/**
+ * Writes the updates specified to either the active-update.xml or the
+ * updates.xml in the old (unmigrated) update directory
+ *
+ * @param aContent
+ * The updates represented as a string to write to the XML file.
+ * @param isActiveUpdate
+ * If true this will write to the active-update.xml otherwise it will
+ * write to the updates.xml file.
+ */
+function writeUpdatesToOldXMLFile(aContent, aIsActiveUpdate) {
+ writeFile(getOldUpdatesXMLFile(aIsActiveUpdate), aContent);
+}
+
+/**
+ * Writes the given update operation/state to a file in the old (unmigrated)
+ * patch directory, indicating to the patching system what operations need
+ * to be performed.
+ *
+ * @param aStatus
+ * The status value to write.
+ */
+function writeOldStatusFile(aStatus) {
+ let file = getOldUpdatesPatchDir();
+ file.append(FILE_UPDATE_STATUS);
+ writeFile(file, aStatus + "\n");
+}
+
+/**
+ * Writes the given data to the config file in the old (unmigrated)
+ * patch directory.
+ *
+ * @param aData
+ * The config data to write.
+ */
+function writeOldConfigFile(aData) {
+ let file = getOldUpdatesRootDir();
+ file.append(FILE_UPDATE_CONFIG_JSON);
+ writeFile(file, aData);
+}
+
+/**
+ * Gets the specified update log from the old (unmigrated) update directory
+ *
+ * @param aLogLeafName
+ * The leaf name of the log to get.
+ * @return nsIFile for the update log.
+ */
+function getOldUpdateLog(aLogLeafName) {
+ let updateLog = getOldUpdatesDir();
+ if (aLogLeafName == FILE_UPDATE_LOG) {
+ updateLog.append(DIR_PATCH);
+ }
+ updateLog.append(aLogLeafName);
+ return updateLog;
+}
+
+async function run_test() {
+ setupTestCommon(null);
+
+ debugDump(
+ "testing that the update directory is migrated after a successful update"
+ );
+
+ Services.prefs.setIntPref(PREF_APP_UPDATE_CANCELATIONS, 5);
+
+ let patchProps = { state: STATE_PENDING };
+ let patches = getLocalPatchString(patchProps);
+ let updates = getLocalUpdateString({}, patches);
+ writeUpdatesToOldXMLFile(getLocalUpdatesXMLString(updates), true);
+ writeOldStatusFile(STATE_SUCCEEDED);
+ writeOldConfigFile('{"app.update.auto":false}');
+
+ let log = getOldUpdateLog(FILE_UPDATE_LOG);
+ writeFile(log, "Last Update Log");
+
+ let oldUninstallPingFile = getOldUpdatesRootDir();
+ const hash = oldUninstallPingFile.leafName;
+ const uninstallPingFilename = `uninstall_ping_${hash}_98537294-d37b-4b8b-a4e9-ab417a5d7a87.json`;
+ oldUninstallPingFile = oldUninstallPingFile.parent.parent;
+ oldUninstallPingFile.append(uninstallPingFilename);
+ const uninstallPingContents = "arbitrary uninstall ping file contents";
+ writeFile(oldUninstallPingFile, uninstallPingContents);
+
+ let oldBackgroundUpdateLog1File = getOldUpdatesRootDir();
+ const oldBackgroundUpdateLog1Filename = "backgroundupdate.moz_log";
+ oldBackgroundUpdateLog1File.append(oldBackgroundUpdateLog1Filename);
+ const oldBackgroundUpdateLog1Contents = "arbitrary log 1 contents";
+ writeFile(oldBackgroundUpdateLog1File, oldBackgroundUpdateLog1Contents);
+
+ let oldBackgroundUpdateLog2File = getOldUpdatesRootDir();
+ const oldBackgroundUpdateLog2Filename = "backgroundupdate.child-1.moz_log";
+ oldBackgroundUpdateLog2File.append(oldBackgroundUpdateLog2Filename);
+ const oldBackgroundUpdateLog2Contents = "arbitrary log 2 contents";
+ writeFile(oldBackgroundUpdateLog2File, oldBackgroundUpdateLog2Contents);
+
+ const pendingPingRelativePath =
+ "backgroundupdate\\datareporting\\glean\\pending_pings\\" +
+ "01234567-89ab-cdef-fedc-0123456789ab";
+ let oldPendingPingFile = getOldUpdatesRootDir();
+ oldPendingPingFile.appendRelativePath(pendingPingRelativePath);
+ const pendingPingContents = "arbitrary pending ping file contents";
+ writeFile(oldPendingPingFile, pendingPingContents);
+
+ standardInit();
+
+ Assert.ok(
+ !gUpdateManager.downloadingUpdate,
+ "there should not be a downloading update"
+ );
+ Assert.ok(!gUpdateManager.readyUpdate, "there should not be a ready update");
+ Assert.equal(
+ gUpdateManager.getUpdateCount(),
+ 1,
+ "the update manager update count" + MSG_SHOULD_EQUAL
+ );
+ await waitForUpdateXMLFiles();
+
+ let cancelations = Services.prefs.getIntPref(PREF_APP_UPDATE_CANCELATIONS, 0);
+ Assert.equal(
+ cancelations,
+ 0,
+ "the " + PREF_APP_UPDATE_CANCELATIONS + " preference " + MSG_SHOULD_EQUAL
+ );
+
+ let oldDir = getOldUpdatesRootDir();
+ let newDir = getUpdateDirFile();
+ if (oldDir.path != newDir.path) {
+ Assert.ok(
+ !oldDir.exists(),
+ "Old update directory should have been deleted after migration"
+ );
+ }
+
+ log = getUpdateDirFile(FILE_UPDATE_LOG);
+ Assert.ok(!log.exists(), MSG_SHOULD_NOT_EXIST);
+
+ log = getUpdateDirFile(FILE_LAST_UPDATE_LOG);
+ Assert.ok(log.exists(), MSG_SHOULD_EXIST);
+ Assert.equal(
+ readFile(log),
+ "Last Update Log",
+ "the last update log contents" + MSG_SHOULD_EQUAL
+ );
+
+ log = getUpdateDirFile(FILE_BACKUP_UPDATE_LOG);
+ Assert.ok(!log.exists(), MSG_SHOULD_NOT_EXIST);
+
+ let dir = getUpdateDirFile(DIR_PATCH);
+ Assert.ok(dir.exists(), MSG_SHOULD_EXIST);
+
+ Assert.equal(
+ await UpdateUtils.getAppUpdateAutoEnabled(),
+ false,
+ "Automatic update download setting should have been migrated."
+ );
+
+ let newUninstallPing = newDir.parent.parent;
+ newUninstallPing.append(uninstallPingFilename);
+ Assert.ok(newUninstallPing.exists(), MSG_SHOULD_EXIST);
+ Assert.equal(
+ readFile(newUninstallPing),
+ uninstallPingContents,
+ "the uninstall ping contents" + MSG_SHOULD_EQUAL
+ );
+
+ let newBackgroundUpdateLog1File = newDir.clone();
+ newBackgroundUpdateLog1File.append(oldBackgroundUpdateLog1Filename);
+ Assert.ok(newBackgroundUpdateLog1File.exists(), MSG_SHOULD_EXIST);
+ Assert.equal(
+ readFile(newBackgroundUpdateLog1File),
+ oldBackgroundUpdateLog1Contents,
+ "background log file 1 contents" + MSG_SHOULD_EQUAL
+ );
+
+ let newBackgroundUpdateLog2File = newDir.clone();
+ newBackgroundUpdateLog2File.append(oldBackgroundUpdateLog2Filename);
+ Assert.ok(newBackgroundUpdateLog2File.exists(), MSG_SHOULD_EXIST);
+ Assert.equal(
+ readFile(newBackgroundUpdateLog2File),
+ oldBackgroundUpdateLog2Contents,
+ "background log file 2 contents" + MSG_SHOULD_EQUAL
+ );
+
+ let newPendingPing = newDir.clone();
+ newPendingPing.appendRelativePath(pendingPingRelativePath);
+ Assert.ok(newPendingPing.exists(), MSG_SHOULD_EXIST);
+ Assert.equal(
+ readFile(newPendingPing),
+ pendingPingContents,
+ "the pending ping contents" + MSG_SHOULD_EQUAL
+ );
+
+ doTestFinish();
+}
diff --git a/toolkit/mozapps/update/tests/unit_aus_update/updateManagerXML.js b/toolkit/mozapps/update/tests/unit_aus_update/updateManagerXML.js
new file mode 100644
index 0000000000..f15e51827e
--- /dev/null
+++ b/toolkit/mozapps/update/tests/unit_aus_update/updateManagerXML.js
@@ -0,0 +1,593 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+function run_test() {
+ setupTestCommon();
+
+ debugDump(
+ "testing addition of a successful update to " +
+ FILE_UPDATES_XML +
+ " and verification of update properties including the format " +
+ "prior to bug 530872"
+ );
+
+ setUpdateChannel("test_channel");
+
+ let patchProps = {
+ type: "partial",
+ url: "http://partial/",
+ size: "86",
+ selected: "true",
+ state: STATE_PENDING,
+ custom1: 'custom1_attr="custom1 patch value"',
+ custom2: 'custom2_attr="custom2 patch value"',
+ };
+ let patches = getLocalPatchString(patchProps);
+ let updateProps = {
+ type: "major",
+ name: "New",
+ displayVersion: "version 4",
+ appVersion: "4.0",
+ buildID: "20070811053724",
+ detailsURL: "http://details1/",
+ serviceURL: "http://service1/",
+ installDate: "1238441300314",
+ statusText: "test status text",
+ isCompleteUpdate: "false",
+ channel: "test_channel",
+ foregroundDownload: "true",
+ promptWaitTime: "345600",
+ previousAppVersion: "3.0",
+ custom1: 'custom1_attr="custom1 value"',
+ custom2: 'custom2_attr="custom2 value"',
+ };
+ let updates = getLocalUpdateString(updateProps, patches);
+ writeUpdatesToXMLFile(getLocalUpdatesXMLString(updates), true);
+ writeStatusFile(STATE_SUCCEEDED);
+
+ patchProps = {
+ type: "complete",
+ url: "http://complete/",
+ size: "75",
+ selected: "true",
+ state: STATE_FAILED,
+ custom1: 'custom3_attr="custom3 patch value"',
+ custom2: 'custom4_attr="custom4 patch value"',
+ };
+ patches = getLocalPatchString(patchProps);
+ updateProps = {
+ type: "minor",
+ name: "Existing",
+ appVersion: "3.0",
+ detailsURL: "http://details2/",
+ serviceURL: "http://service2/",
+ statusText: getString("patchApplyFailure"),
+ isCompleteUpdate: "true",
+ channel: "test_channel",
+ foregroundDownload: "false",
+ promptWaitTime: "691200",
+ custom1: 'custom3_attr="custom3 value"',
+ custom2: 'custom4_attr="custom4 value"',
+ };
+ updates = getLocalUpdateString(updateProps, patches);
+ writeUpdatesToXMLFile(getLocalUpdatesXMLString(updates), false);
+
+ standardInit();
+
+ Assert.ok(
+ !gUpdateManager.downloadingUpdate,
+ "there should not be a downloading update"
+ );
+ Assert.ok(!gUpdateManager.readyUpdate, "there should not be a ready update");
+ Assert.equal(
+ gUpdateManager.getUpdateCount(),
+ 2,
+ "the update manager updateCount attribute" + MSG_SHOULD_EQUAL
+ );
+
+ debugDump("checking the first update properties");
+ let update = gUpdateManager
+ .getUpdateAt(0)
+ .QueryInterface(Ci.nsIWritablePropertyBag);
+ Assert.equal(
+ update.state,
+ STATE_SUCCEEDED,
+ "the update state attribute" + MSG_SHOULD_EQUAL
+ );
+ Assert.equal(
+ update.type,
+ "major",
+ "the update type attribute" + MSG_SHOULD_EQUAL
+ );
+ Assert.equal(
+ update.name,
+ "New",
+ "the update name attribute" + MSG_SHOULD_EQUAL
+ );
+ Assert.equal(
+ update.displayVersion,
+ "version 4",
+ "the update displayVersion attribute" + MSG_SHOULD_EQUAL
+ );
+ Assert.equal(
+ update.appVersion,
+ "4.0",
+ "the update appVersion attribute" + MSG_SHOULD_EQUAL
+ );
+ Assert.equal(
+ update.buildID,
+ "20070811053724",
+ "the update buildID attribute" + MSG_SHOULD_EQUAL
+ );
+ Assert.equal(
+ update.detailsURL,
+ "http://details1/",
+ "the update detailsURL attribute" + MSG_SHOULD_EQUAL
+ );
+ Assert.equal(
+ update.serviceURL,
+ "http://service1/",
+ "the update serviceURL attribute" + MSG_SHOULD_EQUAL
+ );
+ Assert.equal(
+ update.installDate,
+ "1238441300314",
+ "the update installDate attribute" + MSG_SHOULD_EQUAL
+ );
+ // statusText is updated
+ Assert.equal(
+ update.statusText,
+ getString("installSuccess"),
+ "the update statusText attribute" + MSG_SHOULD_EQUAL
+ );
+ Assert.ok(
+ !update.isCompleteUpdate,
+ "the update isCompleteUpdate attribute" + MSG_SHOULD_EQUAL
+ );
+ Assert.equal(
+ update.channel,
+ "test_channel",
+ "the update channel attribute" + MSG_SHOULD_EQUAL
+ );
+ Assert.equal(
+ update.promptWaitTime,
+ "345600",
+ "the update promptWaitTime attribute" + MSG_SHOULD_EQUAL
+ );
+ Assert.equal(
+ update.previousAppVersion,
+ "3.0",
+ "the update previousAppVersion attribute" + MSG_SHOULD_EQUAL
+ );
+ // Custom attributes
+ Assert.equal(
+ update.getProperty("custom1_attr"),
+ "custom1 value",
+ "the update custom1_attr property" + MSG_SHOULD_EQUAL
+ );
+ Assert.equal(
+ update.getProperty("custom2_attr"),
+ "custom2 value",
+ "the update custom2_attr property" + MSG_SHOULD_EQUAL
+ );
+ // nsIPropertyBag enumerator
+ debugDump("checking the first update enumerator");
+ Assert.ok(
+ update.enumerator instanceof Ci.nsISimpleEnumerator,
+ "update enumerator should be an instance of nsISimpleEnumerator"
+ );
+ let results = Array.from(update.enumerator);
+ Assert.equal(
+ results.length,
+ 3,
+ "the length of the array created from the update enumerator" +
+ MSG_SHOULD_EQUAL
+ );
+ Assert.ok(
+ results.every(prop => prop instanceof Ci.nsIProperty),
+ "the objects in the array created from the update enumerator " +
+ "should all be an instance of nsIProperty"
+ );
+ Assert.equal(
+ results[0].name,
+ "custom1_attr",
+ "the first property name" + MSG_SHOULD_EQUAL
+ );
+ Assert.equal(
+ results[0].value,
+ "custom1 value",
+ "the first property value" + MSG_SHOULD_EQUAL
+ );
+ Assert.equal(
+ results[1].name,
+ "custom2_attr",
+ "the second property name" + MSG_SHOULD_EQUAL
+ );
+ Assert.equal(
+ results[1].value,
+ "custom2 value",
+ "the second property value" + MSG_SHOULD_EQUAL
+ );
+ Assert.equal(
+ results[2].name,
+ "foregroundDownload",
+ "the second property name" + MSG_SHOULD_EQUAL
+ );
+ Assert.equal(
+ results[2].value,
+ "true",
+ "the third property value" + MSG_SHOULD_EQUAL
+ );
+
+ debugDump("checking the first update patch properties");
+ let patch = update.selectedPatch.QueryInterface(Ci.nsIWritablePropertyBag);
+ Assert.equal(
+ patch.type,
+ "partial",
+ "the update patch type attribute" + MSG_SHOULD_EQUAL
+ );
+ Assert.equal(
+ patch.URL,
+ "http://partial/",
+ "the update patch URL attribute" + MSG_SHOULD_EQUAL
+ );
+ Assert.equal(
+ patch.size,
+ "86",
+ "the update patch size attribute" + MSG_SHOULD_EQUAL
+ );
+ Assert.ok(
+ !!patch.selected,
+ "the update patch selected attribute" + MSG_SHOULD_EQUAL
+ );
+ Assert.equal(
+ patch.state,
+ STATE_SUCCEEDED,
+ "the update patch state attribute" + MSG_SHOULD_EQUAL
+ );
+ Assert.equal(
+ patch.getProperty("custom1_attr"),
+ "custom1 patch value",
+ "the update patch custom1_attr property" + MSG_SHOULD_EQUAL
+ );
+ Assert.equal(
+ patch.getProperty("custom2_attr"),
+ "custom2 patch value",
+ "the update patch custom2_attr property" + MSG_SHOULD_EQUAL
+ );
+ // nsIPropertyBag enumerator
+ debugDump("checking the first update patch enumerator");
+ Assert.ok(
+ patch.enumerator instanceof Ci.nsISimpleEnumerator,
+ "patch enumerator should be an instance of nsISimpleEnumerator"
+ );
+ results = Array.from(patch.enumerator);
+ Assert.equal(
+ results.length,
+ 2,
+ "the length of the array created from the patch enumerator" +
+ MSG_SHOULD_EQUAL
+ );
+ Assert.ok(
+ results.every(prop => prop instanceof Ci.nsIProperty),
+ "the objects in the array created from the patch enumerator " +
+ "should all be an instance of nsIProperty"
+ );
+ Assert.equal(
+ results[0].name,
+ "custom1_attr",
+ "the first property name" + MSG_SHOULD_EQUAL
+ );
+ Assert.equal(
+ results[0].value,
+ "custom1 patch value",
+ "the first property value" + MSG_SHOULD_EQUAL
+ );
+ Assert.equal(
+ results[1].name,
+ "custom2_attr",
+ "the second property name" + MSG_SHOULD_EQUAL
+ );
+ Assert.equal(
+ results[1].value,
+ "custom2 patch value",
+ "the second property value" + MSG_SHOULD_EQUAL
+ );
+
+ debugDump("checking the second update properties");
+ update = gUpdateManager
+ .getUpdateAt(1)
+ .QueryInterface(Ci.nsIWritablePropertyBag);
+ Assert.equal(
+ update.state,
+ STATE_FAILED,
+ "the update state attribute" + MSG_SHOULD_EQUAL
+ );
+ Assert.equal(
+ update.name,
+ "Existing",
+ "the update name attribute" + MSG_SHOULD_EQUAL
+ );
+ Assert.equal(
+ update.type,
+ "minor",
+ "the update type attribute" + MSG_SHOULD_EQUAL
+ );
+ Assert.equal(
+ update.displayVersion,
+ "3.0",
+ "the update displayVersion attribute" + MSG_SHOULD_EQUAL
+ );
+ Assert.equal(
+ update.appVersion,
+ "3.0",
+ "the update appVersion attribute" + MSG_SHOULD_EQUAL
+ );
+ Assert.equal(
+ update.detailsURL,
+ "http://details2/",
+ "the update detailsURL attribute" + MSG_SHOULD_EQUAL
+ );
+ Assert.equal(
+ update.serviceURL,
+ "http://service2/",
+ "the update serviceURL attribute" + MSG_SHOULD_EQUAL
+ );
+ Assert.equal(
+ update.installDate,
+ "1238441400314",
+ "the update installDate attribute" + MSG_SHOULD_EQUAL
+ );
+ Assert.equal(
+ update.statusText,
+ getString("patchApplyFailure"),
+ "the update statusText attribute" + MSG_SHOULD_EQUAL
+ );
+ Assert.equal(
+ update.buildID,
+ "20080811053724",
+ "the update buildID attribute" + MSG_SHOULD_EQUAL
+ );
+ Assert.ok(
+ !!update.isCompleteUpdate,
+ "the update isCompleteUpdate attribute" + MSG_SHOULD_EQUAL
+ );
+ Assert.equal(
+ update.channel,
+ "test_channel",
+ "the update channel attribute" + MSG_SHOULD_EQUAL
+ );
+ Assert.equal(
+ update.promptWaitTime,
+ "691200",
+ "the update promptWaitTime attribute" + MSG_SHOULD_EQUAL
+ );
+ Assert.equal(
+ update.previousAppVersion,
+ "1.0",
+ "the update previousAppVersion attribute" + MSG_SHOULD_EQUAL
+ );
+ // Custom attributes
+ Assert.equal(
+ update.getProperty("custom3_attr"),
+ "custom3 value",
+ "the update custom3_attr property" + MSG_SHOULD_EQUAL
+ );
+ Assert.equal(
+ update.getProperty("custom4_attr"),
+ "custom4 value",
+ "the update custom4_attr property" + MSG_SHOULD_EQUAL
+ );
+ // nsIPropertyBag enumerator
+ debugDump("checking the second update enumerator");
+ Assert.ok(
+ update.enumerator instanceof Ci.nsISimpleEnumerator,
+ "update enumerator should be an instance of nsISimpleEnumerator"
+ );
+ results = Array.from(update.enumerator);
+ Assert.equal(
+ results.length,
+ 3,
+ "the length of the array created from the update enumerator" +
+ MSG_SHOULD_EQUAL
+ );
+ Assert.ok(
+ results.every(prop => prop instanceof Ci.nsIProperty),
+ "the objects in the array created from the update enumerator " +
+ "should all be an instance of nsIProperty"
+ );
+ Assert.equal(
+ results[0].name,
+ "custom3_attr",
+ "the first property name" + MSG_SHOULD_EQUAL
+ );
+ Assert.equal(
+ results[0].value,
+ "custom3 value",
+ "the first property value" + MSG_SHOULD_EQUAL
+ );
+ Assert.equal(
+ results[1].name,
+ "custom4_attr",
+ "the second property name" + MSG_SHOULD_EQUAL
+ );
+ Assert.equal(
+ results[1].value,
+ "custom4 value",
+ "the second property value" + MSG_SHOULD_EQUAL
+ );
+ Assert.equal(
+ results[2].name,
+ "foregroundDownload",
+ "the third property name" + MSG_SHOULD_EQUAL
+ );
+ Assert.equal(
+ results[2].value,
+ "false",
+ "the third property value" + MSG_SHOULD_EQUAL
+ );
+
+ debugDump("checking the second update patch properties");
+ patch = update.selectedPatch.QueryInterface(Ci.nsIWritablePropertyBag);
+ Assert.equal(
+ patch.type,
+ "complete",
+ "the update patch type attribute" + MSG_SHOULD_EQUAL
+ );
+ Assert.equal(
+ patch.URL,
+ "http://complete/",
+ "the update patch URL attribute" + MSG_SHOULD_EQUAL
+ );
+ Assert.equal(
+ patch.size,
+ "75",
+ "the update patch size attribute" + MSG_SHOULD_EQUAL
+ );
+ Assert.ok(
+ !!patch.selected,
+ "the update patch selected attribute" + MSG_SHOULD_EQUAL
+ );
+ Assert.equal(
+ patch.state,
+ STATE_FAILED,
+ "the update patch state attribute" + MSG_SHOULD_EQUAL
+ );
+ Assert.equal(
+ patch.getProperty("custom3_attr"),
+ "custom3 patch value",
+ "the update patch custom3_attr property" + MSG_SHOULD_EQUAL
+ );
+ Assert.equal(
+ patch.getProperty("custom4_attr"),
+ "custom4 patch value",
+ "the update patch custom4_attr property" + MSG_SHOULD_EQUAL
+ );
+ // nsIPropertyBag enumerator
+ debugDump("checking the second update patch enumerator");
+ Assert.ok(
+ patch.enumerator instanceof Ci.nsISimpleEnumerator,
+ "patch enumerator should be an instance of nsISimpleEnumerator"
+ );
+ results = Array.from(patch.enumerator);
+ Assert.equal(
+ results.length,
+ 2,
+ "the length of the array created from the patch enumerator" +
+ MSG_SHOULD_EQUAL
+ );
+ Assert.ok(
+ results.every(prop => prop instanceof Ci.nsIProperty),
+ "the objects in the array created from the patch enumerator " +
+ "should all be an instance of nsIProperty"
+ );
+ Assert.equal(
+ results[0].name,
+ "custom3_attr",
+ "the first property name" + MSG_SHOULD_EQUAL
+ );
+ Assert.equal(
+ results[0].value,
+ "custom3 patch value",
+ "the first property value" + MSG_SHOULD_EQUAL
+ );
+ Assert.equal(
+ results[1].name,
+ "custom4_attr",
+ "the second property name" + MSG_SHOULD_EQUAL
+ );
+ Assert.equal(
+ results[1].value,
+ "custom4 patch value",
+ "the second property value" + MSG_SHOULD_EQUAL
+ );
+
+ let attrNames = [
+ "appVersion",
+ "buildID",
+ "channel",
+ "detailsURL",
+ "displayVersion",
+ "elevationFailure",
+ "errorCode",
+ "installDate",
+ "isCompleteUpdate",
+ "name",
+ "previousAppVersion",
+ "promptWaitTime",
+ "serviceURL",
+ "state",
+ "statusText",
+ "type",
+ "unsupported",
+ ];
+ checkIllegalProperties(update, attrNames);
+
+ attrNames = [
+ "errorCode",
+ "finalURL",
+ "selected",
+ "size",
+ "state",
+ "type",
+ "URL",
+ ];
+ checkIllegalProperties(patch, attrNames);
+
+ executeSoon(doTestFinish);
+}
+
+function checkIllegalProperties(object, propertyNames) {
+ let objectName =
+ object instanceof Ci.nsIUpdate ? "nsIUpdate" : "nsIUpdatePatch";
+ propertyNames.forEach(function (name) {
+ // Check that calling getProperty, setProperty, and deleteProperty on an
+ // nsIUpdate attribute throws NS_ERROR_ILLEGAL_VALUE
+ let result = 0;
+ try {
+ object.getProperty(name);
+ } catch (e) {
+ result = e.result;
+ }
+ Assert.equal(
+ result,
+ Cr.NS_ERROR_ILLEGAL_VALUE,
+ "calling getProperty using an " +
+ objectName +
+ " attribute " +
+ "name should throw NS_ERROR_ILLEGAL_VALUE"
+ );
+
+ result = 0;
+ try {
+ object.setProperty(name, "value");
+ } catch (e) {
+ result = e.result;
+ }
+ Assert.equal(
+ result,
+ Cr.NS_ERROR_ILLEGAL_VALUE,
+ "calling setProperty using an " +
+ objectName +
+ " attribute " +
+ "name should throw NS_ERROR_ILLEGAL_VALUE"
+ );
+
+ result = 0;
+ try {
+ object.deleteProperty(name);
+ } catch (e) {
+ result = e.result;
+ }
+ Assert.equal(
+ result,
+ Cr.NS_ERROR_ILLEGAL_VALUE,
+ "calling deleteProperty using an " +
+ objectName +
+ " attribute " +
+ "name should throw NS_ERROR_ILLEGAL_VALUE"
+ );
+ });
+}
diff --git a/toolkit/mozapps/update/tests/unit_aus_update/updateSyncManager.js b/toolkit/mozapps/update/tests/unit_aus_update/updateSyncManager.js
new file mode 100644
index 0000000000..9123f9e1e3
--- /dev/null
+++ b/toolkit/mozapps/update/tests/unit_aus_update/updateSyncManager.js
@@ -0,0 +1,105 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+// This test verifies that the update sync manager is working correctly by
+// a) making sure we're the only one that's opened it to begin with, and then
+// b) starting a second copy of the same binary and making sure we can tell we
+// are no longer the only one that's opened it.
+
+const { Subprocess } = ChromeUtils.importESModule(
+ "resource://gre/modules/Subprocess.sys.mjs"
+);
+
+// Save off the real GRE directory and binary path before we register our
+// mock directory service which overrides them both.
+const thisBinary = Services.dirsvc.get("XREExeF", Ci.nsIFile);
+const greDir = Services.dirsvc.get("GreD", Ci.nsIFile);
+
+add_task(async function () {
+ setupTestCommon();
+
+ // First check that we believe we exclusively hold the lock.
+ let syncManager = Cc["@mozilla.org/updates/update-sync-manager;1"].getService(
+ Ci.nsIUpdateSyncManager
+ );
+ Assert.ok(
+ !syncManager.isOtherInstanceRunning(),
+ "no other instance is running yet"
+ );
+
+ // Now start a second copy of this xpcshell binary so that something else
+ // takes the same lock. First we'll define its command line.
+ // Most of the child's code is in a separate script file, so all the command
+ // line has to do is set up a few required path strings we need to pass
+ // through to the child, and then include the script file.
+ const args = [
+ "-g",
+ greDir.path,
+ "-e",
+ `
+ const customGreDirPath = "${getApplyDirFile(
+ DIR_RESOURCES
+ ).path.replaceAll("\\", "\\\\")}";
+ const customGreBinDirPath = "${getApplyDirFile(DIR_MACOS).path.replaceAll(
+ "\\",
+ "\\\\"
+ )}";
+ const customExePath = "${getApplyDirFile(
+ DIR_MACOS + FILE_APP_BIN
+ ).path.replaceAll("\\", "\\\\")}";
+ const customUpdDirPath = "${getMockUpdRootD().path.replaceAll(
+ "\\",
+ "\\\\"
+ )}";
+ const customOldUpdDirPath = "${getMockUpdRootD(true).path.replaceAll(
+ "\\",
+ "\\\\"
+ )}";
+ `,
+ "-f",
+ getTestDirFile("syncManagerTestChild.js").path,
+ ];
+
+ // Run the second copy two times, to show the lock is usable after having
+ // been closed.
+ for (let runs = 0; runs < 2; runs++) {
+ // Now we can actually invoke the process.
+ debugDump(
+ `launching child process at ${thisBinary.path} with args ${args}`
+ );
+ Subprocess.call({
+ command: thisBinary.path,
+ arguments: args,
+ stderr: "stdout",
+ });
+
+ // It will take the new xpcshell a little time to start up, but we should see
+ // the effect on the lock within at most a few seconds.
+ await TestUtils.waitForCondition(
+ () => syncManager.isOtherInstanceRunning(),
+ "waiting for child process to take the lock"
+ ).catch(e => {
+ // Rather than throwing out of waitForCondition(), catch and log the failure
+ // manually so that we get output that's a bit more readable.
+ Assert.ok(
+ syncManager.isOtherInstanceRunning(),
+ "child process has the lock"
+ );
+ });
+
+ // The lock should have been closed when the process exited, but we'll allow
+ // a little time for the OS to clean up the handle.
+ await TestUtils.waitForCondition(
+ () => !syncManager.isOtherInstanceRunning(),
+ "waiting for child process to release the lock"
+ ).catch(e => {
+ Assert.ok(
+ !syncManager.isOtherInstanceRunning(),
+ "child process has released the lock"
+ );
+ });
+ }
+
+ doTestFinish();
+});
diff --git a/toolkit/mozapps/update/tests/unit_aus_update/urlConstruction.js b/toolkit/mozapps/update/tests/unit_aus_update/urlConstruction.js
new file mode 100644
index 0000000000..6fd1cf8a73
--- /dev/null
+++ b/toolkit/mozapps/update/tests/unit_aus_update/urlConstruction.js
@@ -0,0 +1,26 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+/* Application Update URL Construction Tests */
+
+/**
+ * Tests for the majority of values are located in
+ * toolkit/modules/tests/xpcshell/test_UpdateUtils_updatechannel.js
+ * toolkit/modules/tests/xpcshell/test_UpdateUtils_url.js
+ */
+
+async function run_test() {
+ const URL_PREFIX = URL_HOST + "/";
+ setupTestCommon();
+ let url = URL_PREFIX;
+ debugDump("testing url force param is present: " + url);
+ setUpdateURL(url);
+ await waitForUpdateCheck(false, { url: url + "?force=1" });
+ url = URL_PREFIX + "?testparam=1";
+ debugDump("testing url force param when there is already a param: " + url);
+ setUpdateURL(url);
+ await waitForUpdateCheck(false, { url: url + "&force=1" });
+ doTestFinish();
+}
diff --git a/toolkit/mozapps/update/tests/unit_aus_update/verifyChannelPrefsFile.js b/toolkit/mozapps/update/tests/unit_aus_update/verifyChannelPrefsFile.js
new file mode 100644
index 0000000000..4cca77c73d
--- /dev/null
+++ b/toolkit/mozapps/update/tests/unit_aus_update/verifyChannelPrefsFile.js
@@ -0,0 +1,38 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+/**
+ * This test exists solely to ensure that channel-prefs.js is not changed.
+ * If it does get changed, it will cause a variation of Bug 1431342.
+ * To summarize, our updater doesn't update that file. But, on macOS, it is
+ * still used to compute the application's signature. This means that if Firefox
+ * updates and that file has been changed, the signature no will no longer
+ * validate.
+ */
+
+const expectedChannelPrefsContents = `/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+//
+// This pref is in its own file for complex reasons. See the comment in
+// browser/app/Makefile.in, bug 756325, and bug 1431342 for details. Do not add
+// other prefs to this file.
+
+pref("app.update.channel", "${UpdateUtils.UpdateChannel}");
+`;
+
+async function run_test() {
+ let channelPrefsFile = Services.dirsvc.get("GreD", Ci.nsIFile);
+ channelPrefsFile.append("defaults");
+ channelPrefsFile.append("pref");
+ channelPrefsFile.append("channel-prefs.js");
+
+ const contents = await IOUtils.readUTF8(channelPrefsFile.path);
+ Assert.equal(
+ contents,
+ expectedChannelPrefsContents,
+ "Channel Prefs file should should not change"
+ );
+}
diff --git a/toolkit/mozapps/update/tests/unit_aus_update/xpcshell.ini b/toolkit/mozapps/update/tests/unit_aus_update/xpcshell.ini
new file mode 100644
index 0000000000..bb0a575d72
--- /dev/null
+++ b/toolkit/mozapps/update/tests/unit_aus_update/xpcshell.ini
@@ -0,0 +1,76 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+[DEFAULT]
+tags = appupdate
+head = head_update.js
+skip-if =
+ (os == 'win' && (ccov || msix)) # Our updater is disabled in MSIX builds
+prefs =
+ app.update.staging.enabled=false
+support-files =
+ ../data/shared.js
+ ../data/sharedUpdateXML.js
+ ../data/xpcshellUtilsAUS.js
+ ../data/app_update.sjs
+ testConstants.js
+ ../data/simple.mar
+
+[ausReadStrings.js]
+[canCheckForAndCanApplyUpdates.js]
+[urlConstruction.js]
+skip-if =
+ socketprocess_networking # Bug 1759035
+[updateManagerXML.js]
+[remoteUpdateXML.js]
+skip-if =
+ socketprocess_networking # Bug 1759035
+[cleanupDownloadingForOlderAppVersion.js]
+[cleanupDownloadingForDifferentChannel.js]
+[cleanupDownloadingForSameVersionAndBuildID.js]
+[cleanupDownloadingIncorrectStatus.js]
+[cleanupPendingVersionFileIncorrectStatus.js]
+[cleanupSuccessLogMove.js]
+[cleanupSuccessLogsFIFO.js]
+[downloadInterruptedOffline.js]
+skip-if =
+ socketprocess_networking # Bug 1759035
+[downloadInterruptedNoRecovery.js]
+skip-if =
+ socketprocess_networking # Bug 1759035
+[downloadInterruptedRecovery.js]
+skip-if =
+ socketprocess_networking # Bug 1759035
+[downloadResumeForSameAppVersion.js]
+[languagePackUpdates.js]
+skip-if =
+ socketprocess_networking # Bug 1759035
+[updateSyncManager.js]
+[updateAutoPrefMigrate.js]
+skip-if = os != 'win'
+reason = Update pref migration is currently Windows only
+[updateDirectoryMigrate.js]
+skip-if = os != 'win'
+reason = Update directory migration is currently Windows only
+[multiUpdate.js]
+skip-if =
+ socketprocess_networking # Bug 1759035
+[perInstallationPrefs.js]
+[onlyDownloadUpdatesThisSession.js]
+skip-if =
+ socketprocess_networking # Bug 1759035
+[disableBackgroundUpdatesBackgroundTask.js]
+skip-if =
+ socketprocess_networking # Bug 1759035
+[disableBackgroundUpdatesNonBackgroundTask.js]
+skip-if =
+ socketprocess_networking # Bug 1759035
+[ensureExperimentToRolloutTransitionPerformed.js]
+run-if = os == 'win' && appname == 'firefox'
+reason = Feature is Firefox-specific and Windows-specific.
+[verifyChannelPrefsFile.js]
+run-if = appname == 'firefox'
+reason = File being verified is Firefox-specific.
+[backgroundUpdateTaskInternalUpdater.js]
+[updateCheckCombine.js]
diff --git a/toolkit/mozapps/update/tests/unit_background_update/head.js b/toolkit/mozapps/update/tests/unit_background_update/head.js
new file mode 100644
index 0000000000..c7ed24a0a7
--- /dev/null
+++ b/toolkit/mozapps/update/tests/unit_background_update/head.js
@@ -0,0 +1,58 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+/* import-globals-from ../data/xpcshellUtilsAUS.js */
+load("xpcshellUtilsAUS.js");
+gIsServiceTest = false;
+
+const { BackgroundTasksTestUtils } = ChromeUtils.importESModule(
+ "resource://testing-common/BackgroundTasksTestUtils.sys.mjs"
+);
+BackgroundTasksTestUtils.init(this);
+const do_backgroundtask = BackgroundTasksTestUtils.do_backgroundtask.bind(
+ BackgroundTasksTestUtils
+);
+const setupProfileService = BackgroundTasksTestUtils.setupProfileService.bind(
+ BackgroundTasksTestUtils
+);
+
+// Helper function to register a callback to catch a Glean ping before its
+// submission. The function returns all string_list items, but not as they
+// appear in the ping itself, but as full text representation, which is the
+// value of the corresponding field. This makes the test more unique, because
+// the values often contain chars, which are not allowed in glean metric labels
+//
+// @returns: an array which contains all glean metrics, but as full text
+// representation from the BackgroundUpdate.REASON object => its
+// values, see description for further details.
+//
+async function checkGleanPing() {
+ let retval = ["EMPTY"];
+ let ping_submitted = false;
+
+ const { maybeSubmitBackgroundUpdatePing } = ChromeUtils.importESModule(
+ "resource://gre/modules/backgroundtasks/BackgroundTask_backgroundupdate.sys.mjs"
+ );
+ const { BackgroundUpdate } = ChromeUtils.importESModule(
+ "resource://gre/modules/BackgroundUpdate.sys.mjs"
+ );
+
+ GleanPings.backgroundUpdate.testBeforeNextSubmit(_ => {
+ ping_submitted = true;
+ retval = Glean.backgroundUpdate.reasonsToNotUpdate.testGetValue().map(v => {
+ return BackgroundUpdate.REASON[v];
+ });
+ Assert.ok(Array.isArray(retval));
+ return retval;
+ });
+ await maybeSubmitBackgroundUpdatePing();
+ Assert.ok(ping_submitted, "Glean ping successfully submitted");
+
+ // The metric has `lifetime: application` set, but when testing we do not
+ // want to keep the results around and avoid, that one test can influence
+ // another. That is why we clear this string_list.
+ Glean.backgroundUpdate.reasonsToNotUpdate.set([]);
+
+ return retval;
+}
diff --git a/toolkit/mozapps/update/tests/unit_background_update/test_backgroundupdate_exitcodes.js b/toolkit/mozapps/update/tests/unit_background_update/test_backgroundupdate_exitcodes.js
new file mode 100644
index 0000000000..f862815ab1
--- /dev/null
+++ b/toolkit/mozapps/update/tests/unit_background_update/test_backgroundupdate_exitcodes.js
@@ -0,0 +1,80 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*-
+ * vim: sw=4 ts=4 sts=4 et
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+// This test exercises functionality and also ensures the exit codes,
+// which are a public API, do not change over time.
+const { EXIT_CODE } = ChromeUtils.importESModule(
+ "resource://gre/modules/BackgroundUpdate.sys.mjs"
+).BackgroundUpdate;
+
+setupProfileService();
+
+// Ensure launched background tasks don't see this xpcshell as a concurrent
+// instance.
+let syncManager = Cc["@mozilla.org/updates/update-sync-manager;1"].getService(
+ Ci.nsIUpdateSyncManager
+);
+let lockFile = do_get_profile();
+lockFile.append("customExePath");
+lockFile.append("customExe");
+syncManager.resetLock(lockFile);
+
+add_task(async function test_default_profile_does_not_exist() {
+ // Pretend there's no default profile.
+ let exitCode = await do_backgroundtask("backgroundupdate", {
+ extraEnv: {
+ MOZ_BACKGROUNDTASKS_NO_DEFAULT_PROFILE: "1",
+ },
+ });
+ Assert.equal(EXIT_CODE.DEFAULT_PROFILE_DOES_NOT_EXIST, exitCode);
+ Assert.equal(11, exitCode);
+});
+
+add_task(async function test_default_profile_cannot_be_locked() {
+ // Now, lock the default profile.
+ let profileService = Cc["@mozilla.org/toolkit/profile-service;1"].getService(
+ Ci.nsIToolkitProfileService
+ );
+
+ let file = do_get_profile();
+ file.append("profile_cannot_be_locked");
+
+ let profile = profileService.createUniqueProfile(
+ file,
+ "test_default_profile"
+ );
+ let lock = profile.lock({});
+
+ try {
+ let exitCode = await do_backgroundtask("backgroundupdate", {
+ extraEnv: {
+ MOZ_BACKGROUNDTASKS_DEFAULT_PROFILE_PATH: lock.directory.path,
+ },
+ });
+ Assert.equal(EXIT_CODE.DEFAULT_PROFILE_CANNOT_BE_LOCKED, exitCode);
+ Assert.equal(12, exitCode);
+ } finally {
+ lock.unlock();
+ }
+});
+
+add_task(async function test_default_profile_cannot_be_read() {
+ // Finally, provide an empty default profile, one without prefs.
+ let file = do_get_profile();
+ file.append("profile_cannot_be_read");
+
+ await IOUtils.makeDirectory(file.path);
+
+ let exitCode = await do_backgroundtask("backgroundupdate", {
+ extraEnv: {
+ MOZ_BACKGROUNDTASKS_DEFAULT_PROFILE_PATH: file.path,
+ },
+ });
+ Assert.equal(EXIT_CODE.DEFAULT_PROFILE_CANNOT_BE_READ, exitCode);
+ Assert.equal(13, exitCode);
+});
diff --git a/toolkit/mozapps/update/tests/unit_background_update/test_backgroundupdate_glean.js b/toolkit/mozapps/update/tests/unit_background_update/test_backgroundupdate_glean.js
new file mode 100644
index 0000000000..c66c98ebc9
--- /dev/null
+++ b/toolkit/mozapps/update/tests/unit_background_update/test_backgroundupdate_glean.js
@@ -0,0 +1,224 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*-
+ * vim: sw=4 ts=4 sts=4 et
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+const { ASRouterTargeting } = ChromeUtils.import(
+ "resource://activity-stream/lib/ASRouterTargeting.jsm"
+);
+
+const { BackgroundUpdate } = ChromeUtils.importESModule(
+ "resource://gre/modules/BackgroundUpdate.sys.mjs"
+);
+
+const { maybeSubmitBackgroundUpdatePing } = ChromeUtils.importESModule(
+ "resource://gre/modules/backgroundtasks/BackgroundTask_backgroundupdate.sys.mjs"
+);
+
+XPCOMUtils.defineLazyServiceGetter(
+ this,
+ "UpdateService",
+ "@mozilla.org/updates/update-service;1",
+ "nsIApplicationUpdateService"
+);
+
+add_setup(function test_setup() {
+ // FOG needs a profile directory to put its data in.
+ do_get_profile();
+
+ // We need to initialize it once, otherwise operations will be stuck in the pre-init queue.
+ Services.fog.initializeFOG();
+
+ setupProfileService();
+});
+
+add_task(async function test_record_update_environment() {
+ await BackgroundUpdate.recordUpdateEnvironment();
+
+ let pingSubmitted = false;
+ let appUpdateAutoEnabled = await UpdateUtils.getAppUpdateAutoEnabled();
+ let backgroundUpdateEnabled = await UpdateUtils.readUpdateConfigSetting(
+ "app.update.background.enabled"
+ );
+ GleanPings.backgroundUpdate.testBeforeNextSubmit(reason => {
+ Assert.equal(reason, "backgroundupdate_task");
+
+ pingSubmitted = true;
+ Assert.equal(
+ Services.prefs.getBoolPref("app.update.service.enabled", false),
+ Glean.update.serviceEnabled.testGetValue()
+ );
+
+ Assert.equal(
+ appUpdateAutoEnabled,
+ Glean.update.autoDownload.testGetValue()
+ );
+
+ Assert.equal(
+ backgroundUpdateEnabled,
+ Glean.update.backgroundUpdate.testGetValue()
+ );
+
+ Assert.equal(
+ UpdateUtils.UpdateChannel,
+ Glean.update.channel.testGetValue()
+ );
+ Assert.equal(
+ !Services.policies || Services.policies.isAllowed("appUpdate"),
+ Glean.update.enabled.testGetValue()
+ );
+
+ Assert.equal(
+ UpdateService.canUsuallyApplyUpdates,
+ Glean.update.canUsuallyApplyUpdates.testGetValue()
+ );
+ Assert.equal(
+ UpdateService.canUsuallyCheckForUpdates,
+ Glean.update.canUsuallyCheckForUpdates.testGetValue()
+ );
+ Assert.equal(
+ UpdateService.canUsuallyStageUpdates,
+ Glean.update.canUsuallyStageUpdates.testGetValue()
+ );
+ Assert.equal(
+ UpdateService.canUsuallyUseBits,
+ Glean.update.canUsuallyUseBits.testGetValue()
+ );
+ });
+
+ // There's nothing async in this function atm, but it's annotated async, so..
+ await maybeSubmitBackgroundUpdatePing();
+
+ ok(pingSubmitted, "'background-update' ping was submitted");
+});
+
+async function do_readTargeting(content, beforeNextSubmitCallback) {
+ let profileService = Cc["@mozilla.org/toolkit/profile-service;1"].getService(
+ Ci.nsIToolkitProfileService
+ );
+
+ let file = do_get_profile();
+ file.append("profile_cannot_be_locked");
+
+ let profile = profileService.createUniqueProfile(
+ file,
+ "test_default_profile"
+ );
+
+ let targetingSnapshot = profile.rootDir.clone();
+ targetingSnapshot.append("targeting.snapshot.json");
+
+ if (content) {
+ await IOUtils.writeUTF8(targetingSnapshot.path, content);
+ }
+
+ let lock = profile.lock({});
+
+ Services.fog.testResetFOG();
+ try {
+ await BackgroundUpdate.readFirefoxMessagingSystemTargetingSnapshot(lock);
+ } finally {
+ lock.unlock();
+ }
+
+ let pingSubmitted = false;
+ GleanPings.backgroundUpdate.testBeforeNextSubmit(reason => {
+ pingSubmitted = true;
+ return beforeNextSubmitCallback(reason);
+ });
+
+ // There's nothing async in this function atm, but it's annotated async, so..
+ await maybeSubmitBackgroundUpdatePing();
+
+ ok(pingSubmitted, "'background-update' ping was submitted");
+}
+
+// Missing targeting is anticipated.
+add_task(async function test_targeting_missing() {
+ await do_readTargeting(null, reason => {
+ Assert.equal(false, Glean.backgroundUpdate.targetingExists.testGetValue());
+
+ Assert.equal(
+ false,
+ Glean.backgroundUpdate.targetingException.testGetValue()
+ );
+ });
+});
+
+// Malformed JSON yields an exception.
+add_task(async function test_targeting_exception() {
+ await do_readTargeting("{", reason => {
+ Assert.equal(false, Glean.backgroundUpdate.targetingExists.testGetValue());
+
+ Assert.equal(
+ true,
+ Glean.backgroundUpdate.targetingException.testGetValue()
+ );
+ });
+});
+
+// Well formed targeting values are reflected into the Glean telemetry.
+add_task(async function test_targeting_exists() {
+ // We can't take a full environment snapshot under `xpcshell`; these are just
+ // the items we need.
+ let target = {
+ currentDate: ASRouterTargeting.Environment.currentDate,
+ profileAgeCreated: ASRouterTargeting.Environment.profileAgeCreated,
+ firefoxVersion: ASRouterTargeting.Environment.firefoxVersion,
+ };
+ let targetSnapshot = await ASRouterTargeting.getEnvironmentSnapshot(target);
+
+ await do_readTargeting(JSON.stringify(targetSnapshot), reason => {
+ Assert.equal(true, Glean.backgroundUpdate.targetingExists.testGetValue());
+
+ Assert.equal(
+ false,
+ Glean.backgroundUpdate.targetingException.testGetValue()
+ );
+
+ // `environment.firefoxVersion` is a positive integer.
+ Assert.ok(
+ Glean.backgroundUpdate.targetingEnvFirefoxVersion.testGetValue() > 0
+ );
+
+ Assert.equal(
+ targetSnapshot.environment.firefoxVersion,
+ Glean.backgroundUpdate.targetingEnvFirefoxVersion.testGetValue()
+ );
+
+ let profileAge =
+ Glean.backgroundUpdate.targetingEnvProfileAge.testGetValue();
+
+ Assert.ok(profileAge instanceof Date);
+ Assert.ok(0 < profileAge.getTime());
+ Assert.ok(profileAge.getTime() < Date.now());
+
+ // `environment.profileAgeCreated` is an integer, milliseconds since the
+ // Unix epoch.
+ let targetProfileAge = new Date(
+ targetSnapshot.environment.profileAgeCreated
+ );
+ // Our `time_unit: day` has Glean round to the nearest day *in the local
+ // timezone*, so we must do the same.
+ targetProfileAge.setHours(0, 0, 0, 0);
+
+ Assert.equal(targetProfileAge.toISOString(), profileAge.toISOString());
+
+ let currentDate =
+ Glean.backgroundUpdate.targetingEnvCurrentDate.testGetValue();
+
+ Assert.ok(0 < currentDate.getTime());
+ Assert.ok(currentDate.getTime() < Date.now());
+
+ // `environment.currentDate` is in ISO string format.
+ let targetCurrentDate = new Date(targetSnapshot.environment.currentDate);
+ // Our `time_unit: day` has Glean round to the nearest day *in the local
+ // timezone*, so we must do the same.
+ targetCurrentDate.setHours(0, 0, 0, 0);
+
+ Assert.equal(targetCurrentDate.toISOString(), currentDate.toISOString());
+ });
+});
diff --git a/toolkit/mozapps/update/tests/unit_background_update/test_backgroundupdate_reason.js b/toolkit/mozapps/update/tests/unit_background_update/test_backgroundupdate_reason.js
new file mode 100644
index 0000000000..71ce7f0361
--- /dev/null
+++ b/toolkit/mozapps/update/tests/unit_background_update/test_backgroundupdate_reason.js
@@ -0,0 +1,66 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*-
+ * vim: sw=2 ts=2 sts=2 et
+ * this source code form is subject to the terms of the mozilla public license,
+ * v. 2.0. if a copy of the mpl was not distributed with this file, you can
+ * obtain one at http://mozilla.org/mpl/2.0/. */
+
+"use strict";
+
+const { BackgroundUpdate } = ChromeUtils.importESModule(
+ "resource://gre/modules/BackgroundUpdate.sys.mjs"
+);
+
+// These tests use per-installation prefs, and those are a shared resource, so
+// they require some non-trivial setup.
+setupTestCommon(null);
+standardInit();
+
+add_setup(function test_setup() {
+ // FOG needs a profile directory to put its data in.
+ do_get_profile();
+
+ // We need to initialize it once, otherwise operations will be stuck in the
+ // pre-init queue.
+ Services.fog.initializeFOG();
+});
+
+// Because we want to use the keys from REASON as strings and send these with
+// Glean, we have to make sure, that they meet the requirements for `String
+// Lists` and are not too long.
+add_task(async function test_reasons_length() {
+ for (const key of Object.keys(BackgroundUpdate.REASON)) {
+ Glean.backgroundUpdate.reasonsToNotUpdate.add(key);
+ // No exception means success.
+ Assert.ok(
+ Array.isArray(Glean.backgroundUpdate.reasonsToNotUpdate.testGetValue()),
+ "Glean allows the name of the reason to be '" + key + "'"
+ );
+ }
+});
+
+// The string list in Glean can overflow and has a hard limit of 20 entries.
+// This test toggles a switch to reach this limit and fails if this causes an
+// exception, because we want to avoid that statistical data collection can have
+// an negative impact on the success rate of background updates.
+add_task(async function test_reasons_overflow() {
+ let prev = await UpdateUtils.getAppUpdateAutoEnabled();
+ try {
+ for (let i = 1; i <= 21; i++) {
+ await UpdateUtils.setAppUpdateAutoEnabled(false);
+ await BackgroundUpdate._reasonsToNotUpdateInstallation();
+ await UpdateUtils.setAppUpdateAutoEnabled(true);
+ await BackgroundUpdate._reasonsToNotUpdateInstallation();
+ Assert.ok(true, "Overflow test successful for run #" + i);
+ }
+ } finally {
+ ok(true, "resetting AppUpdateAutoEnabled to " + prev);
+ await UpdateUtils.setAppUpdateAutoEnabled(prev);
+ }
+});
+
+add_task(() => {
+ // `setupTestCommon()` calls `do_test_pending()`; this calls
+ // `do_test_finish()`. The `add_task` schedules this to run after all the
+ // other tests have completed.
+ doTestFinish();
+});
diff --git a/toolkit/mozapps/update/tests/unit_background_update/test_backgroundupdate_reason_schedule.js b/toolkit/mozapps/update/tests/unit_background_update/test_backgroundupdate_reason_schedule.js
new file mode 100644
index 0000000000..277ea993ec
--- /dev/null
+++ b/toolkit/mozapps/update/tests/unit_background_update/test_backgroundupdate_reason_schedule.js
@@ -0,0 +1,136 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*-
+ * vim: sw=4 ts=4 sts=4 et
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+const { BackgroundUpdate } = ChromeUtils.importESModule(
+ "resource://gre/modules/BackgroundUpdate.sys.mjs"
+);
+let reasons = () => BackgroundUpdate._reasonsToNotScheduleUpdates();
+let REASON = BackgroundUpdate.REASON;
+
+const { AddonTestUtils } = ChromeUtils.importESModule(
+ "resource://testing-common/AddonTestUtils.sys.mjs"
+);
+const { ExtensionTestUtils } = ChromeUtils.importESModule(
+ "resource://testing-common/ExtensionXPCShellUtils.sys.mjs"
+);
+
+setupProfileService();
+
+// Setup that allows to install a langpack.
+ExtensionTestUtils.init(this);
+
+AddonTestUtils.init(this);
+AddonTestUtils.overrideCertDB();
+
+AddonTestUtils.createAppInfo(
+ "xpcshell@tests.mozilla.org",
+ "XPCShell",
+ "42",
+ "42"
+);
+
+add_setup(function test_setup() {
+ // FOG needs a profile directory to put its data in.
+ do_get_profile();
+
+ // We need to initialize it once, otherwise operations will be stuck in the pre-init queue.
+ Services.fog.initializeFOG();
+
+ setupProfileService();
+});
+
+add_task(
+ {
+ skip_if: () => !AppConstants.MOZ_BACKGROUNDTASKS,
+ },
+ async function test_reasons_schedule_langpacks() {
+ await AddonTestUtils.promiseStartupManager();
+
+ Services.prefs.setBoolPref("app.update.langpack.enabled", true);
+
+ let result = await reasons();
+ Assert.ok(
+ !result.includes(REASON.LANGPACK_INSTALLED),
+ "Reasons does not include LANGPACK_INSTALLED"
+ );
+
+ // Install a langpack.
+ let langpack = {
+ "manifest.json": {
+ name: "test Language Pack",
+ version: "1.0",
+ manifest_version: 2,
+ browser_specific_settings: {
+ gecko: {
+ id: "@test-langpack",
+ strict_min_version: "42.0",
+ strict_max_version: "42.0",
+ },
+ },
+ langpack_id: "fr",
+ languages: {
+ fr: {
+ chrome_resources: {
+ global: "chrome/fr/locale/fr/global/",
+ },
+ version: "20171001190118",
+ },
+ },
+ sources: {
+ browser: {
+ base_path: "browser/",
+ },
+ },
+ },
+ };
+
+ await Promise.all([
+ TestUtils.topicObserved("webextension-langpack-startup"),
+ AddonTestUtils.promiseInstallXPI(langpack),
+ ]);
+
+ result = await reasons();
+ Assert.ok(
+ result.includes(REASON.LANGPACK_INSTALLED),
+ "Reasons include LANGPACK_INSTALLED"
+ );
+ result = await checkGleanPing();
+ Assert.ok(
+ result.includes(REASON.LANGPACK_INSTALLED),
+ "Recognizes a language pack is installed."
+ );
+
+ // Now turn off langpack updating.
+ Services.prefs.setBoolPref("app.update.langpack.enabled", false);
+
+ result = await reasons();
+ Assert.ok(
+ !result.includes(REASON.LANGPACK_INSTALLED),
+ "Reasons does not include LANGPACK_INSTALLED"
+ );
+ result = await checkGleanPing();
+ Assert.ok(
+ !result.includes(REASON.LANGPACK_INSTALLED),
+ "No Glean metric when no language pack is installed."
+ );
+ }
+);
+
+add_task(
+ {
+ skip_if: () => !AppConstants.MOZ_BACKGROUNDTASKS,
+ },
+ async function test_reasons_schedule_default_profile() {
+ // It's difficult to arrange a default profile in a testing environment, so
+ // this is not as thorough as we'd like.
+ let result = await reasons();
+
+ Assert.ok(result.includes(REASON.NO_DEFAULT_PROFILE_EXISTS));
+ Assert.ok(result.includes(REASON.NOT_DEFAULT_PROFILE));
+ }
+);
diff --git a/toolkit/mozapps/update/tests/unit_background_update/test_backgroundupdate_reason_update.js b/toolkit/mozapps/update/tests/unit_background_update/test_backgroundupdate_reason_update.js
new file mode 100644
index 0000000000..9988b7206d
--- /dev/null
+++ b/toolkit/mozapps/update/tests/unit_background_update/test_backgroundupdate_reason_update.js
@@ -0,0 +1,241 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*-
+ * vim: sw=4 ts=4 sts=4 et
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+const { BackgroundUpdate } = ChromeUtils.importESModule(
+ "resource://gre/modules/BackgroundUpdate.sys.mjs"
+);
+let reasons = () => BackgroundUpdate._reasonsToNotUpdateInstallation();
+let REASON = BackgroundUpdate.REASON;
+const { EnterprisePolicyTesting } = ChromeUtils.importESModule(
+ "resource://testing-common/EnterprisePolicyTesting.sys.mjs"
+);
+const { UpdateService } = ChromeUtils.importESModule(
+ "resource://gre/modules/UpdateService.sys.mjs"
+);
+
+const { sinon } = ChromeUtils.importESModule(
+ "resource://testing-common/Sinon.sys.mjs"
+);
+
+// We can't reasonably check NO_MOZ_BACKGROUNDTASKS, nor NO_OMNIJAR.
+
+// These tests use per-installation prefs, and those are a shared resource, so
+// they require some non-trivial setup.
+setupTestCommon(null);
+standardInit();
+
+function setup_enterprise_policy_testing() {
+ // This initializes the policy engine for xpcshell tests
+ let policies = Cc["@mozilla.org/enterprisepolicies;1"].getService(
+ Ci.nsIObserver
+ );
+ policies.observe(null, "policies-startup", null);
+}
+setup_enterprise_policy_testing();
+
+async function setupPolicyEngineWithJson(json, customSchema) {
+ if (typeof json != "object") {
+ let filePath = do_get_file(json ? json : "non-existing-file.json").path;
+ return EnterprisePolicyTesting.setupPolicyEngineWithJson(
+ filePath,
+ customSchema
+ );
+ }
+ return EnterprisePolicyTesting.setupPolicyEngineWithJson(json, customSchema);
+}
+
+add_setup(function test_setup() {
+ // FOG needs a profile directory to put its data in.
+ do_get_profile();
+
+ // We need to initialize it once, otherwise operations will be stuck in the pre-init queue.
+ Services.fog.initializeFOG();
+
+ setupProfileService();
+});
+
+add_task(async function test_reasons_update_no_app_update_auto() {
+ let prev = await UpdateUtils.getAppUpdateAutoEnabled();
+ try {
+ await UpdateUtils.setAppUpdateAutoEnabled(false);
+ let result = await reasons();
+ Assert.ok(result.includes(REASON.NO_APP_UPDATE_AUTO));
+ result = await checkGleanPing();
+ Assert.ok(result.includes(REASON.NO_APP_UPDATE_AUTO));
+
+ await UpdateUtils.setAppUpdateAutoEnabled(true);
+ result = await reasons();
+ Assert.ok(!result.includes(REASON.NO_APP_UPDATE_AUTO));
+
+ result = await checkGleanPing();
+ Assert.ok(!result.includes(REASON.NO_APP_UPDATE_AUTO));
+ } finally {
+ await UpdateUtils.setAppUpdateAutoEnabled(prev);
+ }
+});
+
+add_task(async function test_reasons_update_no_app_update_background_enabled() {
+ let prev = await UpdateUtils.readUpdateConfigSetting(
+ "app.update.background.enabled"
+ );
+ try {
+ await UpdateUtils.writeUpdateConfigSetting(
+ "app.update.background.enabled",
+ false
+ );
+ let result = await reasons();
+ Assert.ok(result.includes(REASON.NO_APP_UPDATE_BACKGROUND_ENABLED));
+ result = await checkGleanPing();
+ Assert.ok(result.includes(REASON.NO_APP_UPDATE_BACKGROUND_ENABLED));
+
+ await UpdateUtils.writeUpdateConfigSetting(
+ "app.update.background.enabled",
+ true
+ );
+ result = await reasons();
+ Assert.ok(!result.includes(REASON.NO_APP_UPDATE_BACKGROUND_ENABLED));
+ result = await checkGleanPing();
+ Assert.ok(!result.includes(REASON.NO_APP_UPDATE_BACKGROUND_ENABLED));
+ } finally {
+ await UpdateUtils.writeUpdateConfigSetting(
+ "app.update.background.enabled",
+ prev
+ );
+ }
+});
+
+add_task(async function test_reasons_update_cannot_usually_check() {
+ // It's difficult to arrange the conditions in a testing environment, so
+ // we'll use mocks to get a little assurance.
+ let result = await reasons();
+ Assert.ok(!result.includes(REASON.CANNOT_USUALLY_CHECK));
+
+ let sandbox = sinon.createSandbox();
+ try {
+ sandbox
+ .stub(UpdateService.prototype, "canUsuallyCheckForUpdates")
+ .get(() => false);
+ result = await reasons();
+ Assert.ok(result.includes(REASON.CANNOT_USUALLY_CHECK));
+ result = await checkGleanPing();
+ Assert.ok(result.includes(REASON.CANNOT_USUALLY_CHECK));
+ } finally {
+ sandbox.restore();
+ }
+});
+
+add_task(async function test_reasons_update_can_usually_stage_or_appl() {
+ // It's difficult to arrange the conditions in a testing environment, so
+ // we'll use mocks to get a little assurance.
+ let sandbox = sinon.createSandbox();
+ try {
+ sandbox
+ .stub(UpdateService.prototype, "canUsuallyStageUpdates")
+ .get(() => true);
+ sandbox
+ .stub(UpdateService.prototype, "canUsuallyApplyUpdates")
+ .get(() => true);
+ let result = await reasons();
+ Assert.ok(
+ !result.includes(REASON.CANNOT_USUALLY_STAGE_AND_CANNOT_USUALLY_APPLY)
+ );
+ result = await checkGleanPing();
+ Assert.ok(
+ !result.includes(REASON.CANNOT_USUALLY_STAGE_AND_CANNOT_USUALLY_APPLY)
+ );
+
+ sandbox
+ .stub(UpdateService.prototype, "canUsuallyStageUpdates")
+ .get(() => false);
+ sandbox
+ .stub(UpdateService.prototype, "canUsuallyApplyUpdates")
+ .get(() => false);
+ result = await reasons();
+ Assert.ok(
+ result.includes(REASON.CANNOT_USUALLY_STAGE_AND_CANNOT_USUALLY_APPLY)
+ );
+ result = await checkGleanPing();
+ Assert.ok(
+ result.includes(REASON.CANNOT_USUALLY_STAGE_AND_CANNOT_USUALLY_APPLY)
+ );
+ } finally {
+ sandbox.restore();
+ }
+});
+
+add_task(
+ {
+ skip_if: () =>
+ !AppConstants.MOZ_BITS_DOWNLOAD || AppConstants.platform != "win",
+ },
+ async function test_reasons_update_can_usually_use_bits() {
+ let prev = Services.prefs.getBoolPref("app.update.BITS.enabled");
+
+ // Here we use mocks to "get by" preconditions that are not
+ // satisfied in the testing environment.
+ let sandbox = sinon.createSandbox();
+ try {
+ sandbox
+ .stub(UpdateService.prototype, "canUsuallyStageUpdates")
+ .get(() => true);
+ sandbox
+ .stub(UpdateService.prototype, "canUsuallyApplyUpdates")
+ .get(() => true);
+
+ Services.prefs.setBoolPref("app.update.BITS.enabled", false);
+ let result = await reasons();
+ Assert.ok(result.includes(REASON.WINDOWS_CANNOT_USUALLY_USE_BITS));
+ result = await checkGleanPing();
+ Assert.ok(
+ result.includes(REASON.WINDOWS_CANNOT_USUALLY_USE_BITS),
+ "result : " + result.join("', '") + "']"
+ );
+
+ Services.prefs.setBoolPref("app.update.BITS.enabled", true);
+ result = await reasons();
+ Assert.ok(!result.includes(REASON.WINDOWS_CANNOT_USUALLY_USE_BITS));
+ result = await checkGleanPing();
+ Assert.ok(!result.includes(REASON.WINDOWS_CANNOT_USUALLY_USE_BITS));
+ } finally {
+ sandbox.restore();
+ Services.prefs.setBoolPref("app.update.BITS.enabled", prev);
+ }
+ }
+);
+
+add_task(async function test_reasons_update_manual_update_only() {
+ await setupPolicyEngineWithJson({
+ policies: {
+ ManualAppUpdateOnly: true,
+ },
+ });
+ Assert.equal(
+ Services.policies.status,
+ Ci.nsIEnterprisePolicies.ACTIVE,
+ "Engine is active"
+ );
+
+ let result = await reasons();
+ Assert.ok(result.includes(REASON.MANUAL_UPDATE_ONLY));
+ result = await checkGleanPing();
+ Assert.ok(result.includes(REASON.MANUAL_UPDATE_ONLY));
+
+ await setupPolicyEngineWithJson({});
+
+ result = await reasons();
+ Assert.ok(!result.includes(REASON.MANUAL_UPDATE_ONLY));
+ result = await checkGleanPing();
+ Assert.ok(!result.includes(REASON.MANUAL_UPDATE_ONLY));
+});
+
+add_task(() => {
+ // `setupTestCommon()` calls `do_test_pending()`; this calls
+ // `do_test_finish()`. The `add_task` schedules this to run after all the
+ // other tests have completed.
+ doTestFinish();
+});
diff --git a/toolkit/mozapps/update/tests/unit_background_update/xpcshell.ini b/toolkit/mozapps/update/tests/unit_background_update/xpcshell.ini
new file mode 100644
index 0000000000..45016b42e2
--- /dev/null
+++ b/toolkit/mozapps/update/tests/unit_background_update/xpcshell.ini
@@ -0,0 +1,24 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+[DEFAULT]
+firefox-appdir = browser
+skip-if =
+ toolkit == 'android'
+ (os == 'win' && msix) # Our updater is disabled in MSIX builds
+head = head.js
+support-files =
+ ../data/shared.js
+ ../data/sharedUpdateXML.js
+ ../data/xpcshellUtilsAUS.js
+
+[test_backgroundupdate_exitcodes.js]
+run-sequentially = very high failure rate in parallel
+skip-if =
+ os == "win" && os_version == "6.1" # Skip on Azure - frequent failure
+
+[test_backgroundupdate_glean.js]
+[test_backgroundupdate_reason.js]
+[test_backgroundupdate_reason_update.js]
+[test_backgroundupdate_reason_schedule.js]
diff --git a/toolkit/mozapps/update/tests/unit_base_updater/head_update.js b/toolkit/mozapps/update/tests/unit_base_updater/head_update.js
new file mode 100644
index 0000000000..1ab1a70a0b
--- /dev/null
+++ b/toolkit/mozapps/update/tests/unit_base_updater/head_update.js
@@ -0,0 +1,7 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+/* import-globals-from ../data/xpcshellUtilsAUS.js */
+load("xpcshellUtilsAUS.js");
+gIsServiceTest = false;
diff --git a/toolkit/mozapps/update/tests/unit_base_updater/invalidArgCallbackFileNotInInstallDirFailure.js b/toolkit/mozapps/update/tests/unit_base_updater/invalidArgCallbackFileNotInInstallDirFailure.js
new file mode 100644
index 0000000000..cc61a75dd7
--- /dev/null
+++ b/toolkit/mozapps/update/tests/unit_base_updater/invalidArgCallbackFileNotInInstallDirFailure.js
@@ -0,0 +1,32 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+/* Callback file not in install directory or a sub-directory of the install
+ directory failure */
+
+async function run_test() {
+ if (!setupTestCommon()) {
+ return;
+ }
+ const STATE_AFTER_RUNUPDATE = STATE_FAILED_INVALID_CALLBACK_DIR_ERROR;
+ gTestFiles = gTestFilesCompleteSuccess;
+ gTestDirs = gTestDirsCompleteSuccess;
+ setTestFilesAndDirsForFailure();
+ await setupUpdaterTest(FILE_COMPLETE_MAR, false);
+ let path = getTestDirFile(FILE_HELPER_BIN).path;
+ runUpdate(STATE_AFTER_RUNUPDATE, false, 1, true, null, null, null, path);
+ standardInit();
+ checkPostUpdateRunningFile(false);
+ checkFilesAfterUpdateFailure(getApplyDirFile);
+ await waitForUpdateXMLFiles();
+ checkUpdateManager(
+ STATE_NONE,
+ false,
+ STATE_FAILED,
+ INVALID_CALLBACK_DIR_ERROR,
+ 1
+ );
+ waitForFilesInUse();
+}
diff --git a/toolkit/mozapps/update/tests/unit_base_updater/invalidArgCallbackFilePathTooLongFailure.js b/toolkit/mozapps/update/tests/unit_base_updater/invalidArgCallbackFilePathTooLongFailure.js
new file mode 100644
index 0000000000..0b432e2e15
--- /dev/null
+++ b/toolkit/mozapps/update/tests/unit_base_updater/invalidArgCallbackFilePathTooLongFailure.js
@@ -0,0 +1,39 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+/* Too long callback file path failure test */
+
+async function run_test() {
+ if (!setupTestCommon()) {
+ return;
+ }
+ const STATE_AFTER_RUNUPDATE = STATE_FAILED_INVALID_CALLBACK_PATH_ERROR;
+ gTestFiles = gTestFilesCompleteSuccess;
+ gTestDirs = gTestDirsCompleteSuccess;
+ setTestFilesAndDirsForFailure();
+ await setupUpdaterTest(FILE_COMPLETE_MAR, false);
+ let path = "123456789";
+ if (AppConstants.platform == "win") {
+ path = "\\" + path;
+ path = path.repeat(30); // 300 characters
+ path = "C:" + path;
+ } else {
+ path = "/" + path;
+ path = path.repeat(1000); // 10000 characters
+ }
+ runUpdate(STATE_AFTER_RUNUPDATE, false, 1, true, null, null, null, path);
+ standardInit();
+ checkPostUpdateRunningFile(false);
+ checkFilesAfterUpdateFailure(getApplyDirFile);
+ await waitForUpdateXMLFiles();
+ checkUpdateManager(
+ STATE_NONE,
+ false,
+ STATE_FAILED,
+ INVALID_CALLBACK_PATH_ERROR,
+ 1
+ );
+ waitForFilesInUse();
+}
diff --git a/toolkit/mozapps/update/tests/unit_base_updater/invalidArgInstallDirPathTooLongFailure.js b/toolkit/mozapps/update/tests/unit_base_updater/invalidArgInstallDirPathTooLongFailure.js
new file mode 100644
index 0000000000..c2746e2bd1
--- /dev/null
+++ b/toolkit/mozapps/update/tests/unit_base_updater/invalidArgInstallDirPathTooLongFailure.js
@@ -0,0 +1,53 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+/* Too long install directory path failure test */
+
+async function run_test() {
+ if (!setupTestCommon()) {
+ return;
+ }
+ const STATE_AFTER_RUNUPDATE = gIsServiceTest
+ ? STATE_FAILED_SERVICE_INVALID_INSTALL_DIR_PATH_ERROR
+ : STATE_FAILED_INVALID_INSTALL_DIR_PATH_ERROR;
+ gTestFiles = gTestFilesCompleteSuccess;
+ gTestDirs = gTestDirsCompleteSuccess;
+ setTestFilesAndDirsForFailure();
+ await setupUpdaterTest(FILE_COMPLETE_MAR, false);
+ let path = "123456789";
+ if (AppConstants.platform == "win") {
+ path = "\\" + path;
+ path = path.repeat(30); // 300 characters
+ path = "C:" + path;
+ } else {
+ path = "/" + path;
+ path = path.repeat(1000); // 10000 characters
+ }
+ runUpdate(STATE_AFTER_RUNUPDATE, false, 1, true, null, path, null, null);
+ standardInit();
+ checkPostUpdateRunningFile(false);
+ checkFilesAfterUpdateFailure(getApplyDirFile);
+ await waitForUpdateXMLFiles();
+ if (gIsServiceTest) {
+ // The invalid argument service tests launch the maintenance service
+ // directly so the unelevated updater doesn't handle the invalid argument.
+ // By doing this it is possible to test that the maintenance service
+ // properly handles the invalid argument but since the updater isn't used to
+ // launch the maintenance service the update.status file isn't copied from
+ // the secure log directory to the patch directory and the update manager
+ // won't read the failure from the update.status file.
+ checkUpdateManager(STATE_NONE, false, STATE_PENDING_SVC, 0, 1);
+ } else {
+ checkUpdateManager(
+ STATE_NONE,
+ false,
+ STATE_FAILED,
+ INVALID_INSTALL_DIR_PATH_ERROR,
+ 1
+ );
+ }
+
+ waitForFilesInUse();
+}
diff --git a/toolkit/mozapps/update/tests/unit_base_updater/invalidArgInstallDirPathTraversalFailure.js b/toolkit/mozapps/update/tests/unit_base_updater/invalidArgInstallDirPathTraversalFailure.js
new file mode 100644
index 0000000000..b611fac972
--- /dev/null
+++ b/toolkit/mozapps/update/tests/unit_base_updater/invalidArgInstallDirPathTraversalFailure.js
@@ -0,0 +1,50 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+/* Install directory path traversal failure test */
+
+async function run_test() {
+ if (!setupTestCommon()) {
+ return;
+ }
+ const STATE_AFTER_RUNUPDATE = gIsServiceTest
+ ? STATE_FAILED_SERVICE_INVALID_INSTALL_DIR_PATH_ERROR
+ : STATE_FAILED_INVALID_INSTALL_DIR_PATH_ERROR;
+ gTestFiles = gTestFilesCompleteSuccess;
+ gTestDirs = gTestDirsCompleteSuccess;
+ setTestFilesAndDirsForFailure();
+ await setupUpdaterTest(FILE_COMPLETE_MAR, false);
+ let path = "123456789";
+ if (AppConstants.platform == "win") {
+ path = "C:\\" + path + "\\..\\" + path;
+ } else {
+ path = "/" + path + "/../" + path;
+ }
+ runUpdate(STATE_AFTER_RUNUPDATE, false, 1, true, null, path, null, null);
+ standardInit();
+ checkPostUpdateRunningFile(false);
+ checkFilesAfterUpdateFailure(getApplyDirFile);
+ await waitForUpdateXMLFiles();
+ if (gIsServiceTest) {
+ // The invalid argument service tests launch the maintenance service
+ // directly so the unelevated updater doesn't handle the invalid argument.
+ // By doing this it is possible to test that the maintenance service
+ // properly handles the invalid argument but since the updater isn't used to
+ // launch the maintenance service the update.status file isn't copied from
+ // the secure log directory to the patch directory and the update manager
+ // won't read the failure from the update.status file.
+ checkUpdateManager(STATE_NONE, false, STATE_PENDING_SVC, 0, 1);
+ } else {
+ checkUpdateManager(
+ STATE_NONE,
+ false,
+ STATE_FAILED,
+ INVALID_INSTALL_DIR_PATH_ERROR,
+ 1
+ );
+ }
+
+ waitForFilesInUse();
+}
diff --git a/toolkit/mozapps/update/tests/unit_base_updater/invalidArgInstallWorkingDirPathNotSameFailure_win.js b/toolkit/mozapps/update/tests/unit_base_updater/invalidArgInstallWorkingDirPathNotSameFailure_win.js
new file mode 100644
index 0000000000..0506d100fd
--- /dev/null
+++ b/toolkit/mozapps/update/tests/unit_base_updater/invalidArgInstallWorkingDirPathNotSameFailure_win.js
@@ -0,0 +1,45 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+/* Different install and working directories for a regular update failure */
+
+async function run_test() {
+ if (!setupTestCommon()) {
+ return;
+ }
+ const STATE_AFTER_RUNUPDATE = gIsServiceTest
+ ? STATE_FAILED_SERVICE_INVALID_APPLYTO_DIR_ERROR
+ : STATE_FAILED_INVALID_APPLYTO_DIR_ERROR;
+ gTestFiles = gTestFilesCompleteSuccess;
+ gTestDirs = gTestDirsCompleteSuccess;
+ setTestFilesAndDirsForFailure();
+ await setupUpdaterTest(FILE_COMPLETE_MAR, false);
+ let path = getApplyDirFile("..", false).path;
+ runUpdate(STATE_AFTER_RUNUPDATE, false, 1, true, null, null, path, null);
+ standardInit();
+ checkPostUpdateRunningFile(false);
+ checkFilesAfterUpdateFailure(getApplyDirFile);
+ await waitForUpdateXMLFiles();
+ if (gIsServiceTest) {
+ // The invalid argument service tests launch the maintenance service
+ // directly so the unelevated updater doesn't handle the invalid argument.
+ // By doing this it is possible to test that the maintenance service
+ // properly handles the invalid argument but since the updater isn't used to
+ // launch the maintenance service the update.status file isn't copied from
+ // the secure log directory to the patch directory and the update manager
+ // won't read the failure from the update.status file.
+ checkUpdateManager(STATE_NONE, false, STATE_PENDING_SVC, 0, 1);
+ } else {
+ checkUpdateManager(
+ STATE_NONE,
+ false,
+ STATE_FAILED,
+ INVALID_APPLYTO_DIR_ERROR,
+ 1
+ );
+ }
+
+ waitForFilesInUse();
+}
diff --git a/toolkit/mozapps/update/tests/unit_base_updater/invalidArgPatchDirPathTraversalFailure.js b/toolkit/mozapps/update/tests/unit_base_updater/invalidArgPatchDirPathTraversalFailure.js
new file mode 100644
index 0000000000..cf053e3ff5
--- /dev/null
+++ b/toolkit/mozapps/update/tests/unit_base_updater/invalidArgPatchDirPathTraversalFailure.js
@@ -0,0 +1,32 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+/* Patch directory path traversal failure test */
+
+async function run_test() {
+ if (!setupTestCommon()) {
+ return;
+ }
+ const STATE_AFTER_RUNUPDATE = gIsServiceTest
+ ? STATE_PENDING_SVC
+ : STATE_PENDING;
+ gTestFiles = gTestFilesCompleteSuccess;
+ gTestDirs = gTestDirsCompleteSuccess;
+ setTestFilesAndDirsForFailure();
+ await setupUpdaterTest(FILE_COMPLETE_MAR, false);
+ let path = getUpdateDirFile(DIR_PATCH);
+ if (AppConstants.platform == "win") {
+ path = path + "\\..\\";
+ } else {
+ path = path + "/../";
+ }
+ runUpdate(STATE_AFTER_RUNUPDATE, false, 1, true, path, null, null, null);
+ standardInit();
+ checkPostUpdateRunningFile(false);
+ checkFilesAfterUpdateFailure(getApplyDirFile);
+ await waitForUpdateXMLFiles();
+ checkUpdateManager(STATE_NONE, false, STATE_AFTER_RUNUPDATE, 0, 1);
+ waitForFilesInUse();
+}
diff --git a/toolkit/mozapps/update/tests/unit_base_updater/invalidArgStageDirNotInInstallDirFailure_win.js b/toolkit/mozapps/update/tests/unit_base_updater/invalidArgStageDirNotInInstallDirFailure_win.js
new file mode 100644
index 0000000000..4624179bfd
--- /dev/null
+++ b/toolkit/mozapps/update/tests/unit_base_updater/invalidArgStageDirNotInInstallDirFailure_win.js
@@ -0,0 +1,45 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+/* Different install and working directories for a regular update failure */
+
+async function run_test() {
+ if (!setupTestCommon()) {
+ return;
+ }
+ const STATE_AFTER_RUNUPDATE = gIsServiceTest
+ ? STATE_FAILED_SERVICE_INVALID_APPLYTO_DIR_STAGED_ERROR
+ : STATE_FAILED_INVALID_APPLYTO_DIR_STAGED_ERROR;
+ gTestFiles = gTestFilesCompleteSuccess;
+ gTestDirs = gTestDirsCompleteSuccess;
+ setTestFilesAndDirsForFailure();
+ await setupUpdaterTest(FILE_COMPLETE_MAR, false);
+ let path = getApplyDirFile("..", false).path;
+ runUpdate(STATE_AFTER_RUNUPDATE, true, 1, true, null, null, path, null);
+ standardInit();
+ checkPostUpdateRunningFile(false);
+ checkFilesAfterUpdateFailure(getApplyDirFile);
+ await waitForUpdateXMLFiles();
+ if (gIsServiceTest) {
+ // The invalid argument service tests launch the maintenance service
+ // directly so the unelevated updater doesn't handle the invalid argument.
+ // By doing this it is possible to test that the maintenance service
+ // properly handles the invalid argument but since the updater isn't used to
+ // launch the maintenance service the update.status file isn't copied from
+ // the secure log directory to the patch directory and the update manager
+ // won't read the failure from the update.status file.
+ checkUpdateManager(STATE_NONE, false, STATE_PENDING_SVC, 0, 1);
+ } else {
+ checkUpdateManager(
+ STATE_NONE,
+ false,
+ STATE_FAILED,
+ INVALID_APPLYTO_DIR_STAGED_ERROR,
+ 1
+ );
+ }
+
+ waitForFilesInUse();
+}
diff --git a/toolkit/mozapps/update/tests/unit_base_updater/invalidArgWorkingDirPathLocalUNCFailure_win.js b/toolkit/mozapps/update/tests/unit_base_updater/invalidArgWorkingDirPathLocalUNCFailure_win.js
new file mode 100644
index 0000000000..7c0af26e37
--- /dev/null
+++ b/toolkit/mozapps/update/tests/unit_base_updater/invalidArgWorkingDirPathLocalUNCFailure_win.js
@@ -0,0 +1,45 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+/* Working directory path local UNC failure test */
+
+async function run_test() {
+ if (!setupTestCommon()) {
+ return;
+ }
+ const STATE_AFTER_RUNUPDATE = gIsServiceTest
+ ? STATE_FAILED_SERVICE_INVALID_WORKING_DIR_PATH_ERROR
+ : STATE_FAILED_INVALID_WORKING_DIR_PATH_ERROR;
+ gTestFiles = gTestFilesCompleteSuccess;
+ gTestDirs = gTestDirsCompleteSuccess;
+ setTestFilesAndDirsForFailure();
+ await setupUpdaterTest(FILE_COMPLETE_MAR, false);
+ let path = "\\\\.\\" + getApplyDirFile(null, false).path;
+ runUpdate(STATE_AFTER_RUNUPDATE, false, 1, true, null, null, path, null);
+ standardInit();
+ checkPostUpdateRunningFile(false);
+ checkFilesAfterUpdateFailure(getApplyDirFile);
+ await waitForUpdateXMLFiles();
+ if (gIsServiceTest) {
+ // The invalid argument service tests launch the maintenance service
+ // directly so the unelevated updater doesn't handle the invalid argument.
+ // By doing this it is possible to test that the maintenance service
+ // properly handles the invalid argument but since the updater isn't used to
+ // launch the maintenance service the update.status file isn't copied from
+ // the secure log directory to the patch directory and the update manager
+ // won't read the failure from the update.status file.
+ checkUpdateManager(STATE_NONE, false, STATE_PENDING_SVC, 0, 1);
+ } else {
+ checkUpdateManager(
+ STATE_NONE,
+ false,
+ STATE_FAILED,
+ INVALID_WORKING_DIR_PATH_ERROR,
+ 1
+ );
+ }
+
+ waitForFilesInUse();
+}
diff --git a/toolkit/mozapps/update/tests/unit_base_updater/invalidArgWorkingDirPathRelativeFailure.js b/toolkit/mozapps/update/tests/unit_base_updater/invalidArgWorkingDirPathRelativeFailure.js
new file mode 100644
index 0000000000..cfbd9eaec9
--- /dev/null
+++ b/toolkit/mozapps/update/tests/unit_base_updater/invalidArgWorkingDirPathRelativeFailure.js
@@ -0,0 +1,44 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+/* Relative working directory path failure test */
+
+async function run_test() {
+ if (!setupTestCommon()) {
+ return;
+ }
+ const STATE_AFTER_RUNUPDATE = gIsServiceTest
+ ? STATE_FAILED_SERVICE_INVALID_WORKING_DIR_PATH_ERROR
+ : STATE_FAILED_INVALID_WORKING_DIR_PATH_ERROR;
+ gTestFiles = gTestFilesCompleteSuccess;
+ gTestDirs = gTestDirsCompleteSuccess;
+ setTestFilesAndDirsForFailure();
+ await setupUpdaterTest(FILE_COMPLETE_MAR, false);
+ runUpdate(STATE_AFTER_RUNUPDATE, false, 1, true, null, null, "test", null);
+ standardInit();
+ checkPostUpdateRunningFile(false);
+ checkFilesAfterUpdateFailure(getApplyDirFile);
+ await waitForUpdateXMLFiles();
+ if (gIsServiceTest) {
+ // The invalid argument service tests launch the maintenance service
+ // directly so the unelevated updater doesn't handle the invalid argument.
+ // By doing this it is possible to test that the maintenance service
+ // properly handles the invalid argument but since the updater isn't used to
+ // launch the maintenance service the update.status file isn't copied from
+ // the secure log directory to the patch directory and the update manager
+ // won't read the failure from the update.status file.
+ checkUpdateManager(STATE_NONE, false, STATE_PENDING_SVC, 0, 1);
+ } else {
+ checkUpdateManager(
+ STATE_NONE,
+ false,
+ STATE_FAILED,
+ INVALID_WORKING_DIR_PATH_ERROR,
+ 1
+ );
+ }
+
+ waitForFilesInUse();
+}
diff --git a/toolkit/mozapps/update/tests/unit_base_updater/marAppApplyDirLockedStageFailure_win.js b/toolkit/mozapps/update/tests/unit_base_updater/marAppApplyDirLockedStageFailure_win.js
new file mode 100644
index 0000000000..a845cb70c5
--- /dev/null
+++ b/toolkit/mozapps/update/tests/unit_base_updater/marAppApplyDirLockedStageFailure_win.js
@@ -0,0 +1,29 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+/**
+ * Test applying an update by staging an update and launching an application to
+ * apply it.
+ */
+
+async function run_test() {
+ if (!setupTestCommon()) {
+ return;
+ }
+ const STATE_AFTER_STAGE = STATE_PENDING;
+ gTestFiles = gTestFilesCompleteSuccess;
+ gTestDirs = gTestDirsCompleteSuccess;
+ setTestFilesAndDirsForFailure();
+ createUpdateInProgressLockFile(getGREBinDir());
+ await setupUpdaterTest(FILE_COMPLETE_MAR, false);
+ await stageUpdate(STATE_AFTER_STAGE, false);
+ removeUpdateInProgressLockFile(getGREBinDir());
+ checkPostUpdateRunningFile(false);
+ checkFilesAfterUpdateFailure(getApplyDirFile);
+ checkUpdateLogContains(PERFORMING_STAGED_UPDATE);
+ checkUpdateLogContains(ERR_UPDATE_IN_PROGRESS);
+ await waitForUpdateXMLFiles(true, false);
+ checkUpdateManager(STATE_AFTER_STAGE, true, STATE_AFTER_STAGE, 0, 0);
+ waitForFilesInUse();
+}
diff --git a/toolkit/mozapps/update/tests/unit_base_updater/marAppApplyUpdateAppBinInUseStageSuccess_win.js b/toolkit/mozapps/update/tests/unit_base_updater/marAppApplyUpdateAppBinInUseStageSuccess_win.js
new file mode 100644
index 0000000000..319aee8e34
--- /dev/null
+++ b/toolkit/mozapps/update/tests/unit_base_updater/marAppApplyUpdateAppBinInUseStageSuccess_win.js
@@ -0,0 +1,50 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+/**
+ * Test applying an update by staging an update and launching an application to
+ * apply it.
+ */
+
+async function run_test() {
+ if (!setupTestCommon()) {
+ return;
+ }
+ const STATE_AFTER_STAGE = gIsServiceTest ? STATE_APPLIED_SVC : STATE_APPLIED;
+ gTestFiles = gTestFilesCompleteSuccess;
+ gTestDirs = gTestDirsCompleteSuccess;
+ await setupUpdaterTest(FILE_COMPLETE_MAR, false);
+ await stageUpdate(STATE_AFTER_STAGE, true);
+ checkPostUpdateRunningFile(false);
+ checkFilesAfterUpdateSuccess(getStageDirFile, true, false);
+ checkUpdateLogContents(LOG_COMPLETE_SUCCESS, true);
+ lockDirectory(getGREBinDir().path);
+ // Switch the application to the staged application that was updated.
+ await runUpdateUsingApp(STATE_SUCCEEDED);
+ await checkPostUpdateAppLog();
+ checkAppBundleModTime();
+ standardInit();
+ checkPostUpdateRunningFile(true);
+ checkFilesAfterUpdateSuccess(getApplyDirFile);
+ checkUpdateLogContents(LOG_COMPLETE_SUCCESS);
+ await waitForUpdateXMLFiles();
+ checkUpdateManager(STATE_NONE, false, STATE_SUCCEEDED, 0, 1);
+
+ let updatesDir = getUpdateDirFile(DIR_PATCH);
+ Assert.ok(
+ updatesDir.exists(),
+ MSG_SHOULD_EXIST + getMsgPath(updatesDir.path)
+ );
+
+ let log = getUpdateDirFile(FILE_UPDATE_LOG);
+ Assert.ok(!log.exists(), MSG_SHOULD_NOT_EXIST + getMsgPath(log.path));
+
+ log = getUpdateDirFile(FILE_LAST_UPDATE_LOG);
+ Assert.ok(log.exists(), MSG_SHOULD_EXIST + getMsgPath(log.path));
+
+ log = getUpdateDirFile(FILE_BACKUP_UPDATE_LOG);
+ Assert.ok(log.exists(), MSG_SHOULD_EXIST + getMsgPath(log.path));
+
+ waitForFilesInUse();
+}
diff --git a/toolkit/mozapps/update/tests/unit_base_updater/marAppApplyUpdateSkippedWriteAccess_win.js b/toolkit/mozapps/update/tests/unit_base_updater/marAppApplyUpdateSkippedWriteAccess_win.js
new file mode 100644
index 0000000000..aa1336b7ed
--- /dev/null
+++ b/toolkit/mozapps/update/tests/unit_base_updater/marAppApplyUpdateSkippedWriteAccess_win.js
@@ -0,0 +1,74 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+/**
+ * Test an update isn't attempted when the update.status file can't be written
+ * to.
+ */
+
+async function run_test() {
+ if (!setupTestCommon()) {
+ return;
+ }
+ gTestFiles = gTestFilesCompleteSuccess;
+ gTestDirs = gTestDirsCompleteSuccess;
+ setTestFilesAndDirsForFailure();
+ await setupUpdaterTest(FILE_COMPLETE_MAR, false);
+
+ // To simulate a user that doesn't have write access to the update directory
+ // lock the relevant files in the update directory.
+ let filesToLock = [
+ FILE_ACTIVE_UPDATE_XML,
+ FILE_UPDATE_MAR,
+ FILE_UPDATE_STATUS,
+ FILE_UPDATE_TEST,
+ FILE_UPDATE_VERSION,
+ ];
+ filesToLock.forEach(function (aFileLeafName) {
+ let file = getUpdateDirFile(aFileLeafName);
+ if (!file.exists()) {
+ file.create(Ci.nsIFile.NORMAL_FILE_TYPE, 0o444);
+ }
+ file.QueryInterface(Ci.nsILocalFileWin);
+ file.readOnly = true;
+ Assert.ok(file.exists(), MSG_SHOULD_EXIST + getMsgPath(file.path));
+ Assert.ok(!file.isWritable(), "the file should not be writeable");
+ });
+
+ registerCleanupFunction(() => {
+ filesToLock.forEach(function (aFileLeafName) {
+ let file = getUpdateDirFile(aFileLeafName);
+ if (file.exists()) {
+ file.QueryInterface(Ci.nsILocalFileWin);
+ file.readOnly = false;
+ file.remove(false);
+ }
+ });
+ });
+
+ // Reload the update manager now that the update directory files are locked.
+ reloadUpdateManagerData();
+ await runUpdateUsingApp(STATE_PENDING);
+ standardInit();
+ checkPostUpdateRunningFile(false);
+ checkFilesAfterUpdateFailure(getApplyDirFile);
+ checkUpdateManager(STATE_PENDING, false, STATE_NONE, 0, 0);
+
+ let dir = getUpdateDirFile(DIR_PATCH);
+ Assert.ok(dir.exists(), MSG_SHOULD_EXIST + getMsgPath(dir.path));
+
+ let file = getUpdateDirFile(FILE_UPDATES_XML);
+ Assert.ok(!file.exists(), MSG_SHOULD_NOT_EXIST + getMsgPath(file.path));
+
+ file = getUpdateDirFile(FILE_UPDATE_LOG);
+ Assert.ok(!file.exists(), MSG_SHOULD_NOT_EXIST + getMsgPath(file.path));
+
+ file = getUpdateDirFile(FILE_LAST_UPDATE_LOG);
+ Assert.ok(!file.exists(), MSG_SHOULD_NOT_EXIST + getMsgPath(file.path));
+
+ file = getUpdateDirFile(FILE_BACKUP_UPDATE_LOG);
+ Assert.ok(!file.exists(), MSG_SHOULD_NOT_EXIST + getMsgPath(file.path));
+
+ waitForFilesInUse();
+}
diff --git a/toolkit/mozapps/update/tests/unit_base_updater/marAppApplyUpdateStageOldVersionFailure.js b/toolkit/mozapps/update/tests/unit_base_updater/marAppApplyUpdateStageOldVersionFailure.js
new file mode 100644
index 0000000000..3dfad0f58e
--- /dev/null
+++ b/toolkit/mozapps/update/tests/unit_base_updater/marAppApplyUpdateStageOldVersionFailure.js
@@ -0,0 +1,64 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+/**
+ * Test a replace request for a staged update with a version file that specifies
+ * an older version failure. The same check is used in nsUpdateDriver.cpp for
+ * all update types which is why there aren't tests for the maintenance service
+ * as well as for other update types.
+ */
+
+async function run_test() {
+ if (!setupTestCommon()) {
+ return;
+ }
+ const STATE_AFTER_STAGE = STATE_APPLIED;
+ gTestFiles = gTestFilesCompleteSuccess;
+ gTestDirs = gTestDirsCompleteSuccess;
+ await setupUpdaterTest(FILE_COMPLETE_MAR, null, "", false);
+ let patchProps = { state: STATE_AFTER_STAGE };
+ let patches = getLocalPatchString(patchProps);
+ let updateProps = { appVersion: "0.9" };
+ let updates = getLocalUpdateString(updateProps, patches);
+ writeUpdatesToXMLFile(getLocalUpdatesXMLString(updates), true);
+ getUpdateDirFile(FILE_UPDATE_LOG).create(
+ Ci.nsIFile.NORMAL_FILE_TYPE,
+ PERMS_FILE
+ );
+ writeStatusFile(STATE_AFTER_STAGE);
+ // Create the version file with an older version to simulate installing a new
+ // version of the application while there is an update that has been staged.
+ writeVersionFile("0.9");
+ // Try to switch the application to the fake staged application.
+ await runUpdateUsingApp(STATE_AFTER_STAGE);
+ standardInit();
+ checkPostUpdateRunningFile(false);
+ setTestFilesAndDirsForFailure();
+ checkFilesAfterUpdateFailure(getApplyDirFile);
+ await waitForUpdateXMLFiles();
+ checkUpdateManager(
+ STATE_NONE,
+ false,
+ STATE_FAILED,
+ ERR_OLDER_VERSION_OR_SAME_BUILD,
+ 1
+ );
+
+ let updatesDir = getUpdateDirFile(DIR_PATCH);
+ Assert.ok(
+ updatesDir.exists(),
+ MSG_SHOULD_EXIST + getMsgPath(updatesDir.path)
+ );
+
+ let log = getUpdateDirFile(FILE_UPDATE_LOG);
+ Assert.ok(!log.exists(), MSG_SHOULD_NOT_EXIST + getMsgPath(log.path));
+
+ log = getUpdateDirFile(FILE_LAST_UPDATE_LOG);
+ Assert.ok(log.exists(), MSG_SHOULD_EXIST + getMsgPath(log.path));
+
+ log = getUpdateDirFile(FILE_BACKUP_UPDATE_LOG);
+ Assert.ok(!log.exists(), MSG_SHOULD_NOT_EXIST + getMsgPath(log.path));
+
+ waitForFilesInUse();
+}
diff --git a/toolkit/mozapps/update/tests/unit_base_updater/marAppApplyUpdateStageSuccess.js b/toolkit/mozapps/update/tests/unit_base_updater/marAppApplyUpdateStageSuccess.js
new file mode 100644
index 0000000000..34b47866b1
--- /dev/null
+++ b/toolkit/mozapps/update/tests/unit_base_updater/marAppApplyUpdateStageSuccess.js
@@ -0,0 +1,49 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+/**
+ * Test applying an update by staging an update and launching an application to
+ * apply it.
+ */
+
+async function run_test() {
+ if (!setupTestCommon()) {
+ return;
+ }
+ const STATE_AFTER_STAGE = gIsServiceTest ? STATE_APPLIED_SVC : STATE_APPLIED;
+ gTestFiles = gTestFilesCompleteSuccess;
+ gTestDirs = gTestDirsCompleteSuccess;
+ await setupUpdaterTest(FILE_COMPLETE_MAR, true);
+ await stageUpdate(STATE_AFTER_STAGE, true);
+ checkPostUpdateRunningFile(false);
+ checkFilesAfterUpdateSuccess(getStageDirFile, true);
+ checkUpdateLogContents(LOG_COMPLETE_SUCCESS, true);
+ // Switch the application to the staged application that was updated.
+ await runUpdateUsingApp(STATE_SUCCEEDED);
+ await checkPostUpdateAppLog();
+ checkAppBundleModTime();
+ standardInit();
+ checkPostUpdateRunningFile(true);
+ checkFilesAfterUpdateSuccess(getApplyDirFile, false, true);
+ checkUpdateLogContents(LOG_REPLACE_SUCCESS, false, true);
+ await waitForUpdateXMLFiles();
+ checkUpdateManager(STATE_NONE, false, STATE_SUCCEEDED, 0, 1);
+
+ let updatesDir = getUpdateDirFile(DIR_PATCH);
+ Assert.ok(
+ updatesDir.exists(),
+ MSG_SHOULD_EXIST + getMsgPath(updatesDir.path)
+ );
+
+ let log = getUpdateDirFile(FILE_UPDATE_LOG);
+ Assert.ok(!log.exists(), MSG_SHOULD_NOT_EXIST + getMsgPath(log.path));
+
+ log = getUpdateDirFile(FILE_LAST_UPDATE_LOG);
+ Assert.ok(log.exists(), MSG_SHOULD_EXIST + getMsgPath(log.path));
+
+ log = getUpdateDirFile(FILE_BACKUP_UPDATE_LOG);
+ Assert.ok(log.exists(), MSG_SHOULD_EXIST + getMsgPath(log.path));
+
+ waitForFilesInUse();
+}
diff --git a/toolkit/mozapps/update/tests/unit_base_updater/marAppApplyUpdateSuccess.js b/toolkit/mozapps/update/tests/unit_base_updater/marAppApplyUpdateSuccess.js
new file mode 100644
index 0000000000..980b0cb89a
--- /dev/null
+++ b/toolkit/mozapps/update/tests/unit_base_updater/marAppApplyUpdateSuccess.js
@@ -0,0 +1,47 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+/**
+ * Test applying an update by launching an application to apply it.
+ */
+
+async function run_test() {
+ if (!setupTestCommon()) {
+ return;
+ }
+ gTestFiles = gTestFilesCompleteSuccess;
+ gTestDirs = gTestDirsCompleteSuccess;
+ // The third parameter will test that a full path to the post update binary
+ // doesn't execute.
+ await setupUpdaterTest(
+ FILE_COMPLETE_MAR,
+ undefined,
+ getApplyDirFile(null, true).path + "/"
+ );
+ await runUpdateUsingApp(STATE_SUCCEEDED);
+ checkAppBundleModTime();
+ standardInit();
+ checkPostUpdateRunningFile(false);
+ checkFilesAfterUpdateSuccess(getApplyDirFile);
+ checkUpdateLogContents(LOG_COMPLETE_SUCCESS);
+ await waitForUpdateXMLFiles();
+ checkUpdateManager(STATE_NONE, false, STATE_SUCCEEDED, 0, 1);
+
+ let updatesDir = getUpdateDirFile(DIR_PATCH);
+ Assert.ok(
+ updatesDir.exists(),
+ MSG_SHOULD_EXIST + getMsgPath(updatesDir.path)
+ );
+
+ let log = getUpdateDirFile(FILE_UPDATE_LOG);
+ Assert.ok(!log.exists(), MSG_SHOULD_NOT_EXIST + getMsgPath(log.path));
+
+ log = getUpdateDirFile(FILE_LAST_UPDATE_LOG);
+ Assert.ok(log.exists(), MSG_SHOULD_EXIST + getMsgPath(log.path));
+
+ log = getUpdateDirFile(FILE_BACKUP_UPDATE_LOG);
+ Assert.ok(!log.exists(), MSG_SHOULD_NOT_EXIST + getMsgPath(log.path));
+
+ waitForFilesInUse();
+}
diff --git a/toolkit/mozapps/update/tests/unit_base_updater/marAppInUseBackgroundTaskFailure_win.js b/toolkit/mozapps/update/tests/unit_base_updater/marAppInUseBackgroundTaskFailure_win.js
new file mode 100644
index 0000000000..bcb24bee94
--- /dev/null
+++ b/toolkit/mozapps/update/tests/unit_base_updater/marAppInUseBackgroundTaskFailure_win.js
@@ -0,0 +1,51 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+/* Fail to apply a complete MAR when the application is in use and the callback is a background task. */
+
+async function run_test() {
+ if (!setupTestCommon()) {
+ return;
+ }
+
+ gTestFiles = gTestFilesCompleteSuccess;
+ gTestDirs = gTestDirsCompleteSuccess;
+ setTestFilesAndDirsForFailure();
+
+ // Add a dummy --backgroundtask arg; this will have no effect on the
+ // callback (TestAUSHelper) but will cause the updater to detect
+ // that this is a background task.
+ gCallbackArgs = gCallbackArgs.concat(["--backgroundtask", "not_found"]);
+
+ // Run the update with the helper file in use, expecting failure.
+ await setupUpdaterTest(FILE_COMPLETE_MAR, false);
+ await runHelperFileInUse(DIR_RESOURCES + gCallbackBinFile, false);
+ runUpdate(
+ STATE_FAILED_WRITE_ERROR_BACKGROUND_TASK_SHARING_VIOLATION,
+ false, // aSwitchApp
+ 1, // aExpectedExitValue
+ true // aCheckSvcLog
+ );
+ await waitForHelperExit();
+
+ standardInit();
+
+ checkPostUpdateRunningFile(false);
+ checkFilesAfterUpdateFailure(getApplyDirFile);
+ checkUpdateLogContains(ERR_BGTASK_EXCLUSIVE);
+
+ // Check that the update was reset to "pending".
+ await waitForUpdateXMLFiles(
+ true, // aActiveUpdateExists
+ false // aUpdatesExists
+ );
+ checkUpdateManager(
+ STATE_PENDING, // aStatusFileState
+ true, // aHasActiveUpdate
+ STATE_PENDING, // aUpdateStatusState
+ WRITE_ERROR_BACKGROUND_TASK_SHARING_VIOLATION,
+ 0 // aUpdateCount
+ );
+ checkCallbackLog();
+}
diff --git a/toolkit/mozapps/update/tests/unit_base_updater/marAppInUseStageFailureComplete_win.js b/toolkit/mozapps/update/tests/unit_base_updater/marAppInUseStageFailureComplete_win.js
new file mode 100644
index 0000000000..502561ed1e
--- /dev/null
+++ b/toolkit/mozapps/update/tests/unit_base_updater/marAppInUseStageFailureComplete_win.js
@@ -0,0 +1,37 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+/* Application in use complete MAR file staged patch apply failure test */
+
+async function run_test() {
+ if (!setupTestCommon()) {
+ return;
+ }
+ const STATE_AFTER_STAGE = gIsServiceTest ? STATE_APPLIED_SVC : STATE_APPLIED;
+ const STATE_AFTER_RUNUPDATE = gIsServiceTest
+ ? STATE_PENDING_SVC
+ : STATE_PENDING;
+ gTestFiles = gTestFilesCompleteSuccess;
+ gTestDirs = gTestDirsCompleteSuccess;
+ await setupUpdaterTest(FILE_COMPLETE_MAR, false);
+ await runHelperFileInUse(DIR_RESOURCES + gCallbackBinFile, false);
+ await stageUpdate(STATE_AFTER_STAGE, true);
+ checkPostUpdateRunningFile(false);
+ checkFilesAfterUpdateSuccess(getStageDirFile, true);
+ checkUpdateLogContents(LOG_COMPLETE_SUCCESS, true);
+ // Switch the application to the staged application that was updated.
+ runUpdate(STATE_AFTER_RUNUPDATE, true, 1, true);
+ await waitForHelperExit();
+ standardInit();
+ checkPostUpdateRunningFile(false);
+ setTestFilesAndDirsForFailure();
+ checkFilesAfterUpdateFailure(getApplyDirFile);
+ checkUpdateLogContains(ERR_RENAME_FILE);
+ checkUpdateLogContains(
+ ERR_MOVE_DESTDIR_7 + "\n" + STATE_FAILED_WRITE_ERROR + "\n" + CALL_QUIT
+ );
+ await waitForUpdateXMLFiles();
+ checkUpdateManager(STATE_NONE, false, STATE_AFTER_RUNUPDATE, 0, 1);
+ checkCallbackLog();
+}
diff --git a/toolkit/mozapps/update/tests/unit_base_updater/marAppInUseStageSuccessComplete_unix.js b/toolkit/mozapps/update/tests/unit_base_updater/marAppInUseStageSuccessComplete_unix.js
new file mode 100644
index 0000000000..29c2c2a30e
--- /dev/null
+++ b/toolkit/mozapps/update/tests/unit_base_updater/marAppInUseStageSuccessComplete_unix.js
@@ -0,0 +1,68 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+/* Application in use complete MAR file staged patch apply success test */
+
+async function run_test() {
+ if (!setupTestCommon()) {
+ return;
+ }
+ const STATE_AFTER_STAGE = STATE_APPLIED;
+ gTestFiles = gTestFilesCompleteSuccess;
+ gTestFiles[gTestFiles.length - 1].originalContents = null;
+ gTestFiles[gTestFiles.length - 1].compareContents = "FromComplete\n";
+ gTestFiles[gTestFiles.length - 1].comparePerms = 0o644;
+ gTestDirs = gTestDirsCompleteSuccess;
+ await setupUpdaterTest(FILE_COMPLETE_MAR, true);
+ setupSymLinks();
+ await runHelperFileInUse(DIR_RESOURCES + gCallbackBinFile, false);
+ await stageUpdate(STATE_AFTER_STAGE, true);
+ checkPostUpdateRunningFile(false);
+ checkFilesAfterUpdateSuccess(getStageDirFile, true);
+ checkUpdateLogContents(LOG_COMPLETE_SUCCESS, true);
+ // Switch the application to the staged application that was updated.
+ runUpdate(STATE_SUCCEEDED, true, 0, true);
+ await waitForHelperExit();
+ await checkPostUpdateAppLog();
+ checkAppBundleModTime();
+ checkSymLinks();
+ standardInit();
+ checkPostUpdateRunningFile(true);
+ checkFilesAfterUpdateSuccess(getApplyDirFile);
+ checkUpdateLogContents(LOG_REPLACE_SUCCESS, false, true);
+ await waitForUpdateXMLFiles();
+ checkUpdateManager(STATE_NONE, false, STATE_SUCCEEDED, 0, 1);
+ checkCallbackLog();
+}
+
+/**
+ * Setup symlinks for the test.
+ */
+function setupSymLinks() {
+ if (AppConstants.platform == "macosx" || AppConstants.platform == "linux") {
+ removeSymlink();
+ createSymlink();
+ registerCleanupFunction(removeSymlink);
+ gTestFiles.splice(gTestFiles.length - 3, 0, {
+ description: "Readable symlink",
+ fileName: "link",
+ relPathDir: DIR_RESOURCES,
+ originalContents: "test",
+ compareContents: "test",
+ originalFile: null,
+ compareFile: null,
+ originalPerms: 0o666,
+ comparePerms: 0o666,
+ });
+ }
+}
+
+/**
+ * Checks the state of the symlinks for the test.
+ */
+function checkSymLinks() {
+ if (AppConstants.platform == "macosx" || AppConstants.platform == "linux") {
+ checkSymlink();
+ }
+}
diff --git a/toolkit/mozapps/update/tests/unit_base_updater/marAppInUseSuccessComplete.js b/toolkit/mozapps/update/tests/unit_base_updater/marAppInUseSuccessComplete.js
new file mode 100644
index 0000000000..e08777d042
--- /dev/null
+++ b/toolkit/mozapps/update/tests/unit_base_updater/marAppInUseSuccessComplete.js
@@ -0,0 +1,26 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+/* Application in use complete MAR file patch apply success test */
+
+async function run_test() {
+ if (!setupTestCommon()) {
+ return;
+ }
+ gTestFiles = gTestFilesCompleteSuccess;
+ gTestDirs = gTestDirsCompleteSuccess;
+ await setupUpdaterTest(FILE_COMPLETE_MAR, false);
+ await runHelperFileInUse(DIR_RESOURCES + gCallbackBinFile, false);
+ runUpdate(STATE_SUCCEEDED, false, 0, true);
+ await waitForHelperExit();
+ await checkPostUpdateAppLog();
+ checkAppBundleModTime();
+ standardInit();
+ checkPostUpdateRunningFile(true);
+ checkFilesAfterUpdateSuccess(getApplyDirFile);
+ checkUpdateLogContents(LOG_COMPLETE_SUCCESS);
+ await waitForUpdateXMLFiles();
+ checkUpdateManager(STATE_NONE, false, STATE_SUCCEEDED, 0, 1);
+ checkCallbackLog();
+}
diff --git a/toolkit/mozapps/update/tests/unit_base_updater/marCallbackAppStageSuccessComplete_win.js b/toolkit/mozapps/update/tests/unit_base_updater/marCallbackAppStageSuccessComplete_win.js
new file mode 100644
index 0000000000..2f8155c790
--- /dev/null
+++ b/toolkit/mozapps/update/tests/unit_base_updater/marCallbackAppStageSuccessComplete_win.js
@@ -0,0 +1,30 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+/* Replace app binary complete MAR file staged patch apply success test */
+
+async function run_test() {
+ if (!setupTestCommon()) {
+ return;
+ }
+ const STATE_AFTER_STAGE = gIsServiceTest ? STATE_APPLIED_SVC : STATE_APPLIED;
+ gTestFiles = gTestFilesCompleteSuccess;
+ gTestDirs = gTestDirsCompleteSuccess;
+ gCallbackBinFile = "exe0.exe";
+ await setupUpdaterTest(FILE_COMPLETE_MAR, false);
+ await stageUpdate(STATE_AFTER_STAGE, true);
+ checkPostUpdateRunningFile(false);
+ checkFilesAfterUpdateSuccess(getStageDirFile, true);
+ checkUpdateLogContents(LOG_COMPLETE_SUCCESS, true);
+ // Switch the application to the staged application that was updated.
+ runUpdate(STATE_SUCCEEDED, true, 0, true);
+ await checkPostUpdateAppLog();
+ standardInit();
+ checkPostUpdateRunningFile(true);
+ checkFilesAfterUpdateSuccess(getApplyDirFile, false, true);
+ checkUpdateLogContents(LOG_REPLACE_SUCCESS, false, true);
+ await waitForUpdateXMLFiles();
+ checkUpdateManager(STATE_NONE, false, STATE_SUCCEEDED, 0, 1);
+ checkCallbackLog();
+}
diff --git a/toolkit/mozapps/update/tests/unit_base_updater/marCallbackAppStageSuccessPartial_win.js b/toolkit/mozapps/update/tests/unit_base_updater/marCallbackAppStageSuccessPartial_win.js
new file mode 100644
index 0000000000..04c72896b4
--- /dev/null
+++ b/toolkit/mozapps/update/tests/unit_base_updater/marCallbackAppStageSuccessPartial_win.js
@@ -0,0 +1,30 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+/* Patch app binary partial MAR file staged patch apply success test */
+
+async function run_test() {
+ if (!setupTestCommon()) {
+ return;
+ }
+ const STATE_AFTER_STAGE = gIsServiceTest ? STATE_APPLIED_SVC : STATE_APPLIED;
+ gTestFiles = gTestFilesPartialSuccess;
+ gTestDirs = gTestDirsPartialSuccess;
+ gCallbackBinFile = "exe0.exe";
+ await setupUpdaterTest(FILE_PARTIAL_MAR, false);
+ await stageUpdate(STATE_AFTER_STAGE, true);
+ checkPostUpdateRunningFile(false);
+ checkFilesAfterUpdateSuccess(getStageDirFile, true);
+ checkUpdateLogContents(LOG_PARTIAL_SUCCESS, true);
+ // Switch the application to the staged application that was updated.
+ runUpdate(STATE_SUCCEEDED, true, 0, true);
+ await checkPostUpdateAppLog();
+ standardInit();
+ checkPostUpdateRunningFile(true);
+ checkFilesAfterUpdateSuccess(getApplyDirFile, false, true);
+ checkUpdateLogContents(LOG_REPLACE_SUCCESS, false, true);
+ await waitForUpdateXMLFiles();
+ checkUpdateManager(STATE_NONE, false, STATE_SUCCEEDED, 0, 1);
+ checkCallbackLog();
+}
diff --git a/toolkit/mozapps/update/tests/unit_base_updater/marCallbackAppSuccessComplete_win.js b/toolkit/mozapps/update/tests/unit_base_updater/marCallbackAppSuccessComplete_win.js
new file mode 100644
index 0000000000..fa13f07f46
--- /dev/null
+++ b/toolkit/mozapps/update/tests/unit_base_updater/marCallbackAppSuccessComplete_win.js
@@ -0,0 +1,24 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+/* Replace app binary complete MAR file patch apply success test */
+
+async function run_test() {
+ if (!setupTestCommon()) {
+ return;
+ }
+ gTestFiles = gTestFilesCompleteSuccess;
+ gTestDirs = gTestDirsCompleteSuccess;
+ gCallbackBinFile = "exe0.exe";
+ await setupUpdaterTest(FILE_COMPLETE_MAR, false);
+ runUpdate(STATE_SUCCEEDED, false, 0, true);
+ await checkPostUpdateAppLog();
+ standardInit();
+ checkPostUpdateRunningFile(true);
+ checkFilesAfterUpdateSuccess(getApplyDirFile);
+ checkUpdateLogContents(LOG_COMPLETE_SUCCESS);
+ await waitForUpdateXMLFiles();
+ checkUpdateManager(STATE_NONE, false, STATE_SUCCEEDED, 0, 1);
+ checkCallbackLog();
+}
diff --git a/toolkit/mozapps/update/tests/unit_base_updater/marCallbackAppSuccessPartial_win.js b/toolkit/mozapps/update/tests/unit_base_updater/marCallbackAppSuccessPartial_win.js
new file mode 100644
index 0000000000..e74509c06e
--- /dev/null
+++ b/toolkit/mozapps/update/tests/unit_base_updater/marCallbackAppSuccessPartial_win.js
@@ -0,0 +1,24 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+/* Patch app binary partial MAR file patch apply success test */
+
+async function run_test() {
+ if (!setupTestCommon()) {
+ return;
+ }
+ gTestFiles = gTestFilesPartialSuccess;
+ gTestDirs = gTestDirsPartialSuccess;
+ gCallbackBinFile = "exe0.exe";
+ await setupUpdaterTest(FILE_PARTIAL_MAR, false);
+ runUpdate(STATE_SUCCEEDED, false, 0, true);
+ await checkPostUpdateAppLog();
+ standardInit();
+ checkPostUpdateRunningFile(true);
+ checkFilesAfterUpdateSuccess(getApplyDirFile);
+ checkUpdateLogContents(LOG_PARTIAL_SUCCESS);
+ await waitForUpdateXMLFiles();
+ checkUpdateManager(STATE_NONE, false, STATE_SUCCEEDED, 0, 1);
+ checkCallbackLog();
+}
diff --git a/toolkit/mozapps/update/tests/unit_base_updater/marCallbackUmask_unix.js b/toolkit/mozapps/update/tests/unit_base_updater/marCallbackUmask_unix.js
new file mode 100644
index 0000000000..be3b3707b4
--- /dev/null
+++ b/toolkit/mozapps/update/tests/unit_base_updater/marCallbackUmask_unix.js
@@ -0,0 +1,42 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+/* Verify that app callback is launched with the same umask as was set
+ * before applying an update. */
+
+async function run_test() {
+ if (!setupTestCommon()) {
+ return;
+ }
+ gTestFiles = gTestFilesCompleteSuccess;
+ gTestDirs = gTestDirsCompleteSuccess;
+ preventDistributionFiles();
+
+ // Our callback is `TestAUSHelper check-umask <umask from before updating>`.
+ // Including the umask from before updating as an argument allows to re-use
+ // the callback log checking code below. The argument is also used as the log
+ // file name, so we prefix it with "umask" so that it doesn't clash with
+ // numericfile and directory names in the update data. In particular, "2"
+ // clashes with an existing directory name in the update data, leading to
+ // failing tests.
+ let umask = Services.sysinfo.getProperty("umask");
+ gCallbackArgs = ["check-umask", `umask-${umask}`];
+
+ await setupUpdaterTest(FILE_COMPLETE_MAR, true);
+ runUpdate(STATE_SUCCEEDED, false, 0, true);
+ await checkPostUpdateAppLog();
+ checkAppBundleModTime();
+ standardInit();
+ checkPostUpdateRunningFile(true);
+ checkFilesAfterUpdateSuccess(getApplyDirFile);
+ checkUpdateLogContents(LOG_COMPLETE_SUCCESS, false, false, true);
+ await waitForUpdateXMLFiles();
+ checkUpdateManager(STATE_NONE, false, STATE_SUCCEEDED, 0, 1);
+
+ // This compares the callback arguments given, including the umask before
+ // updating, to the umask set when the app callback is launched. They should
+ // be the same.
+ checkCallbackLog(getApplyDirFile(DIR_RESOURCES + "callback_app.log"));
+}
diff --git a/toolkit/mozapps/update/tests/unit_base_updater/marFailurePartial.js b/toolkit/mozapps/update/tests/unit_base_updater/marFailurePartial.js
new file mode 100644
index 0000000000..de8db067bc
--- /dev/null
+++ b/toolkit/mozapps/update/tests/unit_base_updater/marFailurePartial.js
@@ -0,0 +1,39 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+/* General Partial MAR File Patch Apply Failure Test */
+
+async function run_test() {
+ if (!setupTestCommon()) {
+ return;
+ }
+ gTestFiles = gTestFilesPartialSuccess;
+ gTestFiles[11].originalFile = "partial.png";
+ gTestDirs = gTestDirsPartialSuccess;
+ setTestFilesAndDirsForFailure();
+ await setupUpdaterTest(FILE_PARTIAL_MAR, false);
+ // If execv is used the updater process will turn into the callback process
+ // and the updater's return code will be that of the callback process.
+ runUpdate(
+ STATE_FAILED_LOADSOURCE_ERROR_WRONG_SIZE,
+ false,
+ USE_EXECV ? 0 : 1,
+ true
+ );
+ checkAppBundleModTime();
+ standardInit();
+ checkPostUpdateRunningFile(false);
+ checkFilesAfterUpdateFailure(getApplyDirFile);
+ checkUpdateLogContents(LOG_PARTIAL_FAILURE);
+ await waitForUpdateXMLFiles();
+ checkUpdateManager(
+ STATE_NONE,
+ false,
+ STATE_FAILED,
+ LOADSOURCE_ERROR_WRONG_SIZE,
+ 1
+ );
+ checkCallbackLog();
+}
diff --git a/toolkit/mozapps/update/tests/unit_base_updater/marFileInUseStageFailureComplete_win.js b/toolkit/mozapps/update/tests/unit_base_updater/marFileInUseStageFailureComplete_win.js
new file mode 100644
index 0000000000..b93b023934
--- /dev/null
+++ b/toolkit/mozapps/update/tests/unit_base_updater/marFileInUseStageFailureComplete_win.js
@@ -0,0 +1,40 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+/* File in use complete MAR file staged patch apply failure test */
+
+async function run_test() {
+ if (!setupTestCommon()) {
+ return;
+ }
+ const STATE_AFTER_STAGE = gIsServiceTest ? STATE_APPLIED_SVC : STATE_APPLIED;
+ const STATE_AFTER_RUNUPDATE = gIsServiceTest
+ ? STATE_PENDING_SVC
+ : STATE_PENDING;
+ gTestFiles = gTestFilesCompleteSuccess;
+ gTestDirs = gTestDirsCompleteSuccess;
+ await setupUpdaterTest(FILE_COMPLETE_MAR, false);
+ await runHelperFileInUse(
+ gTestFiles[13].relPathDir + gTestFiles[13].fileName,
+ false
+ );
+ await stageUpdate(STATE_AFTER_STAGE, true);
+ checkPostUpdateRunningFile(false);
+ checkFilesAfterUpdateSuccess(getStageDirFile, true);
+ checkUpdateLogContents(LOG_COMPLETE_SUCCESS, true);
+ // Switch the application to the staged application that was updated.
+ runUpdate(STATE_AFTER_RUNUPDATE, true, 1, true);
+ await waitForHelperExit();
+ standardInit();
+ checkPostUpdateRunningFile(false);
+ setTestFilesAndDirsForFailure();
+ checkFilesAfterUpdateFailure(getApplyDirFile);
+ checkUpdateLogContains(ERR_RENAME_FILE);
+ checkUpdateLogContains(
+ ERR_MOVE_DESTDIR_7 + "\n" + STATE_FAILED_WRITE_ERROR + "\n" + CALL_QUIT
+ );
+ await waitForUpdateXMLFiles();
+ checkUpdateManager(STATE_NONE, false, STATE_AFTER_RUNUPDATE, 0, 1);
+ checkCallbackLog();
+}
diff --git a/toolkit/mozapps/update/tests/unit_base_updater/marFileInUseStageFailurePartial_win.js b/toolkit/mozapps/update/tests/unit_base_updater/marFileInUseStageFailurePartial_win.js
new file mode 100644
index 0000000000..b41da12396
--- /dev/null
+++ b/toolkit/mozapps/update/tests/unit_base_updater/marFileInUseStageFailurePartial_win.js
@@ -0,0 +1,40 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+/* File in use partial MAR file staged patch apply failure test */
+
+async function run_test() {
+ if (!setupTestCommon()) {
+ return;
+ }
+ const STATE_AFTER_STAGE = gIsServiceTest ? STATE_APPLIED_SVC : STATE_APPLIED;
+ const STATE_AFTER_RUNUPDATE = gIsServiceTest
+ ? STATE_PENDING_SVC
+ : STATE_PENDING;
+ gTestFiles = gTestFilesPartialSuccess;
+ gTestDirs = gTestDirsPartialSuccess;
+ await setupUpdaterTest(FILE_PARTIAL_MAR, false);
+ await runHelperFileInUse(
+ gTestFiles[11].relPathDir + gTestFiles[11].fileName,
+ false
+ );
+ await stageUpdate(STATE_AFTER_STAGE, true);
+ checkPostUpdateRunningFile(false);
+ checkFilesAfterUpdateSuccess(getStageDirFile, true);
+ checkUpdateLogContents(LOG_PARTIAL_SUCCESS, true);
+ // Switch the application to the staged application that was updated.
+ runUpdate(STATE_AFTER_RUNUPDATE, true, 1, true);
+ await waitForHelperExit();
+ standardInit();
+ checkPostUpdateRunningFile(false);
+ setTestFilesAndDirsForFailure();
+ checkFilesAfterUpdateFailure(getApplyDirFile);
+ checkUpdateLogContains(ERR_RENAME_FILE);
+ checkUpdateLogContains(
+ ERR_MOVE_DESTDIR_7 + "\n" + STATE_FAILED_WRITE_ERROR + "\n" + CALL_QUIT
+ );
+ await waitForUpdateXMLFiles();
+ checkUpdateManager(STATE_NONE, false, STATE_AFTER_RUNUPDATE, 0, 1);
+ checkCallbackLog();
+}
diff --git a/toolkit/mozapps/update/tests/unit_base_updater/marFileInUseSuccessComplete_win.js b/toolkit/mozapps/update/tests/unit_base_updater/marFileInUseSuccessComplete_win.js
new file mode 100644
index 0000000000..4b946ac3e4
--- /dev/null
+++ b/toolkit/mozapps/update/tests/unit_base_updater/marFileInUseSuccessComplete_win.js
@@ -0,0 +1,29 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+/* File in use complete MAR file patch apply success test */
+
+async function run_test() {
+ if (!setupTestCommon()) {
+ return;
+ }
+ gTestFiles = gTestFilesCompleteSuccess;
+ gTestDirs = gTestDirsCompleteSuccess;
+ await setupUpdaterTest(FILE_COMPLETE_MAR, false);
+ await runHelperFileInUse(
+ gTestFiles[13].relPathDir + gTestFiles[13].fileName,
+ false
+ );
+ runUpdate(STATE_SUCCEEDED, false, 0, true);
+ await waitForHelperExit();
+ await checkPostUpdateAppLog();
+ standardInit();
+ checkPostUpdateRunningFile(true);
+ checkFilesAfterUpdateSuccess(getApplyDirFile, false, true);
+ checkUpdateLogContains(ERR_BACKUP_DISCARD);
+ checkUpdateLogContains(STATE_SUCCEEDED + "\n" + CALL_QUIT);
+ await waitForUpdateXMLFiles();
+ checkUpdateManager(STATE_NONE, false, STATE_SUCCEEDED, 0, 1);
+ checkCallbackLog();
+}
diff --git a/toolkit/mozapps/update/tests/unit_base_updater/marFileInUseSuccessPartial_win.js b/toolkit/mozapps/update/tests/unit_base_updater/marFileInUseSuccessPartial_win.js
new file mode 100644
index 0000000000..15c3a1121a
--- /dev/null
+++ b/toolkit/mozapps/update/tests/unit_base_updater/marFileInUseSuccessPartial_win.js
@@ -0,0 +1,29 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+/* File in use partial MAR file patch apply success test */
+
+async function run_test() {
+ if (!setupTestCommon()) {
+ return;
+ }
+ gTestFiles = gTestFilesPartialSuccess;
+ gTestDirs = gTestDirsPartialSuccess;
+ await setupUpdaterTest(FILE_PARTIAL_MAR, false);
+ await runHelperFileInUse(
+ gTestFiles[11].relPathDir + gTestFiles[11].fileName,
+ false
+ );
+ runUpdate(STATE_SUCCEEDED, false, 0, true);
+ await waitForHelperExit();
+ await checkPostUpdateAppLog();
+ standardInit();
+ checkPostUpdateRunningFile(true);
+ checkFilesAfterUpdateSuccess(getApplyDirFile, false, true);
+ checkUpdateLogContains(ERR_BACKUP_DISCARD);
+ checkUpdateLogContains(STATE_SUCCEEDED + "\n" + CALL_QUIT);
+ await waitForUpdateXMLFiles();
+ checkUpdateManager(STATE_NONE, false, STATE_SUCCEEDED, 0, 1);
+ checkCallbackLog();
+}
diff --git a/toolkit/mozapps/update/tests/unit_base_updater/marFileLockedFailureComplete_win.js b/toolkit/mozapps/update/tests/unit_base_updater/marFileLockedFailureComplete_win.js
new file mode 100644
index 0000000000..698ccb7fe5
--- /dev/null
+++ b/toolkit/mozapps/update/tests/unit_base_updater/marFileLockedFailureComplete_win.js
@@ -0,0 +1,27 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+/* File locked complete MAR file patch apply failure test */
+
+async function run_test() {
+ if (!setupTestCommon()) {
+ return;
+ }
+ gTestFiles = gTestFilesCompleteSuccess;
+ gTestDirs = gTestDirsCompleteSuccess;
+ setTestFilesAndDirsForFailure();
+ await setupUpdaterTest(FILE_COMPLETE_MAR, false);
+ await runHelperLockFile(gTestFiles[3]);
+ runUpdate(STATE_FAILED_WRITE_ERROR, false, 1, true);
+ await waitForHelperExit();
+ standardInit();
+ checkPostUpdateRunningFile(false);
+ checkFilesAfterUpdateFailure(getApplyDirFile);
+ checkUpdateLogContains(ERR_RENAME_FILE);
+ checkUpdateLogContains(ERR_BACKUP_CREATE_7);
+ checkUpdateLogContains(STATE_FAILED_WRITE_ERROR + "\n" + CALL_QUIT);
+ await waitForUpdateXMLFiles(true, false);
+ checkUpdateManager(STATE_PENDING, true, STATE_PENDING, WRITE_ERROR, 0);
+ checkCallbackLog();
+}
diff --git a/toolkit/mozapps/update/tests/unit_base_updater/marFileLockedFailurePartial_win.js b/toolkit/mozapps/update/tests/unit_base_updater/marFileLockedFailurePartial_win.js
new file mode 100644
index 0000000000..c8c019ec5c
--- /dev/null
+++ b/toolkit/mozapps/update/tests/unit_base_updater/marFileLockedFailurePartial_win.js
@@ -0,0 +1,26 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+/* File locked partial MAR file patch apply failure test */
+
+async function run_test() {
+ if (!setupTestCommon()) {
+ return;
+ }
+ gTestFiles = gTestFilesPartialSuccess;
+ gTestDirs = gTestDirsPartialSuccess;
+ setTestFilesAndDirsForFailure();
+ await setupUpdaterTest(FILE_PARTIAL_MAR, false);
+ await runHelperLockFile(gTestFiles[2]);
+ runUpdate(STATE_FAILED_READ_ERROR, false, 1, true);
+ await waitForHelperExit();
+ standardInit();
+ checkPostUpdateRunningFile(false);
+ checkFilesAfterUpdateFailure(getApplyDirFile);
+ checkUpdateLogContains(ERR_UNABLE_OPEN_DEST);
+ checkUpdateLogContains(STATE_FAILED_READ_ERROR + "\n" + CALL_QUIT);
+ await waitForUpdateXMLFiles();
+ checkUpdateManager(STATE_NONE, false, STATE_FAILED, READ_ERROR, 1);
+ checkCallbackLog();
+}
diff --git a/toolkit/mozapps/update/tests/unit_base_updater/marFileLockedStageFailureComplete_win.js b/toolkit/mozapps/update/tests/unit_base_updater/marFileLockedStageFailureComplete_win.js
new file mode 100644
index 0000000000..7b582dbd45
--- /dev/null
+++ b/toolkit/mozapps/update/tests/unit_base_updater/marFileLockedStageFailureComplete_win.js
@@ -0,0 +1,34 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+/* File locked complete MAR file staged patch apply failure test */
+
+async function run_test() {
+ if (!setupTestCommon()) {
+ return;
+ }
+ const STATE_AFTER_STAGE = STATE_PENDING;
+ gTestFiles = gTestFilesCompleteSuccess;
+ gTestDirs = gTestDirsCompleteSuccess;
+ setTestFilesAndDirsForFailure();
+ await setupUpdaterTest(FILE_COMPLETE_MAR, false);
+ await runHelperLockFile(gTestFiles[3]);
+ await stageUpdate(STATE_AFTER_STAGE, true);
+ checkPostUpdateRunningFile(false);
+ // Files aren't checked after staging since this test locks a file which
+ // prevents reading the file.
+ checkUpdateLogContains(ERR_ENSURE_COPY);
+ // Switch the application to the staged application that was updated.
+ runUpdate(STATE_FAILED_WRITE_ERROR, false, 1, false);
+ await waitForHelperExit();
+ standardInit();
+ checkPostUpdateRunningFile(false);
+ checkFilesAfterUpdateFailure(getApplyDirFile);
+ checkUpdateLogContains(ERR_RENAME_FILE);
+ checkUpdateLogContains(ERR_BACKUP_CREATE_7);
+ checkUpdateLogContains(STATE_FAILED_WRITE_ERROR + "\n" + CALL_QUIT);
+ await waitForUpdateXMLFiles(true, false);
+ checkUpdateManager(STATE_PENDING, true, STATE_PENDING, WRITE_ERROR, 0);
+ checkCallbackLog();
+}
diff --git a/toolkit/mozapps/update/tests/unit_base_updater/marFileLockedStageFailurePartial_win.js b/toolkit/mozapps/update/tests/unit_base_updater/marFileLockedStageFailurePartial_win.js
new file mode 100644
index 0000000000..bf3abd8c37
--- /dev/null
+++ b/toolkit/mozapps/update/tests/unit_base_updater/marFileLockedStageFailurePartial_win.js
@@ -0,0 +1,33 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+/* File locked partial MAR file staged patch apply failure test */
+
+async function run_test() {
+ if (!setupTestCommon()) {
+ return;
+ }
+ const STATE_AFTER_STAGE = STATE_PENDING;
+ gTestFiles = gTestFilesPartialSuccess;
+ gTestDirs = gTestDirsPartialSuccess;
+ setTestFilesAndDirsForFailure();
+ await setupUpdaterTest(FILE_PARTIAL_MAR, false);
+ await runHelperLockFile(gTestFiles[2]);
+ await stageUpdate(STATE_AFTER_STAGE, true);
+ checkPostUpdateRunningFile(false);
+ // Files aren't checked after staging since this test locks a file which
+ // prevents reading the file.
+ checkUpdateLogContains(ERR_ENSURE_COPY);
+ // Switch the application to the staged application that was updated.
+ runUpdate(STATE_FAILED_READ_ERROR, false, 1, false);
+ await waitForHelperExit();
+ standardInit();
+ checkPostUpdateRunningFile(false);
+ checkFilesAfterUpdateFailure(getApplyDirFile);
+ checkUpdateLogContains(ERR_UNABLE_OPEN_DEST);
+ checkUpdateLogContains(STATE_FAILED_READ_ERROR + "\n" + CALL_QUIT);
+ await waitForUpdateXMLFiles();
+ checkUpdateManager(STATE_NONE, false, STATE_FAILED, READ_ERROR, 1);
+ checkCallbackLog();
+}
diff --git a/toolkit/mozapps/update/tests/unit_base_updater/marMissingUpdateSettings.js b/toolkit/mozapps/update/tests/unit_base_updater/marMissingUpdateSettings.js
new file mode 100644
index 0000000000..b0a0cfe657
--- /dev/null
+++ b/toolkit/mozapps/update/tests/unit_base_updater/marMissingUpdateSettings.js
@@ -0,0 +1,42 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+/* Test update-settings.ini missing channel MAR security check */
+
+async function run_test() {
+ if (!MOZ_VERIFY_MAR_SIGNATURE) {
+ return;
+ }
+
+ if (!setupTestCommon()) {
+ return;
+ }
+ gTestFiles = gTestFilesCompleteSuccess;
+ gTestFiles[gTestFiles.length - 2].originalContents = null;
+ gTestDirs = gTestDirsCompleteSuccess;
+ setTestFilesAndDirsForFailure();
+ await setupUpdaterTest(FILE_COMPLETE_MAR, false);
+ // If execv is used the updater process will turn into the callback process
+ // and the updater's return code will be that of the callback process.
+ runUpdate(
+ STATE_FAILED_UPDATE_SETTINGS_FILE_CHANNEL,
+ false,
+ USE_EXECV ? 0 : 1,
+ false
+ );
+ standardInit();
+ checkPostUpdateRunningFile(false);
+ checkFilesAfterUpdateFailure(getApplyDirFile);
+ checkUpdateLogContains(STATE_FAILED_UPDATE_SETTINGS_FILE_CHANNEL);
+ await waitForUpdateXMLFiles();
+ checkUpdateManager(
+ STATE_NONE,
+ false,
+ STATE_FAILED,
+ UPDATE_SETTINGS_FILE_CHANNEL,
+ 1
+ );
+ waitForFilesInUse();
+}
diff --git a/toolkit/mozapps/update/tests/unit_base_updater/marMissingUpdateSettingsStage.js b/toolkit/mozapps/update/tests/unit_base_updater/marMissingUpdateSettingsStage.js
new file mode 100644
index 0000000000..e26d2aefc3
--- /dev/null
+++ b/toolkit/mozapps/update/tests/unit_base_updater/marMissingUpdateSettingsStage.js
@@ -0,0 +1,35 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+/* Test update-settings.ini missing channel MAR security check */
+
+async function run_test() {
+ if (!MOZ_VERIFY_MAR_SIGNATURE) {
+ return;
+ }
+
+ if (!setupTestCommon()) {
+ return;
+ }
+ const STATE_AFTER_STAGE = STATE_FAILED;
+ gTestFiles = gTestFilesCompleteSuccess;
+ gTestFiles[gTestFiles.length - 2].originalContents = null;
+ gTestDirs = gTestDirsCompleteSuccess;
+ setTestFilesAndDirsForFailure();
+ await setupUpdaterTest(FILE_COMPLETE_MAR, false);
+ await stageUpdate(STATE_AFTER_STAGE, true, true);
+ checkPostUpdateRunningFile(false);
+ checkFilesAfterUpdateFailure(getApplyDirFile);
+ checkUpdateLogContains(STATE_FAILED_UPDATE_SETTINGS_FILE_CHANNEL);
+ await waitForUpdateXMLFiles();
+ checkUpdateManager(
+ STATE_NONE,
+ false,
+ STATE_FAILED,
+ UPDATE_SETTINGS_FILE_CHANNEL,
+ 1
+ );
+ waitForFilesInUse();
+}
diff --git a/toolkit/mozapps/update/tests/unit_base_updater/marPIDPersistsSuccessComplete_win.js b/toolkit/mozapps/update/tests/unit_base_updater/marPIDPersistsSuccessComplete_win.js
new file mode 100644
index 0000000000..4918fea140
--- /dev/null
+++ b/toolkit/mozapps/update/tests/unit_base_updater/marPIDPersistsSuccessComplete_win.js
@@ -0,0 +1,25 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+/* Application in use complete MAR file patch apply success test */
+
+async function run_test() {
+ if (!setupTestCommon()) {
+ return;
+ }
+ gTestFiles = gTestFilesCompleteSuccess;
+ gTestDirs = gTestDirsCompleteSuccess;
+ await setupUpdaterTest(FILE_COMPLETE_MAR, false);
+ await runHelperPIDPersists(DIR_RESOURCES + gCallbackBinFile, false);
+ runUpdate(STATE_SUCCEEDED, false, 0, true);
+ await waitForHelperExit();
+ await checkPostUpdateAppLog();
+ standardInit();
+ checkPostUpdateRunningFile(true);
+ checkFilesAfterUpdateSuccess(getApplyDirFile);
+ checkUpdateLogContains(ERR_PARENT_PID_PERSISTS);
+ await waitForUpdateXMLFiles();
+ checkUpdateManager(STATE_NONE, false, STATE_SUCCEEDED, 0, 1);
+ checkCallbackLog();
+}
diff --git a/toolkit/mozapps/update/tests/unit_base_updater/marRMRFDirFileInUseStageFailureComplete_win.js b/toolkit/mozapps/update/tests/unit_base_updater/marRMRFDirFileInUseStageFailureComplete_win.js
new file mode 100644
index 0000000000..31c5b8bd7a
--- /dev/null
+++ b/toolkit/mozapps/update/tests/unit_base_updater/marRMRFDirFileInUseStageFailureComplete_win.js
@@ -0,0 +1,43 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+/* File in use inside removed dir complete MAR file staged patch apply failure
+ test */
+
+async function run_test() {
+ if (!setupTestCommon()) {
+ return;
+ }
+ const STATE_AFTER_STAGE = gIsServiceTest ? STATE_APPLIED_SVC : STATE_APPLIED;
+ const STATE_AFTER_RUNUPDATE = gIsServiceTest
+ ? STATE_PENDING_SVC
+ : STATE_PENDING;
+ gTestFiles = gTestFilesCompleteSuccess;
+ gTestDirs = gTestDirsCompleteSuccess;
+ await setupUpdaterTest(FILE_COMPLETE_MAR, false);
+ await runHelperFileInUse(
+ gTestDirs[4].relPathDir +
+ gTestDirs[4].subDirs[0] +
+ gTestDirs[4].subDirFiles[0],
+ true
+ );
+ await stageUpdate(STATE_AFTER_STAGE, true);
+ checkPostUpdateRunningFile(false);
+ checkFilesAfterUpdateSuccess(getStageDirFile, true);
+ checkUpdateLogContents(LOG_COMPLETE_SUCCESS, true);
+ // Switch the application to the staged application that was updated.
+ runUpdate(STATE_AFTER_RUNUPDATE, true, 1, true);
+ await waitForHelperExit();
+ standardInit();
+ checkPostUpdateRunningFile(false);
+ setTestFilesAndDirsForFailure();
+ checkFilesAfterUpdateFailure(getApplyDirFile);
+ checkUpdateLogContains(ERR_RENAME_FILE);
+ checkUpdateLogContains(
+ ERR_MOVE_DESTDIR_7 + "\n" + STATE_FAILED_WRITE_ERROR + "\n" + CALL_QUIT
+ );
+ await waitForUpdateXMLFiles();
+ checkUpdateManager(STATE_NONE, false, STATE_AFTER_RUNUPDATE, 0, 1);
+ checkCallbackLog();
+}
diff --git a/toolkit/mozapps/update/tests/unit_base_updater/marRMRFDirFileInUseStageFailurePartial_win.js b/toolkit/mozapps/update/tests/unit_base_updater/marRMRFDirFileInUseStageFailurePartial_win.js
new file mode 100644
index 0000000000..b57f8c81b7
--- /dev/null
+++ b/toolkit/mozapps/update/tests/unit_base_updater/marRMRFDirFileInUseStageFailurePartial_win.js
@@ -0,0 +1,41 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+/* File in use inside removed dir partial MAR file staged patch apply failure
+ test */
+
+async function run_test() {
+ if (!setupTestCommon()) {
+ return;
+ }
+ const STATE_AFTER_STAGE = gIsServiceTest ? STATE_APPLIED_SVC : STATE_APPLIED;
+ const STATE_AFTER_RUNUPDATE = gIsServiceTest
+ ? STATE_PENDING_SVC
+ : STATE_PENDING;
+ gTestFiles = gTestFilesPartialSuccess;
+ gTestDirs = gTestDirsPartialSuccess;
+ await setupUpdaterTest(FILE_PARTIAL_MAR, false);
+ await runHelperFileInUse(
+ gTestDirs[2].relPathDir + gTestDirs[2].files[0],
+ true
+ );
+ await stageUpdate(STATE_AFTER_STAGE, true);
+ checkPostUpdateRunningFile(false);
+ checkFilesAfterUpdateSuccess(getStageDirFile, true);
+ checkUpdateLogContents(LOG_PARTIAL_SUCCESS, true);
+ // Switch the application to the staged application that was updated.
+ runUpdate(STATE_AFTER_RUNUPDATE, true, 1, true);
+ await waitForHelperExit();
+ standardInit();
+ checkPostUpdateRunningFile(false);
+ setTestFilesAndDirsForFailure();
+ checkFilesAfterUpdateFailure(getApplyDirFile);
+ checkUpdateLogContains(ERR_RENAME_FILE);
+ checkUpdateLogContains(
+ ERR_MOVE_DESTDIR_7 + "\n" + STATE_FAILED_WRITE_ERROR + "\n" + CALL_QUIT
+ );
+ await waitForUpdateXMLFiles();
+ checkUpdateManager(STATE_NONE, false, STATE_AFTER_RUNUPDATE, 0, 1);
+ checkCallbackLog();
+}
diff --git a/toolkit/mozapps/update/tests/unit_base_updater/marRMRFDirFileInUseSuccessComplete_win.js b/toolkit/mozapps/update/tests/unit_base_updater/marRMRFDirFileInUseSuccessComplete_win.js
new file mode 100644
index 0000000000..0683df0d8d
--- /dev/null
+++ b/toolkit/mozapps/update/tests/unit_base_updater/marRMRFDirFileInUseSuccessComplete_win.js
@@ -0,0 +1,31 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+/* File in use inside removed dir complete MAR file patch apply success test */
+
+async function run_test() {
+ if (!setupTestCommon()) {
+ return;
+ }
+ gTestFiles = gTestFilesCompleteSuccess;
+ gTestDirs = gTestDirsCompleteSuccess;
+ await setupUpdaterTest(FILE_COMPLETE_MAR, false);
+ await runHelperFileInUse(
+ gTestDirs[4].relPathDir +
+ gTestDirs[4].subDirs[0] +
+ gTestDirs[4].subDirFiles[0],
+ true
+ );
+ runUpdate(STATE_SUCCEEDED, false, 0, true);
+ await waitForHelperExit();
+ await checkPostUpdateAppLog();
+ standardInit();
+ checkPostUpdateRunningFile(true);
+ checkFilesAfterUpdateSuccess(getApplyDirFile, false, true);
+ checkUpdateLogContains(ERR_BACKUP_DISCARD);
+ checkUpdateLogContains(STATE_SUCCEEDED + "\n" + CALL_QUIT);
+ await waitForUpdateXMLFiles();
+ checkUpdateManager(STATE_NONE, false, STATE_SUCCEEDED, 0, 1);
+ checkCallbackLog();
+}
diff --git a/toolkit/mozapps/update/tests/unit_base_updater/marRMRFDirFileInUseSuccessPartial_win.js b/toolkit/mozapps/update/tests/unit_base_updater/marRMRFDirFileInUseSuccessPartial_win.js
new file mode 100644
index 0000000000..d4da3a5f37
--- /dev/null
+++ b/toolkit/mozapps/update/tests/unit_base_updater/marRMRFDirFileInUseSuccessPartial_win.js
@@ -0,0 +1,29 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+/* File in use inside removed dir partial MAR file patch apply success test */
+
+async function run_test() {
+ if (!setupTestCommon()) {
+ return;
+ }
+ gTestFiles = gTestFilesPartialSuccess;
+ gTestDirs = gTestDirsPartialSuccess;
+ await setupUpdaterTest(FILE_PARTIAL_MAR, false);
+ await runHelperFileInUse(
+ gTestDirs[2].relPathDir + gTestDirs[2].files[0],
+ true
+ );
+ runUpdate(STATE_SUCCEEDED, false, 0, true);
+ await waitForHelperExit();
+ await checkPostUpdateAppLog();
+ standardInit();
+ checkPostUpdateRunningFile(true);
+ checkFilesAfterUpdateSuccess(getApplyDirFile, false, true);
+ checkUpdateLogContains(ERR_BACKUP_DISCARD);
+ checkUpdateLogContains(STATE_SUCCEEDED + "\n" + CALL_QUIT);
+ await waitForUpdateXMLFiles();
+ checkUpdateManager(STATE_NONE, false, STATE_SUCCEEDED, 0, 1);
+ checkCallbackLog();
+}
diff --git a/toolkit/mozapps/update/tests/unit_base_updater/marStageFailurePartial.js b/toolkit/mozapps/update/tests/unit_base_updater/marStageFailurePartial.js
new file mode 100644
index 0000000000..a1a0de0fe4
--- /dev/null
+++ b/toolkit/mozapps/update/tests/unit_base_updater/marStageFailurePartial.js
@@ -0,0 +1,31 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+/* General Partial MAR File Staged Patch Apply Failure Test */
+
+async function run_test() {
+ if (!setupTestCommon()) {
+ return;
+ }
+ const STATE_AFTER_STAGE = STATE_FAILED;
+ gTestFiles = gTestFilesPartialSuccess;
+ gTestFiles[11].originalFile = "partial.png";
+ gTestDirs = gTestDirsPartialSuccess;
+ setTestFilesAndDirsForFailure();
+ await setupUpdaterTest(FILE_PARTIAL_MAR, false);
+ await stageUpdate(STATE_AFTER_STAGE, true, true);
+ checkPostUpdateRunningFile(false);
+ checkFilesAfterUpdateFailure(getApplyDirFile);
+ checkUpdateLogContains(ERR_LOADSOURCEFILE_FAILED);
+ await waitForUpdateXMLFiles();
+ checkUpdateManager(
+ STATE_NONE,
+ false,
+ STATE_FAILED,
+ LOADSOURCE_ERROR_WRONG_SIZE,
+ 1
+ );
+ waitForFilesInUse();
+}
diff --git a/toolkit/mozapps/update/tests/unit_base_updater/marStageSuccessComplete.js b/toolkit/mozapps/update/tests/unit_base_updater/marStageSuccessComplete.js
new file mode 100644
index 0000000000..943a45ba95
--- /dev/null
+++ b/toolkit/mozapps/update/tests/unit_base_updater/marStageSuccessComplete.js
@@ -0,0 +1,71 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+/* General Complete MAR File Staged Patch Apply Test */
+
+async function run_test() {
+ if (!setupTestCommon()) {
+ return;
+ }
+ const STATE_AFTER_STAGE = gIsServiceTest ? STATE_APPLIED_SVC : STATE_APPLIED;
+ gTestFiles = gTestFilesCompleteSuccess;
+ gTestFiles[gTestFiles.length - 1].originalContents = null;
+ gTestFiles[gTestFiles.length - 1].compareContents = "FromComplete\n";
+ gTestFiles[gTestFiles.length - 1].comparePerms = 0o644;
+ gTestDirs = gTestDirsCompleteSuccess;
+ setupSymLinks();
+ await setupUpdaterTest(FILE_COMPLETE_MAR, false);
+ await stageUpdate(STATE_AFTER_STAGE, true);
+ checkPostUpdateRunningFile(false);
+ checkFilesAfterUpdateSuccess(getStageDirFile, true);
+ checkUpdateLogContents(LOG_COMPLETE_SUCCESS, true);
+ // Switch the application to the staged application that was updated.
+ runUpdate(STATE_SUCCEEDED, true, 0, true);
+ await checkPostUpdateAppLog();
+ checkAppBundleModTime();
+ checkSymLinks();
+ standardInit();
+ checkPostUpdateRunningFile(true);
+ checkFilesAfterUpdateSuccess(getApplyDirFile, false, true);
+ checkUpdateLogContents(LOG_REPLACE_SUCCESS, false, true);
+ await waitForUpdateXMLFiles();
+ checkUpdateManager(STATE_NONE, false, STATE_SUCCEEDED, 0, 1);
+ checkCallbackLog();
+}
+
+/**
+ * Setup symlinks for the test.
+ */
+function setupSymLinks() {
+ // Don't test symlinks on Mac OS X in this test since it tends to timeout.
+ // It is tested on Mac OS X in marAppInUseStageSuccessComplete_unix.js
+ if (AppConstants.platform == "linux") {
+ removeSymlink();
+ createSymlink();
+ registerCleanupFunction(removeSymlink);
+ gTestFiles.splice(gTestFiles.length - 3, 0, {
+ description: "Readable symlink",
+ fileName: "link",
+ relPathDir: DIR_RESOURCES,
+ originalContents: "test",
+ compareContents: "test",
+ originalFile: null,
+ compareFile: null,
+ originalPerms: 0o666,
+ comparePerms: 0o666,
+ });
+ }
+}
+
+/**
+ * Checks the state of the symlinks for the test.
+ */
+function checkSymLinks() {
+ // Don't test symlinks on Mac OS X in this test since it tends to timeout.
+ // It is tested on Mac OS X in marAppInUseStageSuccessComplete_unix.js
+ if (AppConstants.platform == "linux") {
+ checkSymlink();
+ }
+}
diff --git a/toolkit/mozapps/update/tests/unit_base_updater/marStageSuccessPartial.js b/toolkit/mozapps/update/tests/unit_base_updater/marStageSuccessPartial.js
new file mode 100644
index 0000000000..dd5c240919
--- /dev/null
+++ b/toolkit/mozapps/update/tests/unit_base_updater/marStageSuccessPartial.js
@@ -0,0 +1,35 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+/* General Partial MAR File Staged Patch Apply Test */
+
+async function run_test() {
+ if (!setupTestCommon()) {
+ return;
+ }
+ const STATE_AFTER_STAGE = gIsServiceTest ? STATE_APPLIED_SVC : STATE_APPLIED;
+ gTestFiles = gTestFilesPartialSuccess;
+ gTestFiles[gTestFiles.length - 1].originalContents = null;
+ gTestFiles[gTestFiles.length - 1].compareContents = "FromPartial\n";
+ gTestFiles[gTestFiles.length - 1].comparePerms = 0o644;
+ gTestDirs = gTestDirsPartialSuccess;
+ preventDistributionFiles();
+ await setupUpdaterTest(FILE_PARTIAL_MAR, true);
+ await stageUpdate(STATE_AFTER_STAGE, true);
+ checkPostUpdateRunningFile(false);
+ checkFilesAfterUpdateSuccess(getStageDirFile, true);
+ checkUpdateLogContents(LOG_PARTIAL_SUCCESS, true, false, true);
+ // Switch the application to the staged application that was updated.
+ runUpdate(STATE_SUCCEEDED, true, 0, true);
+ await checkPostUpdateAppLog();
+ checkAppBundleModTime();
+ standardInit();
+ checkPostUpdateRunningFile(true);
+ checkFilesAfterUpdateSuccess(getApplyDirFile, false, true);
+ checkUpdateLogContents(LOG_REPLACE_SUCCESS, false, true, true);
+ await waitForUpdateXMLFiles();
+ checkUpdateManager(STATE_NONE, false, STATE_SUCCEEDED, 0, 1);
+ checkCallbackLog();
+}
diff --git a/toolkit/mozapps/update/tests/unit_base_updater/marSuccessComplete.js b/toolkit/mozapps/update/tests/unit_base_updater/marSuccessComplete.js
new file mode 100644
index 0000000000..2dd1e54f90
--- /dev/null
+++ b/toolkit/mozapps/update/tests/unit_base_updater/marSuccessComplete.js
@@ -0,0 +1,26 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+/* General Complete MAR File Patch Apply Test */
+
+async function run_test() {
+ if (!setupTestCommon()) {
+ return;
+ }
+ gTestFiles = gTestFilesCompleteSuccess;
+ gTestDirs = gTestDirsCompleteSuccess;
+ preventDistributionFiles();
+ await setupUpdaterTest(FILE_COMPLETE_MAR, true);
+ runUpdate(STATE_SUCCEEDED, false, 0, true);
+ await checkPostUpdateAppLog();
+ checkAppBundleModTime();
+ standardInit();
+ checkPostUpdateRunningFile(true);
+ checkFilesAfterUpdateSuccess(getApplyDirFile);
+ checkUpdateLogContents(LOG_COMPLETE_SUCCESS, false, false, true);
+ await waitForUpdateXMLFiles();
+ checkUpdateManager(STATE_NONE, false, STATE_SUCCEEDED, 0, 1);
+ checkCallbackLog();
+}
diff --git a/toolkit/mozapps/update/tests/unit_base_updater/marSuccessPartial.js b/toolkit/mozapps/update/tests/unit_base_updater/marSuccessPartial.js
new file mode 100644
index 0000000000..8e8e9d094a
--- /dev/null
+++ b/toolkit/mozapps/update/tests/unit_base_updater/marSuccessPartial.js
@@ -0,0 +1,29 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+/* General Partial MAR File Patch Apply Test */
+
+async function run_test() {
+ if (!setupTestCommon()) {
+ return;
+ }
+ gTestFiles = gTestFilesPartialSuccess;
+ gTestFiles[gTestFiles.length - 1].originalContents = null;
+ gTestFiles[gTestFiles.length - 1].compareContents = "FromPartial\n";
+ gTestFiles[gTestFiles.length - 1].comparePerms = 0o644;
+ gTestDirs = gTestDirsPartialSuccess;
+ // The third parameter will test that a relative path that contains a
+ // directory traversal to the post update binary doesn't execute.
+ await setupUpdaterTest(FILE_PARTIAL_MAR, false, "test/../");
+ runUpdate(STATE_SUCCEEDED, false, 0, true);
+ checkAppBundleModTime();
+ standardInit();
+ checkPostUpdateRunningFile(false);
+ checkFilesAfterUpdateSuccess(getApplyDirFile);
+ checkUpdateLogContents(LOG_PARTIAL_SUCCESS);
+ await waitForUpdateXMLFiles();
+ checkUpdateManager(STATE_NONE, false, STATE_SUCCEEDED, 0, 1);
+ checkCallbackLog();
+}
diff --git a/toolkit/mozapps/update/tests/unit_base_updater/marSuccessPartialWhileBackgroundTaskRunning.js b/toolkit/mozapps/update/tests/unit_base_updater/marSuccessPartialWhileBackgroundTaskRunning.js
new file mode 100644
index 0000000000..6ad589a32c
--- /dev/null
+++ b/toolkit/mozapps/update/tests/unit_base_updater/marSuccessPartialWhileBackgroundTaskRunning.js
@@ -0,0 +1,125 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+/* General Partial MAR File Patch Apply Test */
+
+const { setTimeout } = ChromeUtils.importESModule(
+ "resource://gre/modules/Timer.sys.mjs"
+);
+
+const { BackgroundTasksTestUtils } = ChromeUtils.importESModule(
+ "resource://testing-common/BackgroundTasksTestUtils.sys.mjs"
+);
+BackgroundTasksTestUtils.init(this);
+const do_backgroundtask = BackgroundTasksTestUtils.do_backgroundtask.bind(
+ BackgroundTasksTestUtils
+);
+
+const { PromiseUtils } = ChromeUtils.importESModule(
+ "resource://gre/modules/PromiseUtils.sys.mjs"
+);
+
+async function run_test() {
+ // Without omnijars, the background task apparatus will fail to find task
+ // definitions.
+ {
+ let omniJa = Services.dirsvc.get("XCurProcD", Ci.nsIFile);
+ omniJa.append("omni.ja");
+ if (!omniJa.exists()) {
+ Assert.ok(
+ false,
+ "This test requires a packaged build, " +
+ "run 'mach package' and then use 'mach xpcshell-test --xre-path=...'"
+ );
+ return;
+ }
+ }
+
+ if (!setupTestCommon()) {
+ return;
+ }
+
+ // `channel-prefs.js` is required for Firefox to launch, including in
+ // background task mode. The testing partial MAR updates `channel-prefs.js`
+ // to have contents `FromPartial`, which is not a valid prefs file and causes
+ // Firefox to crash. However, `channel-prefs.js` is listed as `add-if-not
+ // "defaults/pref/channel-prefs.js" "defaults/pref/channel-prefs.js"`, so it
+ // won't be updated if it already exists. The manipulations below arrange a)
+ // for the file to exist and b) for the comparison afterward to succeed.
+ gTestFiles = gTestFilesPartialSuccess;
+ let channelPrefs = gTestFiles[gTestFiles.length - 1];
+ Assert.equal("channel-prefs.js", channelPrefs.fileName);
+ let f = gGREDirOrig.clone();
+ f.append("defaults");
+ f.append("pref");
+ f.append("channel-prefs.js");
+ // `originalFile` is a relative path, so we can't just point to the one in the
+ // original GRE directory.
+ channelPrefs.originalFile = null;
+ channelPrefs.originalContents = readFile(f);
+ channelPrefs.compareContents = channelPrefs.originalContents;
+ gTestDirs = gTestDirsPartialSuccess;
+ // The third parameter will test that a relative path that contains a
+ // directory traversal to the post update binary doesn't execute.
+ await setupUpdaterTest(FILE_PARTIAL_MAR, false, "test/../", true, {
+ // We need packaged JavaScript to run background tasks.
+ requiresOmnijar: true,
+ });
+
+ // `0/00/00text2` is just a random file in the testing partial MAR that does
+ // not exist before the update and is always written by the update.
+ let exitCode;
+ exitCode = await do_backgroundtask("file_exists", {
+ extraArgs: [getApplyDirFile(DIR_RESOURCES + "0/00/00text2").path],
+ });
+ // Before updating, file doesn't exist.
+ Assert.equal(11, exitCode);
+
+ // This task will wait 10 seconds before exiting, which should overlap with
+ // the update below. We wait for some output from the wait background task,
+ // so that there is meaningful overlap.
+ let taskStarted = PromiseUtils.defer();
+ let p = do_backgroundtask("wait", {
+ onStdoutLine: (line, proc) => {
+ // This sentinel seems pretty safe: it's printed by the task itself and so
+ // there should be a straight line between future test failures and
+ // logging changes.
+ if (line.includes("runBackgroundTask: wait")) {
+ taskStarted.resolve(proc);
+ }
+ },
+ });
+ let proc = await taskStarted.promise;
+
+ runUpdate(STATE_SUCCEEDED, false, 0, true);
+
+ checkAppBundleModTime();
+ standardInit();
+ checkPostUpdateRunningFile(false);
+ checkFilesAfterUpdateSuccess(getApplyDirFile);
+ checkUpdateLogContents(LOG_PARTIAL_SUCCESS);
+ await waitForUpdateXMLFiles();
+ checkUpdateManager(STATE_NONE, false, STATE_SUCCEEDED, 0, 1);
+
+ // Once we've seen what we want, there's no need to let the task wait complete.
+ await proc.kill();
+
+ Assert.ok("Waiting for background task to die after kill()");
+ exitCode = await p;
+
+ // Windows does not support killing processes gracefully, so they will
+ // always exit with -9 there.
+ let retVal = AppConstants.platform == "win" ? -9 : -15;
+ Assert.equal(retVal, exitCode);
+
+ exitCode = await do_backgroundtask("file_exists", {
+ extraArgs: [getApplyDirFile(DIR_RESOURCES + "0/00/00text2").path],
+ });
+ // After updating, file exists.
+ Assert.equal(0, exitCode);
+
+ // This finishes the test, so must be last.
+ checkCallbackLog();
+}
diff --git a/toolkit/mozapps/update/tests/unit_base_updater/marVersionDowngrade.js b/toolkit/mozapps/update/tests/unit_base_updater/marVersionDowngrade.js
new file mode 100644
index 0000000000..6f3e26fe0f
--- /dev/null
+++ b/toolkit/mozapps/update/tests/unit_base_updater/marVersionDowngrade.js
@@ -0,0 +1,41 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+/* Test version downgrade MAR security check */
+
+async function run_test() {
+ if (!MOZ_VERIFY_MAR_SIGNATURE) {
+ return;
+ }
+
+ if (!setupTestCommon()) {
+ return;
+ }
+ gTestFiles = gTestFilesCompleteSuccess;
+ gTestDirs = gTestDirsCompleteSuccess;
+ setTestFilesAndDirsForFailure();
+ await setupUpdaterTest(FILE_OLD_VERSION_MAR, false);
+ // If execv is used the updater process will turn into the callback process
+ // and the updater's return code will be that of the callback process.
+ runUpdate(
+ STATE_FAILED_VERSION_DOWNGRADE_ERROR,
+ false,
+ USE_EXECV ? 0 : 1,
+ false
+ );
+ standardInit();
+ checkPostUpdateRunningFile(false);
+ checkFilesAfterUpdateFailure(getApplyDirFile);
+ checkUpdateLogContains(STATE_FAILED_VERSION_DOWNGRADE_ERROR);
+ await waitForUpdateXMLFiles();
+ checkUpdateManager(
+ STATE_NONE,
+ false,
+ STATE_FAILED,
+ VERSION_DOWNGRADE_ERROR,
+ 1
+ );
+ waitForFilesInUse();
+}
diff --git a/toolkit/mozapps/update/tests/unit_base_updater/marWrongChannel.js b/toolkit/mozapps/update/tests/unit_base_updater/marWrongChannel.js
new file mode 100644
index 0000000000..d31188dcca
--- /dev/null
+++ b/toolkit/mozapps/update/tests/unit_base_updater/marWrongChannel.js
@@ -0,0 +1,43 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+/* Test product/channel MAR security check */
+
+async function run_test() {
+ if (!MOZ_VERIFY_MAR_SIGNATURE) {
+ return;
+ }
+
+ if (!setupTestCommon()) {
+ return;
+ }
+ gTestFiles = gTestFilesCompleteSuccess;
+ gTestFiles[gTestFiles.length - 2].originalContents =
+ UPDATE_SETTINGS_CONTENTS.replace("xpcshell-test", "wrong-channel");
+ gTestDirs = gTestDirsCompleteSuccess;
+ setTestFilesAndDirsForFailure();
+ await setupUpdaterTest(FILE_COMPLETE_MAR, false);
+ // If execv is used the updater process will turn into the callback process
+ // and the updater's return code will be that of the callback process.
+ runUpdate(
+ STATE_FAILED_MAR_CHANNEL_MISMATCH_ERROR,
+ false,
+ USE_EXECV ? 0 : 1,
+ false
+ );
+ standardInit();
+ checkPostUpdateRunningFile(false);
+ checkFilesAfterUpdateFailure(getApplyDirFile);
+ checkUpdateLogContains(STATE_FAILED_MAR_CHANNEL_MISMATCH_ERROR);
+ await waitForUpdateXMLFiles();
+ checkUpdateManager(
+ STATE_NONE,
+ false,
+ STATE_FAILED,
+ MAR_CHANNEL_MISMATCH_ERROR,
+ 1
+ );
+ waitForFilesInUse();
+}
diff --git a/toolkit/mozapps/update/tests/unit_base_updater/marWrongChannelStage.js b/toolkit/mozapps/update/tests/unit_base_updater/marWrongChannelStage.js
new file mode 100644
index 0000000000..4d512fd12a
--- /dev/null
+++ b/toolkit/mozapps/update/tests/unit_base_updater/marWrongChannelStage.js
@@ -0,0 +1,36 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+/* Test product/channel MAR security check */
+
+async function run_test() {
+ if (!MOZ_VERIFY_MAR_SIGNATURE) {
+ return;
+ }
+
+ if (!setupTestCommon()) {
+ return;
+ }
+ const STATE_AFTER_STAGE = STATE_FAILED;
+ gTestFiles = gTestFilesCompleteSuccess;
+ gTestFiles[gTestFiles.length - 2].originalContents =
+ UPDATE_SETTINGS_CONTENTS.replace("xpcshell-test", "wrong-channel");
+ gTestDirs = gTestDirsCompleteSuccess;
+ setTestFilesAndDirsForFailure();
+ await setupUpdaterTest(FILE_COMPLETE_MAR, false);
+ await stageUpdate(STATE_AFTER_STAGE, true, true);
+ checkPostUpdateRunningFile(false);
+ checkFilesAfterUpdateFailure(getApplyDirFile);
+ checkUpdateLogContains(STATE_FAILED_MAR_CHANNEL_MISMATCH_ERROR);
+ await waitForUpdateXMLFiles();
+ checkUpdateManager(
+ STATE_NONE,
+ false,
+ STATE_FAILED,
+ MAR_CHANNEL_MISMATCH_ERROR,
+ 1
+ );
+ waitForFilesInUse();
+}
diff --git a/toolkit/mozapps/update/tests/unit_base_updater/xpcshell.ini b/toolkit/mozapps/update/tests/unit_base_updater/xpcshell.ini
new file mode 100644
index 0000000000..84325c4312
--- /dev/null
+++ b/toolkit/mozapps/update/tests/unit_base_updater/xpcshell.ini
@@ -0,0 +1,136 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+# Tests that require the updater binary.
+
+[DEFAULT]
+tags = appupdate
+head = head_update.js
+skip-if =
+ (os == 'win' && (ccov || msix)) # Our updater is disabled in MSIX builds
+support-files =
+ ../data/shared.js
+ ../data/sharedUpdateXML.js
+ ../data/xpcshellUtilsAUS.js
+
+[invalidArgCallbackFileNotInInstallDirFailure.js]
+[invalidArgCallbackFilePathTooLongFailure.js]
+[invalidArgInstallDirPathTooLongFailure.js]
+[invalidArgInstallDirPathTraversalFailure.js]
+[invalidArgInstallWorkingDirPathNotSameFailure_win.js]
+skip-if = os != 'win'
+reason = Windows only test
+[invalidArgPatchDirPathTraversalFailure.js]
+[invalidArgStageDirNotInInstallDirFailure_win.js]
+skip-if = os != 'win'
+reason = Windows only test
+[invalidArgWorkingDirPathLocalUNCFailure_win.js]
+skip-if = os != 'win'
+reason = Windows only test
+[invalidArgWorkingDirPathRelativeFailure.js]
+[marSuccessComplete.js]
+[marSuccessPartial.js]
+[marSuccessPartialWhileBackgroundTaskRunning.js]
+skip-if =
+ apple_silicon # Bug 1754931
+ apple_catalina # Bug 1754931
+[marFailurePartial.js]
+[marStageSuccessComplete.js]
+skip-if =
+ apple_silicon # bug 1707753
+ apple_catalina # Bug 1713329
+[marStageSuccessPartial.js]
+skip-if =
+ apple_silicon # bug 1707753
+ apple_catalina # Bug 1713329
+[marVersionDowngrade.js]
+[marMissingUpdateSettings.js]
+[marMissingUpdateSettingsStage.js]
+[marWrongChannel.js]
+[marWrongChannelStage.js]
+[marStageFailurePartial.js]
+[marCallbackAppSuccessComplete_win.js]
+skip-if = os != 'win'
+reason = Windows only test
+[marCallbackAppSuccessPartial_win.js]
+skip-if = os != 'win'
+reason = Windows only test
+[marCallbackAppStageSuccessComplete_win.js]
+skip-if = os != 'win'
+reason = Windows only test
+[marCallbackAppStageSuccessPartial_win.js]
+skip-if = os != 'win'
+reason = Windows only test
+[marCallbackUmask_unix.js]
+skip-if =
+ os == 'win' # not a Windows test
+reason = Unix only test
+[marAppInUseBackgroundTaskFailure_win.js]
+skip-if = os != 'win'
+reason = Windows only test
+[marAppInUseSuccessComplete.js]
+[marAppInUseStageSuccessComplete_unix.js]
+skip-if =
+ os == 'win' # not a Windows test
+ apple_silicon # bug 1707753
+ apple_catalina # Bug 1713329
+[marAppInUseStageFailureComplete_win.js]
+skip-if = os != 'win'
+reason = Windows only test
+[marFileLockedFailureComplete_win.js]
+skip-if = os != 'win'
+reason = Windows only test
+[marFileLockedFailurePartial_win.js]
+skip-if = os != 'win'
+reason = Windows only test
+[marFileLockedStageFailureComplete_win.js]
+skip-if = os != 'win'
+reason = Windows only test
+[marFileLockedStageFailurePartial_win.js]
+skip-if = os != 'win'
+reason = Windows only test
+[marFileInUseSuccessComplete_win.js]
+skip-if = os != 'win'
+reason = Windows only test
+[marFileInUseSuccessPartial_win.js]
+skip-if = os != 'win'
+reason = Windows only test
+[marRMRFDirFileInUseSuccessComplete_win.js]
+skip-if = os != 'win'
+reason = Windows only test
+[marRMRFDirFileInUseSuccessPartial_win.js]
+skip-if = os != 'win'
+reason = Windows only test
+[marFileInUseStageFailureComplete_win.js]
+skip-if = os != 'win'
+reason = Windows only test
+[marFileInUseStageFailurePartial_win.js]
+skip-if = os != 'win'
+reason = Windows only test
+[marRMRFDirFileInUseStageFailureComplete_win.js]
+skip-if = os != 'win'
+reason = Windows only test
+[marRMRFDirFileInUseStageFailurePartial_win.js]
+skip-if = os != 'win'
+reason = Windows only test
+[marPIDPersistsSuccessComplete_win.js]
+skip-if = os != 'win'
+reason = Windows only test
+[marAppApplyDirLockedStageFailure_win.js]
+skip-if = os != 'win'
+reason = Windows only test
+[marAppApplyUpdateAppBinInUseStageSuccess_win.js]
+skip-if = os != 'win'
+reason = Windows only test
+[marAppApplyUpdateSuccess.js]
+skip-if =
+ apple_silicon # bug 1724579
+[marAppApplyUpdateStageSuccess.js]
+skip-if =
+ apple_silicon # bug 1707753
+ apple_catalina # Bug 1713329
+[marAppApplyUpdateStageOldVersionFailure.js]
+[marAppApplyUpdateSkippedWriteAccess_win.js]
+skip-if = os != 'win'
+reason = Windows only test
diff --git a/toolkit/mozapps/update/tests/unit_service_updater/bootstrapSvc.js b/toolkit/mozapps/update/tests/unit_service_updater/bootstrapSvc.js
new file mode 100644
index 0000000000..57eb630248
--- /dev/null
+++ b/toolkit/mozapps/update/tests/unit_service_updater/bootstrapSvc.js
@@ -0,0 +1,23 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+/* Bootstrap the tests using the service by installing our own version of the service */
+
+async function run_test() {
+ if (!setupTestCommon()) {
+ return;
+ }
+ // We don't actually care if the MAR has any data, we only care about the
+ // application return code and update.status result.
+ gTestFiles = gTestFilesCommon;
+ gTestDirs = [];
+ await setupUpdaterTest(FILE_COMPLETE_MAR, null);
+ runUpdate(STATE_SUCCEEDED, false, 0, true);
+ checkFilesAfterUpdateSuccess(getApplyDirFile, false, false);
+
+ // We need to check the service log even though this is a bootstrap
+ // because the app bin could be in use by this test by the time the next
+ // test runs.
+ checkCallbackLog();
+}
diff --git a/toolkit/mozapps/update/tests/unit_service_updater/checkUpdaterSigSvc.js b/toolkit/mozapps/update/tests/unit_service_updater/checkUpdaterSigSvc.js
new file mode 100644
index 0000000000..d6e1753f35
--- /dev/null
+++ b/toolkit/mozapps/update/tests/unit_service_updater/checkUpdaterSigSvc.js
@@ -0,0 +1,53 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+/**
+ * We skip authenticode cert checks from the service udpates
+ * so that we can use updater-xpcshell with the wrong certs for testing.
+ * This tests that code path. */
+
+function run_test() {
+ if (!IS_AUTHENTICODE_CHECK_ENABLED) {
+ return;
+ }
+
+ let binDir = getGREBinDir();
+ let maintenanceServiceBin = binDir.clone();
+ maintenanceServiceBin.append(FILE_MAINTENANCE_SERVICE_BIN);
+
+ let updaterBin = binDir.clone();
+ updaterBin.append(FILE_UPDATER_BIN);
+
+ debugDump(
+ "Launching maintenance service bin: " +
+ maintenanceServiceBin.path +
+ " to check updater: " +
+ updaterBin.path +
+ " signature."
+ );
+
+ // Bypass the manifest and run as invoker
+ Services.env.set("__COMPAT_LAYER", "RunAsInvoker");
+
+ let dummyInstallPath = "---";
+ let maintenanceServiceBinArgs = [
+ "check-cert",
+ dummyInstallPath,
+ updaterBin.path,
+ ];
+ let maintenanceServiceBinProcess = Cc[
+ "@mozilla.org/process/util;1"
+ ].createInstance(Ci.nsIProcess);
+ maintenanceServiceBinProcess.init(maintenanceServiceBin);
+ maintenanceServiceBinProcess.run(
+ true,
+ maintenanceServiceBinArgs,
+ maintenanceServiceBinArgs.length
+ );
+ Assert.equal(
+ maintenanceServiceBinProcess.exitValue,
+ 0,
+ "the maintenance service exit value should be 0"
+ );
+}
diff --git a/toolkit/mozapps/update/tests/unit_service_updater/fallbackOnSvcFailure.js b/toolkit/mozapps/update/tests/unit_service_updater/fallbackOnSvcFailure.js
new file mode 100644
index 0000000000..d09ea7b448
--- /dev/null
+++ b/toolkit/mozapps/update/tests/unit_service_updater/fallbackOnSvcFailure.js
@@ -0,0 +1,38 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+/**
+ * If updating with the maintenance service fails in a way specific to the
+ * maintenance service, we should fall back to not using the maintenance
+ * service, which should succeed. This test ensures that that happens as
+ * expected.
+ */
+
+async function run_test() {
+ if (!setupTestCommon()) {
+ return;
+ }
+ // This variable forces the service to fail by having the updater pass it the
+ // wrong number of arguments. Then we can verify that the fallback happens
+ // properly.
+ gEnvForceServiceFallback = true;
+
+ gTestFiles = gTestFilesCompleteSuccess;
+ gTestDirs = gTestDirsCompleteSuccess;
+ preventDistributionFiles();
+ await setupUpdaterTest(FILE_COMPLETE_MAR, true);
+ // It's very important that we pass in true for aCheckSvcLog (4th param),
+ // because otherwise we may not have used the service at all, so we wouldn't
+ // really check that we fell back (to not using the service) properly.
+ runUpdate(STATE_SUCCEEDED, false, 0, true);
+ await checkPostUpdateAppLog();
+ checkAppBundleModTime();
+ standardInit();
+ checkPostUpdateRunningFile(true);
+ checkFilesAfterUpdateSuccess(getApplyDirFile);
+ await waitForUpdateXMLFiles();
+ checkUpdateManager(STATE_NONE, false, STATE_SUCCEEDED, 0, 1);
+ checkCallbackLog();
+}
diff --git a/toolkit/mozapps/update/tests/unit_service_updater/head_update.js b/toolkit/mozapps/update/tests/unit_service_updater/head_update.js
new file mode 100644
index 0000000000..8d30c09e4f
--- /dev/null
+++ b/toolkit/mozapps/update/tests/unit_service_updater/head_update.js
@@ -0,0 +1,7 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+/* import-globals-from ../data/xpcshellUtilsAUS.js */
+load("xpcshellUtilsAUS.js");
+gIsServiceTest = true;
diff --git a/toolkit/mozapps/update/tests/unit_service_updater/invalidArgInstallDirPathTooLongFailureSvc.js b/toolkit/mozapps/update/tests/unit_service_updater/invalidArgInstallDirPathTooLongFailureSvc.js
new file mode 100644
index 0000000000..c2746e2bd1
--- /dev/null
+++ b/toolkit/mozapps/update/tests/unit_service_updater/invalidArgInstallDirPathTooLongFailureSvc.js
@@ -0,0 +1,53 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+/* Too long install directory path failure test */
+
+async function run_test() {
+ if (!setupTestCommon()) {
+ return;
+ }
+ const STATE_AFTER_RUNUPDATE = gIsServiceTest
+ ? STATE_FAILED_SERVICE_INVALID_INSTALL_DIR_PATH_ERROR
+ : STATE_FAILED_INVALID_INSTALL_DIR_PATH_ERROR;
+ gTestFiles = gTestFilesCompleteSuccess;
+ gTestDirs = gTestDirsCompleteSuccess;
+ setTestFilesAndDirsForFailure();
+ await setupUpdaterTest(FILE_COMPLETE_MAR, false);
+ let path = "123456789";
+ if (AppConstants.platform == "win") {
+ path = "\\" + path;
+ path = path.repeat(30); // 300 characters
+ path = "C:" + path;
+ } else {
+ path = "/" + path;
+ path = path.repeat(1000); // 10000 characters
+ }
+ runUpdate(STATE_AFTER_RUNUPDATE, false, 1, true, null, path, null, null);
+ standardInit();
+ checkPostUpdateRunningFile(false);
+ checkFilesAfterUpdateFailure(getApplyDirFile);
+ await waitForUpdateXMLFiles();
+ if (gIsServiceTest) {
+ // The invalid argument service tests launch the maintenance service
+ // directly so the unelevated updater doesn't handle the invalid argument.
+ // By doing this it is possible to test that the maintenance service
+ // properly handles the invalid argument but since the updater isn't used to
+ // launch the maintenance service the update.status file isn't copied from
+ // the secure log directory to the patch directory and the update manager
+ // won't read the failure from the update.status file.
+ checkUpdateManager(STATE_NONE, false, STATE_PENDING_SVC, 0, 1);
+ } else {
+ checkUpdateManager(
+ STATE_NONE,
+ false,
+ STATE_FAILED,
+ INVALID_INSTALL_DIR_PATH_ERROR,
+ 1
+ );
+ }
+
+ waitForFilesInUse();
+}
diff --git a/toolkit/mozapps/update/tests/unit_service_updater/invalidArgInstallDirPathTraversalFailureSvc.js b/toolkit/mozapps/update/tests/unit_service_updater/invalidArgInstallDirPathTraversalFailureSvc.js
new file mode 100644
index 0000000000..b611fac972
--- /dev/null
+++ b/toolkit/mozapps/update/tests/unit_service_updater/invalidArgInstallDirPathTraversalFailureSvc.js
@@ -0,0 +1,50 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+/* Install directory path traversal failure test */
+
+async function run_test() {
+ if (!setupTestCommon()) {
+ return;
+ }
+ const STATE_AFTER_RUNUPDATE = gIsServiceTest
+ ? STATE_FAILED_SERVICE_INVALID_INSTALL_DIR_PATH_ERROR
+ : STATE_FAILED_INVALID_INSTALL_DIR_PATH_ERROR;
+ gTestFiles = gTestFilesCompleteSuccess;
+ gTestDirs = gTestDirsCompleteSuccess;
+ setTestFilesAndDirsForFailure();
+ await setupUpdaterTest(FILE_COMPLETE_MAR, false);
+ let path = "123456789";
+ if (AppConstants.platform == "win") {
+ path = "C:\\" + path + "\\..\\" + path;
+ } else {
+ path = "/" + path + "/../" + path;
+ }
+ runUpdate(STATE_AFTER_RUNUPDATE, false, 1, true, null, path, null, null);
+ standardInit();
+ checkPostUpdateRunningFile(false);
+ checkFilesAfterUpdateFailure(getApplyDirFile);
+ await waitForUpdateXMLFiles();
+ if (gIsServiceTest) {
+ // The invalid argument service tests launch the maintenance service
+ // directly so the unelevated updater doesn't handle the invalid argument.
+ // By doing this it is possible to test that the maintenance service
+ // properly handles the invalid argument but since the updater isn't used to
+ // launch the maintenance service the update.status file isn't copied from
+ // the secure log directory to the patch directory and the update manager
+ // won't read the failure from the update.status file.
+ checkUpdateManager(STATE_NONE, false, STATE_PENDING_SVC, 0, 1);
+ } else {
+ checkUpdateManager(
+ STATE_NONE,
+ false,
+ STATE_FAILED,
+ INVALID_INSTALL_DIR_PATH_ERROR,
+ 1
+ );
+ }
+
+ waitForFilesInUse();
+}
diff --git a/toolkit/mozapps/update/tests/unit_service_updater/invalidArgInstallWorkingDirPathNotSameFailureSvc_win.js b/toolkit/mozapps/update/tests/unit_service_updater/invalidArgInstallWorkingDirPathNotSameFailureSvc_win.js
new file mode 100644
index 0000000000..0506d100fd
--- /dev/null
+++ b/toolkit/mozapps/update/tests/unit_service_updater/invalidArgInstallWorkingDirPathNotSameFailureSvc_win.js
@@ -0,0 +1,45 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+/* Different install and working directories for a regular update failure */
+
+async function run_test() {
+ if (!setupTestCommon()) {
+ return;
+ }
+ const STATE_AFTER_RUNUPDATE = gIsServiceTest
+ ? STATE_FAILED_SERVICE_INVALID_APPLYTO_DIR_ERROR
+ : STATE_FAILED_INVALID_APPLYTO_DIR_ERROR;
+ gTestFiles = gTestFilesCompleteSuccess;
+ gTestDirs = gTestDirsCompleteSuccess;
+ setTestFilesAndDirsForFailure();
+ await setupUpdaterTest(FILE_COMPLETE_MAR, false);
+ let path = getApplyDirFile("..", false).path;
+ runUpdate(STATE_AFTER_RUNUPDATE, false, 1, true, null, null, path, null);
+ standardInit();
+ checkPostUpdateRunningFile(false);
+ checkFilesAfterUpdateFailure(getApplyDirFile);
+ await waitForUpdateXMLFiles();
+ if (gIsServiceTest) {
+ // The invalid argument service tests launch the maintenance service
+ // directly so the unelevated updater doesn't handle the invalid argument.
+ // By doing this it is possible to test that the maintenance service
+ // properly handles the invalid argument but since the updater isn't used to
+ // launch the maintenance service the update.status file isn't copied from
+ // the secure log directory to the patch directory and the update manager
+ // won't read the failure from the update.status file.
+ checkUpdateManager(STATE_NONE, false, STATE_PENDING_SVC, 0, 1);
+ } else {
+ checkUpdateManager(
+ STATE_NONE,
+ false,
+ STATE_FAILED,
+ INVALID_APPLYTO_DIR_ERROR,
+ 1
+ );
+ }
+
+ waitForFilesInUse();
+}
diff --git a/toolkit/mozapps/update/tests/unit_service_updater/invalidArgPatchDirPathSuffixFailureSvc.js b/toolkit/mozapps/update/tests/unit_service_updater/invalidArgPatchDirPathSuffixFailureSvc.js
new file mode 100644
index 0000000000..3eb792ecb4
--- /dev/null
+++ b/toolkit/mozapps/update/tests/unit_service_updater/invalidArgPatchDirPathSuffixFailureSvc.js
@@ -0,0 +1,27 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+/* Patch directory path must end with \updates\0 failure test */
+
+async function run_test() {
+ if (!setupTestCommon()) {
+ return;
+ }
+ const STATE_AFTER_RUNUPDATE = gIsServiceTest
+ ? STATE_PENDING_SVC
+ : STATE_PENDING;
+ gTestFiles = gTestFilesCompleteSuccess;
+ gTestDirs = gTestDirsCompleteSuccess;
+ setTestFilesAndDirsForFailure();
+ await setupUpdaterTest(FILE_COMPLETE_MAR, false);
+ let path = getUpdateDirFile(DIR_PATCH).parent.path;
+ runUpdate(STATE_AFTER_RUNUPDATE, false, 1, true, path, null, null, null);
+ standardInit();
+ checkPostUpdateRunningFile(false);
+ checkFilesAfterUpdateFailure(getApplyDirFile);
+ await waitForUpdateXMLFiles();
+ checkUpdateManager(STATE_NONE, false, STATE_AFTER_RUNUPDATE, 0, 1);
+ waitForFilesInUse();
+}
diff --git a/toolkit/mozapps/update/tests/unit_service_updater/invalidArgPatchDirPathTraversalFailureSvc.js b/toolkit/mozapps/update/tests/unit_service_updater/invalidArgPatchDirPathTraversalFailureSvc.js
new file mode 100644
index 0000000000..cf053e3ff5
--- /dev/null
+++ b/toolkit/mozapps/update/tests/unit_service_updater/invalidArgPatchDirPathTraversalFailureSvc.js
@@ -0,0 +1,32 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+/* Patch directory path traversal failure test */
+
+async function run_test() {
+ if (!setupTestCommon()) {
+ return;
+ }
+ const STATE_AFTER_RUNUPDATE = gIsServiceTest
+ ? STATE_PENDING_SVC
+ : STATE_PENDING;
+ gTestFiles = gTestFilesCompleteSuccess;
+ gTestDirs = gTestDirsCompleteSuccess;
+ setTestFilesAndDirsForFailure();
+ await setupUpdaterTest(FILE_COMPLETE_MAR, false);
+ let path = getUpdateDirFile(DIR_PATCH);
+ if (AppConstants.platform == "win") {
+ path = path + "\\..\\";
+ } else {
+ path = path + "/../";
+ }
+ runUpdate(STATE_AFTER_RUNUPDATE, false, 1, true, path, null, null, null);
+ standardInit();
+ checkPostUpdateRunningFile(false);
+ checkFilesAfterUpdateFailure(getApplyDirFile);
+ await waitForUpdateXMLFiles();
+ checkUpdateManager(STATE_NONE, false, STATE_AFTER_RUNUPDATE, 0, 1);
+ waitForFilesInUse();
+}
diff --git a/toolkit/mozapps/update/tests/unit_service_updater/invalidArgStageDirNotInInstallDirFailureSvc_win.js b/toolkit/mozapps/update/tests/unit_service_updater/invalidArgStageDirNotInInstallDirFailureSvc_win.js
new file mode 100644
index 0000000000..4624179bfd
--- /dev/null
+++ b/toolkit/mozapps/update/tests/unit_service_updater/invalidArgStageDirNotInInstallDirFailureSvc_win.js
@@ -0,0 +1,45 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+/* Different install and working directories for a regular update failure */
+
+async function run_test() {
+ if (!setupTestCommon()) {
+ return;
+ }
+ const STATE_AFTER_RUNUPDATE = gIsServiceTest
+ ? STATE_FAILED_SERVICE_INVALID_APPLYTO_DIR_STAGED_ERROR
+ : STATE_FAILED_INVALID_APPLYTO_DIR_STAGED_ERROR;
+ gTestFiles = gTestFilesCompleteSuccess;
+ gTestDirs = gTestDirsCompleteSuccess;
+ setTestFilesAndDirsForFailure();
+ await setupUpdaterTest(FILE_COMPLETE_MAR, false);
+ let path = getApplyDirFile("..", false).path;
+ runUpdate(STATE_AFTER_RUNUPDATE, true, 1, true, null, null, path, null);
+ standardInit();
+ checkPostUpdateRunningFile(false);
+ checkFilesAfterUpdateFailure(getApplyDirFile);
+ await waitForUpdateXMLFiles();
+ if (gIsServiceTest) {
+ // The invalid argument service tests launch the maintenance service
+ // directly so the unelevated updater doesn't handle the invalid argument.
+ // By doing this it is possible to test that the maintenance service
+ // properly handles the invalid argument but since the updater isn't used to
+ // launch the maintenance service the update.status file isn't copied from
+ // the secure log directory to the patch directory and the update manager
+ // won't read the failure from the update.status file.
+ checkUpdateManager(STATE_NONE, false, STATE_PENDING_SVC, 0, 1);
+ } else {
+ checkUpdateManager(
+ STATE_NONE,
+ false,
+ STATE_FAILED,
+ INVALID_APPLYTO_DIR_STAGED_ERROR,
+ 1
+ );
+ }
+
+ waitForFilesInUse();
+}
diff --git a/toolkit/mozapps/update/tests/unit_service_updater/invalidArgWorkingDirPathLocalUNCFailureSvc_win.js b/toolkit/mozapps/update/tests/unit_service_updater/invalidArgWorkingDirPathLocalUNCFailureSvc_win.js
new file mode 100644
index 0000000000..7c0af26e37
--- /dev/null
+++ b/toolkit/mozapps/update/tests/unit_service_updater/invalidArgWorkingDirPathLocalUNCFailureSvc_win.js
@@ -0,0 +1,45 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+/* Working directory path local UNC failure test */
+
+async function run_test() {
+ if (!setupTestCommon()) {
+ return;
+ }
+ const STATE_AFTER_RUNUPDATE = gIsServiceTest
+ ? STATE_FAILED_SERVICE_INVALID_WORKING_DIR_PATH_ERROR
+ : STATE_FAILED_INVALID_WORKING_DIR_PATH_ERROR;
+ gTestFiles = gTestFilesCompleteSuccess;
+ gTestDirs = gTestDirsCompleteSuccess;
+ setTestFilesAndDirsForFailure();
+ await setupUpdaterTest(FILE_COMPLETE_MAR, false);
+ let path = "\\\\.\\" + getApplyDirFile(null, false).path;
+ runUpdate(STATE_AFTER_RUNUPDATE, false, 1, true, null, null, path, null);
+ standardInit();
+ checkPostUpdateRunningFile(false);
+ checkFilesAfterUpdateFailure(getApplyDirFile);
+ await waitForUpdateXMLFiles();
+ if (gIsServiceTest) {
+ // The invalid argument service tests launch the maintenance service
+ // directly so the unelevated updater doesn't handle the invalid argument.
+ // By doing this it is possible to test that the maintenance service
+ // properly handles the invalid argument but since the updater isn't used to
+ // launch the maintenance service the update.status file isn't copied from
+ // the secure log directory to the patch directory and the update manager
+ // won't read the failure from the update.status file.
+ checkUpdateManager(STATE_NONE, false, STATE_PENDING_SVC, 0, 1);
+ } else {
+ checkUpdateManager(
+ STATE_NONE,
+ false,
+ STATE_FAILED,
+ INVALID_WORKING_DIR_PATH_ERROR,
+ 1
+ );
+ }
+
+ waitForFilesInUse();
+}
diff --git a/toolkit/mozapps/update/tests/unit_service_updater/invalidArgWorkingDirPathRelativeFailureSvc.js b/toolkit/mozapps/update/tests/unit_service_updater/invalidArgWorkingDirPathRelativeFailureSvc.js
new file mode 100644
index 0000000000..cfbd9eaec9
--- /dev/null
+++ b/toolkit/mozapps/update/tests/unit_service_updater/invalidArgWorkingDirPathRelativeFailureSvc.js
@@ -0,0 +1,44 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+/* Relative working directory path failure test */
+
+async function run_test() {
+ if (!setupTestCommon()) {
+ return;
+ }
+ const STATE_AFTER_RUNUPDATE = gIsServiceTest
+ ? STATE_FAILED_SERVICE_INVALID_WORKING_DIR_PATH_ERROR
+ : STATE_FAILED_INVALID_WORKING_DIR_PATH_ERROR;
+ gTestFiles = gTestFilesCompleteSuccess;
+ gTestDirs = gTestDirsCompleteSuccess;
+ setTestFilesAndDirsForFailure();
+ await setupUpdaterTest(FILE_COMPLETE_MAR, false);
+ runUpdate(STATE_AFTER_RUNUPDATE, false, 1, true, null, null, "test", null);
+ standardInit();
+ checkPostUpdateRunningFile(false);
+ checkFilesAfterUpdateFailure(getApplyDirFile);
+ await waitForUpdateXMLFiles();
+ if (gIsServiceTest) {
+ // The invalid argument service tests launch the maintenance service
+ // directly so the unelevated updater doesn't handle the invalid argument.
+ // By doing this it is possible to test that the maintenance service
+ // properly handles the invalid argument but since the updater isn't used to
+ // launch the maintenance service the update.status file isn't copied from
+ // the secure log directory to the patch directory and the update manager
+ // won't read the failure from the update.status file.
+ checkUpdateManager(STATE_NONE, false, STATE_PENDING_SVC, 0, 1);
+ } else {
+ checkUpdateManager(
+ STATE_NONE,
+ false,
+ STATE_FAILED,
+ INVALID_WORKING_DIR_PATH_ERROR,
+ 1
+ );
+ }
+
+ waitForFilesInUse();
+}
diff --git a/toolkit/mozapps/update/tests/unit_service_updater/marAppApplyDirLockedStageFailureSvc_win.js b/toolkit/mozapps/update/tests/unit_service_updater/marAppApplyDirLockedStageFailureSvc_win.js
new file mode 100644
index 0000000000..9280a0736e
--- /dev/null
+++ b/toolkit/mozapps/update/tests/unit_service_updater/marAppApplyDirLockedStageFailureSvc_win.js
@@ -0,0 +1,29 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+/**
+ * Test applying an update by staging an update and launching an application to
+ * apply it.
+ */
+
+async function run_test() {
+ if (!setupTestCommon()) {
+ return;
+ }
+ const STATE_AFTER_STAGE = STATE_PENDING_SVC;
+ gTestFiles = gTestFilesCompleteSuccess;
+ gTestDirs = gTestDirsCompleteSuccess;
+ setTestFilesAndDirsForFailure();
+ createUpdateInProgressLockFile(getGREBinDir());
+ await setupUpdaterTest(FILE_COMPLETE_MAR, false);
+ await stageUpdate(STATE_AFTER_STAGE, false);
+ removeUpdateInProgressLockFile(getGREBinDir());
+ checkPostUpdateRunningFile(false);
+ checkFilesAfterUpdateFailure(getApplyDirFile);
+ checkUpdateLogContains(PERFORMING_STAGED_UPDATE);
+ checkUpdateLogContains(ERR_UPDATE_IN_PROGRESS);
+ await waitForUpdateXMLFiles(true, false);
+ checkUpdateManager(STATE_AFTER_STAGE, true, STATE_AFTER_STAGE, 0, 0);
+ waitForFilesInUse();
+}
diff --git a/toolkit/mozapps/update/tests/unit_service_updater/marAppApplyUpdateAppBinInUseStageSuccessSvc_win.js b/toolkit/mozapps/update/tests/unit_service_updater/marAppApplyUpdateAppBinInUseStageSuccessSvc_win.js
new file mode 100644
index 0000000000..319aee8e34
--- /dev/null
+++ b/toolkit/mozapps/update/tests/unit_service_updater/marAppApplyUpdateAppBinInUseStageSuccessSvc_win.js
@@ -0,0 +1,50 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+/**
+ * Test applying an update by staging an update and launching an application to
+ * apply it.
+ */
+
+async function run_test() {
+ if (!setupTestCommon()) {
+ return;
+ }
+ const STATE_AFTER_STAGE = gIsServiceTest ? STATE_APPLIED_SVC : STATE_APPLIED;
+ gTestFiles = gTestFilesCompleteSuccess;
+ gTestDirs = gTestDirsCompleteSuccess;
+ await setupUpdaterTest(FILE_COMPLETE_MAR, false);
+ await stageUpdate(STATE_AFTER_STAGE, true);
+ checkPostUpdateRunningFile(false);
+ checkFilesAfterUpdateSuccess(getStageDirFile, true, false);
+ checkUpdateLogContents(LOG_COMPLETE_SUCCESS, true);
+ lockDirectory(getGREBinDir().path);
+ // Switch the application to the staged application that was updated.
+ await runUpdateUsingApp(STATE_SUCCEEDED);
+ await checkPostUpdateAppLog();
+ checkAppBundleModTime();
+ standardInit();
+ checkPostUpdateRunningFile(true);
+ checkFilesAfterUpdateSuccess(getApplyDirFile);
+ checkUpdateLogContents(LOG_COMPLETE_SUCCESS);
+ await waitForUpdateXMLFiles();
+ checkUpdateManager(STATE_NONE, false, STATE_SUCCEEDED, 0, 1);
+
+ let updatesDir = getUpdateDirFile(DIR_PATCH);
+ Assert.ok(
+ updatesDir.exists(),
+ MSG_SHOULD_EXIST + getMsgPath(updatesDir.path)
+ );
+
+ let log = getUpdateDirFile(FILE_UPDATE_LOG);
+ Assert.ok(!log.exists(), MSG_SHOULD_NOT_EXIST + getMsgPath(log.path));
+
+ log = getUpdateDirFile(FILE_LAST_UPDATE_LOG);
+ Assert.ok(log.exists(), MSG_SHOULD_EXIST + getMsgPath(log.path));
+
+ log = getUpdateDirFile(FILE_BACKUP_UPDATE_LOG);
+ Assert.ok(log.exists(), MSG_SHOULD_EXIST + getMsgPath(log.path));
+
+ waitForFilesInUse();
+}
diff --git a/toolkit/mozapps/update/tests/unit_service_updater/marAppApplyUpdateStageSuccessSvc.js b/toolkit/mozapps/update/tests/unit_service_updater/marAppApplyUpdateStageSuccessSvc.js
new file mode 100644
index 0000000000..34b47866b1
--- /dev/null
+++ b/toolkit/mozapps/update/tests/unit_service_updater/marAppApplyUpdateStageSuccessSvc.js
@@ -0,0 +1,49 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+/**
+ * Test applying an update by staging an update and launching an application to
+ * apply it.
+ */
+
+async function run_test() {
+ if (!setupTestCommon()) {
+ return;
+ }
+ const STATE_AFTER_STAGE = gIsServiceTest ? STATE_APPLIED_SVC : STATE_APPLIED;
+ gTestFiles = gTestFilesCompleteSuccess;
+ gTestDirs = gTestDirsCompleteSuccess;
+ await setupUpdaterTest(FILE_COMPLETE_MAR, true);
+ await stageUpdate(STATE_AFTER_STAGE, true);
+ checkPostUpdateRunningFile(false);
+ checkFilesAfterUpdateSuccess(getStageDirFile, true);
+ checkUpdateLogContents(LOG_COMPLETE_SUCCESS, true);
+ // Switch the application to the staged application that was updated.
+ await runUpdateUsingApp(STATE_SUCCEEDED);
+ await checkPostUpdateAppLog();
+ checkAppBundleModTime();
+ standardInit();
+ checkPostUpdateRunningFile(true);
+ checkFilesAfterUpdateSuccess(getApplyDirFile, false, true);
+ checkUpdateLogContents(LOG_REPLACE_SUCCESS, false, true);
+ await waitForUpdateXMLFiles();
+ checkUpdateManager(STATE_NONE, false, STATE_SUCCEEDED, 0, 1);
+
+ let updatesDir = getUpdateDirFile(DIR_PATCH);
+ Assert.ok(
+ updatesDir.exists(),
+ MSG_SHOULD_EXIST + getMsgPath(updatesDir.path)
+ );
+
+ let log = getUpdateDirFile(FILE_UPDATE_LOG);
+ Assert.ok(!log.exists(), MSG_SHOULD_NOT_EXIST + getMsgPath(log.path));
+
+ log = getUpdateDirFile(FILE_LAST_UPDATE_LOG);
+ Assert.ok(log.exists(), MSG_SHOULD_EXIST + getMsgPath(log.path));
+
+ log = getUpdateDirFile(FILE_BACKUP_UPDATE_LOG);
+ Assert.ok(log.exists(), MSG_SHOULD_EXIST + getMsgPath(log.path));
+
+ waitForFilesInUse();
+}
diff --git a/toolkit/mozapps/update/tests/unit_service_updater/marAppApplyUpdateSuccessSvc.js b/toolkit/mozapps/update/tests/unit_service_updater/marAppApplyUpdateSuccessSvc.js
new file mode 100644
index 0000000000..980b0cb89a
--- /dev/null
+++ b/toolkit/mozapps/update/tests/unit_service_updater/marAppApplyUpdateSuccessSvc.js
@@ -0,0 +1,47 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+/**
+ * Test applying an update by launching an application to apply it.
+ */
+
+async function run_test() {
+ if (!setupTestCommon()) {
+ return;
+ }
+ gTestFiles = gTestFilesCompleteSuccess;
+ gTestDirs = gTestDirsCompleteSuccess;
+ // The third parameter will test that a full path to the post update binary
+ // doesn't execute.
+ await setupUpdaterTest(
+ FILE_COMPLETE_MAR,
+ undefined,
+ getApplyDirFile(null, true).path + "/"
+ );
+ await runUpdateUsingApp(STATE_SUCCEEDED);
+ checkAppBundleModTime();
+ standardInit();
+ checkPostUpdateRunningFile(false);
+ checkFilesAfterUpdateSuccess(getApplyDirFile);
+ checkUpdateLogContents(LOG_COMPLETE_SUCCESS);
+ await waitForUpdateXMLFiles();
+ checkUpdateManager(STATE_NONE, false, STATE_SUCCEEDED, 0, 1);
+
+ let updatesDir = getUpdateDirFile(DIR_PATCH);
+ Assert.ok(
+ updatesDir.exists(),
+ MSG_SHOULD_EXIST + getMsgPath(updatesDir.path)
+ );
+
+ let log = getUpdateDirFile(FILE_UPDATE_LOG);
+ Assert.ok(!log.exists(), MSG_SHOULD_NOT_EXIST + getMsgPath(log.path));
+
+ log = getUpdateDirFile(FILE_LAST_UPDATE_LOG);
+ Assert.ok(log.exists(), MSG_SHOULD_EXIST + getMsgPath(log.path));
+
+ log = getUpdateDirFile(FILE_BACKUP_UPDATE_LOG);
+ Assert.ok(!log.exists(), MSG_SHOULD_NOT_EXIST + getMsgPath(log.path));
+
+ waitForFilesInUse();
+}
diff --git a/toolkit/mozapps/update/tests/unit_service_updater/marAppInUseBackgroundTaskFailureSvc_win.js b/toolkit/mozapps/update/tests/unit_service_updater/marAppInUseBackgroundTaskFailureSvc_win.js
new file mode 100644
index 0000000000..bcb24bee94
--- /dev/null
+++ b/toolkit/mozapps/update/tests/unit_service_updater/marAppInUseBackgroundTaskFailureSvc_win.js
@@ -0,0 +1,51 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+/* Fail to apply a complete MAR when the application is in use and the callback is a background task. */
+
+async function run_test() {
+ if (!setupTestCommon()) {
+ return;
+ }
+
+ gTestFiles = gTestFilesCompleteSuccess;
+ gTestDirs = gTestDirsCompleteSuccess;
+ setTestFilesAndDirsForFailure();
+
+ // Add a dummy --backgroundtask arg; this will have no effect on the
+ // callback (TestAUSHelper) but will cause the updater to detect
+ // that this is a background task.
+ gCallbackArgs = gCallbackArgs.concat(["--backgroundtask", "not_found"]);
+
+ // Run the update with the helper file in use, expecting failure.
+ await setupUpdaterTest(FILE_COMPLETE_MAR, false);
+ await runHelperFileInUse(DIR_RESOURCES + gCallbackBinFile, false);
+ runUpdate(
+ STATE_FAILED_WRITE_ERROR_BACKGROUND_TASK_SHARING_VIOLATION,
+ false, // aSwitchApp
+ 1, // aExpectedExitValue
+ true // aCheckSvcLog
+ );
+ await waitForHelperExit();
+
+ standardInit();
+
+ checkPostUpdateRunningFile(false);
+ checkFilesAfterUpdateFailure(getApplyDirFile);
+ checkUpdateLogContains(ERR_BGTASK_EXCLUSIVE);
+
+ // Check that the update was reset to "pending".
+ await waitForUpdateXMLFiles(
+ true, // aActiveUpdateExists
+ false // aUpdatesExists
+ );
+ checkUpdateManager(
+ STATE_PENDING, // aStatusFileState
+ true, // aHasActiveUpdate
+ STATE_PENDING, // aUpdateStatusState
+ WRITE_ERROR_BACKGROUND_TASK_SHARING_VIOLATION,
+ 0 // aUpdateCount
+ );
+ checkCallbackLog();
+}
diff --git a/toolkit/mozapps/update/tests/unit_service_updater/marAppInUseStageFailureCompleteSvc_win.js b/toolkit/mozapps/update/tests/unit_service_updater/marAppInUseStageFailureCompleteSvc_win.js
new file mode 100644
index 0000000000..502561ed1e
--- /dev/null
+++ b/toolkit/mozapps/update/tests/unit_service_updater/marAppInUseStageFailureCompleteSvc_win.js
@@ -0,0 +1,37 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+/* Application in use complete MAR file staged patch apply failure test */
+
+async function run_test() {
+ if (!setupTestCommon()) {
+ return;
+ }
+ const STATE_AFTER_STAGE = gIsServiceTest ? STATE_APPLIED_SVC : STATE_APPLIED;
+ const STATE_AFTER_RUNUPDATE = gIsServiceTest
+ ? STATE_PENDING_SVC
+ : STATE_PENDING;
+ gTestFiles = gTestFilesCompleteSuccess;
+ gTestDirs = gTestDirsCompleteSuccess;
+ await setupUpdaterTest(FILE_COMPLETE_MAR, false);
+ await runHelperFileInUse(DIR_RESOURCES + gCallbackBinFile, false);
+ await stageUpdate(STATE_AFTER_STAGE, true);
+ checkPostUpdateRunningFile(false);
+ checkFilesAfterUpdateSuccess(getStageDirFile, true);
+ checkUpdateLogContents(LOG_COMPLETE_SUCCESS, true);
+ // Switch the application to the staged application that was updated.
+ runUpdate(STATE_AFTER_RUNUPDATE, true, 1, true);
+ await waitForHelperExit();
+ standardInit();
+ checkPostUpdateRunningFile(false);
+ setTestFilesAndDirsForFailure();
+ checkFilesAfterUpdateFailure(getApplyDirFile);
+ checkUpdateLogContains(ERR_RENAME_FILE);
+ checkUpdateLogContains(
+ ERR_MOVE_DESTDIR_7 + "\n" + STATE_FAILED_WRITE_ERROR + "\n" + CALL_QUIT
+ );
+ await waitForUpdateXMLFiles();
+ checkUpdateManager(STATE_NONE, false, STATE_AFTER_RUNUPDATE, 0, 1);
+ checkCallbackLog();
+}
diff --git a/toolkit/mozapps/update/tests/unit_service_updater/marAppInUseSuccessCompleteSvc.js b/toolkit/mozapps/update/tests/unit_service_updater/marAppInUseSuccessCompleteSvc.js
new file mode 100644
index 0000000000..e08777d042
--- /dev/null
+++ b/toolkit/mozapps/update/tests/unit_service_updater/marAppInUseSuccessCompleteSvc.js
@@ -0,0 +1,26 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+/* Application in use complete MAR file patch apply success test */
+
+async function run_test() {
+ if (!setupTestCommon()) {
+ return;
+ }
+ gTestFiles = gTestFilesCompleteSuccess;
+ gTestDirs = gTestDirsCompleteSuccess;
+ await setupUpdaterTest(FILE_COMPLETE_MAR, false);
+ await runHelperFileInUse(DIR_RESOURCES + gCallbackBinFile, false);
+ runUpdate(STATE_SUCCEEDED, false, 0, true);
+ await waitForHelperExit();
+ await checkPostUpdateAppLog();
+ checkAppBundleModTime();
+ standardInit();
+ checkPostUpdateRunningFile(true);
+ checkFilesAfterUpdateSuccess(getApplyDirFile);
+ checkUpdateLogContents(LOG_COMPLETE_SUCCESS);
+ await waitForUpdateXMLFiles();
+ checkUpdateManager(STATE_NONE, false, STATE_SUCCEEDED, 0, 1);
+ checkCallbackLog();
+}
diff --git a/toolkit/mozapps/update/tests/unit_service_updater/marCallbackAppStageSuccessCompleteSvc_win.js b/toolkit/mozapps/update/tests/unit_service_updater/marCallbackAppStageSuccessCompleteSvc_win.js
new file mode 100644
index 0000000000..2f8155c790
--- /dev/null
+++ b/toolkit/mozapps/update/tests/unit_service_updater/marCallbackAppStageSuccessCompleteSvc_win.js
@@ -0,0 +1,30 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+/* Replace app binary complete MAR file staged patch apply success test */
+
+async function run_test() {
+ if (!setupTestCommon()) {
+ return;
+ }
+ const STATE_AFTER_STAGE = gIsServiceTest ? STATE_APPLIED_SVC : STATE_APPLIED;
+ gTestFiles = gTestFilesCompleteSuccess;
+ gTestDirs = gTestDirsCompleteSuccess;
+ gCallbackBinFile = "exe0.exe";
+ await setupUpdaterTest(FILE_COMPLETE_MAR, false);
+ await stageUpdate(STATE_AFTER_STAGE, true);
+ checkPostUpdateRunningFile(false);
+ checkFilesAfterUpdateSuccess(getStageDirFile, true);
+ checkUpdateLogContents(LOG_COMPLETE_SUCCESS, true);
+ // Switch the application to the staged application that was updated.
+ runUpdate(STATE_SUCCEEDED, true, 0, true);
+ await checkPostUpdateAppLog();
+ standardInit();
+ checkPostUpdateRunningFile(true);
+ checkFilesAfterUpdateSuccess(getApplyDirFile, false, true);
+ checkUpdateLogContents(LOG_REPLACE_SUCCESS, false, true);
+ await waitForUpdateXMLFiles();
+ checkUpdateManager(STATE_NONE, false, STATE_SUCCEEDED, 0, 1);
+ checkCallbackLog();
+}
diff --git a/toolkit/mozapps/update/tests/unit_service_updater/marCallbackAppStageSuccessPartialSvc_win.js b/toolkit/mozapps/update/tests/unit_service_updater/marCallbackAppStageSuccessPartialSvc_win.js
new file mode 100644
index 0000000000..04c72896b4
--- /dev/null
+++ b/toolkit/mozapps/update/tests/unit_service_updater/marCallbackAppStageSuccessPartialSvc_win.js
@@ -0,0 +1,30 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+/* Patch app binary partial MAR file staged patch apply success test */
+
+async function run_test() {
+ if (!setupTestCommon()) {
+ return;
+ }
+ const STATE_AFTER_STAGE = gIsServiceTest ? STATE_APPLIED_SVC : STATE_APPLIED;
+ gTestFiles = gTestFilesPartialSuccess;
+ gTestDirs = gTestDirsPartialSuccess;
+ gCallbackBinFile = "exe0.exe";
+ await setupUpdaterTest(FILE_PARTIAL_MAR, false);
+ await stageUpdate(STATE_AFTER_STAGE, true);
+ checkPostUpdateRunningFile(false);
+ checkFilesAfterUpdateSuccess(getStageDirFile, true);
+ checkUpdateLogContents(LOG_PARTIAL_SUCCESS, true);
+ // Switch the application to the staged application that was updated.
+ runUpdate(STATE_SUCCEEDED, true, 0, true);
+ await checkPostUpdateAppLog();
+ standardInit();
+ checkPostUpdateRunningFile(true);
+ checkFilesAfterUpdateSuccess(getApplyDirFile, false, true);
+ checkUpdateLogContents(LOG_REPLACE_SUCCESS, false, true);
+ await waitForUpdateXMLFiles();
+ checkUpdateManager(STATE_NONE, false, STATE_SUCCEEDED, 0, 1);
+ checkCallbackLog();
+}
diff --git a/toolkit/mozapps/update/tests/unit_service_updater/marCallbackAppSuccessCompleteSvc_win.js b/toolkit/mozapps/update/tests/unit_service_updater/marCallbackAppSuccessCompleteSvc_win.js
new file mode 100644
index 0000000000..fa13f07f46
--- /dev/null
+++ b/toolkit/mozapps/update/tests/unit_service_updater/marCallbackAppSuccessCompleteSvc_win.js
@@ -0,0 +1,24 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+/* Replace app binary complete MAR file patch apply success test */
+
+async function run_test() {
+ if (!setupTestCommon()) {
+ return;
+ }
+ gTestFiles = gTestFilesCompleteSuccess;
+ gTestDirs = gTestDirsCompleteSuccess;
+ gCallbackBinFile = "exe0.exe";
+ await setupUpdaterTest(FILE_COMPLETE_MAR, false);
+ runUpdate(STATE_SUCCEEDED, false, 0, true);
+ await checkPostUpdateAppLog();
+ standardInit();
+ checkPostUpdateRunningFile(true);
+ checkFilesAfterUpdateSuccess(getApplyDirFile);
+ checkUpdateLogContents(LOG_COMPLETE_SUCCESS);
+ await waitForUpdateXMLFiles();
+ checkUpdateManager(STATE_NONE, false, STATE_SUCCEEDED, 0, 1);
+ checkCallbackLog();
+}
diff --git a/toolkit/mozapps/update/tests/unit_service_updater/marCallbackAppSuccessPartialSvc_win.js b/toolkit/mozapps/update/tests/unit_service_updater/marCallbackAppSuccessPartialSvc_win.js
new file mode 100644
index 0000000000..e74509c06e
--- /dev/null
+++ b/toolkit/mozapps/update/tests/unit_service_updater/marCallbackAppSuccessPartialSvc_win.js
@@ -0,0 +1,24 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+/* Patch app binary partial MAR file patch apply success test */
+
+async function run_test() {
+ if (!setupTestCommon()) {
+ return;
+ }
+ gTestFiles = gTestFilesPartialSuccess;
+ gTestDirs = gTestDirsPartialSuccess;
+ gCallbackBinFile = "exe0.exe";
+ await setupUpdaterTest(FILE_PARTIAL_MAR, false);
+ runUpdate(STATE_SUCCEEDED, false, 0, true);
+ await checkPostUpdateAppLog();
+ standardInit();
+ checkPostUpdateRunningFile(true);
+ checkFilesAfterUpdateSuccess(getApplyDirFile);
+ checkUpdateLogContents(LOG_PARTIAL_SUCCESS);
+ await waitForUpdateXMLFiles();
+ checkUpdateManager(STATE_NONE, false, STATE_SUCCEEDED, 0, 1);
+ checkCallbackLog();
+}
diff --git a/toolkit/mozapps/update/tests/unit_service_updater/marFailurePartialSvc.js b/toolkit/mozapps/update/tests/unit_service_updater/marFailurePartialSvc.js
new file mode 100644
index 0000000000..de8db067bc
--- /dev/null
+++ b/toolkit/mozapps/update/tests/unit_service_updater/marFailurePartialSvc.js
@@ -0,0 +1,39 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+/* General Partial MAR File Patch Apply Failure Test */
+
+async function run_test() {
+ if (!setupTestCommon()) {
+ return;
+ }
+ gTestFiles = gTestFilesPartialSuccess;
+ gTestFiles[11].originalFile = "partial.png";
+ gTestDirs = gTestDirsPartialSuccess;
+ setTestFilesAndDirsForFailure();
+ await setupUpdaterTest(FILE_PARTIAL_MAR, false);
+ // If execv is used the updater process will turn into the callback process
+ // and the updater's return code will be that of the callback process.
+ runUpdate(
+ STATE_FAILED_LOADSOURCE_ERROR_WRONG_SIZE,
+ false,
+ USE_EXECV ? 0 : 1,
+ true
+ );
+ checkAppBundleModTime();
+ standardInit();
+ checkPostUpdateRunningFile(false);
+ checkFilesAfterUpdateFailure(getApplyDirFile);
+ checkUpdateLogContents(LOG_PARTIAL_FAILURE);
+ await waitForUpdateXMLFiles();
+ checkUpdateManager(
+ STATE_NONE,
+ false,
+ STATE_FAILED,
+ LOADSOURCE_ERROR_WRONG_SIZE,
+ 1
+ );
+ checkCallbackLog();
+}
diff --git a/toolkit/mozapps/update/tests/unit_service_updater/marFileInUseStageFailureCompleteSvc_win.js b/toolkit/mozapps/update/tests/unit_service_updater/marFileInUseStageFailureCompleteSvc_win.js
new file mode 100644
index 0000000000..b93b023934
--- /dev/null
+++ b/toolkit/mozapps/update/tests/unit_service_updater/marFileInUseStageFailureCompleteSvc_win.js
@@ -0,0 +1,40 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+/* File in use complete MAR file staged patch apply failure test */
+
+async function run_test() {
+ if (!setupTestCommon()) {
+ return;
+ }
+ const STATE_AFTER_STAGE = gIsServiceTest ? STATE_APPLIED_SVC : STATE_APPLIED;
+ const STATE_AFTER_RUNUPDATE = gIsServiceTest
+ ? STATE_PENDING_SVC
+ : STATE_PENDING;
+ gTestFiles = gTestFilesCompleteSuccess;
+ gTestDirs = gTestDirsCompleteSuccess;
+ await setupUpdaterTest(FILE_COMPLETE_MAR, false);
+ await runHelperFileInUse(
+ gTestFiles[13].relPathDir + gTestFiles[13].fileName,
+ false
+ );
+ await stageUpdate(STATE_AFTER_STAGE, true);
+ checkPostUpdateRunningFile(false);
+ checkFilesAfterUpdateSuccess(getStageDirFile, true);
+ checkUpdateLogContents(LOG_COMPLETE_SUCCESS, true);
+ // Switch the application to the staged application that was updated.
+ runUpdate(STATE_AFTER_RUNUPDATE, true, 1, true);
+ await waitForHelperExit();
+ standardInit();
+ checkPostUpdateRunningFile(false);
+ setTestFilesAndDirsForFailure();
+ checkFilesAfterUpdateFailure(getApplyDirFile);
+ checkUpdateLogContains(ERR_RENAME_FILE);
+ checkUpdateLogContains(
+ ERR_MOVE_DESTDIR_7 + "\n" + STATE_FAILED_WRITE_ERROR + "\n" + CALL_QUIT
+ );
+ await waitForUpdateXMLFiles();
+ checkUpdateManager(STATE_NONE, false, STATE_AFTER_RUNUPDATE, 0, 1);
+ checkCallbackLog();
+}
diff --git a/toolkit/mozapps/update/tests/unit_service_updater/marFileInUseStageFailurePartialSvc_win.js b/toolkit/mozapps/update/tests/unit_service_updater/marFileInUseStageFailurePartialSvc_win.js
new file mode 100644
index 0000000000..b41da12396
--- /dev/null
+++ b/toolkit/mozapps/update/tests/unit_service_updater/marFileInUseStageFailurePartialSvc_win.js
@@ -0,0 +1,40 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+/* File in use partial MAR file staged patch apply failure test */
+
+async function run_test() {
+ if (!setupTestCommon()) {
+ return;
+ }
+ const STATE_AFTER_STAGE = gIsServiceTest ? STATE_APPLIED_SVC : STATE_APPLIED;
+ const STATE_AFTER_RUNUPDATE = gIsServiceTest
+ ? STATE_PENDING_SVC
+ : STATE_PENDING;
+ gTestFiles = gTestFilesPartialSuccess;
+ gTestDirs = gTestDirsPartialSuccess;
+ await setupUpdaterTest(FILE_PARTIAL_MAR, false);
+ await runHelperFileInUse(
+ gTestFiles[11].relPathDir + gTestFiles[11].fileName,
+ false
+ );
+ await stageUpdate(STATE_AFTER_STAGE, true);
+ checkPostUpdateRunningFile(false);
+ checkFilesAfterUpdateSuccess(getStageDirFile, true);
+ checkUpdateLogContents(LOG_PARTIAL_SUCCESS, true);
+ // Switch the application to the staged application that was updated.
+ runUpdate(STATE_AFTER_RUNUPDATE, true, 1, true);
+ await waitForHelperExit();
+ standardInit();
+ checkPostUpdateRunningFile(false);
+ setTestFilesAndDirsForFailure();
+ checkFilesAfterUpdateFailure(getApplyDirFile);
+ checkUpdateLogContains(ERR_RENAME_FILE);
+ checkUpdateLogContains(
+ ERR_MOVE_DESTDIR_7 + "\n" + STATE_FAILED_WRITE_ERROR + "\n" + CALL_QUIT
+ );
+ await waitForUpdateXMLFiles();
+ checkUpdateManager(STATE_NONE, false, STATE_AFTER_RUNUPDATE, 0, 1);
+ checkCallbackLog();
+}
diff --git a/toolkit/mozapps/update/tests/unit_service_updater/marFileInUseSuccessCompleteSvc_win.js b/toolkit/mozapps/update/tests/unit_service_updater/marFileInUseSuccessCompleteSvc_win.js
new file mode 100644
index 0000000000..4b946ac3e4
--- /dev/null
+++ b/toolkit/mozapps/update/tests/unit_service_updater/marFileInUseSuccessCompleteSvc_win.js
@@ -0,0 +1,29 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+/* File in use complete MAR file patch apply success test */
+
+async function run_test() {
+ if (!setupTestCommon()) {
+ return;
+ }
+ gTestFiles = gTestFilesCompleteSuccess;
+ gTestDirs = gTestDirsCompleteSuccess;
+ await setupUpdaterTest(FILE_COMPLETE_MAR, false);
+ await runHelperFileInUse(
+ gTestFiles[13].relPathDir + gTestFiles[13].fileName,
+ false
+ );
+ runUpdate(STATE_SUCCEEDED, false, 0, true);
+ await waitForHelperExit();
+ await checkPostUpdateAppLog();
+ standardInit();
+ checkPostUpdateRunningFile(true);
+ checkFilesAfterUpdateSuccess(getApplyDirFile, false, true);
+ checkUpdateLogContains(ERR_BACKUP_DISCARD);
+ checkUpdateLogContains(STATE_SUCCEEDED + "\n" + CALL_QUIT);
+ await waitForUpdateXMLFiles();
+ checkUpdateManager(STATE_NONE, false, STATE_SUCCEEDED, 0, 1);
+ checkCallbackLog();
+}
diff --git a/toolkit/mozapps/update/tests/unit_service_updater/marFileInUseSuccessPartialSvc_win.js b/toolkit/mozapps/update/tests/unit_service_updater/marFileInUseSuccessPartialSvc_win.js
new file mode 100644
index 0000000000..15c3a1121a
--- /dev/null
+++ b/toolkit/mozapps/update/tests/unit_service_updater/marFileInUseSuccessPartialSvc_win.js
@@ -0,0 +1,29 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+/* File in use partial MAR file patch apply success test */
+
+async function run_test() {
+ if (!setupTestCommon()) {
+ return;
+ }
+ gTestFiles = gTestFilesPartialSuccess;
+ gTestDirs = gTestDirsPartialSuccess;
+ await setupUpdaterTest(FILE_PARTIAL_MAR, false);
+ await runHelperFileInUse(
+ gTestFiles[11].relPathDir + gTestFiles[11].fileName,
+ false
+ );
+ runUpdate(STATE_SUCCEEDED, false, 0, true);
+ await waitForHelperExit();
+ await checkPostUpdateAppLog();
+ standardInit();
+ checkPostUpdateRunningFile(true);
+ checkFilesAfterUpdateSuccess(getApplyDirFile, false, true);
+ checkUpdateLogContains(ERR_BACKUP_DISCARD);
+ checkUpdateLogContains(STATE_SUCCEEDED + "\n" + CALL_QUIT);
+ await waitForUpdateXMLFiles();
+ checkUpdateManager(STATE_NONE, false, STATE_SUCCEEDED, 0, 1);
+ checkCallbackLog();
+}
diff --git a/toolkit/mozapps/update/tests/unit_service_updater/marFileLockedFailureCompleteSvc_win.js b/toolkit/mozapps/update/tests/unit_service_updater/marFileLockedFailureCompleteSvc_win.js
new file mode 100644
index 0000000000..698ccb7fe5
--- /dev/null
+++ b/toolkit/mozapps/update/tests/unit_service_updater/marFileLockedFailureCompleteSvc_win.js
@@ -0,0 +1,27 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+/* File locked complete MAR file patch apply failure test */
+
+async function run_test() {
+ if (!setupTestCommon()) {
+ return;
+ }
+ gTestFiles = gTestFilesCompleteSuccess;
+ gTestDirs = gTestDirsCompleteSuccess;
+ setTestFilesAndDirsForFailure();
+ await setupUpdaterTest(FILE_COMPLETE_MAR, false);
+ await runHelperLockFile(gTestFiles[3]);
+ runUpdate(STATE_FAILED_WRITE_ERROR, false, 1, true);
+ await waitForHelperExit();
+ standardInit();
+ checkPostUpdateRunningFile(false);
+ checkFilesAfterUpdateFailure(getApplyDirFile);
+ checkUpdateLogContains(ERR_RENAME_FILE);
+ checkUpdateLogContains(ERR_BACKUP_CREATE_7);
+ checkUpdateLogContains(STATE_FAILED_WRITE_ERROR + "\n" + CALL_QUIT);
+ await waitForUpdateXMLFiles(true, false);
+ checkUpdateManager(STATE_PENDING, true, STATE_PENDING, WRITE_ERROR, 0);
+ checkCallbackLog();
+}
diff --git a/toolkit/mozapps/update/tests/unit_service_updater/marFileLockedFailurePartialSvc_win.js b/toolkit/mozapps/update/tests/unit_service_updater/marFileLockedFailurePartialSvc_win.js
new file mode 100644
index 0000000000..c8c019ec5c
--- /dev/null
+++ b/toolkit/mozapps/update/tests/unit_service_updater/marFileLockedFailurePartialSvc_win.js
@@ -0,0 +1,26 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+/* File locked partial MAR file patch apply failure test */
+
+async function run_test() {
+ if (!setupTestCommon()) {
+ return;
+ }
+ gTestFiles = gTestFilesPartialSuccess;
+ gTestDirs = gTestDirsPartialSuccess;
+ setTestFilesAndDirsForFailure();
+ await setupUpdaterTest(FILE_PARTIAL_MAR, false);
+ await runHelperLockFile(gTestFiles[2]);
+ runUpdate(STATE_FAILED_READ_ERROR, false, 1, true);
+ await waitForHelperExit();
+ standardInit();
+ checkPostUpdateRunningFile(false);
+ checkFilesAfterUpdateFailure(getApplyDirFile);
+ checkUpdateLogContains(ERR_UNABLE_OPEN_DEST);
+ checkUpdateLogContains(STATE_FAILED_READ_ERROR + "\n" + CALL_QUIT);
+ await waitForUpdateXMLFiles();
+ checkUpdateManager(STATE_NONE, false, STATE_FAILED, READ_ERROR, 1);
+ checkCallbackLog();
+}
diff --git a/toolkit/mozapps/update/tests/unit_service_updater/marFileLockedStageFailureCompleteSvc_win.js b/toolkit/mozapps/update/tests/unit_service_updater/marFileLockedStageFailureCompleteSvc_win.js
new file mode 100644
index 0000000000..7b582dbd45
--- /dev/null
+++ b/toolkit/mozapps/update/tests/unit_service_updater/marFileLockedStageFailureCompleteSvc_win.js
@@ -0,0 +1,34 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+/* File locked complete MAR file staged patch apply failure test */
+
+async function run_test() {
+ if (!setupTestCommon()) {
+ return;
+ }
+ const STATE_AFTER_STAGE = STATE_PENDING;
+ gTestFiles = gTestFilesCompleteSuccess;
+ gTestDirs = gTestDirsCompleteSuccess;
+ setTestFilesAndDirsForFailure();
+ await setupUpdaterTest(FILE_COMPLETE_MAR, false);
+ await runHelperLockFile(gTestFiles[3]);
+ await stageUpdate(STATE_AFTER_STAGE, true);
+ checkPostUpdateRunningFile(false);
+ // Files aren't checked after staging since this test locks a file which
+ // prevents reading the file.
+ checkUpdateLogContains(ERR_ENSURE_COPY);
+ // Switch the application to the staged application that was updated.
+ runUpdate(STATE_FAILED_WRITE_ERROR, false, 1, false);
+ await waitForHelperExit();
+ standardInit();
+ checkPostUpdateRunningFile(false);
+ checkFilesAfterUpdateFailure(getApplyDirFile);
+ checkUpdateLogContains(ERR_RENAME_FILE);
+ checkUpdateLogContains(ERR_BACKUP_CREATE_7);
+ checkUpdateLogContains(STATE_FAILED_WRITE_ERROR + "\n" + CALL_QUIT);
+ await waitForUpdateXMLFiles(true, false);
+ checkUpdateManager(STATE_PENDING, true, STATE_PENDING, WRITE_ERROR, 0);
+ checkCallbackLog();
+}
diff --git a/toolkit/mozapps/update/tests/unit_service_updater/marFileLockedStageFailurePartialSvc_win.js b/toolkit/mozapps/update/tests/unit_service_updater/marFileLockedStageFailurePartialSvc_win.js
new file mode 100644
index 0000000000..bf3abd8c37
--- /dev/null
+++ b/toolkit/mozapps/update/tests/unit_service_updater/marFileLockedStageFailurePartialSvc_win.js
@@ -0,0 +1,33 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+/* File locked partial MAR file staged patch apply failure test */
+
+async function run_test() {
+ if (!setupTestCommon()) {
+ return;
+ }
+ const STATE_AFTER_STAGE = STATE_PENDING;
+ gTestFiles = gTestFilesPartialSuccess;
+ gTestDirs = gTestDirsPartialSuccess;
+ setTestFilesAndDirsForFailure();
+ await setupUpdaterTest(FILE_PARTIAL_MAR, false);
+ await runHelperLockFile(gTestFiles[2]);
+ await stageUpdate(STATE_AFTER_STAGE, true);
+ checkPostUpdateRunningFile(false);
+ // Files aren't checked after staging since this test locks a file which
+ // prevents reading the file.
+ checkUpdateLogContains(ERR_ENSURE_COPY);
+ // Switch the application to the staged application that was updated.
+ runUpdate(STATE_FAILED_READ_ERROR, false, 1, false);
+ await waitForHelperExit();
+ standardInit();
+ checkPostUpdateRunningFile(false);
+ checkFilesAfterUpdateFailure(getApplyDirFile);
+ checkUpdateLogContains(ERR_UNABLE_OPEN_DEST);
+ checkUpdateLogContains(STATE_FAILED_READ_ERROR + "\n" + CALL_QUIT);
+ await waitForUpdateXMLFiles();
+ checkUpdateManager(STATE_NONE, false, STATE_FAILED, READ_ERROR, 1);
+ checkCallbackLog();
+}
diff --git a/toolkit/mozapps/update/tests/unit_service_updater/marRMRFDirFileInUseStageFailureCompleteSvc_win.js b/toolkit/mozapps/update/tests/unit_service_updater/marRMRFDirFileInUseStageFailureCompleteSvc_win.js
new file mode 100644
index 0000000000..31c5b8bd7a
--- /dev/null
+++ b/toolkit/mozapps/update/tests/unit_service_updater/marRMRFDirFileInUseStageFailureCompleteSvc_win.js
@@ -0,0 +1,43 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+/* File in use inside removed dir complete MAR file staged patch apply failure
+ test */
+
+async function run_test() {
+ if (!setupTestCommon()) {
+ return;
+ }
+ const STATE_AFTER_STAGE = gIsServiceTest ? STATE_APPLIED_SVC : STATE_APPLIED;
+ const STATE_AFTER_RUNUPDATE = gIsServiceTest
+ ? STATE_PENDING_SVC
+ : STATE_PENDING;
+ gTestFiles = gTestFilesCompleteSuccess;
+ gTestDirs = gTestDirsCompleteSuccess;
+ await setupUpdaterTest(FILE_COMPLETE_MAR, false);
+ await runHelperFileInUse(
+ gTestDirs[4].relPathDir +
+ gTestDirs[4].subDirs[0] +
+ gTestDirs[4].subDirFiles[0],
+ true
+ );
+ await stageUpdate(STATE_AFTER_STAGE, true);
+ checkPostUpdateRunningFile(false);
+ checkFilesAfterUpdateSuccess(getStageDirFile, true);
+ checkUpdateLogContents(LOG_COMPLETE_SUCCESS, true);
+ // Switch the application to the staged application that was updated.
+ runUpdate(STATE_AFTER_RUNUPDATE, true, 1, true);
+ await waitForHelperExit();
+ standardInit();
+ checkPostUpdateRunningFile(false);
+ setTestFilesAndDirsForFailure();
+ checkFilesAfterUpdateFailure(getApplyDirFile);
+ checkUpdateLogContains(ERR_RENAME_FILE);
+ checkUpdateLogContains(
+ ERR_MOVE_DESTDIR_7 + "\n" + STATE_FAILED_WRITE_ERROR + "\n" + CALL_QUIT
+ );
+ await waitForUpdateXMLFiles();
+ checkUpdateManager(STATE_NONE, false, STATE_AFTER_RUNUPDATE, 0, 1);
+ checkCallbackLog();
+}
diff --git a/toolkit/mozapps/update/tests/unit_service_updater/marRMRFDirFileInUseStageFailurePartialSvc_win.js b/toolkit/mozapps/update/tests/unit_service_updater/marRMRFDirFileInUseStageFailurePartialSvc_win.js
new file mode 100644
index 0000000000..b57f8c81b7
--- /dev/null
+++ b/toolkit/mozapps/update/tests/unit_service_updater/marRMRFDirFileInUseStageFailurePartialSvc_win.js
@@ -0,0 +1,41 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+/* File in use inside removed dir partial MAR file staged patch apply failure
+ test */
+
+async function run_test() {
+ if (!setupTestCommon()) {
+ return;
+ }
+ const STATE_AFTER_STAGE = gIsServiceTest ? STATE_APPLIED_SVC : STATE_APPLIED;
+ const STATE_AFTER_RUNUPDATE = gIsServiceTest
+ ? STATE_PENDING_SVC
+ : STATE_PENDING;
+ gTestFiles = gTestFilesPartialSuccess;
+ gTestDirs = gTestDirsPartialSuccess;
+ await setupUpdaterTest(FILE_PARTIAL_MAR, false);
+ await runHelperFileInUse(
+ gTestDirs[2].relPathDir + gTestDirs[2].files[0],
+ true
+ );
+ await stageUpdate(STATE_AFTER_STAGE, true);
+ checkPostUpdateRunningFile(false);
+ checkFilesAfterUpdateSuccess(getStageDirFile, true);
+ checkUpdateLogContents(LOG_PARTIAL_SUCCESS, true);
+ // Switch the application to the staged application that was updated.
+ runUpdate(STATE_AFTER_RUNUPDATE, true, 1, true);
+ await waitForHelperExit();
+ standardInit();
+ checkPostUpdateRunningFile(false);
+ setTestFilesAndDirsForFailure();
+ checkFilesAfterUpdateFailure(getApplyDirFile);
+ checkUpdateLogContains(ERR_RENAME_FILE);
+ checkUpdateLogContains(
+ ERR_MOVE_DESTDIR_7 + "\n" + STATE_FAILED_WRITE_ERROR + "\n" + CALL_QUIT
+ );
+ await waitForUpdateXMLFiles();
+ checkUpdateManager(STATE_NONE, false, STATE_AFTER_RUNUPDATE, 0, 1);
+ checkCallbackLog();
+}
diff --git a/toolkit/mozapps/update/tests/unit_service_updater/marRMRFDirFileInUseSuccessCompleteSvc_win.js b/toolkit/mozapps/update/tests/unit_service_updater/marRMRFDirFileInUseSuccessCompleteSvc_win.js
new file mode 100644
index 0000000000..0683df0d8d
--- /dev/null
+++ b/toolkit/mozapps/update/tests/unit_service_updater/marRMRFDirFileInUseSuccessCompleteSvc_win.js
@@ -0,0 +1,31 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+/* File in use inside removed dir complete MAR file patch apply success test */
+
+async function run_test() {
+ if (!setupTestCommon()) {
+ return;
+ }
+ gTestFiles = gTestFilesCompleteSuccess;
+ gTestDirs = gTestDirsCompleteSuccess;
+ await setupUpdaterTest(FILE_COMPLETE_MAR, false);
+ await runHelperFileInUse(
+ gTestDirs[4].relPathDir +
+ gTestDirs[4].subDirs[0] +
+ gTestDirs[4].subDirFiles[0],
+ true
+ );
+ runUpdate(STATE_SUCCEEDED, false, 0, true);
+ await waitForHelperExit();
+ await checkPostUpdateAppLog();
+ standardInit();
+ checkPostUpdateRunningFile(true);
+ checkFilesAfterUpdateSuccess(getApplyDirFile, false, true);
+ checkUpdateLogContains(ERR_BACKUP_DISCARD);
+ checkUpdateLogContains(STATE_SUCCEEDED + "\n" + CALL_QUIT);
+ await waitForUpdateXMLFiles();
+ checkUpdateManager(STATE_NONE, false, STATE_SUCCEEDED, 0, 1);
+ checkCallbackLog();
+}
diff --git a/toolkit/mozapps/update/tests/unit_service_updater/marRMRFDirFileInUseSuccessPartialSvc_win.js b/toolkit/mozapps/update/tests/unit_service_updater/marRMRFDirFileInUseSuccessPartialSvc_win.js
new file mode 100644
index 0000000000..d4da3a5f37
--- /dev/null
+++ b/toolkit/mozapps/update/tests/unit_service_updater/marRMRFDirFileInUseSuccessPartialSvc_win.js
@@ -0,0 +1,29 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+/* File in use inside removed dir partial MAR file patch apply success test */
+
+async function run_test() {
+ if (!setupTestCommon()) {
+ return;
+ }
+ gTestFiles = gTestFilesPartialSuccess;
+ gTestDirs = gTestDirsPartialSuccess;
+ await setupUpdaterTest(FILE_PARTIAL_MAR, false);
+ await runHelperFileInUse(
+ gTestDirs[2].relPathDir + gTestDirs[2].files[0],
+ true
+ );
+ runUpdate(STATE_SUCCEEDED, false, 0, true);
+ await waitForHelperExit();
+ await checkPostUpdateAppLog();
+ standardInit();
+ checkPostUpdateRunningFile(true);
+ checkFilesAfterUpdateSuccess(getApplyDirFile, false, true);
+ checkUpdateLogContains(ERR_BACKUP_DISCARD);
+ checkUpdateLogContains(STATE_SUCCEEDED + "\n" + CALL_QUIT);
+ await waitForUpdateXMLFiles();
+ checkUpdateManager(STATE_NONE, false, STATE_SUCCEEDED, 0, 1);
+ checkCallbackLog();
+}
diff --git a/toolkit/mozapps/update/tests/unit_service_updater/marStageFailurePartialSvc.js b/toolkit/mozapps/update/tests/unit_service_updater/marStageFailurePartialSvc.js
new file mode 100644
index 0000000000..a1a0de0fe4
--- /dev/null
+++ b/toolkit/mozapps/update/tests/unit_service_updater/marStageFailurePartialSvc.js
@@ -0,0 +1,31 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+/* General Partial MAR File Staged Patch Apply Failure Test */
+
+async function run_test() {
+ if (!setupTestCommon()) {
+ return;
+ }
+ const STATE_AFTER_STAGE = STATE_FAILED;
+ gTestFiles = gTestFilesPartialSuccess;
+ gTestFiles[11].originalFile = "partial.png";
+ gTestDirs = gTestDirsPartialSuccess;
+ setTestFilesAndDirsForFailure();
+ await setupUpdaterTest(FILE_PARTIAL_MAR, false);
+ await stageUpdate(STATE_AFTER_STAGE, true, true);
+ checkPostUpdateRunningFile(false);
+ checkFilesAfterUpdateFailure(getApplyDirFile);
+ checkUpdateLogContains(ERR_LOADSOURCEFILE_FAILED);
+ await waitForUpdateXMLFiles();
+ checkUpdateManager(
+ STATE_NONE,
+ false,
+ STATE_FAILED,
+ LOADSOURCE_ERROR_WRONG_SIZE,
+ 1
+ );
+ waitForFilesInUse();
+}
diff --git a/toolkit/mozapps/update/tests/unit_service_updater/marStageSuccessCompleteSvc.js b/toolkit/mozapps/update/tests/unit_service_updater/marStageSuccessCompleteSvc.js
new file mode 100644
index 0000000000..943a45ba95
--- /dev/null
+++ b/toolkit/mozapps/update/tests/unit_service_updater/marStageSuccessCompleteSvc.js
@@ -0,0 +1,71 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+/* General Complete MAR File Staged Patch Apply Test */
+
+async function run_test() {
+ if (!setupTestCommon()) {
+ return;
+ }
+ const STATE_AFTER_STAGE = gIsServiceTest ? STATE_APPLIED_SVC : STATE_APPLIED;
+ gTestFiles = gTestFilesCompleteSuccess;
+ gTestFiles[gTestFiles.length - 1].originalContents = null;
+ gTestFiles[gTestFiles.length - 1].compareContents = "FromComplete\n";
+ gTestFiles[gTestFiles.length - 1].comparePerms = 0o644;
+ gTestDirs = gTestDirsCompleteSuccess;
+ setupSymLinks();
+ await setupUpdaterTest(FILE_COMPLETE_MAR, false);
+ await stageUpdate(STATE_AFTER_STAGE, true);
+ checkPostUpdateRunningFile(false);
+ checkFilesAfterUpdateSuccess(getStageDirFile, true);
+ checkUpdateLogContents(LOG_COMPLETE_SUCCESS, true);
+ // Switch the application to the staged application that was updated.
+ runUpdate(STATE_SUCCEEDED, true, 0, true);
+ await checkPostUpdateAppLog();
+ checkAppBundleModTime();
+ checkSymLinks();
+ standardInit();
+ checkPostUpdateRunningFile(true);
+ checkFilesAfterUpdateSuccess(getApplyDirFile, false, true);
+ checkUpdateLogContents(LOG_REPLACE_SUCCESS, false, true);
+ await waitForUpdateXMLFiles();
+ checkUpdateManager(STATE_NONE, false, STATE_SUCCEEDED, 0, 1);
+ checkCallbackLog();
+}
+
+/**
+ * Setup symlinks for the test.
+ */
+function setupSymLinks() {
+ // Don't test symlinks on Mac OS X in this test since it tends to timeout.
+ // It is tested on Mac OS X in marAppInUseStageSuccessComplete_unix.js
+ if (AppConstants.platform == "linux") {
+ removeSymlink();
+ createSymlink();
+ registerCleanupFunction(removeSymlink);
+ gTestFiles.splice(gTestFiles.length - 3, 0, {
+ description: "Readable symlink",
+ fileName: "link",
+ relPathDir: DIR_RESOURCES,
+ originalContents: "test",
+ compareContents: "test",
+ originalFile: null,
+ compareFile: null,
+ originalPerms: 0o666,
+ comparePerms: 0o666,
+ });
+ }
+}
+
+/**
+ * Checks the state of the symlinks for the test.
+ */
+function checkSymLinks() {
+ // Don't test symlinks on Mac OS X in this test since it tends to timeout.
+ // It is tested on Mac OS X in marAppInUseStageSuccessComplete_unix.js
+ if (AppConstants.platform == "linux") {
+ checkSymlink();
+ }
+}
diff --git a/toolkit/mozapps/update/tests/unit_service_updater/marStageSuccessPartialSvc.js b/toolkit/mozapps/update/tests/unit_service_updater/marStageSuccessPartialSvc.js
new file mode 100644
index 0000000000..dd5c240919
--- /dev/null
+++ b/toolkit/mozapps/update/tests/unit_service_updater/marStageSuccessPartialSvc.js
@@ -0,0 +1,35 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+/* General Partial MAR File Staged Patch Apply Test */
+
+async function run_test() {
+ if (!setupTestCommon()) {
+ return;
+ }
+ const STATE_AFTER_STAGE = gIsServiceTest ? STATE_APPLIED_SVC : STATE_APPLIED;
+ gTestFiles = gTestFilesPartialSuccess;
+ gTestFiles[gTestFiles.length - 1].originalContents = null;
+ gTestFiles[gTestFiles.length - 1].compareContents = "FromPartial\n";
+ gTestFiles[gTestFiles.length - 1].comparePerms = 0o644;
+ gTestDirs = gTestDirsPartialSuccess;
+ preventDistributionFiles();
+ await setupUpdaterTest(FILE_PARTIAL_MAR, true);
+ await stageUpdate(STATE_AFTER_STAGE, true);
+ checkPostUpdateRunningFile(false);
+ checkFilesAfterUpdateSuccess(getStageDirFile, true);
+ checkUpdateLogContents(LOG_PARTIAL_SUCCESS, true, false, true);
+ // Switch the application to the staged application that was updated.
+ runUpdate(STATE_SUCCEEDED, true, 0, true);
+ await checkPostUpdateAppLog();
+ checkAppBundleModTime();
+ standardInit();
+ checkPostUpdateRunningFile(true);
+ checkFilesAfterUpdateSuccess(getApplyDirFile, false, true);
+ checkUpdateLogContents(LOG_REPLACE_SUCCESS, false, true, true);
+ await waitForUpdateXMLFiles();
+ checkUpdateManager(STATE_NONE, false, STATE_SUCCEEDED, 0, 1);
+ checkCallbackLog();
+}
diff --git a/toolkit/mozapps/update/tests/unit_service_updater/marSuccessCompleteSvc.js b/toolkit/mozapps/update/tests/unit_service_updater/marSuccessCompleteSvc.js
new file mode 100644
index 0000000000..2dd1e54f90
--- /dev/null
+++ b/toolkit/mozapps/update/tests/unit_service_updater/marSuccessCompleteSvc.js
@@ -0,0 +1,26 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+/* General Complete MAR File Patch Apply Test */
+
+async function run_test() {
+ if (!setupTestCommon()) {
+ return;
+ }
+ gTestFiles = gTestFilesCompleteSuccess;
+ gTestDirs = gTestDirsCompleteSuccess;
+ preventDistributionFiles();
+ await setupUpdaterTest(FILE_COMPLETE_MAR, true);
+ runUpdate(STATE_SUCCEEDED, false, 0, true);
+ await checkPostUpdateAppLog();
+ checkAppBundleModTime();
+ standardInit();
+ checkPostUpdateRunningFile(true);
+ checkFilesAfterUpdateSuccess(getApplyDirFile);
+ checkUpdateLogContents(LOG_COMPLETE_SUCCESS, false, false, true);
+ await waitForUpdateXMLFiles();
+ checkUpdateManager(STATE_NONE, false, STATE_SUCCEEDED, 0, 1);
+ checkCallbackLog();
+}
diff --git a/toolkit/mozapps/update/tests/unit_service_updater/marSuccessPartialSvc.js b/toolkit/mozapps/update/tests/unit_service_updater/marSuccessPartialSvc.js
new file mode 100644
index 0000000000..8e8e9d094a
--- /dev/null
+++ b/toolkit/mozapps/update/tests/unit_service_updater/marSuccessPartialSvc.js
@@ -0,0 +1,29 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+/* General Partial MAR File Patch Apply Test */
+
+async function run_test() {
+ if (!setupTestCommon()) {
+ return;
+ }
+ gTestFiles = gTestFilesPartialSuccess;
+ gTestFiles[gTestFiles.length - 1].originalContents = null;
+ gTestFiles[gTestFiles.length - 1].compareContents = "FromPartial\n";
+ gTestFiles[gTestFiles.length - 1].comparePerms = 0o644;
+ gTestDirs = gTestDirsPartialSuccess;
+ // The third parameter will test that a relative path that contains a
+ // directory traversal to the post update binary doesn't execute.
+ await setupUpdaterTest(FILE_PARTIAL_MAR, false, "test/../");
+ runUpdate(STATE_SUCCEEDED, false, 0, true);
+ checkAppBundleModTime();
+ standardInit();
+ checkPostUpdateRunningFile(false);
+ checkFilesAfterUpdateSuccess(getApplyDirFile);
+ checkUpdateLogContents(LOG_PARTIAL_SUCCESS);
+ await waitForUpdateXMLFiles();
+ checkUpdateManager(STATE_NONE, false, STATE_SUCCEEDED, 0, 1);
+ checkCallbackLog();
+}
diff --git a/toolkit/mozapps/update/tests/unit_service_updater/xpcshell.ini b/toolkit/mozapps/update/tests/unit_service_updater/xpcshell.ini
new file mode 100644
index 0000000000..977162c585
--- /dev/null
+++ b/toolkit/mozapps/update/tests/unit_service_updater/xpcshell.ini
@@ -0,0 +1,99 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+# Tests that require the updater binary and the maintenance service.
+
+[DEFAULT]
+skip-if =
+ os == "win" && verify
+ os == "win" && ccov # 1532801
+ os == "win" && asan # updater binary must be signed for these tests, but it isn't in this build config
+ os == 'win' && msix # Updates are disabled for MSIX builds
+tags = appupdate
+head = head_update.js
+support-files =
+ ../data/shared.js
+ ../data/sharedUpdateXML.js
+ ../data/xpcshellUtilsAUS.js
+
+[bootstrapSvc.js]
+run-sequentially = Uses the Mozilla Maintenance Service.
+[invalidArgInstallDirPathTooLongFailureSvc.js]
+run-sequentially = Uses the Mozilla Maintenance Service.
+[invalidArgInstallDirPathTraversalFailureSvc.js]
+run-sequentially = Uses the Mozilla Maintenance Service.
+[invalidArgInstallWorkingDirPathNotSameFailureSvc_win.js]
+run-sequentially = Uses the Mozilla Maintenance Service.
+[invalidArgPatchDirPathSuffixFailureSvc.js]
+run-sequentially = Uses the Mozilla Maintenance Service.
+[invalidArgPatchDirPathTraversalFailureSvc.js]
+run-sequentially = Uses the Mozilla Maintenance Service.
+[invalidArgStageDirNotInInstallDirFailureSvc_win.js]
+run-sequentially = Uses the Mozilla Maintenance Service.
+[invalidArgWorkingDirPathLocalUNCFailureSvc_win.js]
+run-sequentially = Uses the Mozilla Maintenance Service.
+[invalidArgWorkingDirPathRelativeFailureSvc.js]
+run-sequentially = Uses the Mozilla Maintenance Service.
+[marSuccessCompleteSvc.js]
+run-sequentially = Uses the Mozilla Maintenance Service.
+[marSuccessPartialSvc.js]
+run-sequentially = Uses the Mozilla Maintenance Service.
+[marFailurePartialSvc.js]
+run-sequentially = Uses the Mozilla Maintenance Service.
+[marStageSuccessCompleteSvc.js]
+run-sequentially = Uses the Mozilla Maintenance Service.
+[marStageSuccessPartialSvc.js]
+run-sequentially = Uses the Mozilla Maintenance Service.
+[marStageFailurePartialSvc.js]
+run-sequentially = Uses the Mozilla Maintenance Service.
+[marCallbackAppSuccessCompleteSvc_win.js]
+run-sequentially = Uses the Mozilla Maintenance Service.
+[marCallbackAppSuccessPartialSvc_win.js]
+run-sequentially = Uses the Mozilla Maintenance Service.
+[marCallbackAppStageSuccessCompleteSvc_win.js]
+run-sequentially = Uses the Mozilla Maintenance Service.
+[marCallbackAppStageSuccessPartialSvc_win.js]
+run-sequentially = Uses the Mozilla Maintenance Service.
+[marAppInUseBackgroundTaskFailureSvc_win.js]
+run-sequentially = Uses the Mozilla Maintenance Service.
+[marAppInUseSuccessCompleteSvc.js]
+run-sequentially = Uses the Mozilla Maintenance Service.
+[marAppInUseStageFailureCompleteSvc_win.js]
+run-sequentially = Uses the Mozilla Maintenance Service.
+[marFileLockedFailureCompleteSvc_win.js]
+run-sequentially = Uses the Mozilla Maintenance Service.
+[marFileLockedFailurePartialSvc_win.js]
+run-sequentially = Uses the Mozilla Maintenance Service.
+[marFileLockedStageFailureCompleteSvc_win.js]
+run-sequentially = Uses the Mozilla Maintenance Service.
+[marFileLockedStageFailurePartialSvc_win.js]
+run-sequentially = Uses the Mozilla Maintenance Service.
+[marFileInUseSuccessCompleteSvc_win.js]
+run-sequentially = Uses the Mozilla Maintenance Service.
+[marFileInUseSuccessPartialSvc_win.js]
+run-sequentially = Uses the Mozilla Maintenance Service.
+[marRMRFDirFileInUseSuccessCompleteSvc_win.js]
+run-sequentially = Uses the Mozilla Maintenance Service.
+[marRMRFDirFileInUseSuccessPartialSvc_win.js]
+run-sequentially = Uses the Mozilla Maintenance Service.
+[marFileInUseStageFailureCompleteSvc_win.js]
+run-sequentially = Uses the Mozilla Maintenance Service.
+[marFileInUseStageFailurePartialSvc_win.js]
+run-sequentially = Uses the Mozilla Maintenance Service.
+[marRMRFDirFileInUseStageFailureCompleteSvc_win.js]
+run-sequentially = Uses the Mozilla Maintenance Service.
+[marRMRFDirFileInUseStageFailurePartialSvc_win.js]
+run-sequentially = Uses the Mozilla Maintenance Service.
+[marAppApplyDirLockedStageFailureSvc_win.js]
+run-sequentially = Uses the Mozilla Maintenance Service.
+[marAppApplyUpdateAppBinInUseStageSuccessSvc_win.js]
+run-sequentially = Uses the Mozilla Maintenance Service.
+skip-if = (ccov && os == "win") #Bug 1651090
+[marAppApplyUpdateSuccessSvc.js]
+run-sequentially = Uses the Mozilla Maintenance Service.
+[marAppApplyUpdateStageSuccessSvc.js]
+run-sequentially = Uses the Mozilla Maintenance Service.
+[checkUpdaterSigSvc.js]
+[fallbackOnSvcFailure.js]
+run-sequentially = Uses the Mozilla Maintenance Service.