Skip to content

Commit 8f4b3bc

Browse files
authored
feat(smoke-tests): test Setup.exe installer for on Windows COMPASS-8706 (#6671)
* Add installer for windows setup.exe * Add windows_setup to CI * Reading app location from registry
1 parent d351d0c commit 8f4b3bc

File tree

4 files changed

+140
-1
lines changed

4 files changed

+140
-1
lines changed

.evergreen/functions.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -677,7 +677,7 @@ functions:
677677
npm i -w packages/compass-smoke-tests https://x-access-token:${generated_token}@github.com/10gen/compass-mongodb-com --engine-strict=false
678678
679679
if [[ "$IS_WINDOWS" == "true" ]]; then
680-
# TODO: windows_setup
680+
npm run --unsafe-perm --workspace @mongodb-js/compass-smoke-tests start -- --package=windows_setup --tests time-to-first-query
681681
npm run --unsafe-perm --workspace @mongodb-js/compass-smoke-tests start -- --package=windows_zip --tests auto-update-from
682682
npm run --unsafe-perm --workspace @mongodb-js/compass-smoke-tests start -- --package=windows_msi --tests auto-update-from
683683
fi

packages/compass-smoke-tests/src/installers/index.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { installMacDMG } from './mac-dmg';
33
import { installMacZIP } from './mac-zip';
44
import { installWindowsZIP } from './windows-zip';
55
import { installWindowsMSI } from './windows-msi';
6+
import { installWindowsSetup } from './windows-setup';
67

78
export function getInstaller(kind: PackageKind) {
89
if (kind === 'osx_dmg') {
@@ -13,6 +14,8 @@ export function getInstaller(kind: PackageKind) {
1314
return installWindowsZIP;
1415
} else if (kind === 'windows_msi') {
1516
return installWindowsMSI;
17+
} else if (kind === 'windows_setup') {
18+
return installWindowsSetup;
1619
} else {
1720
throw new Error(`Installer for '${kind}' is not yet implemented`);
1821
}
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
import cp from 'node:child_process';
2+
3+
type RegistryEntry = {
4+
key: string;
5+
type: string;
6+
value: string;
7+
};
8+
9+
function isRegistryEntry(value: unknown): value is RegistryEntry {
10+
return (
11+
typeof value === 'object' &&
12+
value !== null &&
13+
'key' in value &&
14+
typeof value.key === 'string' &&
15+
'type' in value &&
16+
typeof value.type === 'string' &&
17+
'value' in value &&
18+
typeof value.value === 'string'
19+
);
20+
}
21+
22+
/**
23+
* Parse the putput of a "reg query" call.
24+
*/
25+
function parseQueryRegistryOutput(output: string): RegistryEntry[] {
26+
const result = output.matchAll(
27+
/^\s*(?<key>\w+) +(?<type>\w+) *(?<value>.*)$/gm
28+
);
29+
return [...result].map(({ groups }) => groups).filter(isRegistryEntry);
30+
}
31+
32+
/**
33+
* Query the Windows registry by key.
34+
*/
35+
export function query(key: string) {
36+
const result = cp.spawnSync('reg', ['query', key], { encoding: 'utf8' });
37+
if (result.status === 0) {
38+
const entries = parseQueryRegistryOutput(result.stdout);
39+
return Object.fromEntries(entries.map(({ key, value }) => [key, value]));
40+
} else if (
41+
result.status === 1 &&
42+
result.stderr.trim() ===
43+
'ERROR: The system was unable to find the specified registry key or value.'
44+
) {
45+
return null;
46+
} else {
47+
throw Error(
48+
`Expected either an entry or status code 1, got status ${
49+
result.status ?? '?'
50+
}: ${result.stderr}`
51+
);
52+
}
53+
}
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
import path from 'node:path';
2+
import assert from 'node:assert/strict';
3+
import fs from 'node:fs';
4+
import cp from 'node:child_process';
5+
6+
import type { InstalledAppInfo, InstallablePackage } from './types';
7+
import { execute } from '../execute';
8+
import * as windowsRegistry from './windows-registry';
9+
10+
type UninstallOptions = {
11+
/**
12+
* Expect the app to already be uninstalled, warn if that's not the case.
13+
*/
14+
expectMissing?: boolean;
15+
};
16+
17+
/**
18+
* Install using the Windows installer.
19+
*/
20+
export function installWindowsSetup({
21+
appName,
22+
filepath,
23+
}: InstallablePackage): InstalledAppInfo {
24+
function queryRegistry() {
25+
return windowsRegistry.query(
26+
`HKEY_CURRENT_USER\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\${appName}`
27+
);
28+
}
29+
30+
function uninstall({ expectMissing = false }: UninstallOptions = {}) {
31+
const entry = queryRegistry();
32+
if (entry) {
33+
const {
34+
InstallLocation: installLocation,
35+
UninstallString: uninstallCommand,
36+
} = entry;
37+
assert(
38+
typeof installLocation === 'string',
39+
'Expected an entry in the registry with the install location'
40+
);
41+
assert(
42+
typeof uninstallCommand === 'string',
43+
'Expected an entry in the registry with the uninstall command'
44+
);
45+
if (expectMissing) {
46+
console.warn(
47+
'Found an existing registry entry (likely from a previous run)'
48+
);
49+
}
50+
assert(
51+
typeof uninstallCommand === 'string',
52+
'Expected an UninstallString in the registry entry'
53+
);
54+
console.log(`Running command to uninstall: ${uninstallCommand}`);
55+
cp.execSync(uninstallCommand, { stdio: 'inherit' });
56+
// Removing the any remaining files manually
57+
fs.rmSync(installLocation, { recursive: true, force: true });
58+
}
59+
}
60+
61+
uninstall({ expectMissing: true });
62+
63+
// Run the installer
64+
console.warn(
65+
"Installing globally, since we haven't discovered a way to specify an install path"
66+
);
67+
execute(filepath, []);
68+
69+
const entry = queryRegistry();
70+
assert(entry !== null, 'Expected an entry in the registry after installing');
71+
const { InstallLocation: appPath } = entry;
72+
assert(
73+
typeof appPath === 'string',
74+
'Expected an entry in the registry with the install location'
75+
);
76+
const appExecutablePath = path.resolve(appPath, `${appName}.exe`);
77+
execute(appExecutablePath, ['--version']);
78+
79+
return {
80+
appPath,
81+
uninstall,
82+
};
83+
}

0 commit comments

Comments
 (0)