From 5305b9331f77d4a84b3f6cb22380d16060036dd5 Mon Sep 17 00:00:00 2001 From: lip Date: Wed, 17 Sep 2025 16:58:46 +0800 Subject: [PATCH 1/2] test --- .github/workflows/pull_request.yml | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/.github/workflows/pull_request.yml b/.github/workflows/pull_request.yml index c829630d..0a95dde1 100644 --- a/.github/workflows/pull_request.yml +++ b/.github/workflows/pull_request.yml @@ -7,24 +7,31 @@ jobs: name: run benchmark runs-on: ubuntu-latest steps: - - uses: actions/checkout@v1 + - uses: actions/checkout@v5 with: ref: ${{github.event.pull_request.head.ref}} repository: ${{github.event.pull_request.head.repo.full_name}} + fetch-depth: 0 - uses: actions-rs/toolchain@v1 with: toolchain: stable override: true profile: minimal - name: Cache cargo - uses: actions/cache@v2.1.4 + uses: actions/cache@v4 with: path: | - target - ~/.cargo/git - ~/.cargo/registry + ~/.cargo/bin/ + ~/.cargo/registry/index/ + ~/.cargo/registry/cache/ + ~/.cargo/git/db/ + target/ key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }} - - uses: smrpn/criterion-compare-action@move_to_actions + restore-keys: | + ${{ runner.os }}-cargo- + - name: Install critcmp with force + run: cargo install critcmp --force + - uses: smrpn/criterion-compare-action@master with: cwd: benches token: ${{ secrets.GITHUB_TOKEN }} From 542a8275beeb3cba23a3fc14a6f1f7167cad2022 Mon Sep 17 00:00:00 2001 From: lip Date: Fri, 19 Sep 2025 14:49:12 +0800 Subject: [PATCH 2/2] Update Watcher callback to accept String parameter and add tests --- src/watcher.rs | 341 ++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 340 insertions(+), 1 deletion(-) diff --git a/src/watcher.rs b/src/watcher.rs index 07e63911..344ff98d 100644 --- a/src/watcher.rs +++ b/src/watcher.rs @@ -1,6 +1,345 @@ use crate::emitter::EventData; pub trait Watcher: Send + Sync { - fn set_update_callback(&mut self, cb: Box); + fn set_update_callback(&mut self, cb: Box); fn update(&mut self, d: EventData); } + +#[cfg(test)] +mod tests { + use super::*; + use crate::prelude::*; + use std::sync::{Arc, Mutex}; + + // Sample watcher implementation for testing + struct SampleWatcher { + callback: Option>, + } + + impl SampleWatcher { + fn new() -> Self { + SampleWatcher { callback: None } + } + } + + impl Watcher for SampleWatcher { + fn set_update_callback( + &mut self, + cb: Box, + ) { + self.callback = Some(cb); + } + + fn update(&mut self, d: EventData) { + if let Some(ref mut callback) = self.callback { + callback(d.to_string()); + } + } + } + + #[cfg(not(target_arch = "wasm32"))] + #[cfg_attr( + all(feature = "runtime-async-std", not(target_arch = "wasm32")), + async_std::test + )] + #[cfg_attr( + all(feature = "runtime-tokio", not(target_arch = "wasm32")), + tokio::test + )] + async fn test_set_watcher() { + let mut e = Enforcer::new( + "examples/rbac_model.conf", + "examples/rbac_policy.csv", + ) + .await + .unwrap(); + + let sample_watcher = SampleWatcher::new(); + e.set_watcher(Box::new(sample_watcher)); + + // calls watcher.update() + e.save_policy().await.unwrap(); + } + + #[cfg(not(target_arch = "wasm32"))] + #[cfg_attr( + all(feature = "runtime-async-std", not(target_arch = "wasm32")), + async_std::test + )] + #[cfg_attr( + all(feature = "runtime-tokio", not(target_arch = "wasm32")), + tokio::test + )] + async fn test_self_modify() { + let mut e = Enforcer::new( + "examples/rbac_model.conf", + "examples/rbac_policy.csv", + ) + .await + .unwrap(); + + let sample_watcher = SampleWatcher::new(); + e.set_watcher(Box::new(sample_watcher)); + + // Test callback for add_policy (should be called) + let called = Arc::new(Mutex::new(-1)); + let called_clone = Arc::clone(&called); + + if let Some(watcher) = e.get_mut_watcher() { + watcher.set_update_callback(Box::new(move |_s: String| { + let mut c = called_clone.lock().unwrap(); + *c = 1; + })); + } + + // calls watcher.update() + let result = e + .add_policy(vec![ + "eva".to_string(), + "data".to_string(), + "read".to_string(), + ]) + .await; + assert!(result.unwrap()); + + let called_value = *called.lock().unwrap(); + assert_eq!(called_value, 1, "callback should be called"); + + // Test callback for self_add_policy (should not be called for self operations) + let called2 = Arc::new(Mutex::new(-1)); + let called2_clone = Arc::clone(&called2); + + if let Some(watcher) = e.get_mut_watcher() { + watcher.set_update_callback(Box::new(move |_s: String| { + let mut c = called2_clone.lock().unwrap(); + *c = 1; + })); + } + + // Note: casbin-rs doesn't have self_add_policy, using add_policy instead + // This test demonstrates the watcher callback functionality + let result = e + .add_policy(vec![ + "eva".to_string(), + "data".to_string(), + "write".to_string(), + ]) + .await; + assert!(result.unwrap()); + + // In casbin-rs, watcher is called for all policy changes + let called2_value = *called2.lock().unwrap(); + assert_eq!( + called2_value, 1, + "callback should be called for policy changes" + ); + } + + #[test] + fn test_watcher_callback_with_event_data() { + let mut sample_watcher = SampleWatcher::new(); + + let called_data = Arc::new(Mutex::new(String::new())); + let called_data_clone = Arc::clone(&called_data); + + sample_watcher.set_update_callback(Box::new(move |data: String| { + let mut d = called_data_clone.lock().unwrap(); + *d = data; + })); + + // Test with AddPolicy event + let event_data = EventData::AddPolicy( + "p".to_string(), + "p".to_string(), + vec!["alice".to_string(), "data1".to_string(), "read".to_string()], + ); + + sample_watcher.update(event_data); + + let result = called_data.lock().unwrap(); + assert!(result.contains("AddPolicy")); + assert!(result.contains("alice")); + assert!(result.contains("data1")); + assert!(result.contains("read")); + } + + // Sample extended watcher implementation for comprehensive testing + struct SampleWatcherEx { + callback: Option>, + update_calls: Arc>>, + } + + impl SampleWatcherEx { + fn new() -> Self { + SampleWatcherEx { + callback: None, + update_calls: Arc::new(Mutex::new(Vec::new())), + } + } + } + + impl Watcher for SampleWatcherEx { + fn set_update_callback( + &mut self, + cb: Box, + ) { + self.callback = Some(cb); + } + + fn update(&mut self, d: EventData) { + let event_str = d.to_string(); + + // Record the update call + self.update_calls.lock().unwrap().push(event_str.clone()); + + // Call the callback if set + if let Some(ref mut callback) = self.callback { + callback(event_str); + } + } + } + + #[cfg(not(target_arch = "wasm32"))] + #[cfg_attr( + all(feature = "runtime-async-std", not(target_arch = "wasm32")), + async_std::test + )] + #[cfg_attr( + all(feature = "runtime-tokio", not(target_arch = "wasm32")), + tokio::test + )] + async fn test_set_watcher_ex() { + let mut e = Enforcer::new( + "examples/rbac_model.conf", + "examples/rbac_policy.csv", + ) + .await + .unwrap(); + + let sample_watcher_ex = SampleWatcherEx::new(); + let update_calls = Arc::clone(&sample_watcher_ex.update_calls); + e.set_watcher(Box::new(sample_watcher_ex)); + + // calls watcher.update() for SavePolicy + e.save_policy().await.unwrap(); + + // calls watcher.update() for AddPolicy + let _ = e + .add_policy(vec![ + "admin".to_string(), + "data1".to_string(), + "read".to_string(), + ]) + .await; + + // calls watcher.update() for RemovePolicy + let _ = e + .remove_policy(vec![ + "admin".to_string(), + "data1".to_string(), + "read".to_string(), + ]) + .await; + + // calls watcher.update() for RemoveFilteredPolicy + let _ = e.remove_filtered_policy(1, vec!["data1".to_string()]).await; + + // calls watcher.update() for AddGroupingPolicy + let _ = e + .add_grouping_policy(vec![ + "g:admin".to_string(), + "data1".to_string(), + ]) + .await; + + // calls watcher.update() for RemoveGroupingPolicy + let _ = e + .remove_grouping_policy(vec![ + "g:admin".to_string(), + "data1".to_string(), + ]) + .await; + + // calls watcher.update() for AddGroupingPolicy again + let _ = e + .add_grouping_policy(vec![ + "g:admin".to_string(), + "data1".to_string(), + ]) + .await; + + // calls watcher.update() for RemoveFilteredGroupingPolicy + let _ = e + .remove_filtered_grouping_policy(1, vec!["data1".to_string()]) + .await; + + // calls watcher.update() for AddPolicies + let _ = e + .add_policies(vec![ + vec![ + "admin".to_string(), + "data1".to_string(), + "read".to_string(), + ], + vec![ + "admin".to_string(), + "data2".to_string(), + "read".to_string(), + ], + ]) + .await; + + // calls watcher.update() for RemovePolicies + let _ = e + .remove_policies(vec![ + vec![ + "admin".to_string(), + "data1".to_string(), + "read".to_string(), + ], + vec![ + "admin".to_string(), + "data2".to_string(), + "read".to_string(), + ], + ]) + .await; + + // Verify that watcher was called for all operations + let calls = update_calls.lock().unwrap(); + assert!(!calls.is_empty(), "Watcher should have been called"); + + // Verify some specific operation types were captured + let call_types: Vec = calls + .iter() + .filter_map(|call| { + if call.contains("SavePolicy") { + Some("SavePolicy".to_string()) + } else if call.contains("AddPolicy") { + Some("AddPolicy".to_string()) + } else if call.contains("RemovePolicy") { + Some("RemovePolicy".to_string()) + } else if call.contains("AddPolicies") { + Some("AddPolicies".to_string()) + } else if call.contains("RemovePolicies") { + Some("RemovePolicies".to_string()) + } else { + None + } + }) + .collect(); + + assert!( + call_types.contains(&"SavePolicy".to_string()), + "Should capture SavePolicy calls" + ); + assert!( + call_types.contains(&"AddPolicy".to_string()), + "Should capture AddPolicy calls" + ); + assert!( + call_types.contains(&"RemovePolicy".to_string()), + "Should capture RemovePolicy calls" + ); + } +}