Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 1 addition & 4 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
@@ -1,9 +1,6 @@
name: Build

on:
push:
branches: [main]
pull_request:
on: workflow_dispatch

jobs:
flatpak:
Expand Down
104 changes: 104 additions & 0 deletions .github/workflows/rust.yml
Original file line number Diff line number Diff line change
@@ -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
1 change: 0 additions & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

16 changes: 9 additions & 7 deletions reflection-doc/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
46 changes: 35 additions & 11 deletions reflection-doc/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -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);
Expand All @@ -128,21 +126,29 @@ 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)]
async fn sync_multiple_changes() {
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);
Expand All @@ -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;

Expand All @@ -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);
Expand All @@ -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;

Expand Down
3 changes: 3 additions & 0 deletions reflection-node/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@ authors = [
"Julian Sparber <julian@sparber.net>"
]

[features]
test_utils = []

[dependencies]
thiserror = "2.0.18"
chrono = "0.4.43"
Expand Down
21 changes: 13 additions & 8 deletions reflection-node/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -70,14 +71,16 @@ mod tests {
fn author_joined(&self, _author: PublicKey) {}
fn author_left(&self, _author: PublicKey) {}
fn ephemeral_bytes_received(&self, _author: PublicKey, _data: Vec<u8>) {}
fn error(&self, _error: crate::topic::SubscriptionError) {}
}

#[tokio::test]
#[test_log::test]
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();

Expand All @@ -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();

Expand Down
17 changes: 11 additions & 6 deletions reflection-node/src/network.rs
Original file line number Diff line number Diff line change
Expand Up @@ -82,16 +82,21 @@ impl Network {
) -> Result<Self, NetworkError> {
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)
Expand Down
Loading