diff --git a/.github/workflows/full_check.yaml b/.github/workflows/full_check.yaml index 2beda33b..abc91f87 100644 --- a/.github/workflows/full_check.yaml +++ b/.github/workflows/full_check.yaml @@ -585,7 +585,8 @@ jobs: cd impls/monero.ts deno run --unstable-ffi --allow-ffi checksum.ts - regression_check: + regression_tests_linux: + name: linux regression tests strategy: matrix: coin: [monero, wownero] @@ -611,7 +612,35 @@ jobs: - name: Run regression tests run: COIN="${{ matrix.coin }}" deno test -A tests/regression.test.ts - integration_check: + regression_tests_macos: + name: macos regression tests + strategy: + matrix: + coin: [monero, wownero] + needs: [ + lib_macos + ] + runs-on: macos-14 + steps: + - uses: denoland/setup-deno@v2 + with: + deno-version: v2.x + + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + submodules: recursive + + - uses: actions/download-artifact@v4 + with: + name: linux ${{ matrix.coin }} + path: release/${{ matrix.coin }} + + - name: Run regression tests + run: COIN="${{ matrix.coin }}" deno test -A tests/regression.test.ts + + integration_tests_linux: + name: linux integration tests strategy: matrix: coin: [monero, wownero] @@ -642,6 +671,38 @@ jobs: SECRET_WALLET_RESTORE_HEIGHT: ${{ secrets.SECRET_WALLET_RESTORE_HEIGHT }} + integration_tests_macos: + name: macos integration tests + strategy: + matrix: + coin: [monero, wownero] + needs: [ + lib_macos + ] + runs-on: macos-14 + steps: + - uses: denoland/setup-deno@v2 + with: + deno-version: v2.x + + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + submodules: recursive + + - uses: actions/download-artifact@v4 + with: + name: linux ${{ matrix.coin }} + path: release/${{ matrix.coin }} + + - name: Run integration tests + run: COIN="${{ matrix.coin }}" deno test -A tests/integration.test.ts + env: + SECRET_WALLET_PASSWORD: ${{ secrets.SECRET_WALLET_PASSWORD }} + SECRET_WALLET_MNEMONIC: ${{ secrets.SECRET_WALLET_MNEMONIC }} + SECRET_WALLET_RESTORE_HEIGHT: ${{ secrets.SECRET_WALLET_RESTORE_HEIGHT }} + + comment_pr: name: comment on pr runs-on: ubuntu-latest diff --git a/.gitignore b/.gitignore index 9159f7de..7dc59830 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,4 @@ release/ build/ -tests/monero-cli -tests/wownero-cli -tests/libs +tests/dependencies tests/wallets diff --git a/tests/compare.ts b/tests/compare.ts index 2fd27b8e..8c13fc53 100755 --- a/tests/compare.ts +++ b/tests/compare.ts @@ -1,24 +1,13 @@ import { assertEquals } from "jsr:@std/assert"; -import { - loadMoneroDylib, - loadWowneroDylib, - moneroSymbols, - WalletManager, - wowneroSymbols, -} from "../impls/monero.ts/mod.ts"; +import { WalletManager } from "../impls/monero.ts/mod.ts"; +import { loadDylib } from "./utils.ts"; const coin = Deno.args[0] as "monero" | "wownero"; const version = Deno.args[1]; const walletInfo = JSON.parse(Deno.args[2]); -if (coin === "monero") { - const dylib = Deno.dlopen(`tests/libs/${version}/monero_libwallet2_api_c.so`, moneroSymbols); - loadMoneroDylib(dylib); -} else { - const dylib = Deno.dlopen(`tests/libs/${version}/wownero_libwallet2_api_c.so`, wowneroSymbols); - loadWowneroDylib(dylib); -} +loadDylib(coin, version); const walletManager = await WalletManager.new(); const wallet = await walletManager.openWallet(walletInfo.path, walletInfo.password); diff --git a/tests/deno.lock b/tests/deno.lock index b67d77d4..8d368f6c 100644 --- a/tests/deno.lock +++ b/tests/deno.lock @@ -4,19 +4,15 @@ "jsr:@david/dax@*": "0.42.0", "jsr:@david/path@0.2": "0.2.0", "jsr:@david/which@~0.4.1": "0.4.1", - "jsr:@std/assert@*": "0.221.0", "jsr:@std/assert@0.221": "0.221.0", "jsr:@std/bytes@0.221": "0.221.0", - "jsr:@std/fmt@0.221": "0.221.0", "jsr:@std/fmt@1": "1.0.2", - "jsr:@std/fs@1": "1.0.4", + "jsr:@std/fs@1": "1.0.5", "jsr:@std/io@0.221": "0.221.0", - "jsr:@std/path@1": "1.0.6", - "jsr:@std/path@1.0.8": "1.0.8", - "jsr:@std/path@^1.0.6": "1.0.6", - "jsr:@std/streams@0.221": "0.221.0", - "jsr:@std/streams@^1.0.7": "1.0.7", - "jsr:@std/tar@*": "0.1.2" + "jsr:@std/path@*": "1.0.8", + "jsr:@std/path@1": "1.0.8", + "jsr:@std/path@^1.0.7": "1.0.8", + "jsr:@std/streams@0.221": "0.221.0" }, "jsr": { "@david/dax@0.42.0": { @@ -24,11 +20,11 @@ "dependencies": [ "jsr:@david/path", "jsr:@david/which", - "jsr:@std/fmt@1", + "jsr:@std/fmt", "jsr:@std/fs", "jsr:@std/io", "jsr:@std/path@1", - "jsr:@std/streams@0.221" + "jsr:@std/streams" ] }, "@david/path@0.2.0": { @@ -42,36 +38,27 @@ "integrity": "896a682b111f92ab866cc70c5b4afab2f5899d2f9bde31ed00203b9c250f225e" }, "@std/assert@0.221.0": { - "integrity": "a5f1aa6e7909dbea271754fd4ab3f4e687aeff4873b4cef9a320af813adb489a", - "dependencies": [ - "jsr:@std/fmt@0.221" - ] + "integrity": "a5f1aa6e7909dbea271754fd4ab3f4e687aeff4873b4cef9a320af813adb489a" }, "@std/bytes@0.221.0": { "integrity": "64a047011cf833890a4a2ab7293ac55a1b4f5a050624ebc6a0159c357de91966" }, - "@std/fmt@0.221.0": { - "integrity": "379fed69bdd9731110f26b9085aeb740606b20428ce6af31ef6bd45ef8efa62a" - }, "@std/fmt@1.0.2": { "integrity": "87e9dfcdd3ca7c066e0c3c657c1f987c82888eb8103a3a3baa62684ffeb0f7a7" }, - "@std/fs@1.0.4": { - "integrity": "2907d32d8d1d9e540588fd5fe0ec21ee638134bd51df327ad4e443aaef07123c", + "@std/fs@1.0.5": { + "integrity": "41806ad6823d0b5f275f9849a2640d87e4ef67c51ee1b8fb02426f55e02fd44e", "dependencies": [ - "jsr:@std/path@^1.0.6" + "jsr:@std/path@^1.0.7" ] }, "@std/io@0.221.0": { "integrity": "faf7f8700d46ab527fa05cc6167f4b97701a06c413024431c6b4d207caa010da", "dependencies": [ - "jsr:@std/assert@0.221", + "jsr:@std/assert", "jsr:@std/bytes" ] }, - "@std/path@1.0.6": { - "integrity": "ab2c55f902b380cf28e0eec501b4906e4c1960d13f00e11cfbcd21de15f18fed" - }, "@std/path@1.0.8": { "integrity": "548fa456bb6a04d3c1a1e7477986b6cffbce95102d0bb447c67c4ee70e0364be" }, @@ -80,15 +67,6 @@ "dependencies": [ "jsr:@std/io" ] - }, - "@std/streams@1.0.7": { - "integrity": "1a93917ca0c58c01b2bfb93647189229b1702677f169b6fb61ad6241cd2e499b" - }, - "@std/tar@0.1.2": { - "integrity": "98183102395decd6268253996177804f818580ef547a25b81da0e7cc334db708", - "dependencies": [ - "jsr:@std/streams@^1.0.7" - ] } } } diff --git a/tests/integration.test.ts b/tests/integration.test.ts index 1a650097..100bd433 100644 --- a/tests/integration.test.ts +++ b/tests/integration.test.ts @@ -1,16 +1,7 @@ -import { - CoinsInfo, - type Dylib, - loadMoneroDylib, - loadWowneroDylib, - moneroSymbols, - Wallet, - WalletManager, - wowneroSymbols, -} from "../impls/monero.ts/mod.ts"; +import { CoinsInfo, Wallet, WalletManager } from "../impls/monero.ts/mod.ts"; import { assert, assertEquals } from "jsr:@std/assert"; -import { $, downloadCli, getMoneroC } from "./utils.ts"; +import { $, loadDylib, prepareCli, prepareMoneroC } from "./utils.ts"; const coin = Deno.env.get("COIN"); if (coin !== "monero" && coin !== "wownero") { @@ -53,7 +44,7 @@ const DESTINATION_ADDRESS = coin === "monero" ? MONERO_DESTINATION_ADDRESS : WOW const BILLION = 10n ** 9n; -await getMoneroC(coin, "next"); +await prepareMoneroC(coin, "next"); interface WalletInfo { name: string; @@ -74,14 +65,7 @@ async function clearWallets() { await Deno.mkdir("tests/wallets/"); } -let dylib: Dylib; -if (coin === "monero") { - dylib = Deno.dlopen(`tests/libs/next/monero_libwallet2_api_c.so`, moneroSymbols); - loadMoneroDylib(dylib); -} else { - dylib = Deno.dlopen(`tests/libs/next/wownero_libwallet2_api_c.so`, wowneroSymbols); - loadWowneroDylib(dylib); -} +loadDylib(coin, "next"); Deno.test("0001-polyseed.patch", async (t) => { const WALLETS: Record<"monero" | "wownero", WalletInfo[]> = { @@ -487,7 +471,7 @@ Deno.test("0004-coin-control.patch", { Deno.test("0009-Add-recoverDeterministicWalletFromSpendKey.patch", async () => { await Promise.all([ - downloadCli(coin), + prepareCli(coin), clearWallets(), ]); @@ -498,7 +482,7 @@ Deno.test("0009-Add-recoverDeterministicWalletFromSpendKey.patch", async () => { await Deno.remove("./tests/wallets/stoat"); - const cliPath = `./tests/${coin}-cli/${coin}-wallet-cli`; + const cliPath = `./tests/dependencies/${coin}-cli/${coin}-wallet-cli`; const moneroCliSeed = (await $.raw`${cliPath} --wallet-file ./tests/wallets/stoat --password gornostay --command seed` .stdinText(`gornostay\n`) .lines()).slice(-3).join(" "); diff --git a/tests/regression.test.ts b/tests/regression.test.ts index 82a9f955..14e98476 100755 --- a/tests/regression.test.ts +++ b/tests/regression.test.ts @@ -1,4 +1,4 @@ -import { $, createWalletViaCli, downloadCli, getMoneroC, getMoneroCTags } from "./utils.ts"; +import { $, createWalletViaCli, getMoneroCTags, prepareCli, prepareMoneroC } from "./utils.ts"; const coin = Deno.env.get("COIN"); if (coin !== "monero" && coin !== "wownero") { @@ -11,7 +11,7 @@ Deno.test(`Regression tests (${coin})`, async (t) => { const tags = await getMoneroCTags(); const latestTag = tags[0]; - await Promise.all([getMoneroC(coin, "next"), await getMoneroC(coin, latestTag), downloadCli(coin)]); + await Promise.all([prepareMoneroC(coin, "next"), await prepareMoneroC(coin, latestTag), prepareCli(coin)]); await t.step("Simple (next, latest, next)", async () => { const walletInfo = await createWalletViaCli(coin, "dog", "sobaka"); @@ -27,7 +27,7 @@ Deno.test(`Regression tests (${coin})`, async (t) => { const walletInfo = await createWalletViaCli(coin, "cat", "koshka"); for (const version of tags.toReversed()) { - if (version !== "next" && version !== tags[0]) await getMoneroC(coin, version); + if (version !== "next" && version !== tags[0]) await prepareMoneroC(coin, version); await $`deno run -A ./tests/compare.ts ${coin} ${version} ${JSON.stringify(walletInfo)}`; } diff --git a/tests/utils.ts b/tests/utils.ts index cd052320..60afdb5c 100755 --- a/tests/utils.ts +++ b/tests/utils.ts @@ -1,4 +1,15 @@ import { build$, CommandBuilder } from "jsr:@david/dax"; +import { dirname, join } from "jsr:@std/path"; +import { + downloadDependencies, + getFileInfo, + moneroCInfos, + moneroCliInfo, + Target, + target, + wowneroCliInfo, +} from "./download_deps.ts"; +import { loadMoneroDylib, loadWowneroDylib, moneroSymbols, wowneroSymbols } from "../impls/monero.ts/mod.ts"; export const $ = build$({ commandBuilder: new CommandBuilder() @@ -10,32 +21,69 @@ export const $ = build$({ type Coin = "monero" | "wownero"; -export async function downloadMoneroCli() { - const MONERO_CLI_FILE_NAME = "monero-linux-x64-v0.18.3.4"; - const MONERO_WALLET_CLI_URL = `https://downloads.getmonero.org/cli/${MONERO_CLI_FILE_NAME}.tar.bz2`; +export const dylibNames: (coin: Coin) => Partial> = (coin) => ({ + linux_x86_64: `${coin}_x86_64-linux-gnu_libwallet2_api_c.so`, + darwin_aarch64: `${coin}_aarch64-apple-darwin11_libwallet2_api_c.dylib`, + windows_x86_64: `${coin}_x86_64-w64-mingw32_libwallet2_api_c.dll`, +}); - await $`wget -q -o /dev/null ${MONERO_WALLET_CLI_URL}`; - await $ - .raw`tar -xf ${MONERO_CLI_FILE_NAME}.tar.bz2 --one-top-level=monero-cli --strip-components=1 -C tests`; - await $.raw`rm ${MONERO_CLI_FILE_NAME}.tar.bz2`; +export const moneroTsDylibNames: (coin: Coin) => Partial> = (coin) => ({ + linux_x86_64: `${coin}_libwallet2_api_c.so`, + darwin_aarch64: `${coin}_aarch64-apple-darwin11_libwallet2_api_c.dylib`, + windows_x86_64: `${coin}_libwallet2_api_c.dll`, +}); + +export function loadDylib(coin: Coin, version: MoneroCVersion) { + const dylibName = moneroTsDylibNames(coin)[target]!; + + if (coin === "monero") { + const dylib = Deno.dlopen(`tests/dependencies/libs/${version}/${dylibName}`, moneroSymbols); + loadMoneroDylib(dylib); + return dylib; + } else { + const dylib = Deno.dlopen(`tests/dependencies/libs/${version}/${dylibName}`, wowneroSymbols); + loadWowneroDylib(dylib); + return dylib; + } } -export async function downloadWowneroCli() { - const WOWNERO_CLI_FILE_NAME = "wownero-x86_64-linux-gnu-59db3fe8d"; - const WOWNERO_WALLET_CLI_URL = - `https://codeberg.org/wownero/wownero/releases/download/v0.11.2.0/wownero-x86_64-linux-gnu-59db3fe8d.tar.bz2`; +export async function extract(path: string, out: string) { + const outDir = out.endsWith("/") ? out : dirname(out); + await Deno.mkdir(outDir, { recursive: true }); + + if (path.endsWith(".tar.bz2")) { + let args = `-C ${dirname(out)}`; + if (outDir !== out) { + args = `-C ${out} --strip-components=1`; + } + await $.raw`tar -xf ${path} ${args}`; + } else if (path.endsWith(".zip")) { + await $.raw`unzip ${path} -nu -d ${outDir}`; + } else if (path.endsWith(".xz")) { + await $.raw`xz -kd ${path}`; + await Deno.rename(path.slice(0, -3), out); + } else { + throw new Error("Unsupported archive file for:" + path); + } +} - await $`wget -q -o /dev/null ${WOWNERO_WALLET_CLI_URL}`; - await $ - .raw`tar -xf ${WOWNERO_CLI_FILE_NAME}.tar.bz2 --one-top-level=wownero-cli --strip-components=1 -C tests`; - await $.raw`rm ${WOWNERO_CLI_FILE_NAME}.tar.bz2`; +export async function prepareMoneroCli() { + downloadDependencies(moneroCliInfo); + const path = join("./tests/dependencies", moneroCliInfo.outDir ?? "", getFileInfo(moneroCliInfo).name); + await extract(path, "./tests/dependencies/monero-cli/"); } -export function downloadCli(coin: Coin) { +export async function prepareWowneroCli() { + downloadDependencies(wowneroCliInfo); + const path = join("./tests/dependecies", wowneroCliInfo.outDir ?? "", getFileInfo(wowneroCliInfo).name); + await extract(path, "./tests/dependencies/wownero-cli/"); +} + +export function prepareCli(coin: Coin) { if (coin === "wownero") { - return downloadWowneroCli(); + return prepareWowneroCli(); } - return downloadMoneroCli(); + return prepareMoneroCli(); } interface WalletInfo { @@ -54,7 +102,7 @@ export async function createWalletViaCli( password: string, ): Promise { const path = `./tests/wallets/${name}`; - const cliPath = `./tests/${coin}-cli/${coin}-wallet-cli`; + const cliPath = `./tests/dependencies/${coin}-cli/${coin}-wallet-cli`; await $ .raw`${cliPath} --generate-new-wallet ${path} --password ${password} --mnemonic-language English --command exit` @@ -97,35 +145,42 @@ export async function createWalletViaCli( export type MoneroCVersion = "next" | (string & {}); export async function getMoneroCTags(): Promise { - return (( - await (await fetch( - "https://api.github.com/repos/MrCyjanek/monero_c/releases", - )).json() - ) as { tag_name: string }[]) - .map(({ tag_name }) => tag_name); + const response = await fetch( + "https://api.github.com/repos/MrCyjanek/monero_c/releases", + ); + + if (!response.ok) { + throw new Error("Could not receive monero_c release tags"); + } + + const json = await response.json() as { tag_name: string }[]; + return json.map(({ tag_name }) => tag_name); } -export async function getMoneroC(coin: Coin, version: MoneroCVersion) { - const dylibName = `${coin}_x86_64-linux-gnu_libwallet2_api_c.so`; - const endpointDylibName = `${coin}_libwallet2_api_c.so`; + +export async function prepareMoneroC(coin: Coin, version: MoneroCVersion) { + const dylibName = dylibNames(coin)[target]; + const moneroTsDylibName = moneroTsDylibNames(coin)[target]; + + if (!dylibName || !moneroTsDylibName) { + throw new Error(`Missing dylib name value for target: ${target}`); + } + const releaseDylibName = dylibName.slice(`${coin}_`.length); if (version === "next") { - await $.raw`xz -kd release/${coin}/${releaseDylibName}.xz`; - await $`mkdir -p tests/libs/next`; - await $`mv release/${coin}/${releaseDylibName} tests/libs/next/${endpointDylibName}`; + await extract( + `./release/${coin}/${releaseDylibName}.xz`, + `./tests/dependencies/libs/next/${moneroTsDylibName}`, + ); } else { - const downloadUrl = `https://github.com/MrCyjaneK/monero_c/releases/download/${version}/${dylibName}.xz`; - - const file = await Deno.open(`./tests/${dylibName}.xz`, { - create: true, - write: true, - }); - file.write(await (await fetch(downloadUrl)).bytes()); - file.close(); - - await $.raw`xz -d ./tests/${dylibName}.xz`; - await $.raw`mkdir -p ./tests/libs/${version}`; - await $ - .raw`mv ./tests/${dylibName} ./tests/libs/${version}/${endpointDylibName}`; + const downloadInfo = moneroCInfos.find((info) => info.outDir?.includes(version)); + if (downloadInfo) { + await downloadDependencies(downloadInfo); + } + + await extract( + `./tests/dependencies/libs/${version}/${dylibName}.xz`, + `./tests/dependencies/libs/${version}/${moneroTsDylibName}`, + ); } }