Skip to content

Commit

Permalink
test(cli): core commands e2e tests (#5090)
Browse files Browse the repository at this point in the history
### Description

Adds more E2E tests to the core commands in the CLI 

### Drive-by changes

- Removes the unused `--skip-confirmation` from the `core deploy`
command
- Updates the `warp init` tests to use the `handlePrompts` function 
- Moves the core e2e tests into the `test/core` folder

### Related issues

- #5080 

### Backward compatibility

- YES
### Testing

- Manual
- E2E
  • Loading branch information
xeno097 authored Jan 2, 2025
1 parent c152576 commit 03a9123
Show file tree
Hide file tree
Showing 11 changed files with 854 additions and 226 deletions.
5 changes: 5 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -126,10 +126,15 @@ jobs:
fail-fast: false
matrix:
test:
# Core Commands
- core-apply
- core-check
- core-deploy
- core-init
- core-read
# Other commands
- relay
# Warp Commands
- warp-init
- warp-read
- warp-apply
Expand Down
2 changes: 0 additions & 2 deletions typescript/cli/src/commands/core.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,6 @@ import {
fromAddressCommandOption,
inputFileCommandOption,
outputFileCommandOption,
skipConfirmationOption,
} from './options.js';

/**
Expand Down Expand Up @@ -117,7 +116,6 @@ export const deploy: CommandModuleWithWriteContext<{
),
'dry-run': dryRunCommandOption,
'from-address': fromAddressCommandOption,
'skip-confirmation': skipConfirmationOption,
},
handler: async ({ context, chain, config: configFilePath, dryRun }) => {
logCommandHeader(`Hyperlane Core deployment${dryRun ? ' dry-run' : ''}`);
Expand Down
98 changes: 97 additions & 1 deletion typescript/cli/src/tests/commands/core.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,45 @@
import { $ } from 'zx';
import { $, ProcessPromise } from 'zx';

import { DerivedCoreConfig } from '@hyperlane-xyz/sdk';
import { Address } from '@hyperlane-xyz/utils';

import { readYamlOrJson } from '../../utils/files.js';

import { ANVIL_KEY, REGISTRY_PATH } from './helpers.js';

/**
* Deploys the Hyperlane core contracts to the specified chain using the provided config.
*/
export function hyperlaneCoreDeployRaw(
coreInputPath: string,
privateKey?: string,
skipConfirmationPrompts?: boolean,
hypKey?: string,
): ProcessPromise {
if (hypKey) {
return $`HYP_KEY=${hypKey} yarn workspace @hyperlane-xyz/cli run hyperlane core deploy \
--registry ${REGISTRY_PATH} \
--config ${coreInputPath} \
--verbosity debug \
${skipConfirmationPrompts ? '--yes' : ''}`;
}

if (privateKey) {
return $`yarn workspace @hyperlane-xyz/cli run hyperlane core deploy \
--registry ${REGISTRY_PATH} \
--config ${coreInputPath} \
--key ${privateKey} \
--verbosity debug \
${skipConfirmationPrompts ? '--yes' : ''}`;
}

return $`yarn workspace @hyperlane-xyz/cli run hyperlane core deploy \
--registry ${REGISTRY_PATH} \
--config ${coreInputPath} \
--verbosity debug \
${skipConfirmationPrompts ? '--yes' : ''}`;
}

/**
* Deploys the Hyperlane core contracts to the specified chain using the provided config.
*/
Expand Down Expand Up @@ -34,6 +68,68 @@ export async function hyperlaneCoreRead(chain: string, coreOutputPath: string) {
--yes`;
}

/**
* Verifies that a Hyperlane core deployment matches the provided config on the specified chain.
*/
export function hyperlaneCoreCheck(
chain: string,
coreOutputPath: string,
mailbox?: Address,
): ProcessPromise {
if (mailbox) {
return $`yarn workspace @hyperlane-xyz/cli run hyperlane core check \
--registry ${REGISTRY_PATH} \
--config ${coreOutputPath} \
--chain ${chain} \
--mailbox ${mailbox} \
--verbosity debug \
--yes`;
}

return $`yarn workspace @hyperlane-xyz/cli run hyperlane core check \
--registry ${REGISTRY_PATH} \
--config ${coreOutputPath} \
--chain ${chain} \
--verbosity debug \
--yes`;
}

/**
* Creates a Hyperlane core deployment config
*/
export function hyperlaneCoreInit(
coreOutputPath: string,
privateKey?: string,
hyp_key?: string,
): ProcessPromise {
if (hyp_key) {
return $`${
hyp_key ? `HYP_KEY=${hyp_key}` : ''
} yarn workspace @hyperlane-xyz/cli run hyperlane core init \
--registry ${REGISTRY_PATH} \
--config ${coreOutputPath} \
--verbosity debug \
--yes`;
}

if (privateKey) {
return $`${
hyp_key ? 'HYP_KEY=${hyp_key}' : ''
} yarn workspace @hyperlane-xyz/cli run hyperlane core init \
--registry ${REGISTRY_PATH} \
--config ${coreOutputPath} \
--verbosity debug \
--key ${privateKey} \
--yes`;
}

return $`yarn workspace @hyperlane-xyz/cli run hyperlane core init \
--registry ${REGISTRY_PATH} \
--config ${coreOutputPath} \
--verbosity debug \
--yes`;
}

/**
* Updates a Hyperlane core deployment on the specified chain using the provided config.
*/
Expand Down
99 changes: 83 additions & 16 deletions typescript/cli/src/tests/commands/helpers.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { ethers } from 'ethers';
import { $, ProcessPromise } from 'zx';
import { $, ProcessOutput, ProcessPromise } from 'zx';

import { ERC20Test__factory, ERC4626Test__factory } from '@hyperlane-xyz/core';
import { ChainAddresses } from '@hyperlane-xyz/registry';
Expand Down Expand Up @@ -34,6 +34,7 @@ export const CHAIN_NAME_3 = 'anvil3';

export const EXAMPLES_PATH = './examples';
export const CORE_CONFIG_PATH = `${EXAMPLES_PATH}/core-config.yaml`;
export const CORE_CONFIG_PATH_2 = `${TEMP_PATH}/${CHAIN_NAME_2}/core-config.yaml`;
export const CORE_READ_CONFIG_PATH_2 = `${TEMP_PATH}/${CHAIN_NAME_2}/core-config-read.yaml`;
export const CHAIN_2_METADATA_PATH = `${REGISTRY_PATH}/chains/${CHAIN_NAME_2}/metadata.yaml`;
export const CHAIN_3_METADATA_PATH = `${REGISTRY_PATH}/chains/${CHAIN_NAME_3}/metadata.yaml`;
Expand All @@ -60,23 +61,89 @@ export async function asyncStreamInputWrite(
await sleep(500);
}

export async function selectAnvil2AndAnvil3(
stream: ProcessPromise,
): Promise<void> {
// Scroll down through the mainnet chains list and select anvil2
await asyncStreamInputWrite(
stream.stdin,
`${KeyBoardKeys.ARROW_DOWN.repeat(3)}${KeyBoardKeys.TAB}`,
);
// Scroll down through the mainnet chains list again and select anvil3
await asyncStreamInputWrite(
stream.stdin,
`${KeyBoardKeys.ARROW_DOWN.repeat(2)}${KeyBoardKeys.TAB}${
KeyBoardKeys.ENTER
}`,
);
export type TestPromptAction = {
check: (currentOutput: string) => boolean;
input: string;
};

/**
* Takes a {@link ProcessPromise} and a list of inputs that will be supplied
* in the provided order when the check in the {@link TestPromptAction} matches the output
* of the {@link ProcessPromise}.
*/
export async function handlePrompts(
processPromise: Readonly<ProcessPromise>,
actions: TestPromptAction[],
): Promise<ProcessOutput> {
let expectedStep = 0;
for await (const out of processPromise.stdout) {
const currentLine: string = out.toString();

const currentAction = actions[expectedStep];
if (currentAction && currentAction.check(currentLine)) {
// Select mainnet chains
await asyncStreamInputWrite(processPromise.stdin, currentAction.input);
expectedStep++;
}
}

return processPromise;
}

export const SELECT_ANVIL_2_FROM_MULTICHAIN_PICKER = `${KeyBoardKeys.ARROW_DOWN.repeat(
3,
)}${KeyBoardKeys.TAB}`;

export const SELECT_ANVIL_3_AFTER_ANVIL_2_FROM_MULTICHAIN_PICKER = `${KeyBoardKeys.ARROW_DOWN.repeat(
2,
)}${KeyBoardKeys.TAB}`;

export const SELECT_MAINNET_CHAIN_TYPE_STEP: TestPromptAction = {
check: (currentOutput: string) =>
currentOutput.includes('Select network type'),
// Select mainnet chains
input: KeyBoardKeys.ENTER,
};

export const SELECT_ANVIL_2_AND_ANVIL_3_STEPS: ReadonlyArray<TestPromptAction> =
[
{
check: (currentOutput: string) =>
currentOutput.includes('--Mainnet Chains--'),
input: `${SELECT_ANVIL_2_FROM_MULTICHAIN_PICKER}`,
},
{
check: (currentOutput: string) =>
currentOutput.includes('--Mainnet Chains--'),
input: `${SELECT_ANVIL_3_AFTER_ANVIL_2_FROM_MULTICHAIN_PICKER}${KeyBoardKeys.ENTER}`,
},
];

export const CONFIRM_DETECTED_OWNER_STEP: Readonly<TestPromptAction> = {
check: (currentOutput: string) =>
currentOutput.includes('Detected owner address as'),
input: KeyBoardKeys.ENTER,
};

export const SETUP_CHAIN_SIGNERS_MANUALLY_STEPS: ReadonlyArray<TestPromptAction> =
[
{
check: (currentOutput) =>
currentOutput.includes('Please enter the private key for chain'),
input: `${ANVIL_KEY}${KeyBoardKeys.ENTER}`,
},
{
check: (currentOutput) =>
currentOutput.includes('Please enter the private key for chain'),
input: `${ANVIL_KEY}${KeyBoardKeys.ENTER}`,
},
{
check: (currentOutput) =>
currentOutput.includes('Please enter the private key for chain'),
input: `${ANVIL_KEY}${KeyBoardKeys.ENTER}`,
},
];

/**
* Retrieves the deployed Warp address from the Warp core config.
*/
Expand Down
108 changes: 0 additions & 108 deletions typescript/cli/src/tests/core-deploy.e2e-test.ts

This file was deleted.

Loading

0 comments on commit 03a9123

Please sign in to comment.