Skip to content

Commit

Permalink
Add sandboxing and refactored smoke test harness (#6621)
Browse files Browse the repository at this point in the history
* Add sandboxing and refactored existing code

* Add dep on "undici-types"

* Ignore ephemeral files for prettier

* Call cleanup functions in sequence and reverse order
  • Loading branch information
kraenhansen authored Jan 16, 2025
1 parent 2b21379 commit 84ffd4d
Show file tree
Hide file tree
Showing 15 changed files with 503 additions and 360 deletions.
2 changes: 2 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions packages/compass-e2e-tests/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,8 @@
fixtures/*.csv
fixtures/*.json
hadron-build-info.json

# Ignoring sandboxes (created per test run)
.smoke-sandboxes/
# Cache of downloaded binaries
.smoke-downloads/
5 changes: 5 additions & 0 deletions packages/compass-e2e-tests/.prettierignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,8 @@ fixtures
.nyc_output
coverage
hadron-build-info.json

# Ignoring sandboxes (created per test run)
.smoke-sandboxes/
# Cache of downloaded binaries
.smoke-downloads/
80 changes: 0 additions & 80 deletions packages/compass-e2e-tests/helpers/buildinfo.ts

This file was deleted.

189 changes: 189 additions & 0 deletions packages/compass-e2e-tests/helpers/smoke-test/build-info.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,189 @@
import assert from 'node:assert/strict';
import fs from 'node:fs';
import path from 'node:path';

import { handler as writeBuildInfo } from 'hadron-build/commands/info';

import { type PackageKind } from './packages';
import { type SmokeTestsContext } from './context';
import { pick } from 'lodash';

function assertObjectHasKeys(
obj: unknown,
name: string,
keys: readonly string[]
) {
assert(
typeof obj === 'object' && obj !== null,
'Expected buildInfo to be an object'
);

for (const key of keys) {
assert(key in obj, `Expected '${name}' to have '${key}'`);
}
}

// subsets of the hadron-build info result

export const commonKeys = ['productName'] as const;
export type CommonBuildInfo = Record<typeof commonKeys[number], string>;

export function assertCommonBuildInfo(
buildInfo: unknown
): asserts buildInfo is CommonBuildInfo {
assertObjectHasKeys(buildInfo, 'buildInfo', commonKeys);
}

export const windowsFilenameKeys = [
'windows_setup_filename',
'windows_msi_filename',
'windows_zip_filename',
'windows_nupkg_full_filename',
] as const;
export type WindowsBuildInfo = CommonBuildInfo &
Record<typeof windowsFilenameKeys[number], string>;

export function assertBuildInfoIsWindows(
buildInfo: unknown
): asserts buildInfo is WindowsBuildInfo {
assertObjectHasKeys(buildInfo, 'buildInfo', commonKeys);
assertObjectHasKeys(buildInfo, 'buildInfo', windowsFilenameKeys);
}

export const osxFilenameKeys = [
'osx_dmg_filename',
'osx_zip_filename',
] as const;
export type OSXBuildInfo = CommonBuildInfo &
Record<typeof osxFilenameKeys[number], string>;

export function assertBuildInfoIsOSX(
buildInfo: unknown
): asserts buildInfo is OSXBuildInfo {
assertObjectHasKeys(buildInfo, 'buildInfo', commonKeys);
assertObjectHasKeys(buildInfo, 'buildInfo', osxFilenameKeys);
}

export const ubuntuFilenameKeys = [
'linux_deb_filename',
'linux_tar_filename',
] as const;
export type UbuntuBuildInfo = CommonBuildInfo &
Record<typeof ubuntuFilenameKeys[number], string>;

export function assertBuildInfoIsUbuntu(
buildInfo: unknown
): asserts buildInfo is UbuntuBuildInfo {
assertObjectHasKeys(buildInfo, 'buildInfo', commonKeys);
assertObjectHasKeys(buildInfo, 'buildInfo', ubuntuFilenameKeys);
}

const rhelFilenameKeys = ['linux_rpm_filename', 'rhel_tar_filename'] as const;
export type RHELBuildInfo = CommonBuildInfo &
Record<typeof rhelFilenameKeys[number], string>;

export function assertBuildInfoIsRHEL(
buildInfo: unknown
): asserts buildInfo is RHELBuildInfo {
assertObjectHasKeys(buildInfo, 'buildInfo', commonKeys);
assertObjectHasKeys(buildInfo, 'buildInfo', rhelFilenameKeys);
}

export type PackageDetails = {
kind: PackageKind;
filename: string;
} & (
| {
kind: 'windows_setup' | 'windows_msi' | 'windows_zip';
buildInfo: WindowsBuildInfo;
}
| {
kind: 'osx_dmg' | 'osx_zip';
buildInfo: OSXBuildInfo;
}
| {
kind: 'linux_deb' | 'linux_tar';
buildInfo: UbuntuBuildInfo;
}
| {
kind: 'linux_rpm' | 'rhel_tar';
buildInfo: RHELBuildInfo;
}
);

/**
* Extracts the filename of the packaged app from the build info, specific to a kind of package.
*/
export function getPackageDetails(
kind: PackageKind,
buildInfo: unknown
): PackageDetails {
if (
kind === 'windows_setup' ||
kind === 'windows_msi' ||
kind === 'windows_zip'
) {
assertBuildInfoIsWindows(buildInfo);
return { kind, buildInfo, filename: buildInfo[`${kind}_filename`] };
} else if (kind === 'osx_dmg' || kind === 'osx_zip') {
assertBuildInfoIsOSX(buildInfo);
return { kind, buildInfo, filename: buildInfo[`${kind}_filename`] };
} else if (kind === 'linux_deb' || kind === 'linux_tar') {
assertBuildInfoIsUbuntu(buildInfo);
return { kind, buildInfo, filename: buildInfo[`${kind}_filename`] };
} else if (kind === 'linux_rpm' || kind === 'rhel_tar') {
assertBuildInfoIsRHEL(buildInfo);
return { kind, buildInfo, filename: buildInfo[`${kind}_filename`] };
} else {
// eslint-disable-next-line @typescript-eslint/restrict-template-expressions
throw new Error(`Unsupported package kind: ${kind}`);
}
}

function readJson<T extends object>(...segments: string[]): T {
const result = JSON.parse(
fs.readFileSync(path.join(...segments), 'utf8')
) as unknown;
assert(typeof result === 'object' && result !== null, 'Expected an object');
return result as T;
}

export function readPackageDetails(
kind: PackageKind,
filePath: string
): PackageDetails {
const result = readJson(filePath);
return getPackageDetails(kind, result);
}

export function writeAndReadPackageDetails(
context: SmokeTestsContext
): PackageDetails {
const compassDir = path.resolve(__dirname, '../../../compass');
const infoArgs = {
format: 'json',
dir: compassDir,
platform: context.platform,
arch: context.arch,
out: path.resolve(context.sandboxPath, 'target.json'),
};
console.log({ infoArgs });

// These are known environment variables that will affect the way
// writeBuildInfo works. Log them as a reminder and for our own sanity
console.log(
'info env vars',
pick(process.env, [
'HADRON_DISTRIBUTION',
'HADRON_APP_VERSION',
'HADRON_PRODUCT',
'HADRON_PRODUCT_NAME',
'HADRON_READONLY',
'HADRON_ISOLATED',
'DEV_VERSION_IDENTIFIER',
'IS_RHEL',
])
);
writeBuildInfo(infoArgs);
return readPackageDetails(context.package, infoArgs.out);
}
12 changes: 12 additions & 0 deletions packages/compass-e2e-tests/helpers/smoke-test/context.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { type PackageKind } from './packages';

export type SmokeTestsContext = {
bucketName?: string;
bucketKeyPrefix?: string;
platform: 'win32' | 'darwin' | 'linux';
arch: 'x64' | 'arm64';
package: PackageKind;
forceDownload?: boolean;
localPackage?: boolean;
sandboxPath: string;
};
32 changes: 32 additions & 0 deletions packages/compass-e2e-tests/helpers/smoke-test/directories.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import assert from 'node:assert';
import crypto from 'node:crypto';
import fs from 'node:fs';
import path from 'node:path';

function ensureSandboxesDirectory() {
const sandboxesPath = path.resolve(__dirname, '../../.smoke-sandboxes');
if (!fs.existsSync(sandboxesPath)) {
fs.mkdirSync(sandboxesPath, { recursive: true });
}
return sandboxesPath;
}

export function createSandbox() {
const nonce = crypto.randomBytes(4).toString('hex');
const sandboxPath = path.resolve(ensureSandboxesDirectory(), nonce);
assert.equal(
fs.existsSync(sandboxPath),
false,
`Failed to create sandbox at '${sandboxPath}' - it already exists`
);
fs.mkdirSync(sandboxPath);
return sandboxPath;
}

export function ensureDownloadsDirectory() {
const downloadsPath = path.resolve(__dirname, '../../.smoke-downloads');
if (!fs.existsSync(downloadsPath)) {
fs.mkdirSync(downloadsPath, { recursive: true });
}
return downloadsPath;
}
Loading

0 comments on commit 84ffd4d

Please sign in to comment.