From 3b59a2a6ee8b0ec358561d4ba6e79372e9a52811 Mon Sep 17 00:00:00 2001 From: jgresham Date: Mon, 6 May 2024 14:44:02 -0700 Subject: [PATCH] chore: podman install ci tests by container matrix --- .github/workflows/podman-install-linux.yml | 40 ++++++ README.md | 6 + package.json | 8 +- src/__tests__/ci/installPodman.test.ts | 125 ++++++++++++++++++ src/main/files.ts | 4 + .../podman/install/debianInstallScript.ts | 19 +-- src/main/podman/install/install.ts | 2 +- .../podman/install/ubuntuInstallScript.ts | 18 +-- .../podman/uninstall/aptUninstallScript.ts | 2 + .../podman/uninstall/dnfUninstallScript.ts | 1 + src/main/podman/uninstall/uninstallOnLinux.ts | 35 ++--- .../podman/uninstall/yumUninstallScript.ts | 1 + vite.base.config.ts | 24 ++-- vite.main.config.ts | 10 +- vite.renderer.config.ts | 13 +- vitest.config.ts | 16 --- wdio.conf.ts | 6 +- 17 files changed, 246 insertions(+), 84 deletions(-) create mode 100644 .github/workflows/podman-install-linux.yml create mode 100644 src/__tests__/ci/installPodman.test.ts create mode 100644 src/main/podman/uninstall/aptUninstallScript.ts create mode 100644 src/main/podman/uninstall/dnfUninstallScript.ts create mode 100644 src/main/podman/uninstall/yumUninstallScript.ts delete mode 100644 vitest.config.ts diff --git a/.github/workflows/podman-install-linux.yml b/.github/workflows/podman-install-linux.yml new file mode 100644 index 000000000..7c977425f --- /dev/null +++ b/.github/workflows/podman-install-linux.yml @@ -0,0 +1,40 @@ +name: Podman Install on Linux Distros +on: + push: + # branches: [ main ] + workflow_dispatch: + +jobs: + container-test-job: + runs-on: ubuntu-latest + strategy: + matrix: + container: [ + "ubuntu:24", + "ubuntu:23", + "ubuntu:22", + "debian:12", + "fedora:40", + "fedora:39" + ] + + container: + image: ${{ matrix.container }} + env: + NODE_ENV: test + steps: + - name: print os version details + run: cat /etc/os-release + + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: 20 + + - name: 🧱 Install Dependencies + run: | + npm ci + + - name: 🧱 Run tests + run: | + npm run testCi diff --git a/README.md b/README.md index a92b0e7a5..902216477 100644 --- a/README.md +++ b/README.md @@ -70,6 +70,12 @@ arch options include: ia32, x64, armv7l, arm64, mips64el, universal Unit tests with `npm run test` +### Frontend (React) tests +`npm run test -- --config vite.renderer.config.ts --dir src/__tests__/react` + +### Backend (Nodejs) tests +`npm run test -- --config vite.main.config.ts --dir src/__tests__/node` + ### End-to-end (e2e) tests For e2e tests, we use webdriver and an electron plugin to automate testing. diff --git a/package.json b/package.json index 50cbcf981..c62aa8b7f 100644 --- a/package.json +++ b/package.json @@ -14,9 +14,11 @@ "lint": "biome lint ./src", "safeFix": "biome check --apply ./src", "unsafeFix": "biome check --apply-unsafe ./src", - "test": "vitest --run", - "testWatch": "vitest --watch", - "coverage": "vitest run --coverage", + "test": "npm run testFrontend && npm run testBackend", + "testFrontend": "vitest run --config vite.renderer.config.ts --dir src/__tests__/react", + "testBackend": "vitest run --config vite.main.config.ts --dir src/__tests__/node", + "testCi": "vitest run --dir src/__tests__/ci --config vite.main.config.ts", + "coverage": "npm run testFrontend -- --coverage && npm run testBackend -- --coverage", "prepare": "husky", "storybook": "storybook dev -p 6006", "build-storybook": "storybook build", diff --git a/src/__tests__/ci/installPodman.test.ts b/src/__tests__/ci/installPodman.test.ts new file mode 100644 index 000000000..9684d4931 --- /dev/null +++ b/src/__tests__/ci/installPodman.test.ts @@ -0,0 +1,125 @@ +import { execSync } from 'node:child_process'; +import { beforeAll, describe, expect, it, vi } from 'vitest'; +import { isLinux } from '../../main/platform.js'; +import { + PODMAN_MIN_VERSION, + getInstalledPodmanVersion, +} from '../../main/podman/install/install.js'; +// import installPodman from '../../main/podman/install/install.js'; +import installOnLinux from '../../main/podman/install/installOnLinux.js'; +import uninstallOnLinux from '../../main/podman/uninstall/uninstallOnLinux.js'; + +beforeAll(async () => { + // called once before all tests run + if (!isLinux()) { + throw new Error('This test suite is only for Linux'); + } + + vi.mock('../../main/logger.js', () => { + return { + default: { + info: vi.fn((msg: string) => { + console.log(msg); + }), + error: vi.fn((msg: string) => { + console.log(msg); + }), + }, + autoUpdateLogger: vi.fn(), + }; + }); + + vi.mock('../../main/execHelper.js', () => { + return { + execAwait: vi.fn((command: string) => { + return execSync(command); + }), + }; + }); + + vi.mock('electron', () => { + return { + app: { + getAppPath: vi.fn(() => { + return '/we/out/here/nice-node'; + }), + getPath: vi.fn((pathName: string) => { + return pathName; + }), + isPackaged: false, + on: vi.fn(), + getName: () => 'NiceNode', + }, + powerMonitor: { + isOnBatteryPower: vi.fn(), + }, + autoUpdater: vi.fn(), + nativeTheme: { + on: vi.fn(), + }, + }; + }); + vi.mock('@sentry/electron/main', () => { + return { + init: vi.fn(() => { + return {}; + }), + }; + }); + vi.mock('../../main/i18nMain', () => { + return { + default: { + // Mock any other properties or methods if needed + getFixedT: vi.fn((x, y) => { + return { + t: (k: any) => k, // just return the key for simplicity + }; + }), + }, + // changeLanguage: vi.fn(), + // init: vi.fn(), + // use: vi.fn(() => i18nMock), // for chaining .use() calls + }; + }); +}); +const versionRegex = /(\d+\.\d+\.\d+)/; + +describe.sequential('Install and Uninstall Podman', () => { + it('If pre-installed, uninstall to test install next', async () => { + const versionBefore = await getInstalledPodmanVersion(); + if (versionBefore !== undefined) { + expect(versionBefore).toMatch(versionRegex); + const resultInstall = await uninstallOnLinux(); + expect(resultInstall).toEqual(true); + const versionAfter = await getInstalledPodmanVersion(); + expect(versionAfter).toBeUndefined(); + } + }); + + it('Install is successful and returns true', async () => { + const versionBefore = await getInstalledPodmanVersion(); + expect(versionBefore).toBeUndefined(); + const resultInstall = await installOnLinux(); + expect(resultInstall).toEqual(true); + const versionAfter = await getInstalledPodmanVersion(); + expect(versionAfter).toMatch(versionRegex); + expect(versionAfter > PODMAN_MIN_VERSION).toBeTruthy(); + }, 20000); // 20 seconds timeout + + it('Uninstall is successful and returns true', async () => { + const versionBefore = await getInstalledPodmanVersion(); + expect(versionBefore).toMatch(versionRegex); + expect(versionBefore > PODMAN_MIN_VERSION).toBeTruthy(); + // expect(versionBefore).toEqual('4.3.1'); + const resultInstall = await uninstallOnLinux(); + expect(resultInstall).toEqual(true); + const versionAfter = await getInstalledPodmanVersion(); + expect(versionAfter).toBeUndefined(); + }); + // check not installed before + // version? + // it('Is successful and returns true', async () => { + // const resultInstall = await installPodman(); + // expect(resultInstall).toEqual(true); + // }); +}); diff --git a/src/main/files.ts b/src/main/files.ts index a6f88eb58..9cf695c3a 100644 --- a/src/main/files.ts +++ b/src/main/files.ts @@ -230,5 +230,9 @@ export const getAssetsFolder = (): string => { return path.resolve(__dirname, '..', '..', 'assets'); } + if (process.env.NODE_ENV === 'test') { + return 'assets'; + } + return path.resolve((process as any).resourcesPath, 'assets'); }; diff --git a/src/main/podman/install/debianInstallScript.ts b/src/main/podman/install/debianInstallScript.ts index 2e46f742d..aeba963fe 100644 --- a/src/main/podman/install/debianInstallScript.ts +++ b/src/main/podman/install/debianInstallScript.ts @@ -1,15 +1,4 @@ -// Same script as on https://podman.io/docs/installation, except the 'sudo' is removed as -// sudo-prompt will not execute a command with sudo -export const script = `apt-get -y update -qq -apt-get -y install curl -mkdir -p /etc/apt/keyrings -curl -fsSL https://download.opensuse.org/repositories/devel:kubic:libcontainers:unstable/Debian_Unstable/Release.key \ - | gpg --dearmor \ - | tee /etc/apt/keyrings/devel_kubic_libcontainers_unstable.gpg > /dev/null -echo \ - "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/devel_kubic_libcontainers_unstable.gpg]\ - https://download.opensuse.org/repositories/devel:kubic:libcontainers:unstable/Debian_Unstable/ /" \ - | tee /etc/apt/sources.list.d/devel:kubic:libcontainers:unstable.list > /dev/null -apt-get -y update -apt-get -y upgrade -apt-get -y install podman`; +// Reference https://podman.io/docs/installation +export const script = `rm -rf /etc/apt/sources.list.d/devel:kubic:libcontainers:unstable.list +apt -y update -qq +apt install -y podman`; diff --git a/src/main/podman/install/install.ts b/src/main/podman/install/install.ts index ffbe8d05d..8ee5622f8 100644 --- a/src/main/podman/install/install.ts +++ b/src/main/podman/install/install.ts @@ -6,7 +6,7 @@ import installOnMac from './installOnMac'; import installOnWindows from './installOnWindows'; export const PODMAN_LATEST_VERSION = '5.0.2'; -export const PODMAN_MIN_VERSION = '4.6.0'; +export const PODMAN_MIN_VERSION = '4.3.0'; const installPodman = async (): Promise => { logger.info('Starting podman install...'); diff --git a/src/main/podman/install/ubuntuInstallScript.ts b/src/main/podman/install/ubuntuInstallScript.ts index 738a57815..aeba963fe 100644 --- a/src/main/podman/install/ubuntuInstallScript.ts +++ b/src/main/podman/install/ubuntuInstallScript.ts @@ -1,14 +1,4 @@ -// Same script as on https://podman.io/docs/installation, except the 'sudo' is removed as -// sudo-prompt will not execute a command with sudo -export const script = `apt-get -y update -qq -apt-get -y -qq install curl -mkdir -p /etc/apt/keyrings -curl -fsSL https://download.opensuse.org/repositories/devel:kubic:libcontainers:unstable/xUbuntu_22.04/Release.key \ - | gpg --dearmor \ - | tee /etc/apt/keyrings/devel_kubic_libcontainers_unstable.gpg > /dev/null -echo \ - "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/devel_kubic_libcontainers_unstable.gpg]\ - https://download.opensuse.org/repositories/devel:kubic:libcontainers:unstable/xUbuntu_22.04/ /" \ - | tee /etc/apt/sources.list.d/devel:kubic:libcontainers:unstable.list > /dev/null -apt-get update -qq -apt-get -qq -y install podman`; +// Reference https://podman.io/docs/installation +export const script = `rm -rf /etc/apt/sources.list.d/devel:kubic:libcontainers:unstable.list +apt -y update -qq +apt install -y podman`; diff --git a/src/main/podman/uninstall/aptUninstallScript.ts b/src/main/podman/uninstall/aptUninstallScript.ts new file mode 100644 index 000000000..166263980 --- /dev/null +++ b/src/main/podman/uninstall/aptUninstallScript.ts @@ -0,0 +1,2 @@ +export const script = `rm -rf /etc/apt/sources.list.d/devel:kubic:libcontainers:unstable.list +apt remove -y podman`; diff --git a/src/main/podman/uninstall/dnfUninstallScript.ts b/src/main/podman/uninstall/dnfUninstallScript.ts new file mode 100644 index 000000000..4a43e5409 --- /dev/null +++ b/src/main/podman/uninstall/dnfUninstallScript.ts @@ -0,0 +1 @@ +export const script = 'dnf remove -y podman'; diff --git a/src/main/podman/uninstall/uninstallOnLinux.ts b/src/main/podman/uninstall/uninstallOnLinux.ts index c3195c7bc..af619fa74 100644 --- a/src/main/podman/uninstall/uninstallOnLinux.ts +++ b/src/main/podman/uninstall/uninstallOnLinux.ts @@ -2,6 +2,13 @@ import { reportEvent } from '../../events'; import { execAwait } from '../../execHelper'; // import { app } from 'electron'; import logger from '../../logger'; +import { + type PackageManager, + findPackageManager, +} from '../../nn-auto-updater/findPackageManager.js'; +import { script as aptUninstallScript } from './aptUninstallScript'; +import { script as dnfUninstallScript } from './dnfUninstallScript'; +import { script as yumUninstallScript } from './yumUninstallScript'; /** * Uninstall podman by removing binaries and various configuration files @@ -12,29 +19,23 @@ const uninstallOnLinux = async (): Promise => { // Returns /Users/ (Ex. /Users/johns) // const userHome = app.getPath('home'); - // todo: delete more? - const foldersToDelete = [ - '/etc/apt/sources.list.d/devel:kubic:libcontainers:unstable.list', - // '/opt/podman', - // `${userHome}/.local/share/containers`, - // `${userHome}/.config/containers`, - // `${userHome}/.ssh/*podman*`, - // `${userHome}/.ssh/*nicenode*`, - // `/private/etc/paths.d/podman-pkg`, - // `/usr/local/podman`, - // `/Library/LaunchDaemons/*podman*`, - ]; // 1. (applies to mac, also linux?) This can throw return an error if a file or folder doesn't exist. // This is ok, because it will still delete the other folders that do exist. // 2. Combine sudo commands so that user is only prompted for password once - const rmCommand = ` - rm -rf ${foldersToDelete.join(' ')} - apt-get remove -y podman`; - const { stdout, stderr } = await execAwait(rmCommand, { + const pkgManager: PackageManager = await findPackageManager(); + let uninstallScript = ''; + if (pkgManager === 'dpkg') { + uninstallScript = aptUninstallScript; + } else if (pkgManager === 'dnf') { + uninstallScript = dnfUninstallScript; + } else if (pkgManager === 'yum') { + uninstallScript = yumUninstallScript; + } + const { stdout, stderr } = await execAwait(uninstallScript, { log: true, sudo: true, }); - logger.info(`${rmCommand} stdout, stderr ${stdout} ${stderr}`); + logger.info(`${uninstallScript} stdout, stderr ${stdout} ${stderr}`); return true; } catch (err: any) { logger.error(err); diff --git a/src/main/podman/uninstall/yumUninstallScript.ts b/src/main/podman/uninstall/yumUninstallScript.ts new file mode 100644 index 000000000..7ea1cf8ec --- /dev/null +++ b/src/main/podman/uninstall/yumUninstallScript.ts @@ -0,0 +1 @@ +export const script = 'yum remove -y podman'; diff --git a/vite.base.config.ts b/vite.base.config.ts index 7f210fbe5..bb6971f9b 100644 --- a/vite.base.config.ts +++ b/vite.base.config.ts @@ -45,18 +45,20 @@ export function getDefineKeys(names: string[]) { export function getBuildDefine(env: ConfigEnv<'build'>) { const { command, forgeConfig } = env; - const names = forgeConfig.renderer.filter(({ name }) => name != null).map(({ name }) => name!); - const defineKeys = getDefineKeys(names); - const define = Object.entries(defineKeys).reduce((acc, [name, keys]) => { - const { VITE_DEV_SERVER_URL, VITE_NAME } = keys; - const def = { - [VITE_DEV_SERVER_URL]: command === 'serve' ? JSON.stringify(process.env[VITE_DEV_SERVER_URL]) : undefined, - [VITE_NAME]: JSON.stringify(name), - }; - return { ...acc, ...def }; - }, {} as Record); + if(forgeConfig?.renderer) { + const names = forgeConfig.renderer.filter(({ name }) => name != null).map(({ name }) => name!); + const defineKeys = getDefineKeys(names); + const define = Object.entries(defineKeys)?.reduce((acc, [name, keys]) => { + const { VITE_DEV_SERVER_URL, VITE_NAME } = keys; + const def = { + [VITE_DEV_SERVER_URL]: command === 'serve' ? JSON.stringify(process.env[VITE_DEV_SERVER_URL]) : undefined, + [VITE_NAME]: JSON.stringify(name), + }; + return { ...acc, ...def }; + }, {} as Record); - return define; + return define; + } } export function pluginExposeRenderer(name: string): Plugin { diff --git a/vite.main.config.ts b/vite.main.config.ts index b2b11fa8a..1b232c5e7 100644 --- a/vite.main.config.ts +++ b/vite.main.config.ts @@ -1,4 +1,4 @@ -import type { ConfigEnv, UserConfig } from 'vite'; +import type { ConfigEnv, Plugin, PluginOption, UserConfig } from 'vite'; import { defineConfig, mergeConfig } from 'vite'; import { getBuildConfig, getBuildDefine, external, pluginHotRestart } from './vite.base.config.js'; @@ -9,10 +9,14 @@ export default defineConfig((env) => { const forgeEnv = env as ConfigEnv<'build'>; const { forgeConfigSelf } = forgeEnv; const define = getBuildDefine(forgeEnv); + const plugins: PluginOption[] = [] + if(process.env.NODE_ENV !== 'test') { + plugins.push(pluginHotRestart('restart')) + } const config: UserConfig = { build: { lib: { - entry: forgeConfigSelf.entry!, + entry: forgeConfigSelf?.entry, fileName: () => '[name].js', formats: ['es'], }, @@ -23,7 +27,7 @@ export default defineConfig((env) => { // main process static assets // ./assets dir is copied to .vite/build dir publicDir: './assets', - plugins: [pluginHotRestart('restart')], + plugins, define, resolve: { // Load the Node.js entry. diff --git a/vite.renderer.config.ts b/vite.renderer.config.ts index 50987b3d1..b3202db01 100644 --- a/vite.renderer.config.ts +++ b/vite.renderer.config.ts @@ -1,4 +1,4 @@ -import type { ConfigEnv, UserConfig } from 'vite'; +import type { ConfigEnv, PluginOption, UserConfig } from 'vite'; import { defineConfig } from 'vite'; import { vanillaExtractPlugin } from '@vanilla-extract/vite-plugin'; import svgr from "vite-plugin-svgr"; @@ -11,7 +11,14 @@ console.log("vite.renderer.config.ts"); export default defineConfig((env) => { const forgeEnv = env as ConfigEnv<'renderer'>; const { root, mode, forgeConfigSelf } = forgeEnv; - const name = forgeConfigSelf.name ?? ''; + const name = forgeConfigSelf?.name ?? ''; + + const plugins: PluginOption[] = [] + if(process.env.NODE_ENV !== 'test') { + plugins.push(pluginExposeRenderer(name)) + } + plugins.push(vanillaExtractPlugin(), svgr()) + console.log("vite.renderer.config.ts plugins: ", plugins); return { root, @@ -21,7 +28,7 @@ export default defineConfig((env) => { outDir: `.vite/renderer/${name}`, publicDir: 'assets' }, - plugins: [pluginExposeRenderer(name), vanillaExtractPlugin(), svgr()], + plugins, resolve: { preserveSymlinks: true, }, diff --git a/vitest.config.ts b/vitest.config.ts deleted file mode 100644 index ad442f736..000000000 --- a/vitest.config.ts +++ /dev/null @@ -1,16 +0,0 @@ -/// - -import { defineConfig } from 'vite'; -import { vanillaExtractPlugin } from '@vanilla-extract/vite-plugin'; - -console.log("vitest.config.ts"); - -// https://vitejs.dev/config -export default defineConfig((env) => { - return { - plugins: [vanillaExtractPlugin()], - } -}); - -// todo: reocmmended to join this with other vite configs -// https://vitest.dev/guide/#configuring-vitest diff --git a/wdio.conf.ts b/wdio.conf.ts index e826ff947..ad1ae7b2e 100644 --- a/wdio.conf.ts +++ b/wdio.conf.ts @@ -73,6 +73,9 @@ export const config: Options.Testrunner = { browserName: 'electron', // Electron service options // see https://webdriver.io/docs/wdio-electron-service/#configuration + 'goog:chromeOptions': { + args:['--headless', '--disable-dev-shm-usage', '--no-sandbox', '--disable-gpu', '--window-size=1680,1050'] + }, 'wdio:electronServiceOptions': { // custom application args // See https://github.com/webdriverio-community/wdio-electron-service#appargs @@ -90,7 +93,8 @@ export const config: Options.Testrunner = { // Define all options that are relevant for the WebdriverIO instance here // // Level of logging verbosity: trace | debug | info | warn | error | silent - logLevel: 'info', + // logLevel: 'info', + logLevel: 'debug', // // Set specific log levels per logger // loggers: