diff --git a/.evergreen/buildvariants-and-tasks.in.yml b/.evergreen/buildvariants-and-tasks.in.yml index b9370851955..1ec21489680 100644 --- a/.evergreen/buildvariants-and-tasks.in.yml +++ b/.evergreen/buildvariants-and-tasks.in.yml @@ -80,18 +80,18 @@ const SMOKETEST_BUILD_VARIANTS = [ // run_on: 'rhel80-large', // depends_on: 'package-rhel', // }, -// { -// name: 'smoketest-macos-x64', -// display_name: 'Smoketest MacOS Intel', -// run_on: 'macos-14', -// depends_on: 'package-macos-x64', -// }, -// { -// name: 'smoketest-macos-arm', -// display_name: 'Smoketest MacOS Arm64', -// run_on: 'macos-14-arm64', -// depends_on: 'package-macos-arm', -// } + { + name: 'smoketest-macos-x64', + display_name: 'Smoketest MacOS Intel', + run_on: 'macos-14', + depends_on: 'package-macos-x64', + }, + { + name: 'smoketest-macos-arm', + display_name: 'Smoketest MacOS Arm64', + run_on: 'macos-14-arm64', + depends_on: 'package-macos-arm', + } ]; const TEST_PACKAGED_APP_BUILD_VARIANTS = [ diff --git a/.evergreen/buildvariants-and-tasks.yml b/.evergreen/buildvariants-and-tasks.yml index b5987af6c34..d0a40dec336 100644 --- a/.evergreen/buildvariants-and-tasks.yml +++ b/.evergreen/buildvariants-and-tasks.yml @@ -84,6 +84,22 @@ buildvariants: variant: package-ubuntu tasks: - name: smoketest-compass + - name: smoketest-macos-x64-compass + display_name: Smoketest MacOS Intel (compass) + run_on: macos-14 + depends_on: + - name: package-compass + variant: package-macos-x64 + tasks: + - name: smoketest-compass + - name: smoketest-macos-arm-compass + display_name: Smoketest MacOS Arm64 (compass) + run_on: macos-14-arm64 + depends_on: + - name: package-compass + variant: package-macos-arm + tasks: + - name: smoketest-compass - name: test-eol-servers display_name: Test EoL Servers run_on: ubuntu1804-large diff --git a/packages/compass-e2e-tests/installers/helpers.ts b/packages/compass-e2e-tests/installers/helpers.ts new file mode 100644 index 00000000000..9e0b6895661 --- /dev/null +++ b/packages/compass-e2e-tests/installers/helpers.ts @@ -0,0 +1,19 @@ +import type { SpawnSyncReturns } from 'child_process'; + +export function assertSpawnSyncResult( + result: SpawnSyncReturns, + name: string +) { + if (result.status === null) { + if (result.signal !== null) { + throw new Error(`${name} terminated due to signal ${result.signal}`); + } + + // not supposed to be possible to get here, but just in case + throw new Error(`${name} terminated with no status or signal`); + } + + if (result.status !== 0) { + throw new Error(`${name} failed with exit code ${result.status}`); + } +} diff --git a/packages/compass-e2e-tests/installers/mac-dmg.ts b/packages/compass-e2e-tests/installers/mac-dmg.ts new file mode 100644 index 00000000000..7da9ac1be91 --- /dev/null +++ b/packages/compass-e2e-tests/installers/mac-dmg.ts @@ -0,0 +1,39 @@ +import { existsSync } from 'fs'; +import { assertSpawnSyncResult } from './helpers'; +import type { InstalledAppInfo, Package } from './types'; +import { spawnSync } from 'child_process'; + +function exec(command: string, args: string[]) { + console.log(command, ...args); + + assertSpawnSyncResult( + spawnSync(command, args, { + encoding: 'utf8', + stdio: 'inherit', + }), + `${command} ${args.join(' ')}` + ); +} + +export async function installMacDMG( + appName: string, + { filepath }: Package +): Promise { + const fullDestinationPath = `/Applications/${appName}.app`; + + if (existsSync(fullDestinationPath)) { + throw new Error(`${fullDestinationPath} already exists`); + } + + exec('hdiutil', ['attach', filepath]); + try { + exec('cp', ['-r', `/Volumes/${appName}/${appName}.app`, '/Applications']); + } finally { + exec('hdiutil', ['detach', `/Volumes/${appName}`]); + } + + return Promise.resolve({ + appName, + appPath: `/Applications/${appName}.app`, + }); +} diff --git a/packages/compass-e2e-tests/installers/types.ts b/packages/compass-e2e-tests/installers/types.ts new file mode 100644 index 00000000000..70855624aa3 --- /dev/null +++ b/packages/compass-e2e-tests/installers/types.ts @@ -0,0 +1,9 @@ +export type Package = { + filename: string; + filepath: string; +}; + +export type InstalledAppInfo = { + appName: string; + appPath: string; +}; diff --git a/packages/compass-e2e-tests/smoke-test.ts b/packages/compass-e2e-tests/smoke-test.ts index 67a9cd2de64..2417f0731e9 100755 --- a/packages/compass-e2e-tests/smoke-test.ts +++ b/packages/compass-e2e-tests/smoke-test.ts @@ -1,4 +1,5 @@ #!/usr/bin/env npx ts-node +import { spawnSync } from 'child_process'; import { createWriteStream, existsSync, promises as fs } from 'fs'; import path from 'path'; import yargs from 'yargs'; @@ -7,6 +8,9 @@ import { hideBin } from 'yargs/helpers'; import https from 'https'; import { pick } from 'lodash'; import { handler as writeBuildInfo } from 'hadron-build/commands/info'; +import type { InstalledAppInfo, Package } from './installers/types'; +import { installMacDMG } from './installers/mac-dmg'; +import { assertSpawnSyncResult } from './installers/helpers'; const argv = yargs(hideBin(process.argv)) .scriptName('smoke-tests') @@ -137,6 +141,10 @@ async function run() { writeBuildInfo(infoArgs); const buildInfo = JSON.parse(await fs.readFile(infoArgs.out, 'utf8')); + if (!buildInfoIsCommon(buildInfo)) { + throw new Error('buildInfo is missing'); + } + // filter the extensions given the platform (isWindows, isOSX, isUbuntu, isRHEL) and extension const { isWindows, isOSX, isRHEL, isUbuntu, extension } = context; @@ -150,9 +158,9 @@ async function run() { if (!context.skipDownload) { await Promise.all( - packages.map(async ({ name, filepath }) => { + packages.map(async ({ filename, filepath }) => { await fs.mkdir(path.dirname(filepath), { recursive: true }); - const url = `https://${context.bucketName}.s3.amazonaws.com/${context.bucketKeyPrefix}/${name}`; + const url = `https://${context.bucketName}.s3.amazonaws.com/${context.bucketKeyPrefix}/${filename}`; console.log(url); return downloadFile(url, filepath); }) @@ -162,6 +170,24 @@ async function run() { verifyPackagesExist(packages); // TODO(COMPASS-8533): extract or install each package and then test the Compass binary + for (const pkg of packages) { + let appInfo: InstalledAppInfo | undefined = undefined; + + console.log('installing', pkg.filepath); + + if (pkg.filename.endsWith('.dmg')) { + appInfo = await installMacDMG(buildInfo.productName, pkg); + } + + // TODO: all the other installers go here + + if (appInfo) { + console.log('testing', appInfo.appPath); + testInstalledApp(appInfo); + } else { + console.log(`no app got installed for ${pkg.filename}`); + } + } } function platformFromContext( @@ -189,6 +215,18 @@ type PackageFilterConfig = Pick< // subsets of the hadron-build info result +const commonKeys = ['productName']; +type CommonBuildInfo = Record; + +function buildInfoIsCommon(buildInfo: any): buildInfo is CommonBuildInfo { + for (const key of commonKeys) { + if (!buildInfo[key]) { + return false; + } + } + return true; +} + const windowsFilenameKeys = [ 'windows_setup_filename', 'windows_msi_filename', @@ -245,11 +283,6 @@ function buildInfoIsRHEL(buildInfo: any): buildInfo is RHELBuildInfo { return true; } -type Package = { - name: string; - filepath: string; -}; - function getFilteredPackages( compassDir: string, buildInfo: any, @@ -282,11 +315,11 @@ function getFilteredPackages( const extension = config.extension; return names - .filter((name) => !extension || name.endsWith(extension)) - .map((name) => { + .filter((filename) => !extension || filename.endsWith(extension)) + .map((filename) => { return { - name, - filepath: path.join(compassDir, 'dist', name), + filename, + filepath: path.join(compassDir, 'dist', filename), }; }); } @@ -333,6 +366,32 @@ function verifyPackagesExist(packages: Package[]): void { } } +function testInstalledApp(appInfo: InstalledAppInfo) { + const result = spawnSync( + 'npm', + [ + 'run', + '--unsafe-perm', + 'test-packaged', + '--workspace', + 'compass-e2e-tests', + '--', + '--test-filter=time-to-first-query', + ], + { + encoding: 'utf8', + stdio: 'inherit', + env: { + ...process.env, + COMPASS_APP_NAME: appInfo.appName, + COMPASS_APP_PATH: appInfo.appPath, + }, + } + ); + + assertSpawnSyncResult(result, 'npm run test-packaged'); +} + run() .then(function () { console.log('done');