diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 691ac1ea..65cf9f80 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -1,9 +1,6 @@ name: Build -on: - push: - branches: [main] - pull_request: +on: workflow_dispatch jobs: flatpak: diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml new file mode 100644 index 00000000..8f9b5092 --- /dev/null +++ b/.github/workflows/rust.yml @@ -0,0 +1,104 @@ +name: Rust + +on: push + +env: + CARGO_TERM_COLOR: always + RUST_TOOLCHAIN: 1.94.0 + EXCLUDE_PACKAGES: reflection + PKG_CONFIG_PATH: /usr/lib/x86_64-linux-gnu/pkgconfig + +jobs: + test: + runs-on: ubuntu-latest + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Install glib + run: | + sudo apt-get update + sudo apt-get install -y libglib2.0-dev pkg-config + + - name: Setup Rust toolchain + uses: moonrepo/setup-rust@v1 + with: + channel: ${{ env.RUST_TOOLCHAIN }} + + - name: Run tests + run: | + cargo test \ + --workspace \ + --exclude ${{ env.EXCLUDE_PACKAGES }} \ + --all-features + + check: + runs-on: ubuntu-latest + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Install glib + run: | + sudo apt-get update + sudo apt-get install -y libglib2.0-dev pkg-config + + - name: Setup Rust toolchain + uses: moonrepo/setup-rust@v1 + with: + channel: ${{ env.RUST_TOOLCHAIN }} + + - name: Check project and dependencies + run: | + cargo check \ + --workspace \ + --exclude ${{ env.EXCLUDE_PACKAGES }} \ + --all-features + + fmt: + runs-on: ubuntu-latest + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Setup Rust toolchain + uses: moonrepo/setup-rust@v1 + with: + components: rustfmt + channel: ${{ env.RUST_TOOLCHAIN }} + + - name: Check formatting + # The `config` module gets auto-generated by the build system and is + # otherwise missing, we create an empty file so at least fmt will not + # wonder where that module went. + run: | + echo "" > ${{ github.workspace }}/reflection-app/src/config.rs + cargo fmt -- --check + + clippy: + runs-on: ubuntu-latest + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Install glib + run: | + sudo apt-get update + sudo apt-get install -y libglib2.0-dev pkg-config + + - name: Setup Rust toolchain + uses: moonrepo/setup-rust@v1 + with: + components: clippy + channel: ${{ env.RUST_TOOLCHAIN }} + + - name: Check code with clippy + run: | + cargo clippy \ + --workspace \ + --exclude ${{ env.EXCLUDE_PACKAGES }} \ + --all-features -- -D warnings --no-deps diff --git a/Cargo.lock b/Cargo.lock index efdad07f..cdc663f5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4462,7 +4462,6 @@ name = "reflection-doc" version = "0.2.0" dependencies = [ "anyhow", - "async-channel", "gio", "glib", "hex", diff --git a/reflection-doc/Cargo.toml b/reflection-doc/Cargo.toml index 5b06cd14..48cd592f 100644 --- a/reflection-doc/Cargo.toml +++ b/reflection-doc/Cargo.toml @@ -8,16 +8,18 @@ authors = [ ] [dependencies] -reflection-node = { path = "../reflection-node" } anyhow = "1.0.101" -async-channel = "2.5.0" -glib = "0.21" gio = "0.21" +glib = "0.21" +hex = "0.4.3" +indexmap = "2.13.0" loro = { tag = "loro-crdt@1.10.6", git = "https://github.com/loro-dev/loro.git" } +rand = "0.10.0" +reflection-node = { path = "../reflection-node" } +serde = { version = "1.0.228", features = ["derive"] } thiserror = "2.0.18" tracing = "0.1" + +[dev-dependencies] +reflection-node = { path = "../reflection-node", features = ["test_utils"] } test-log = { version = "0.2.19", default-features = false, features = ["trace", "color"] } -serde = { version = "1.0.228", features = ["derive"] } -rand = "0.10.0" -hex = "0.4.3" -indexmap = "2.13.0" diff --git a/reflection-doc/src/lib.rs b/reflection-doc/src/lib.rs index cc653423..8fabd3ac 100644 --- a/reflection-doc/src/lib.rs +++ b/reflection-doc/src/lib.rs @@ -89,7 +89,6 @@ mod tests { let test_string = "Hello World"; let context = glib::MainContext::ref_thread_default(); - println!("Context: {context:?}"); let private_key = PrivateKey::new(); let service = Service::new(&private_key, None); @@ -106,10 +105,9 @@ mod tests { #[test_log::test(glib::async_test)] async fn basic_sync() { - let test_string = "Hello World"; + let expected_string = "Hello World"; let context = glib::MainContext::ref_thread_default(); - println!("Context: {context:?}"); let private_key = PrivateKey::new(); let service = Service::new(&private_key, None); @@ -128,13 +126,22 @@ mod tests { assert_eq!(document.id(), document2.id()); - assert!(document.insert_text(0, test_string).is_ok()); - assert_eq!(document.text(), test_string); + assert!(document.insert_text(0, expected_string).is_ok()); + assert_eq!(document.text(), expected_string); + + // Wait until text got synced. + loop { + glib::timeout_future(std::time::Duration::from_millis(50)).await; + + if document2.text() == expected_string { + break; + } + } service.shutdown().await; service2.shutdown().await; - assert_eq!(document2.text(), test_string); + assert_eq!(document2.text(), expected_string); } #[test_log::test(glib::async_test)] @@ -142,7 +149,6 @@ mod tests { let expected_string = "Hello, World!"; let context = glib::MainContext::ref_thread_default(); - println!("Context: {context:?}"); let private_key = PrivateKey::new(); let service = Service::new(&private_key, None); @@ -167,6 +173,15 @@ mod tests { assert!(document.insert_text(7, "W").is_ok()); assert_eq!(document.text(), expected_string); + // Wait until text got synced. + loop { + glib::timeout_future(std::time::Duration::from_millis(50)).await; + + if document2.text() == expected_string { + break; + } + } + service.shutdown().await; service2.shutdown().await; @@ -175,16 +190,16 @@ mod tests { #[test_log::test(glib::async_test)] async fn sync_longer_text() { - let test_string = "Et aut omnis eos corporis ut. Qui est blanditiis blanditiis. - Sit quia nam maxime accusantium ut voluptatem. Fuga consequuntur animi et et est. - Unde voluptas consequatur mollitia id odit optio harum sint. Fugit quo aut et laborum aut cupiditate."; + let test_string = "Et aut omnis eos corporis ut. Qui est blanditiis blanditiis. Sit quia + nam maxime accusantium ut voluptatem. Fuga consequuntur animi et et est. Unde voluptas + consequatur mollitia id odit optio harum sint. Fugit quo aut et laborum aut cupiditate."; + let expected_string = format!( "{}{}{}{}", test_string, test_string, test_string, test_string ); let context = glib::MainContext::ref_thread_default(); - println!("Context: {context:?}"); let private_key = PrivateKey::new(); let service = Service::new(&private_key, None); @@ -209,6 +224,15 @@ mod tests { assert!(document.insert_text(0, test_string).is_ok()); assert!(document.insert_text(0, test_string).is_ok()); + // Wait until text got synced. + loop { + glib::timeout_future(std::time::Duration::from_millis(50)).await; + + if document2.text() == expected_string { + break; + } + } + service.shutdown().await; service2.shutdown().await; diff --git a/reflection-node/Cargo.toml b/reflection-node/Cargo.toml index 80b936ee..ee817e73 100644 --- a/reflection-node/Cargo.toml +++ b/reflection-node/Cargo.toml @@ -8,6 +8,9 @@ authors = [ "Julian Sparber " ] +[features] +test_utils = [] + [dependencies] thiserror = "2.0.18" chrono = "0.4.43" diff --git a/reflection-node/src/lib.rs b/reflection-node/src/lib.rs index 5eb23d16..d1cddf8a 100644 --- a/reflection-node/src/lib.rs +++ b/reflection-node/src/lib.rs @@ -15,22 +15,23 @@ pub use topic::SubscribableTopic; #[cfg(test)] mod tests { - use crate::SubscribableTopic; - use crate::node::{ConnectionMode, Node}; + use std::sync::Arc; + use p2panda_core::Hash; use p2panda_core::PrivateKey; use p2panda_core::PublicKey; - use std::sync::Arc; use tokio::sync::{Mutex, mpsc}; + use crate::node::ConnectionMode; + use crate::node::Node; + use crate::topic::SubscribableTopic; + #[tokio::test] #[test_log::test] async fn create_topic() { let private_key = PrivateKey::new(); let network_id = Hash::new(b"reflection"); - let node = Node::new(private_key, network_id, None, ConnectionMode::Network) - .await - .unwrap(); + let node = Node::new(private_key, network_id, None).await.unwrap(); let id: [u8; 32] = [0; 32]; let _sub = node.subscribe(id, TestTopic::new()).await; @@ -70,6 +71,7 @@ mod tests { fn author_joined(&self, _author: PublicKey) {} fn author_left(&self, _author: PublicKey) {} fn ephemeral_bytes_received(&self, _author: PublicKey, _data: Vec) {} + fn error(&self, _error: crate::topic::SubscriptionError) {} } #[tokio::test] @@ -77,7 +79,8 @@ mod tests { async fn subscribe_topic() { let private_key = PrivateKey::new(); let network_id = Hash::new(b"reflection"); - let node = Node::new(private_key, network_id, None, ConnectionMode::Network) + let node = Node::new(private_key, network_id, None).await.unwrap(); + node.set_connection_mode(ConnectionMode::Network) .await .unwrap(); @@ -92,7 +95,9 @@ mod tests { let private_key2 = PrivateKey::new(); let network_id2 = Hash::new(b"reflection"); - let node2 = Node::new(private_key2, network_id2, None, ConnectionMode::Network) + let node2 = Node::new(private_key2, network_id2, None).await.unwrap(); + node2 + .set_connection_mode(ConnectionMode::Network) .await .unwrap(); diff --git a/reflection-node/src/network.rs b/reflection-node/src/network.rs index 69f62b09..c9aee3f2 100644 --- a/reflection-node/src/network.rs +++ b/reflection-node/src/network.rs @@ -82,16 +82,21 @@ impl Network { ) -> Result { let address_book = AddressBook::builder().spawn().await?; - if let Err(error) = address_book.insert_node_info(BOOTSTRAP_NODE.clone()).await { + if cfg!(not(any(test, feature = "test_utils"))) + && let Err(error) = address_book.insert_node_info(BOOTSTRAP_NODE.clone()).await + { error!("Failed to add bootstrap node to the address book: {error}"); } - let endpoint = Endpoint::builder(address_book.clone()) + let mut builder = Endpoint::builder(address_book.clone()) .network_id(network_id.into()) - .private_key(private_key.clone()) - .relay_url(RELAY_URL.clone()) - .spawn() - .await?; + .private_key(private_key.clone()); + + if cfg!(not(any(test, feature = "test_utils"))) { + builder = builder.relay_url(RELAY_URL.clone()); + } + + let endpoint = builder.spawn().await?; let mdns_discovery = MdnsDiscovery::builder(address_book.clone(), endpoint.clone()) .mode(MdnsDiscoveryMode::Active)