Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: refactor integration tests to fix macos issues #198

Merged
merged 13 commits into from
Jul 19, 2023
7 changes: 4 additions & 3 deletions .github/workflows/integration.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,17 +11,18 @@ jobs:
test:
strategy:
matrix:
os: [ubuntu-latest, macos-latest]
os: [ubuntu-latest, macos-latest-xl]
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

macos runner upgrade was approved by David, we should hopefully expect faster builds

runs-on: ${{ matrix.os }}
name: Test
steps:
- uses: actions/checkout@v3
- name: Install Docker
if: ${{ matrix.os == 'macos-latest' }}
if: ${{ matrix.os == 'macos-latest-xl' }}
run: |
brew install docker
colima start
#
# Colima does not expose the Docker socket by default, we have to symlink it
# https://github.com/abiosoft/colima/blob/main/docs/FAQ.md#cannot-connect-to-the-docker-daemon-at-unixvarrundockersock-is-the-docker-daemon-running
sudo ln -sf $HOME/.colima/default/docker.sock /var/run/docker.sock
- name: Login to GitHub Container Registry
uses: docker/login-action@v1
Expand Down
2 changes: 1 addition & 1 deletion integration-tests/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ near-crypto = "0.16.1"
near-jsonrpc-client = "0.5.1"
near-primitives = "0.16.1"
near-units = "0.2.0"
once_cell = "1"
portpicker = "0.1"
serde = "1"
serde_json = "1"
Expand All @@ -28,7 +29,6 @@ tracing-subscriber = { version = "0.3", features = ["env-filter"] }
workspaces = { git = "https://github.com/near/workspaces-rs", branch = "main" }

[dev-dependencies]
once_cell = "1"
rand = "0.8"
test-log = { version = "0.2.12", features = ["log", "trace"] }
env_logger = "0.10.0"
Expand Down
91 changes: 85 additions & 6 deletions integration-tests/src/containers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,9 @@
use std::str::FromStr;

use anyhow::{anyhow, Ok};
use bollard::Docker;
use bollard::{container::LogsOptions, network::CreateNetworkOptions, service::Ipam, Docker};
use ed25519_dalek::ed25519::signature::digest::{consts::U32, generic_array::GenericArray};
use futures::{lock::Mutex, StreamExt};
use hyper::StatusCode;
use mpc_recovery::{
msg::{
Expand All @@ -15,24 +16,28 @@ use mpc_recovery::{
relayer::NearRpcAndRelayerClient,
};
use multi_party_eddsa::protocols::ExpandedKeyPair;
use near_crypto::{PublicKey, SecretKey};
use near_crypto::PublicKey;
use near_primitives::{
account::{AccessKey, AccessKeyPermission},
delegate_action::{DelegateAction, SignedDelegateAction},
transaction::{Action, AddKeyAction},
views::FinalExecutionStatus,
};
use once_cell::sync::Lazy;
use testcontainers::{
clients::Cli,
core::{ExecCommand, WaitFor},
images::generic::GenericImage,
Container, Image, RunnableImage,
};
use tokio::io::AsyncWriteExt;
use tracing;
use workspaces::AccountId;
use workspaces::{types::SecretKey, AccountId};

use crate::util;

static NETWORK_MUTEX: Lazy<Mutex<i32>> = Lazy::new(|| Mutex::new(0));

pub struct DockerClient {
pub docker: Docker,
pub cli: Cli,
Expand Down Expand Up @@ -78,12 +83,72 @@ impl DockerClient {

Ok(ip_address)
}

pub async fn create_network(&self, network: &str) -> anyhow::Result<()> {
let _lock = &NETWORK_MUTEX.lock().await;
let list = self.docker.list_networks::<&str>(None).await?;
if list.iter().any(|n| n.name == Some(network.to_string())) {
return Ok(());
}

let create_network_options = CreateNetworkOptions {
name: network,
check_duplicate: true,
driver: if cfg!(windows) {
"transparent"
} else {
"bridge"
},
ipam: Ipam {
config: None,
..Default::default()
},
..Default::default()
};
let _response = &self.docker.create_network(create_network_options).await?;

Ok(())
}

pub async fn continuously_print_logs(&self, id: &str) -> anyhow::Result<()> {
let mut output = self.docker.logs::<String>(
id,
Some(LogsOptions {
follow: true,
stdout: true,
stderr: true,
..Default::default()
}),
);

// Asynchronous process that pipes docker attach output into stdout.
// Will die automatically once Docker container output is closed.
tokio::spawn(async move {
let mut stdout = tokio::io::stdout();

while let Some(Result::Ok(output)) = output.next().await {
stdout
.write_all(output.into_bytes().as_ref())
.await
.unwrap();
stdout.flush().await.unwrap();
}
});

Ok(())
}
}

impl Default for DockerClient {
fn default() -> Self {
Self {
docker: Docker::connect_with_local_defaults().unwrap(),
docker: Docker::connect_with_local(
"unix:///var/run/docker.sock",
// 10 minutes timeout for all requests in case a lot of tests are being ran in parallel.
600,
bollard::API_DEFAULT_VERSION,
)
.unwrap(),
cli: Default::default(),
}
}
Expand Down Expand Up @@ -114,6 +179,7 @@ impl<'a> Redis<'a> {
pub struct Sandbox<'a> {
pub container: Container<'a, GenericImage>,
pub address: String,
pub local_address: String,
}

impl<'a> Sandbox<'a> {
Expand Down Expand Up @@ -148,24 +214,35 @@ impl<'a> Sandbox<'a> {
let address = docker_client
.get_network_ip_address(&container, network)
.await?;
let host_port = container.get_host_port_ipv4(Self::CONTAINER_RPC_PORT);

container.exec(ExecCommand {
cmd: format!("bash -c 'while [[ \"$(curl -s -o /dev/null -w ''%{{http_code}}'' localhost:{})\" != \"200\" ]]; do sleep 1; done'", Self::CONTAINER_RPC_PORT),
ready_conditions: vec![]
cmd: format!(
"bash -c 'while [[ \"$(curl -H \"Content-type: application/json\" -X POST -s -o /dev/null -w ''%{{http_code}}'' -d ''{{
\"jsonrpc\": \"2.0\",
\"id\": \"dontcare\",
\"method\": \"status\",
\"params\": []
}}'' localhost:{})\" != \"200\" ]]; do sleep 1; done; echo \"sandbox is ready to accept connections\"'",
Self::CONTAINER_RPC_PORT
),
ready_conditions: vec![WaitFor::StdErrMessage { message: "ready".to_string() }]
});

let full_address = format!("http://{}:{}", address, Self::CONTAINER_RPC_PORT);
tracing::info!("Sandbox container is running at {}", full_address);
Ok(Sandbox {
container,
address: full_address,
local_address: format!("http://localhost:{host_port}"),
})
}
}

pub struct Relayer<'a> {
pub container: Container<'a, GenericImage>,
pub address: String,
pub local_address: String,
}

impl<'a> Relayer<'a> {
Expand Down Expand Up @@ -212,12 +289,14 @@ impl<'a> Relayer<'a> {
let ip_address = docker_client
.get_network_ip_address(&container, network)
.await?;
let host_port = container.get_host_port_ipv4(Self::CONTAINER_PORT);

let full_address = format!("http://{}:{}", ip_address, Self::CONTAINER_PORT);
tracing::info!("Relayer container is running at {}", full_address);
Ok(Relayer {
container,
address: full_address,
local_address: format!("http://localhost:{host_port}"),
})
}
}
Expand Down
36 changes: 19 additions & 17 deletions integration-tests/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
use bollard::exec::{CreateExecOptions, StartExecResults};
use futures::StreamExt;
use near_crypto::{KeyFile, SecretKey};
use near_crypto::KeyFile;
use near_units::parse_near;
use workspaces::{
network::{Sandbox, ValidatorKey},
AccountId, Worker,
Account, Worker,
};

pub mod containers;
Expand Down Expand Up @@ -56,8 +56,7 @@ pub struct RelayerCtx<'a> {
pub redis: containers::Redis<'a>,
pub relayer: containers::Relayer<'a>,
pub worker: Worker<Sandbox>,
pub creator_account_id: AccountId,
pub creator_account_sk: SecretKey,
pub creator_account: Account,
}

pub async fn initialize_relayer<'a>(
Expand Down Expand Up @@ -86,12 +85,16 @@ pub async fn initialize_relayer<'a>(
let social_db = sandbox::initialize_social_db(&worker).await?;
sandbox::initialize_linkdrop(&worker).await?;
tracing::info!("Initializing relayer accounts...");
let (relayer_account_id, relayer_account_sk) = sandbox::create_account(&worker).await?;
let (creator_account_id, creator_account_sk) = sandbox::create_account(&worker).await?;
let (social_account_id, social_account_sk) = sandbox::create_account(&worker).await?;
sandbox::up_funds_for_account(&worker, &social_account_id, parse_near!("1000 N")).await?;
tracing::info!("Relayer accounts initialized. Relayer account: {}, Creator account: {}, Social account: {}",
relayer_account_id, creator_account_id, social_account_id);
let relayer_account =
sandbox::create_account(&worker, "relayer", parse_near!("1000 N")).await?;
let creator_account = sandbox::create_account(&worker, "creator", parse_near!("200 N")).await?;
let social_account = sandbox::create_account(&worker, "social", parse_near!("1000 N")).await?;
tracing::info!(
"Relayer accounts initialized. Relayer account: {}, Creator account: {}, Social account: {}",
relayer_account.id(),
creator_account.id(),
social_account.id()
);

let redis = containers::Redis::run(docker_client, network).await?;

Expand All @@ -100,12 +103,12 @@ pub async fn initialize_relayer<'a>(
network,
&sandbox.address,
&redis.address,
&relayer_account_id,
&relayer_account_sk,
&creator_account_id,
relayer_account.id(),
relayer_account.secret_key(),
creator_account.id(),
social_db.id(),
&social_account_id,
&social_account_sk,
social_account.id(),
social_account.secret_key(),
)
.await?;

Expand All @@ -114,7 +117,6 @@ pub async fn initialize_relayer<'a>(
redis,
relayer,
worker,
creator_account_id,
creator_account_sk,
creator_account,
})
}
4 changes: 2 additions & 2 deletions integration-tests/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -85,9 +85,9 @@ async fn main() -> anyhow::Result<()> {
"--near-root-account".to_string(),
near_root_account.id().to_string(),
"--account-creator-id".to_string(),
relayer_ctx.creator_account_id.to_string(),
relayer_ctx.creator_account.id().to_string(),
"--account-creator-sk".to_string(),
relayer_ctx.creator_account_sk.to_string(),
relayer_ctx.creator_account.secret_key().to_string(),
"--pagoda-firebase-audience-id".to_string(),
FIREBASE_AUDIENCE_ID.to_string(),
"--gcp-project-id".to_string(),
Expand Down
64 changes: 11 additions & 53 deletions integration-tests/src/sandbox.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use workspaces::{network::Sandbox, AccountId, Contract, Worker};
use workspaces::{network::Sandbox, Account, Contract, Worker};

pub async fn initialize_social_db(worker: &Worker<Sandbox>) -> anyhow::Result<Contract> {
tracing::info!("Initializing social DB contract...");
Expand Down Expand Up @@ -38,60 +38,18 @@ pub async fn initialize_linkdrop(worker: &Worker<Sandbox>) -> anyhow::Result<()>

pub async fn create_account(
worker: &Worker<Sandbox>,
) -> anyhow::Result<(AccountId, near_crypto::SecretKey)> {
prefix: &str,
initial_balance: u128,
) -> anyhow::Result<Account> {
tracing::info!("Creating account with random account_id...");
let (account_id, account_sk) = worker.dev_generate().await;
worker
.create_tla(account_id.clone(), account_sk.clone())
let new_account = worker
.root_account()?
.create_subaccount(prefix)
.initial_balance(initial_balance)
.transact()
.await?
.into_result()?;

let account_sk: near_crypto::SecretKey =
serde_json::from_str(&serde_json::to_string(&account_sk)?)?;

tracing::info!("Account created: {}", account_id);
Ok((account_id, account_sk))
}

// Makes sure that the target account has at least target amount of NEAR
pub async fn up_funds_for_account(
worker: &Worker<Sandbox>,
target_account_id: &AccountId,
target_amount: u128,
) -> anyhow::Result<()> {
tracing::info!(
"Up funds for account {} to {}...",
target_account_id,
target_amount
);
// Max balance we can transfer out of a freshly created dev account
const DEV_ACCOUNT_AVAILABLE_BALANCE: u128 = 99 * 10u128.pow(24);

let diff: u128 = target_amount - worker.view_account(target_account_id).await?.balance;
// Integer ceiling division
let n = (diff + DEV_ACCOUNT_AVAILABLE_BALANCE - 1) / DEV_ACCOUNT_AVAILABLE_BALANCE;
let futures = (0..n).map(|_| async {
let tmp_account = worker.dev_create_account().await?;
tmp_account
.transfer_near(target_account_id, DEV_ACCOUNT_AVAILABLE_BALANCE)
.await?
.into_result()?;
tmp_account
.delete_account(target_account_id)
.await?
.into_result()?;

Ok::<(), anyhow::Error>(())
});
futures::future::join_all(futures)
.await
.into_iter()
.collect::<Result<Vec<_>, _>>()?;

tracing::info!(
"Account {} now has {} NEAR",
target_account_id,
target_amount
);
Ok(())
tracing::info!("Account created: {}", new_account.id());
Ok(new_account)
}
Loading