From e57b0655b81b968de75dc693873b52f83d33e167 Mon Sep 17 00:00:00 2001 From: Elizabeth Engelman <4752801+elizabethengelman@users.noreply.github.com> Date: Tue, 16 Jul 2024 16:30:33 -0400 Subject: [PATCH 1/8] Add nocapture flag to tests --- .github/workflows/ledger-emulator.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ledger-emulator.yml b/.github/workflows/ledger-emulator.yml index 87df86b94..2bf371b63 100644 --- a/.github/workflows/ledger-emulator.yml +++ b/.github/workflows/ledger-emulator.yml @@ -25,4 +25,4 @@ jobs: run: | sudo apt install -y libudev-dev - run: | - cargo test --manifest-path cmd/crates/stellar-ledger/Cargo.toml --features "emulator-tests" \ No newline at end of file + cargo test --manifest-path cmd/crates/stellar-ledger/Cargo.toml --features "emulator-tests" -- --nocapture \ No newline at end of file From e1e69ae6c4dbb3c01bf547697711ddea113d80f8 Mon Sep 17 00:00:00 2001 From: Elizabeth Engelman <4752801+elizabethengelman@users.noreply.github.com> Date: Tue, 16 Jul 2024 18:51:07 -0400 Subject: [PATCH 2/8] Print some more stuff out --- cmd/crates/stellar-ledger/tests/test/emulator_tests.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/cmd/crates/stellar-ledger/tests/test/emulator_tests.rs b/cmd/crates/stellar-ledger/tests/test/emulator_tests.rs index d22830089..228fc4409 100644 --- a/cmd/crates/stellar-ledger/tests/test/emulator_tests.rs +++ b/cmd/crates/stellar-ledger/tests/test/emulator_tests.rs @@ -379,9 +379,11 @@ async fn get_emulator_events_with_retries( return resp.events; } Err(e) => { - println!("this many retries: {retries}"); + println!("Retry count: {retries}"); + println!("Wait time: {wait_time:?}"); retries += 1; if retries >= max_retries { + println!("Exeeded max retries"); panic!("Failed to get emulator events: {e}"); } sleep(wait_time).await; From cba0bdcc58ef1b424b524e1bb48f8fc8512b9897 Mon Sep 17 00:00:00 2001 From: Elizabeth Engelman <4752801+elizabethengelman@users.noreply.github.com> Date: Tue, 30 Jul 2024 14:04:30 -0400 Subject: [PATCH 3/8] Only publish necessary ports for emulator container also make sure to randomize host port so that tests don't clobber each other when ran in parallel --- .../tests/test/emulator_tests.rs | 72 ++++++++++++------- 1 file changed, 45 insertions(+), 27 deletions(-) diff --git a/cmd/crates/stellar-ledger/tests/test/emulator_tests.rs b/cmd/crates/stellar-ledger/tests/test/emulator_tests.rs index 228fc4409..4483296bb 100644 --- a/cmd/crates/stellar-ledger/tests/test/emulator_tests.rs +++ b/cmd/crates/stellar-ledger/tests/test/emulator_tests.rs @@ -4,6 +4,7 @@ use soroban_env_host::xdr::{self, Operation, OperationBody, Uint256}; use soroban_env_host::xdr::{Hash, Transaction}; use std::vec; +use std::net::TcpListener; use stellar_ledger::hd_path::HdPath; use stellar_ledger::{Blob, Error, LedgerSigner}; @@ -14,7 +15,7 @@ use stellar_xdr::curr::{ Memo, MuxedAccount, PaymentOp, Preconditions, SequenceNumber, TransactionExt, }; -use testcontainers::clients; +use testcontainers::{clients, core::Port, RunnableImage}; use tokio::time::sleep; pub const TEST_NETWORK_PASSPHRASE: &[u8] = b"Test SDF Network ; September 2015"; @@ -44,11 +45,9 @@ use test_helpers::test::{ #[test_case("nanosp".to_string() ; "when the device is NanoS Plus")] #[tokio::test] async fn test_get_public_key(ledger_device_model: String) { - let args = Args { - ledger_device_model, - }; + let runnable_image = get_runnable_image(ledger_device_model.clone()); let docker = clients::Cli::default(); - let node = docker.run((Speculos::new(), args)); + let node = docker.run(runnable_image); let host_port = node.get_host_port_ipv4(9998); let ui_host_port: u16 = node.get_host_port_ipv4(5000); wait_for_emulator_start_text(ui_host_port).await; @@ -78,11 +77,9 @@ async fn test_get_public_key(ledger_device_model: String) { #[test_case("nanosp".to_string() ; "when the device is NanoS Plus")] #[tokio::test] async fn test_get_app_configuration(ledger_device_model: String) { - let args = Args { - ledger_device_model, - }; + let runnable_image = get_runnable_image(ledger_device_model.clone()); let docker = clients::Cli::default(); - let node = docker.run((Speculos::new(), args)); + let node = docker.run(runnable_image); let host_port = node.get_host_port_ipv4(9998); let ui_host_port: u16 = node.get_host_port_ipv4(5000); wait_for_emulator_start_text(ui_host_port).await; @@ -108,11 +105,9 @@ async fn test_get_app_configuration(ledger_device_model: String) { #[test_case("nanosp".to_string() ; "when the device is NanoS Plus")] #[tokio::test] async fn test_sign_tx(ledger_device_model: String) { - let args = Args { - ledger_device_model, - }; + let runnable_image = get_runnable_image(ledger_device_model.clone()); let docker = clients::Cli::default(); - let node = docker.run((Speculos::new(), args.clone())); + let node = docker.run(runnable_image); let host_port = node.get_host_port_ipv4(9998); let ui_host_port: u16 = node.get_host_port_ipv4(5000); wait_for_emulator_start_text(ui_host_port).await; @@ -175,7 +170,7 @@ async fn test_sign_tx(ledger_device_model: String) { let ledger = Arc::clone(&ledger); async move { ledger.sign_transaction(path, tx, test_network_hash()).await } }); - let approve = tokio::task::spawn(approve_tx_signature(ui_host_port, args.ledger_device_model)); + let approve = tokio::task::spawn(approve_tx_signature(ui_host_port, ledger_device_model)); let result = sign.await.unwrap(); let _ = approve.await.unwrap(); @@ -199,12 +194,9 @@ async fn test_sign_tx(ledger_device_model: String) { #[test_case("nanosp".to_string() ; "when the device is NanoS Plus")] #[tokio::test] async fn test_sign_tx_hash_when_hash_signing_is_not_enabled(ledger_device_model: String) { - let args = Args { - ledger_device_model, - }; - + let runnable_image = get_runnable_image(ledger_device_model.clone()); let docker = clients::Cli::default(); - let node = docker.run((Speculos::new(), args)); + let node = docker.run(runnable_image); let host_port = node.get_host_port_ipv4(9998); let ui_host_port: u16 = node.get_host_port_ipv4(5000); wait_for_emulator_start_text(ui_host_port).await; @@ -231,11 +223,9 @@ async fn test_sign_tx_hash_when_hash_signing_is_not_enabled(ledger_device_model: #[test_case("nanosp".to_string() ; "when the device is NanoS Plus")] #[tokio::test] async fn test_sign_tx_hash_when_hash_signing_is_enabled(ledger_device_model: String) { - let args = Args { - ledger_device_model, - }; + let runnable_image = get_runnable_image(ledger_device_model.clone()); let docker = clients::Cli::default(); - let node = docker.run((Speculos::new(), args.clone())); + let node = docker.run(runnable_image); let host_port = node.get_host_port_ipv4(9998); let ui_host_port: u16 = node.get_host_port_ipv4(5000); @@ -262,10 +252,7 @@ async fn test_sign_tx_hash_when_hash_signing_is_enabled(ledger_device_model: Str let ledger = Arc::clone(&ledger); async move { ledger.sign_transaction_hash(path, &test_hash).await } }); - let approve = tokio::task::spawn(approve_tx_hash_signature( - ui_host_port, - args.ledger_device_model, - )); + let approve = tokio::task::spawn(approve_tx_hash_signature(ui_host_port, ledger_device_model)); let response = sign.await.unwrap(); let _ = approve.await.unwrap(); @@ -338,6 +325,37 @@ struct EventsResponse { events: Vec, } +fn get_runnable_image(ledger_device_model: String) -> RunnableImage { + let args = Args { + ledger_device_model, + }; + let runnable_image: RunnableImage = (Speculos::new(), args).into(); + + // doing this to randomize the ports on the host so that parallel tests don't clobber each other + let (tcp_port_1, tcp_port_2) = get_available_ports(); + runnable_image + .with_mapped_port(Port { + local: tcp_port_1, + internal: 9998, + }) + .with_mapped_port(Port { + local: tcp_port_2, + internal: 5000, + }) +} + +fn get_available_ports() -> (u16, u16) { + let listener1 = TcpListener::bind("127.0.0.1:0").unwrap(); + let listener2 = TcpListener::bind("127.0.0.1:0").unwrap(); + println!("listener1: {:?}", listener1); + let port_1 = listener1.local_addr().unwrap().port(); + let port_2 = listener2.local_addr().unwrap().port(); + drop(listener1); + drop(listener2); + + (port_1, port_2) +} + fn get_http_transport(host: &str, port: u16) -> Result { Ok(EmulatorHttpTransport::new(host, port)) } From 01b00967e4a513576c0b754bd877fe52ec406dc2 Mon Sep 17 00:00:00 2001 From: Elizabeth Engelman <4752801+elizabethengelman@users.noreply.github.com> Date: Tue, 30 Jul 2024 15:06:52 -0400 Subject: [PATCH 4/8] run tests just on this branch for now --- .github/workflows/temp-ledger-emulator.yml | 30 ++++++++++++++++++++++ 1 file changed, 30 insertions(+) create mode 100644 .github/workflows/temp-ledger-emulator.yml diff --git a/.github/workflows/temp-ledger-emulator.yml b/.github/workflows/temp-ledger-emulator.yml new file mode 100644 index 000000000..bd585cab6 --- /dev/null +++ b/.github/workflows/temp-ledger-emulator.yml @@ -0,0 +1,30 @@ +name: Ledger Emulator Tests (temp) +on: + push: + branches: + - AhaLabs:fix/troubleshooting-emulator-tests + pull_request: + branches: + - AhaLabs:fix/troubleshooting-emulator-tests + +concurrency: + group: ${{ github.workflow }}-${{ github.ref_protected == 'true' && github.sha || github.ref }} + cancel-in-progress: true + +defaults: + run: + shell: bash + +jobs: + emulator-tests: + runs-on: ubuntu-latest + env: + CI_TESTS: true + steps: + - uses: actions/checkout@v3 + - uses: stellar/actions/rust-cache@main + - name: install libudev-dev + run: | + sudo apt install -y libudev-dev + - run: | + cargo test --manifest-path cmd/crates/stellar-ledger/Cargo.toml --features "emulator-tests" -- --nocapture From e29f11ba8ac3185a6becafcf426d925160be0eea Mon Sep 17 00:00:00 2001 From: Elizabeth Engelman <4752801+elizabethengelman@users.noreply.github.com> Date: Wed, 31 Jul 2024 11:17:57 -0400 Subject: [PATCH 5/8] Check that emulated device port is available and retry when not in tests --- .../tests/test/emulator_tests.rs | 48 ++++++++++++------- 1 file changed, 32 insertions(+), 16 deletions(-) diff --git a/cmd/crates/stellar-ledger/tests/test/emulator_tests.rs b/cmd/crates/stellar-ledger/tests/test/emulator_tests.rs index 4483296bb..3fe93594b 100644 --- a/cmd/crates/stellar-ledger/tests/test/emulator_tests.rs +++ b/cmd/crates/stellar-ledger/tests/test/emulator_tests.rs @@ -24,8 +24,8 @@ pub fn test_network_hash() -> Hash { Hash(sha2::Sha256::digest(TEST_NETWORK_PASSPHRASE).into()) } -fn ledger(host_port: u16) -> LedgerSigner { - LedgerSigner::new(get_http_transport("127.0.0.1", host_port).unwrap()) +async fn ledger(host_port: u16) -> LedgerSigner { + LedgerSigner::new(get_http_transport("127.0.0.1", host_port).await.unwrap()) } mod test_helpers { @@ -52,7 +52,7 @@ async fn test_get_public_key(ledger_device_model: String) { let ui_host_port: u16 = node.get_host_port_ipv4(5000); wait_for_emulator_start_text(ui_host_port).await; - let ledger = ledger(host_port); + let ledger = ledger(host_port).await; match ledger.get_public_key(&0.into()).await { Ok(public_key) => { @@ -84,7 +84,7 @@ async fn test_get_app_configuration(ledger_device_model: String) { let ui_host_port: u16 = node.get_host_port_ipv4(5000); wait_for_emulator_start_text(ui_host_port).await; - let ledger = ledger(host_port); + let ledger = ledger(host_port).await; match ledger.get_app_configuration().await { Ok(config) => { @@ -112,7 +112,7 @@ async fn test_sign_tx(ledger_device_model: String) { let ui_host_port: u16 = node.get_host_port_ipv4(5000); wait_for_emulator_start_text(ui_host_port).await; - let ledger = Arc::new(ledger(host_port)); + let ledger = Arc::new(ledger(host_port).await); let path = HdPath(0); @@ -201,7 +201,7 @@ async fn test_sign_tx_hash_when_hash_signing_is_not_enabled(ledger_device_model: let ui_host_port: u16 = node.get_host_port_ipv4(5000); wait_for_emulator_start_text(ui_host_port).await; - let ledger = ledger(host_port); + let ledger = ledger(host_port).await; let path = 0; let test_hash = b"313e8447f569233bb8db39aa607c8889"; @@ -232,7 +232,7 @@ async fn test_sign_tx_hash_when_hash_signing_is_enabled(ledger_device_model: Str wait_for_emulator_start_text(ui_host_port).await; enable_hash_signing(ui_host_port).await; - let ledger = Arc::new(ledger(host_port)); + let ledger = Arc::new(ledger(host_port).await); let path = 0; let mut test_hash = [0u8; 32]; @@ -347,7 +347,6 @@ fn get_runnable_image(ledger_device_model: String) -> RunnableImage { fn get_available_ports() -> (u16, u16) { let listener1 = TcpListener::bind("127.0.0.1:0").unwrap(); let listener2 = TcpListener::bind("127.0.0.1:0").unwrap(); - println!("listener1: {:?}", listener1); let port_1 = listener1.local_addr().unwrap().port(); let port_2 = listener2.local_addr().unwrap().port(); drop(listener1); @@ -356,8 +355,29 @@ fn get_available_ports() -> (u16, u16) { (port_1, port_2) } -fn get_http_transport(host: &str, port: u16) -> Result { - Ok(EmulatorHttpTransport::new(host, port)) +async fn get_http_transport(host: &str, port: u16) -> Result { + let max_retries = 5; + let mut retries = 0; + let mut wait_time = Duration::from_secs(1); + // ping the emulator port to make sure it's up and running + // retry with exponential backoff + loop { + match reqwest::get(format!("http://{host}:{port}")).await { + Ok(_) => return Ok(EmulatorHttpTransport::new(host, port)), + Err(e) => { + retries += 1; + if retries >= max_retries { + println!("get_http_transport: Exceeded max retries for connecting to emulated device"); + + return Err(Error::APDUExchangeError(format!( + "Failed to connect to emulator: {e}" + ))); + } + sleep(wait_time).await; + wait_time *= 2; + } + } + } } async fn wait_for_emulator_start_text(ui_host_port: u16) { @@ -383,9 +403,7 @@ async fn get_emulator_events_with_retries( ) -> Vec { let client = reqwest::Client::new(); let mut retries = 0; - let mut wait_time = Duration::from_secs(1); - loop { match client .get(format!("http://localhost:{ui_host_port}/events")) @@ -397,12 +415,10 @@ async fn get_emulator_events_with_retries( return resp.events; } Err(e) => { - println!("Retry count: {retries}"); - println!("Wait time: {wait_time:?}"); retries += 1; if retries >= max_retries { - println!("Exeeded max retries"); - panic!("Failed to get emulator events: {e}"); + println!("get_emulator_events_with_retries: Exceeded max retries"); + panic!("get_emulator_events_with_retries: Failed to get emulator events: {e}"); } sleep(wait_time).await; wait_time *= 2; From 0b8850348d168ffac72a44b0d46f5e4b18003413 Mon Sep 17 00:00:00 2001 From: Elizabeth Engelman <4752801+elizabethengelman@users.noreply.github.com> Date: Wed, 31 Jul 2024 12:29:21 -0400 Subject: [PATCH 6/8] Make sure that ports are not conflicting between test threads --- .../tests/test/emulator_tests.rs | 29 +++++++++++++------ 1 file changed, 20 insertions(+), 9 deletions(-) diff --git a/cmd/crates/stellar-ledger/tests/test/emulator_tests.rs b/cmd/crates/stellar-ledger/tests/test/emulator_tests.rs index 3fe93594b..7a7d91883 100644 --- a/cmd/crates/stellar-ledger/tests/test/emulator_tests.rs +++ b/cmd/crates/stellar-ledger/tests/test/emulator_tests.rs @@ -1,7 +1,10 @@ use ledger_transport::Exchange; +use once_cell::sync::Lazy; use serde::Deserialize; use soroban_env_host::xdr::{self, Operation, OperationBody, Uint256}; use soroban_env_host::xdr::{Hash, Transaction}; +use std::ops::Range; +use std::sync::Mutex; use std::vec; use std::net::TcpListener; @@ -18,6 +21,8 @@ use stellar_xdr::curr::{ use testcontainers::{clients, core::Port, RunnableImage}; use tokio::time::sleep; +static PORT_RANGE: Lazy>> = Lazy::new(|| Mutex::new(40000..50000)); + pub const TEST_NETWORK_PASSPHRASE: &[u8] = b"Test SDF Network ; September 2015"; pub fn test_network_hash() -> Hash { use sha2::Digest; @@ -332,7 +337,7 @@ fn get_runnable_image(ledger_device_model: String) -> RunnableImage { let runnable_image: RunnableImage = (Speculos::new(), args).into(); // doing this to randomize the ports on the host so that parallel tests don't clobber each other - let (tcp_port_1, tcp_port_2) = get_available_ports(); + let (tcp_port_1, tcp_port_2) = get_available_ports(2); runnable_image .with_mapped_port(Port { local: tcp_port_1, @@ -344,15 +349,21 @@ fn get_runnable_image(ledger_device_model: String) -> RunnableImage { }) } -fn get_available_ports() -> (u16, u16) { - let listener1 = TcpListener::bind("127.0.0.1:0").unwrap(); - let listener2 = TcpListener::bind("127.0.0.1:0").unwrap(); - let port_1 = listener1.local_addr().unwrap().port(); - let port_2 = listener2.local_addr().unwrap().port(); - drop(listener1); - drop(listener2); +fn get_available_ports(n: usize) -> (u16, u16) { + let mut range = PORT_RANGE.lock().unwrap(); + let mut ports = Vec::with_capacity(n); + while ports.len() < n { + if let Some(port) = range.next() { + if let Ok(listener) = TcpListener::bind(("127.0.0.1", port)) { + ports.push(port); + drop(listener); + } + } else { + panic!("No more available ports"); + } + } - (port_1, port_2) + (ports[0], ports[1]) } async fn get_http_transport(host: &str, port: u16) -> Result { From c1f895180b78cc5fade1b653281799ef395e0e65 Mon Sep 17 00:00:00 2001 From: Elizabeth Engelman <4752801+elizabethengelman@users.noreply.github.com> Date: Wed, 31 Jul 2024 15:43:47 -0400 Subject: [PATCH 7/8] Use 0.0.0.0 to check if a port is in use instead of 127.0.0.1 --- cmd/crates/stellar-ledger/tests/test/emulator_tests.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/crates/stellar-ledger/tests/test/emulator_tests.rs b/cmd/crates/stellar-ledger/tests/test/emulator_tests.rs index 7a7d91883..6e9235bb4 100644 --- a/cmd/crates/stellar-ledger/tests/test/emulator_tests.rs +++ b/cmd/crates/stellar-ledger/tests/test/emulator_tests.rs @@ -354,7 +354,7 @@ fn get_available_ports(n: usize) -> (u16, u16) { let mut ports = Vec::with_capacity(n); while ports.len() < n { if let Some(port) = range.next() { - if let Ok(listener) = TcpListener::bind(("127.0.0.1", port)) { + if let Ok(listener) = TcpListener::bind(("0.0.0.0", port)) { ports.push(port); drop(listener); } From 3749430fc3ee154339e823b56e8fb44bde8504c6 Mon Sep 17 00:00:00 2001 From: Elizabeth Engelman <4752801+elizabethengelman@users.noreply.github.com> Date: Thu, 1 Aug 2024 10:26:53 -0400 Subject: [PATCH 8/8] Revert "run tests just on this branch for now" This reverts commit 01b00967e4a513576c0b754bd877fe52ec406dc2. --- .github/workflows/temp-ledger-emulator.yml | 30 ---------------------- 1 file changed, 30 deletions(-) delete mode 100644 .github/workflows/temp-ledger-emulator.yml diff --git a/.github/workflows/temp-ledger-emulator.yml b/.github/workflows/temp-ledger-emulator.yml deleted file mode 100644 index bd585cab6..000000000 --- a/.github/workflows/temp-ledger-emulator.yml +++ /dev/null @@ -1,30 +0,0 @@ -name: Ledger Emulator Tests (temp) -on: - push: - branches: - - AhaLabs:fix/troubleshooting-emulator-tests - pull_request: - branches: - - AhaLabs:fix/troubleshooting-emulator-tests - -concurrency: - group: ${{ github.workflow }}-${{ github.ref_protected == 'true' && github.sha || github.ref }} - cancel-in-progress: true - -defaults: - run: - shell: bash - -jobs: - emulator-tests: - runs-on: ubuntu-latest - env: - CI_TESTS: true - steps: - - uses: actions/checkout@v3 - - uses: stellar/actions/rust-cache@main - - name: install libudev-dev - run: | - sudo apt install -y libudev-dev - - run: | - cargo test --manifest-path cmd/crates/stellar-ledger/Cargo.toml --features "emulator-tests" -- --nocapture