Bug 2063316: Record the time of the most recent update in the installation directory. r=application-update-reviewers,iholmes

Differential Revision: https://phabricator.services.mozilla.com/D322904
This commit is contained in:
Dave Townsend
2026-09-11 18:21:20 +00:00
committed by dtownsend@mozilla.com
parent 29f1aafc3c
commit 732bf65881
12 changed files with 82 additions and 0 deletions
@@ -1379,6 +1379,51 @@ function checkAppBundleModTime() {
);
}
/**
* Checks that the updater wrote update_telemetry.json to the install directory
* with a valid, recent install_timestamp.
*/
function checkUpdateTelemetry() {
let telemetryFile = getApplyDirFile("update_telemetry.json");
Assert.ok(
telemetryFile.exists(),
"update_telemetry.json should exist in the install directory"
);
let contents = readFile(telemetryFile);
Assert.ok(contents, "update_telemetry.json should not be empty");
Assert.ok(
!contents.includes("\0"),
"update_telemetry.json should be UTF-8 encoded (no null bytes)"
);
let data = JSON.parse(contents);
Assert.ok(
"install_timestamp" in data,
"update_telemetry.json should contain install_timestamp"
);
let ts = parseInt(data.install_timestamp, 10);
Assert.ok(
!isNaN(ts) && ts > 0,
"install_timestamp should be a positive number"
);
let nowMs = Date.now();
Assert.less(
nowMs - ts,
300000,
"install_timestamp should be within the last 5 minutes"
);
}
/**
* Checks that update_telemetry.json was NOT written to the install directory.
*/
function checkNoUpdateTelemetry() {
let telemetryFile = getApplyDirFile("update_telemetry.json");
Assert.ok(
!telemetryFile.exists(),
"update_telemetry.json should not exist in the install directory"
);
}
/**
* 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.
@@ -23,6 +23,7 @@ async function run_test() {
true
);
checkAppBundleModTime();
checkNoUpdateTelemetry();
await testPostUpdateProcessing();
checkPostUpdateRunningFile(false);
checkFilesAfterUpdateFailure(getApplyDirFile);
@@ -23,6 +23,7 @@ async function run_test() {
true
);
checkAppBundleModTime();
checkNoUpdateTelemetry();
await testPostUpdateProcessing();
checkPostUpdateRunningFile(false);
checkFilesAfterUpdateFailure(getApplyDirFile);
@@ -17,6 +17,7 @@ async function run_test() {
await setupUpdaterTest(FILE_PARTIAL_MAR, false);
await stageUpdate(STATE_AFTER_STAGE, true, true);
checkPostUpdateRunningFile(false);
checkNoUpdateTelemetry();
checkFilesAfterUpdateFailure(getApplyDirFile);
checkUpdateLogContains(ERR_LOADSOURCEFILE_FAILED);
await waitForUpdateXMLFiles();
@@ -17,6 +17,7 @@ async function run_test() {
await setupUpdaterTest(FILE_PARTIAL_ZUCCHINI_MAR, false);
await stageUpdate(STATE_AFTER_STAGE, true, true);
checkPostUpdateRunningFile(false);
checkNoUpdateTelemetry();
checkFilesAfterUpdateFailure(getApplyDirFile);
checkUpdateLogContains(ERR_LOADSOURCEFILE_FAILED);
await waitForUpdateXMLFiles();
@@ -20,12 +20,14 @@ async function run_test() {
await setupUpdaterTest(FILE_COMPLETE_MAR, false);
await stageUpdate(STATE_AFTER_STAGE, true);
checkPostUpdateRunningFile(false);
checkNoUpdateTelemetry();
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();
checkUpdateTelemetry();
checkSymLinks();
await testPostUpdateProcessing();
checkPostUpdateRunningFile(true);
@@ -20,12 +20,14 @@ async function run_test() {
await setupUpdaterTest(FILE_PARTIAL_MAR, true);
await stageUpdate(STATE_AFTER_STAGE, true);
checkPostUpdateRunningFile(false);
checkNoUpdateTelemetry();
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();
checkUpdateTelemetry();
await testPostUpdateProcessing();
checkPostUpdateRunningFile(true);
checkFilesAfterUpdateSuccess(getApplyDirFile, false, true);
@@ -20,12 +20,14 @@ async function run_test() {
await setupUpdaterTest(FILE_PARTIAL_ZUCCHINI_MAR, true);
await stageUpdate(STATE_AFTER_STAGE, true);
checkPostUpdateRunningFile(false);
checkNoUpdateTelemetry();
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();
checkUpdateTelemetry();
await testPostUpdateProcessing();
checkPostUpdateRunningFile(true);
checkFilesAfterUpdateSuccess(getApplyDirFile, false, true);
@@ -16,6 +16,7 @@ async function run_test() {
runUpdate(STATE_SUCCEEDED, false, 0, true);
await checkPostUpdateAppLog();
checkAppBundleModTime();
checkUpdateTelemetry();
await testPostUpdateProcessing();
checkPostUpdateRunningFile(true);
checkFilesAfterUpdateSuccess(getApplyDirFile);
@@ -20,6 +20,7 @@ async function run_test() {
await setupUpdaterTest(FILE_PARTIAL_MAR, false, "test/../");
runUpdate(STATE_SUCCEEDED, false, 0, true);
checkAppBundleModTime();
checkUpdateTelemetry();
await testPostUpdateProcessing();
checkPostUpdateRunningFile(false);
checkFilesAfterUpdateSuccess(getApplyDirFile);
@@ -20,6 +20,7 @@ async function run_test() {
await setupUpdaterTest(FILE_PARTIAL_ZUCCHINI_MAR, false, "test/../");
runUpdate(STATE_SUCCEEDED, false, 0, true);
checkAppBundleModTime();
checkUpdateTelemetry();
await testPostUpdateProcessing();
checkPostUpdateRunningFile(false);
checkFilesAfterUpdateSuccess(getApplyDirFile);
@@ -51,6 +51,8 @@
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <chrono>
#include <time.h>
#include <sys/stat.h>
#include <fcntl.h>
@@ -2802,6 +2804,24 @@ static void LaunchCallbackApp(const NS_tchar* workingDir, int argc,
#endif
}
static void WriteUpdateTelemetry(const NS_tchar* aInstallDir) {
NS_tchar path[MAXPATHLEN];
NS_tsnprintf(path, sizeof(path) / sizeof(path[0]),
NS_T("%s/update_telemetry.json"), aInstallDir);
auto nowMs = std::chrono::duration_cast<std::chrono::milliseconds>(
std::chrono::system_clock::now().time_since_epoch())
.count();
char content[128];
snprintf(content, sizeof(content) / sizeof(content[0]),
"{\"install_timestamp\":\"%lld\"}", (long long)nowMs);
AutoFile file(CreateAndOpenFile(path, true));
if (file != nullptr) {
fwrite(content, strlen(content), 1, file);
}
}
static bool WriteToFile(const NS_tchar* aFilename, const char* aStatus) {
LOG(("Writing status to file: %s", aStatus));
@@ -3277,6 +3297,7 @@ static int ProcessReplaceRequest() {
#endif
gSucceeded = true;
WriteUpdateTelemetry(gInstallDirPath);
return 0;
}
@@ -3473,6 +3494,9 @@ static void UpdateThreadFunc(void* param) {
LOG(("Couldn't set access/modification time on application bundle."));
}
#endif
if (!sStagedUpdate) {
WriteUpdateTelemetry(gInstallDirPath);
}
LOG(("succeeded"));
}
WriteStatusFile(rv);