Repository: farcaster-project/farcaster-node
Pull request #292 (2021-12-04 14:41:39)

Use GH action services for functional tests
- Use services instead of docker-compose (compose is still use and documented for local tests)
- Use one matrix for all three tests: `bitcoin`, `monero`, and `swap`
- Log output of farcaster daemon for `swap` tests and upload the artifact

codecov-commenter: # [Codecov](https://codecov.io/gh/farcaster-project/farcaster-node/pull/292?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=None) Report
> Merging [#292](https://codecov.io/gh/farcaster-project/farcaster-node/pull/292?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=None) (33ecd29) into [main](https://codecov.io/gh/farcaster-project/farcaster-node/commit/738a8c6a173d3ab73d13762c4b45b9254d6f6ba1?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=None) (738a8c6) will **not change** coverage.
> The diff coverage is `n/a`.

[![Impacted file tree graph](https://codecov.io/gh/farcaster-project/farcaster-node/pull/292/graphs/tree.svg?width=650&height=150&src=pr&token=ZZ9RnFhgFC&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=None)](https://codecov.io/gh/farcaster-project/farcaster-node/pull/292?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=None)

```diff
@@          Coverage Diff          @@
##            main    #292   +/-   ##
=====================================
  Coverage   15.4%   15.4%           
=====================================
  Files         27      27           
  Lines       6523    6523           
=====================================
  Hits        1004    1004           
  Misses      5519    5519           
```



------

[Continue to review full report at Codecov](https://codecov.io/gh/farcaster-project/farcaster-node/pull/292?src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=None).
> **Legend** - [Click here to learn more](https://docs.codecov.io/docs/codecov-delta?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=None)
> `Δ = absolute <relative> (impact)`, `ø = not affected`, `? = missing data`
> Powered by [Codecov](https://codecov.io/gh/farcaster-project/farcaster-node/pull/292?src=pr&el=footer&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=None). Last update [738a8c6...33ecd29](https://codecov.io/gh/farcaster-project/farcaster-node/pull/292?src=pr&el=lastupdated&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=None). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=None).


Patch:
diff --git a/.gitignore b/.gitignore
index 8399fe74..2867bad6 100644
--- a/.gitignore
+++ b/.gitignore
@@ -14,5 +14,7 @@
 
 # Functional tests: generated folders
 /tests/data_dir
-/tests/.farcaster_node_0
-/tests/.farcaster_node_1
+/tests/.farcaster_1
+/tests/farcasterd_1.log
+/tests/.farcaster_2
+/tests/farcasterd_2.log


Test patch:
diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
index 51b7c0f0..b8c3de20 100644
--- a/.github/workflows/test.yml
+++ b/.github/workflows/test.yml
@@ -47,66 +47,89 @@ jobs:
           file: ${{ steps.coverage.outputs.report }}
           directory: ./coverage/reports/
 
-  functional-syncer-test:
-    name: Functional syncer tests
+  functional-test:
+    name: Functional tests
+    timeout-minutes: 45
     strategy:
+      fail-fast: false
       matrix:
-        test: [
-          bitcoin,
-          monero,
-        ]
+        test: [bitcoin, monero, swap]
+        include:
+          - test: swap
+            logs: true
     runs-on: ubuntu-latest
+    container:
+      image: rust:slim-buster
+      volumes:
+        - bitcoind-data:/data
+
+    services:
+      bitcoin-core:
+        image: ghcr.io/farcaster-project/containers/bitcoin-core:0.21.1
+        env:
+          NETWORK: regtest
+          RPC_PORT: 18443
+          FALLBACKFEE: "0.00001"
+        volumes:
+          - bitcoind-data:/data
+      electrs:
+        image: ghcr.io/farcaster-project/containers/electrs:0.8.11
+        env:
+          NETWORK: regtest
+          DAEMON_RPC_ADDR: bitcoin-core:18443
+          ELECTRUM_RPC_PORT: 50001
+        volumes:
+          - bitcoind-data:/data
+      monerod:
+        image: ghcr.io/farcaster-project/containers/monerod:0.17.2.3
+        env:
+          NETWORK: regtest
+          MONEROD_RPC_PORT: 18081
+          OFFLINE: --offline
+          DIFFICULTY: 1
+      monero-wallet-rpc-1:
+        image: ghcr.io/farcaster-project/containers/monero-wallet-rpc:0.17.2.3
+        env:
+          MONEROD_ADDRESS: monerod:18081
+          WALLET_RPC_PORT: 18083
+      monero-wallet-rpc-2:
+        image: ghcr.io/farcaster-project/containers/monero-wallet-rpc:0.17.2.3
+        env:
+          MONEROD_ADDRESS: monerod:18081
+          WALLET_RPC_PORT: 18084
+      monero-wallet-rpc-3:
+        image: ghcr.io/farcaster-project/containers/monero-wallet-rpc:0.17.2.3
+        env:
+          MONEROD_ADDRESS: monerod:18081
+          WALLET_RPC_PORT: 18085
+
     steps:
       - uses: actions/checkout@v2
-      - name: Install dependencies
-        run: sudo apt-get install -y cmake libzmq3-dev docker-compose
-
-      - name: Compose docker services
-        run: cd tests && docker-compose up -d
 
-      - name: Install Rust stable
-        uses: actions-rs/toolchain@v1
-        with:
-          toolchain: stable
-
-      - name: Correct permissions
-        run: sudo chown -R $USER tests
+      - name: Refresh cache and add apt-utils
+        run: apt-get update -y && apt-get install -y --no-install-recommends apt-utils
+      - name: Install dependencies
+        run: DEBIAN_FRONTEND=noninteractive apt-get install -y libsqlite3-dev libssl-dev libzmq3-dev pkg-config build-essential cmake
 
       - name: Rust cache
         uses: Swatinem/rust-cache@v1.3.0
         with:
           key: test-${{ matrix.test }}
 
-      - name: Cargo test
-        uses: actions-rs/cargo@v1
-        with:
-          command: test
-          args: ${{ matrix.test }} --workspace --all-targets --all-features --no-fail-fast --verbose -- --ignored --test-threads=1
-
-  functional-swap-test:
-    name: Functional swap tests
-    runs-on: ubuntu-latest
-    steps:
-      - uses: actions/checkout@v2
-      - name: Install dependencies
-        run: sudo apt-get install -y cmake libzmq3-dev docker-compose
-
-      - name: Compose docker services
-        run: cd tests && docker-compose up -d
-
-      - name: Install Rust stable
-        uses: actions-rs/toolchain@v1
-        with:
-          toolchain: stable
-
-      - name: Correct permissions
-        run: sudo chown -R $USER tests
-
-      - name: Rust cache
-        uses: Swatinem/rust-cache@v1.3.0
-
-      - name: Cargo test
-        uses: actions-rs/cargo@v1
+      - name: Cargo functional test
+        run: cargo test ${{ matrix.test }} --workspace --all-targets --all-features --no-fail-fast --verbose -- --ignored --test-threads=1
+        env:
+          BITCOIN_COOKIE: /data/regtest/.cookie
+          BITCOIN_HOST: bitcoin-core
+          ELECTRS_HOST: electrs
+          MONERO_DAEMON_HOST: monerod
+          MONERO_WALLET_HOST_1: monero-wallet-rpc-1
+          MONERO_WALLET_HOST_2: monero-wallet-rpc-2
+
+      - name: Archive farcasterd logs
+        uses: actions/upload-artifact@v2
+        if: ${{ matrix.logs }}
         with:
-          command: test
-          args: swap_test --workspace --all-targets --all-features --no-fail-fast --verbose -- --ignored --test-threads=1
\ No newline at end of file
+          name: faracsterd-logs
+          path: |
+            tests/*.log
diff --git a/tests/.farcasterd_1.ci.toml b/tests/.farcasterd_1.ci.toml
new file mode 100644
index 00000000..3f9e52d7
--- /dev/null
+++ b/tests/.farcasterd_1.ci.toml
@@ -0,0 +1,5 @@
+[syncers.local]
+electrum_server = "tcp://electrs:50001"
+monero_daemon = "http://monerod:18081"
+monero_rpc_wallet = "http://monero-wallet-rpc-2:18084"
+
diff --git a/tests/.farcasterd_2.ci.toml b/tests/.farcasterd_2.ci.toml
new file mode 100644
index 00000000..efefbbc5
--- /dev/null
+++ b/tests/.farcasterd_2.ci.toml
@@ -0,0 +1,5 @@
+[syncers.local]
+electrum_server = "tcp://electrs:50001"
+monero_daemon = "http://monerod:18081"
+monero_rpc_wallet = "http://monero-wallet-rpc-3:18085"
+
diff --git a/tests/README.md b/tests/README.md
index 2fb42b18..4b8e0f86 100644
--- a/tests/README.md
+++ b/tests/README.md
@@ -1,11 +1,28 @@
 # Running functional tests
 
-Run the tests with:
+Start needed containers for supporting the tests with:
 
 ```
 docker-compose up -d
 sudo chown -R $USER data_dir
-cargo test --workspace --all-targets --all-features --no-fail-fast -- --ignored --test-threads=1
+```
+
+Run `bitcoin` tests:
+
+```
+cargo test bitcoin --workspace --all-targets --all-features --no-fail-fast -- --ignored --test-threads=1
+```
+
+Run `monero` tests:
+
+```
+cargo test monero --workspace --all-targets --all-features --no-fail-fast -- --ignored --test-threads=1
+```
+
+Run `swap` tests:
+
+```
+cargo test swap --workspace --all-targets --all-features --no-fail-fast -- --ignored --test-threads=1
 ```
 
 ## Steps
diff --git a/tests/docker-compose.yml b/tests/docker-compose.yml
index f52f92dc..22a848dc 100644
--- a/tests/docker-compose.yml
+++ b/tests/docker-compose.yml
@@ -13,7 +13,10 @@ services:
             - 18444:18444
     electrs:
         image: ghcr.io/farcaster-project/containers/electrs:0.8.11
-        command: "/usr/bin/electrs -vv --network regtest --daemon-dir /data --daemon-rpc-addr bitcoin-core:18443 --electrum-rpc-addr 0.0.0.0:50001 --txid-limit 100000"
+        environment:
+            NETWORK: regtest
+            DAEMON_RPC_ADDR: bitcoin-core:18443
+            ELECTRUM_RPC_PORT: 50001
         depends_on:
             - "bitcoin-core"
         volumes:
diff --git a/tests/functional-swap.rs b/tests/functional-swap.rs
index 84ece773..a17a39f2 100644
--- a/tests/functional-swap.rs
+++ b/tests/functional-swap.rs
@@ -14,6 +14,8 @@ use sysinfo::{ProcessExt, System, SystemExt};
 use tokio::sync::Mutex;
 
 use std::collections::HashMap;
+use std::env;
+use std::path::PathBuf;
 use std::str;
 use std::str::FromStr;
 
@@ -24,7 +26,7 @@ const ALLOWED_RETRIES: u32 = 120;
 #[tokio::test]
 #[timeout(600000)]
 #[ignore]
-async fn swap_test_bob_maker() {
+async fn swap_bob_maker() {
     let execution_mutex = Arc::new(Mutex::new(0));
     let bitcoin_rpc = Arc::new(bitcoin_setup());
     let (monero_regtest, monero_wallet) = monero_setup().await;
@@ -62,7 +64,7 @@ async fn swap_test_bob_maker() {
 #[tokio::test]
 #[timeout(600000)]
 #[ignore]
-async fn swap_test_alice_maker() {
+async fn swap_alice_maker() {
     let execution_mutex = Arc::new(Mutex::new(0));
     let bitcoin_rpc = Arc::new(bitcoin_setup());
     let (monero_regtest, monero_wallet) = monero_setup().await;
@@ -100,7 +102,7 @@ async fn swap_test_alice_maker() {
 #[tokio::test]
 #[timeout(600000)]
 #[ignore]
-async fn swap_test_parallel() {
+async fn swap_parallel() {
     let execution_mutex = Arc::new(Mutex::new(0));
     let bitcoin_rpc = Arc::new(bitcoin_setup());
     let (monero_regtest, monero_wallet) = monero_setup().await;
@@ -207,21 +209,23 @@ async fn swap_test_parallel() {
 
 async fn setup_farcaster_clients() -> (process::Child, Vec<String>, process::Child, Vec<String>) {
     // data directories
-    let data_dir_maker = vec!["-d".to_string(), "tests/.farcaster_node_0".to_string()];
-    let data_dir_taker = vec!["-d".to_string(), "tests/.farcaster_node_1".to_string()];
+    let data_dir_maker = vec!["-d".to_string(), "tests/.farcaster_1".to_string()];
+    let data_dir_taker = vec!["-d".to_string(), "tests/.farcaster_2".to_string()];
 
-    let server_args_maker = vec!["-vv", "--config", "tests/.farcasterd_1.toml"]
-        .iter()
-        .map(|i| i.to_string())
-        .collect();
-
-    let server_args_taker = vec!["-vv", "--config", "tests/.farcasterd_2.toml"]
-        .iter()
-        .map(|i| i.to_string())
-        .collect();
+    // If we are in CI we use .ci.toml files, otherwise .toml
+    let ctx = env::var("CI").unwrap_or("false".into());
+    let ext = if ctx == "false" { ".toml" } else { ".ci.toml" };
 
-    let farcasterd_maker_args = farcasterd_args(data_dir_maker.clone(), server_args_maker);
-    let farcasterd_taker_args = farcasterd_args(data_dir_taker.clone(), server_args_taker);
+    let farcasterd_maker_args = farcasterd_args(
+        data_dir_maker.clone(),
+        vec!["-vv", "--config", &format!("tests/.farcasterd_1{}", ext)],
+        vec!["2>&1", "|", "tee", "-a", "tests/farcasterd_1.log"],
+    );
+    let farcasterd_taker_args = farcasterd_args(
+        data_dir_taker.clone(),
+        vec!["-vv", "--config", &format!("tests/.farcasterd_2{}", ext)],
+        vec!["2>&1", "|", "tee", "-a", "tests/farcasterd_2.log"],
+    );
 
     let farcasterd_maker = launch("../farcasterd", farcasterd_maker_args).unwrap();
     let farcasterd_taker = launch("../farcasterd", farcasterd_taker_args).unwrap();
@@ -439,8 +443,12 @@ fn info_args(data_dir: Vec<String>) -> Vec<String> {
         .collect()
 }
 
-fn farcasterd_args(data_dir: Vec<String>, server_args: Vec<String>) -> Vec<String> {
-    data_dir.into_iter().chain(server_args).collect()
+fn farcasterd_args(data_dir: Vec<String>, server_args: Vec<&str>, extra: Vec<&str>) -> Vec<String> {
+    data_dir
+        .into_iter()
+        .chain(server_args.into_iter().map(|s| s.into()))
+        .chain(extra.into_iter().map(|s| s.into()))
+        .collect()
 }
 
 fn make_offer_args(
@@ -636,8 +644,11 @@ async fn retry_until_finish_state_transition(
 }
 
 fn bitcoin_setup() -> bitcoincore_rpc::Client {
-    let path = std::path::PathBuf::from_str("tests/data_dir/regtest/.cookie").unwrap();
-    let bitcoin_rpc = Client::new("http://localhost:18443", Auth::CookieFile(path)).unwrap();
+    let cookie = env::var("BITCOIN_COOKIE").unwrap_or("tests/data_dir/regtest/.cookie".into());
+    let path = PathBuf::from_str(&cookie).unwrap();
+    let host = env::var("BITCOIN_HOST").unwrap_or("localhost".into());
+    let bitcoin_rpc =
+        Client::new(&format!("http://{}:18443", host), Auth::CookieFile(path)).unwrap();
 
     // make sure a wallet is created and loaded
     if bitcoin_rpc
@@ -656,10 +667,12 @@ async fn monero_setup() -> (
     monero_rpc::RegtestDaemonClient,
     Arc<Mutex<monero_rpc::WalletClient>>,
 ) {
-    let daemon_client = monero_rpc::RpcClient::new("http://localhost:18081".to_string());
+    let dhost = env::var("MONERO_DAEMON_HOST").unwrap_or("localhost".into());
+    let daemon_client = monero_rpc::RpcClient::new(format!("http://{}:18081", dhost));
     let daemon = daemon_client.daemon();
     let regtest = daemon.regtest();
-    let wallet_client = monero_rpc::RpcClient::new("http://localhost:18083".to_string());
+    let whost = env::var("MONERO_WALLET_HOST_1").unwrap_or("localhost".into());
+    let wallet_client = monero_rpc::RpcClient::new(format!("http://{}:18083", whost));
     let wallet = wallet_client.wallet();
 
     if wallet.open_wallet("test".to_string(), None).await.is_err() {
@@ -755,32 +768,30 @@ pub fn run(
     Ok((stdout, stderr))
 }
 
-pub fn launch(
-    name: &str,
-    args: impl IntoIterator<Item = impl AsRef<OsStr>>,
-) -> io::Result<process::Child> {
+pub fn launch(name: &str, args: impl IntoIterator<Item = String>) -> io::Result<process::Child> {
     let mut bin_path = std::env::current_exe().map_err(|err| {
         error!("Unable to detect binary directory: {}", err);
         err
     })?;
     bin_path.pop();
-
     bin_path.push(name);
-    #[cfg(target_os = "windows")]
-    bin_path.set_extension("exe");
 
-    debug!(
+    println!(
         "Launching {} as a separate process using `{}` as binary",
         name,
         bin_path.to_string_lossy()
     );
 
-    let mut cmd = process::Command::new(bin_path);
+    let cmdargs = args.into_iter().collect::<Vec<String>>().join(" ");
+    println!("Command arguments: \"{}\"", cmdargs);
 
-    cmd.args(args);
+    let mut shell = process::Command::new("sh");
+    shell
+        .arg("-c")
+        .arg(format!("{} {}", bin_path.to_string_lossy(), cmdargs));
 
-    trace!("Executing `{:?}`", cmd);
-    cmd.spawn().map_err(|err| {
+    println!("Executing `{:?}`", shell);
+    shell.spawn().map_err(|err| {
         error!("Error launching {}: {}", name, err);
         err
     })
diff --git a/tests/functional.rs b/tests/functional.rs
index e1785442..b87a807d 100644
--- a/tests/functional.rs
+++ b/tests/functional.rs
@@ -27,6 +27,8 @@ use ntest::timeout;
 
 use bitcoin::hashes::Hash;
 use internet2::{CreateUnmarshaller, Unmarshall};
+use std::env;
+use std::path::PathBuf;
 use std::str::FromStr;
 
 use farcaster_core::blockchain::Network;
@@ -828,11 +830,12 @@ fn create_bitcoin_syncer(
     rx_event.bind(&addr).unwrap();
     let mut syncer = BitcoinSyncer::new();
 
+    let ehost = env::var("ELECTRS_HOST").unwrap_or("localhost".into());
     let opts = Opts::parse_from(vec!["syncerd"].into_iter().chain(vec![
         "--coin",
         "Bitcoin",
         "--electrum-server",
-        "tcp://localhost:50001",
+        &format!("tcp://{}:50001", ehost),
     ]));
 
     syncer
@@ -869,8 +872,11 @@ fn find_coinbase_transaction_amount(txs: Vec<bitcoin::Transaction>) -> u64 {
 }
 
 fn bitcoin_setup() -> bitcoincore_rpc::Client {
-    let path = std::path::PathBuf::from_str("tests/data_dir/regtest/.cookie").unwrap();
-    let bitcoin_rpc = Client::new("http://localhost:18443", Auth::CookieFile(path)).unwrap();
+    let cookie = env::var("BITCOIN_COOKIE").unwrap_or("tests/data_dir/regtest/.cookie".into());
+    let path = PathBuf::from_str(&cookie).unwrap();
+    let host = env::var("BITCOIN_HOST").unwrap_or("localhost".into());
+    let bitcoin_rpc =
+        Client::new(&format!("http://{}:18443", host), Auth::CookieFile(path)).unwrap();
 
     // make sure a wallet is created and loaded
     if bitcoin_rpc
@@ -1542,10 +1548,12 @@ async fn monero_syncer_broadcast_tx_test() {
 }
 
 async fn setup_monero() -> (monero_rpc::RegtestDaemonClient, monero_rpc::WalletClient) {
-    let daemon_client = monero_rpc::RpcClient::new("http://localhost:18081".to_string());
+    let dhost = env::var("MONERO_DAEMON_HOST").unwrap_or("localhost".into());
+    let daemon_client = monero_rpc::RpcClient::new(format!("http://{}:18081", dhost));
     let daemon = daemon_client.daemon();
     let regtest = daemon.regtest();
-    let wallet_client = monero_rpc::RpcClient::new("http://localhost:18083".to_string());
+    let whost = env::var("MONERO_WALLET_HOST_1").unwrap_or("localhost".into());
+    let wallet_client = monero_rpc::RpcClient::new(format!("http://{}:18083", whost));
     let wallet = wallet_client.wallet();
     // Ignore if fails, maybe the wallet already exists
     let _ = wallet
@@ -1567,13 +1575,15 @@ fn create_monero_syncer(socket_name: &str) -> (std::sync::mpsc::Sender<SyncerdTa
     rx_event.bind(&addr).unwrap();
     let mut syncer = MoneroSyncer::new();
 
+    let dhost = env::var("MONERO_DAEMON_HOST").unwrap_or("localhost".into());
+    let whost = env::var("MONERO_WALLET_HOST_2").unwrap_or("localhost".into());
     let opts = Opts::parse_from(vec!["syncerd"].into_iter().chain(vec![
         "--coin",
         "Monero",
         "--monero-daemon",
-        "http://localhost:18081",
+        &format!("http://{}:18081", dhost),
         "--monero-rpc-wallet",
-        "http://localhost:18084",
+        &format!("http://{}:18084", whost),
     ]));
 
     syncer