From 23b4051a0901ec41aae35b4d897439f1b7a4c8aa Mon Sep 17 00:00:00 2001 From: Stu Hood Date: Sun, 1 Jul 2018 19:20:02 -0700 Subject: [PATCH] Add a concurrent invalidation test, and a validation function for node outputs. --- src/rust/engine/Cargo.lock | 29 ++++++++ src/rust/engine/graph/Cargo.toml | 3 + src/rust/engine/graph/src/lib.rs | 112 ++++++++++++++++++++++++++++++- 3 files changed, 143 insertions(+), 1 deletion(-) diff --git a/src/rust/engine/Cargo.lock b/src/rust/engine/Cargo.lock index 5baeaa3a62b0..8918c389cf6b 100644 --- a/src/rust/engine/Cargo.lock +++ b/src/rust/engine/Cargo.lock @@ -168,6 +168,14 @@ dependencies = [ "vec_map 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "cloudabi" +version = "0.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "bitflags 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "cmake" version = "0.1.31" @@ -438,6 +446,7 @@ dependencies = [ "futures 0.1.21 (registry+https://github.com/rust-lang/crates.io-index)", "hashing 0.0.1", "petgraph 0.4.12 (registry+https://github.com/rust-lang/crates.io-index)", + "rand 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -838,6 +847,23 @@ dependencies = [ "winapi 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "rand" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "cloudabi 0.0.3 (registry+https://github.com/rust-lang/crates.io-index)", + "fuchsia-zircon 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.40 (registry+https://github.com/rust-lang/crates.io-index)", + "rand_core 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "rand_core" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "redox_syscall" version = "0.1.37" @@ -1318,6 +1344,7 @@ dependencies = [ "checksum cc 1.0.15 (registry+https://github.com/rust-lang/crates.io-index)" = "0ebb87d1116151416c0cf66a0e3fb6430cccd120fd6300794b4dfaa050ac40ba" "checksum cfg-if 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "405216fd8fe65f718daa7102ea808a946b6ce40c742998fbfd3463645552de18" "checksum clap 2.31.2 (registry+https://github.com/rust-lang/crates.io-index)" = "f0f16b89cbb9ee36d87483dc939fe9f1e13c05898d56d7b230a0d4dff033a536" +"checksum cloudabi 0.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "ddfc5b9aa5d4507acaf872de71051dfd0e309860e88966e1051e462a077aac4f" "checksum cmake 0.1.31 (registry+https://github.com/rust-lang/crates.io-index)" = "95470235c31c726d72bf2e1f421adc1e65b9d561bf5529612cbe1a72da1467b3" "checksum crossbeam 0.2.12 (registry+https://github.com/rust-lang/crates.io-index)" = "bd66663db5a988098a89599d4857919b3acf7f61402e61365acfd3919857b9be" "checksum crossbeam-deque 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "fe8153ef04a7594ded05b427ffad46ddeaf22e63fd48d42b3e1e3bb4db07cae7" @@ -1384,6 +1411,8 @@ dependencies = [ "checksum quote 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)" = "7a6e920b65c65f10b2ae65c831a81a073a89edd28c7cce89475bff467ab4167a" "checksum rand 0.3.22 (registry+https://github.com/rust-lang/crates.io-index)" = "15a732abf9d20f0ad8eeb6f909bf6868722d9a06e1e50802b6a70351f40b4eb1" "checksum rand 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "eba5f8cb59cc50ed56be8880a5c7b496bfd9bd26394e176bc67884094145c2c5" +"checksum rand 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)" = "6802c0e883716383777e147b7c21323d5de7527257c8b6dc1365a7f2983e90f6" +"checksum rand_core 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "edecf0f94da5551fc9b492093e30b041a891657db7940ee221f9d2f66e82eef2" "checksum redox_syscall 0.1.37 (registry+https://github.com/rust-lang/crates.io-index)" = "0d92eecebad22b767915e4d529f89f28ee96dbbf5a4810d2b844373f136417fd" "checksum redox_termios 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "7e891cfe48e9100a70a3b6eb652fef28920c117d366339687bd5576160db0f76" "checksum regex 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)" = "9329abc99e39129fcceabd24cf5d85b4671ef7c29c50e972bc5afe32438ec384" diff --git a/src/rust/engine/graph/Cargo.toml b/src/rust/engine/graph/Cargo.toml index 8fde7f7100b1..8f027249853e 100644 --- a/src/rust/engine/graph/Cargo.toml +++ b/src/rust/engine/graph/Cargo.toml @@ -9,3 +9,6 @@ fnv = "1.0.5" futures = "^0.1.16" hashing = { path = "../hashing" } petgraph = "0.4.5" + +[dev-dependencies] +rand = "0.5.3" diff --git a/src/rust/engine/graph/src/lib.rs b/src/rust/engine/graph/src/lib.rs index d4e714b8db70..ac5d25026f07 100644 --- a/src/rust/engine/graph/src/lib.rs +++ b/src/rust/engine/graph/src/lib.rs @@ -1129,13 +1129,20 @@ impl<'a, N: Node + 'a, P: Fn(EntryId, Level) -> bool> Iterator for LeveledWalk<' #[cfg(test)] mod tests { - use std::sync::{Arc, Mutex}; + extern crate rand; + + use std::cmp; + use std::collections::HashSet; + use std::sync::{mpsc, Arc, Mutex}; use std::thread; + use std::time::Duration; use boxfuture::{BoxFuture, Boxable}; use futures::future::{self, Future}; use hashing::Digest; + use self::rand::Rng; + use super::{EntryId, Graph, InvalidationResult, Node, NodeContext, NodeError}; #[test] @@ -1208,6 +1215,62 @@ mod tests { assert_eq!(context1.runs(), vec![TNode(1), TNode(2)]); } + #[test] + fn invalidate_randomly() { + let graph = Arc::new(Graph::new()); + + let invalidations = 10; + let sleep_per_invalidation = Duration::from_millis(100); + let range = 100; + + // Spawn a background thread to randomly invalidate in the relevant range. Hold its handle so + // it doesn't detach. + let graph2 = graph.clone(); + let (send, recv) = mpsc::channel(); + let _join = thread::spawn(move || { + let mut rng = rand::thread_rng(); + let mut invalidations = invalidations; + while invalidations > 0 { + invalidations -= 1; + + // Invalidate a random node in the graph. + let candidate = rng.gen_range(0, range); + graph2.invalidate_from_roots(|&TNode(n)| n == candidate); + + thread::sleep(sleep_per_invalidation); + } + send.send(()).unwrap(); + }); + + // Continuously re-request the root with increasing context values, and assert that Node and + // context values are ascending. + let mut iterations = 0; + let mut max_distinct_context_values = 0; + loop { + let context = TContext::new(iterations, graph.clone()); + + // Compute the root, and validate its output. + let node_output = graph.create(TNode(100), &context).wait().unwrap(); + max_distinct_context_values = cmp::max( + max_distinct_context_values, + TNode::validate(&node_output).unwrap(), + ); + + // Poll the channel to see whether the background thread has exited. + if let Ok(_) = recv.try_recv() { + break; + } + iterations += 1; + } + + assert!( + max_distinct_context_values > 1, + "In {} iterations, observed a maximum of {} distinct context values.", + iterations, + max_distinct_context_values + ); + } + /// /// A token containing the id of a Node and the id of a Context, respectively. Has a short name /// to minimize the verbosity of tests. @@ -1252,6 +1315,53 @@ mod tests { } } + impl TNode { + /// + /// Validates the given TNode output. Both node ids and context ids should increase left to + /// right: node ids monotonically, and context ids non-monotonically. + /// + /// Valid: + /// (0,0), (1,1), (2,2), (3,3) + /// (0,0), (1,0), (2,1), (3,1) + /// + /// Invalid: + /// (0,0), (1,1), (2,1), (3,0) + /// (0,0), (1,0), (2,0), (1,0) + /// + /// If successful, returns the count of distinct context ids in the path. + /// + fn validate(output: &Vec) -> Result { + let (node_ids, context_ids): (Vec<_>, Vec<_>) = output + .iter() + .map(|&T(node_id, context_id)| { + // We cast to isize to allow comparison to -1. + (node_id as isize, context_id) + }) + .unzip(); + // Confirm monotonically ordered. + let mut previous: isize = -1; + for node_id in node_ids { + if previous + 1 != node_id { + return Err(format!( + "Node ids in {:?} were not monotonically ordered.", + output + )); + } + previous = node_id; + } + // Confirm ordered (non-monotonically). + let mut previous: usize = 0; + for &context_id in &context_ids { + if previous > context_id { + return Err(format!("Context ids in {:?} were not ordered.", output)); + } + previous = context_id; + } + + Ok(context_ids.into_iter().collect::>().len()) + } + } + /// /// A context that keeps a record of Nodes that have been run. ///