mirror of https://github.com/poanetwork/hbbft.git
Fix test-case shrinking issues (#254)
* Use inverse of `is_bft` function for checking in `average_higher`. * Inline in `NetworkDimension`. * Check more invariants when averaging network sizes. * Added test that finds shrinking regression of `NetworkDimension`. * Use bijection for shrinking of network dimensions. * Fix types on dimensions. * Hide `size` and `faulty` behind accessor methods. * Make limits dependant on input size. * Fixed clippy lints. * Simplify averaging.
This commit is contained in:
parent
79e5ef19fd
commit
6f0b53436f
|
@ -42,6 +42,11 @@ itertools = "0.7"
|
|||
serde_derive = "1.0.55"
|
||||
signifix = "0.9"
|
||||
proptest = "0.8.7"
|
||||
# Note: `rand_core` is solely used for the randomness adapter in `net_utils.rs`
|
||||
# tests and should be removed as soon as a migration path to rand 0.5
|
||||
# appears.
|
||||
rand_core = "0.2.1"
|
||||
integer-sqrt = "0.1.1"
|
||||
|
||||
[[example]]
|
||||
name = "consensus-node"
|
||||
|
|
|
@ -150,8 +150,8 @@ fn do_example_test(seed: TestRngSeed, dimension: NetworkDimension) {
|
|||
// Instantiates a new random number generator for the test.
|
||||
let mut rng: TestRng = TestRng::from_seed(cfg.seed);
|
||||
|
||||
let mut net = NetBuilder::new(0..dimension.size)
|
||||
.num_faulty(cfg.dimension.faulty)
|
||||
let mut net = NetBuilder::new(0..dimension.size())
|
||||
.num_faulty(cfg.dimension.faulty())
|
||||
// Setting `rng` ensures that randomness will only be retrieved by
|
||||
// `VirtualNet` from the `TestRng` instantiated above.
|
||||
.rng(rng)
|
||||
|
@ -199,8 +199,8 @@ proptest! {
|
|||
}
|
||||
|
||||
fn do_basic_operations(dimension: NetworkDimension, num_txs: u32) {
|
||||
let mut net = NetBuilder::new(0..cfg.dimension.size)
|
||||
.num_faulty(cfg.dimension.faulty)
|
||||
let mut net = NetBuilder::new(0..cfg.dimension.size())
|
||||
.num_faulty(cfg.dimension.faulty())
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
|
|
@ -491,7 +491,9 @@ where
|
|||
|
||||
// Note: Closure is not redundant, won't compile without it.
|
||||
#[cfg_attr(feature = "cargo-clippy", allow(redundant_closure))]
|
||||
let mut net = VirtualNet::new(self.node_ids, self.num_faulty, rng, move |node| cons(node))?;
|
||||
let mut net = VirtualNet::new(self.node_ids, self.num_faulty as usize, rng, move |node| {
|
||||
cons(node)
|
||||
})?;
|
||||
|
||||
if self.adversary.is_some() {
|
||||
net.adversary = self.adversary;
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
//! This module houses strategies to generate (and reduce/expand) various `hbbft` and `net` related
|
||||
//! structures.
|
||||
|
||||
use integer_sqrt::IntegerSquareRoot;
|
||||
use proptest::arbitrary::any;
|
||||
use proptest::prelude::Rng;
|
||||
use proptest::strategy::{Strategy, ValueTree};
|
||||
|
@ -34,62 +35,69 @@ pub fn gen_seed() -> impl Strategy<Value = TestRngSeed> {
|
|||
/// A `NetworkDimension` describes the number of correct and faulty nodes in a network. It can also
|
||||
/// be checked, "averaged" (using the `average_higher` function) and generated using
|
||||
/// `NetworkDimensionTree`.
|
||||
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
|
||||
#[derive(Copy, Clone, Debug, Eq, PartialEq, Ord, PartialOrd)]
|
||||
pub struct NetworkDimension {
|
||||
/// Total number of nodes in network.
|
||||
pub size: usize,
|
||||
size: u16,
|
||||
/// Number of faulty nodes in a network.
|
||||
pub faulty: usize,
|
||||
faulty: u16,
|
||||
}
|
||||
|
||||
impl NetworkDimension {
|
||||
/// Creates a new `NetworkDimension` with the supplied parameters.
|
||||
///
|
||||
/// Dimensions that do not satisfy BFT conditions (see `is_bft`) can be created using this
|
||||
/// function.
|
||||
pub fn new(size: usize, faulty: usize) -> Self {
|
||||
NetworkDimension { size, faulty }
|
||||
/// # Panics
|
||||
///
|
||||
|
||||
#[inline]
|
||||
pub fn new(size: u16, faulty: u16) -> Self {
|
||||
let dim = NetworkDimension { size, faulty };
|
||||
assert!(
|
||||
dim.is_bft(),
|
||||
"Tried to create network dimension that violates BFT-property."
|
||||
);
|
||||
dim
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn faulty(self) -> usize {
|
||||
self.faulty.into()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn size(self) -> usize {
|
||||
self.size.into()
|
||||
}
|
||||
|
||||
/// Checks whether the network dimension satisfies the `3 * faulty + 1 <= size` condition.
|
||||
pub fn is_bft(&self) -> bool {
|
||||
#[inline]
|
||||
fn is_bft(self) -> bool {
|
||||
self.faulty * 3 < self.size
|
||||
}
|
||||
|
||||
/// Creates a new dimension of average complexity.
|
||||
///
|
||||
/// The new dimension is approximately half way in the interval of `[self, high]` and will
|
||||
/// conform to the constraint checked by `is_bft()`.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// `high` must be have a higher or equal size and faulty node count.
|
||||
pub fn average_higher(&self, high: NetworkDimension) -> NetworkDimension {
|
||||
assert!(high.size >= self.size);
|
||||
assert!(high.faulty >= self.faulty);
|
||||
|
||||
// We try halving both values, rounding down. If `size` is at the minimum, `faulty` will
|
||||
// shrink afterwards.
|
||||
let mut half = NetworkDimension {
|
||||
size: self.size + (high.size - self.size) / 2,
|
||||
faulty: self.faulty + (high.faulty - self.faulty) / 2,
|
||||
};
|
||||
|
||||
// Reduce the number of faulty nodes, if we are outside our limits.
|
||||
if half.faulty * 3 > half.size {
|
||||
half.faulty -= 1;
|
||||
}
|
||||
|
||||
// This assert just checks for bugs.
|
||||
assert!(half.is_bft());
|
||||
|
||||
half
|
||||
}
|
||||
|
||||
/// Creates a proptest strategy to create network dimensions within a certain range.
|
||||
pub fn range(min_size: usize, max_size: usize) -> NetworkDimensionStrategy {
|
||||
#[inline]
|
||||
pub fn range(min_size: u16, max_size: u16) -> NetworkDimensionStrategy {
|
||||
NetworkDimensionStrategy { min_size, max_size }
|
||||
}
|
||||
|
||||
/// Returns next-larger network dimension.
|
||||
///
|
||||
/// The order on `NetworkDimension` is canonically defined by `(size, faulty)`. The `succ`
|
||||
/// function returns the next-higher valid instance by first trying to increase `faulty`, then
|
||||
/// `size`.
|
||||
#[inline]
|
||||
pub fn succ(self) -> NetworkDimension {
|
||||
let mut n = self.size;
|
||||
let mut f = self.faulty + 1;
|
||||
|
||||
if 3 * f >= n {
|
||||
f = 0;
|
||||
n += 1;
|
||||
}
|
||||
|
||||
NetworkDimension::new(n, f)
|
||||
}
|
||||
}
|
||||
|
||||
/// Network dimension tree for proptest generation.
|
||||
|
@ -98,11 +106,11 @@ impl NetworkDimension {
|
|||
#[derive(Copy, Clone, Debug)]
|
||||
pub struct NetworkDimensionTree {
|
||||
/// The upper bound for any generated dimension.
|
||||
high: NetworkDimension,
|
||||
high: u32,
|
||||
/// The currently generated network dimension.
|
||||
current: NetworkDimension,
|
||||
current: u32,
|
||||
/// The lower bound for any generated dimension value (changes during generation or shrinking).
|
||||
low: NetworkDimension,
|
||||
low: u32,
|
||||
}
|
||||
|
||||
impl NetworkDimensionTree {
|
||||
|
@ -114,7 +122,7 @@ impl NetworkDimensionTree {
|
|||
/// # Panics
|
||||
///
|
||||
/// The minimum `min_size` is 1 and `min_size` must be less than or equal `max_size`.
|
||||
pub fn gen<R: Rng>(mut rng: R, min_size: usize, max_size: usize) -> Self {
|
||||
pub fn gen<R: Rng>(mut rng: R, min_size: u16, max_size: u16) -> Self {
|
||||
// A common mistake, add an extra assert for a more helpful error message.
|
||||
assert!(min_size > 0, "minimum network size is 1");
|
||||
|
||||
|
@ -122,22 +130,12 @@ impl NetworkDimensionTree {
|
|||
let max_faulty = (total - 1) / 3;
|
||||
let faulty = rng.gen_range(0, max_faulty + 1);
|
||||
|
||||
let high = NetworkDimension {
|
||||
size: total,
|
||||
faulty,
|
||||
};
|
||||
assert!(high.is_bft());
|
||||
|
||||
let low = NetworkDimension {
|
||||
size: min_size,
|
||||
faulty: 0,
|
||||
};
|
||||
assert!(low.is_bft());
|
||||
let high = NetworkDimension::new(total, faulty);
|
||||
|
||||
NetworkDimensionTree {
|
||||
high,
|
||||
current: high,
|
||||
low,
|
||||
high: high.into(),
|
||||
current: high.into(),
|
||||
low: 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -146,15 +144,14 @@ impl ValueTree for NetworkDimensionTree {
|
|||
type Value = NetworkDimension;
|
||||
|
||||
fn current(&self) -> Self::Value {
|
||||
self.current
|
||||
self.current.into()
|
||||
}
|
||||
|
||||
fn simplify(&mut self) -> bool {
|
||||
// Shrinking is simply done through `average_higher`.
|
||||
let prev = *self;
|
||||
|
||||
self.high = prev.current;
|
||||
self.current = self.low.average_higher(prev.high);
|
||||
self.high = self.current;
|
||||
self.current = (self.low + self.high) / 2;
|
||||
|
||||
(prev.high != self.high || prev.current != self.current)
|
||||
}
|
||||
|
@ -162,34 +159,78 @@ impl ValueTree for NetworkDimensionTree {
|
|||
fn complicate(&mut self) -> bool {
|
||||
let prev = *self;
|
||||
|
||||
// Minimally increase the faulty-node ratio by adjusting the number of faulty nodes and the
|
||||
// size slightly less. If we are at the maximum number of faulty nodes, we would end up
|
||||
// increasing the network size instead (see branch below though).
|
||||
let mut new_low = self.current;
|
||||
new_low.faulty += 1;
|
||||
new_low.size = (new_low.size + 2).max(new_low.faulty * 3 + 1);
|
||||
assert!(new_low.is_bft());
|
||||
|
||||
// Instead of growing the network, return unchanged if the new network would be larger than
|
||||
// the current high.
|
||||
if new_low.size > self.high.size {
|
||||
if self.high == self.current {
|
||||
return false;
|
||||
}
|
||||
|
||||
self.current = new_low.average_higher(self.high);
|
||||
self.low = new_low;
|
||||
self.low = self.current + 1;
|
||||
self.current = (self.low + self.high) / 2;
|
||||
|
||||
(prev.current != self.current || prev.low != self.low)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<NetworkDimension> for u32 {
|
||||
fn from(dim: NetworkDimension) -> u32 {
|
||||
// `b` is the "Block index" here. Counting through `NetworkDimensions` a pattern shows:
|
||||
//
|
||||
// n f
|
||||
// 1 0 \
|
||||
// 2 0 |- Block 0
|
||||
// 3 0 /
|
||||
// 4 0 \
|
||||
// 4 1 \
|
||||
// 5 0 > Block 1
|
||||
// 5 1 |
|
||||
// 6 0 /
|
||||
// 6 1 /
|
||||
// 7 0 ...
|
||||
//
|
||||
// We observe that each block starts at index `3 * (b(b+1)/2)`. Along with the offset,
|
||||
// we can calculate a mapping onto the natural numbers using this:
|
||||
|
||||
let b = (u32::from(dim.size) - 1) / 3;
|
||||
let start = 3 * b * (b + 1) / 2;
|
||||
let offset = (u32::from(dim.size) - 3 * b - 1) * (b + 1) + u32::from(dim.faulty);
|
||||
|
||||
start + offset
|
||||
}
|
||||
}
|
||||
|
||||
impl From<u32> for NetworkDimension {
|
||||
fn from(n: u32) -> NetworkDimension {
|
||||
// Inverse of `u32 as From<NetworkDimension>`:
|
||||
|
||||
// Find the block number first:
|
||||
let b = max_sum(n / 3);
|
||||
|
||||
// Calculate the block start and the resulting offset of `n`:
|
||||
let start = 3 * b * (b + 1) / 2;
|
||||
let offset = n - start;
|
||||
|
||||
let faulty = offset % (b + 1);
|
||||
let size = 3 * b + 1 + offset / (b + 1);
|
||||
|
||||
NetworkDimension::new(size as u16, faulty as u16)
|
||||
}
|
||||
}
|
||||
|
||||
/// Finds the largest consecutive summand less or equal than `n`.
|
||||
///
|
||||
/// The return value `k` will satisfy `SUM 1..k <= n`.
|
||||
pub fn max_sum(n: u32) -> u32 {
|
||||
// Derived by quadratically solving `n(n+1)/2`; we only want the "positive" result.
|
||||
// `integer_sqrt` functions as a `floor` function here.
|
||||
((1 + 8 * n).integer_sqrt() - 1) / 2
|
||||
}
|
||||
|
||||
/// Network dimension strategy for proptest.
|
||||
#[derive(Debug)]
|
||||
pub struct NetworkDimensionStrategy {
|
||||
/// Minimum number of nodes for newly generated networks dimensions.
|
||||
pub min_size: usize,
|
||||
pub min_size: u16,
|
||||
/// Maximum number of nodes for newly generated networks dimensions.
|
||||
pub max_size: usize,
|
||||
pub max_size: u16,
|
||||
}
|
||||
|
||||
impl Strategy for NetworkDimensionStrategy {
|
||||
|
|
|
@ -2,12 +2,13 @@ extern crate failure;
|
|||
extern crate hbbft;
|
||||
#[macro_use]
|
||||
extern crate proptest;
|
||||
extern crate integer_sqrt;
|
||||
extern crate rand;
|
||||
extern crate threshold_crypto;
|
||||
|
||||
pub mod net;
|
||||
|
||||
use std::collections;
|
||||
use std::{collections, time};
|
||||
|
||||
use hbbft::dynamic_honey_badger::{Change, ChangeState, DynamicHoneyBadger, Input};
|
||||
use hbbft::messaging::DistAlgorithm;
|
||||
|
@ -92,10 +93,14 @@ fn do_drop_and_readd(cfg: TestConfig) {
|
|||
let mut rng: TestRng = TestRng::from_seed(cfg.seed);
|
||||
|
||||
// First, we create a new test network with Honey Badger instances.
|
||||
let mut net = NetBuilder::new(0..cfg.dimension.size)
|
||||
.num_faulty(cfg.dimension.faulty)
|
||||
.message_limit(200_000) // Limited to 200k messages for now.
|
||||
.rng(rng.gen::<TestRng>()) // Ensure runs are reproducible.
|
||||
let mut net = NetBuilder::new(0..cfg.dimension.size())
|
||||
.num_faulty(cfg.dimension.faulty())
|
||||
// Limited to 15k messages per node.
|
||||
.message_limit(15_000 * cfg.dimension.size() as usize)
|
||||
// 30 secs per node.
|
||||
.time_limit(time::Duration::from_secs(30 * cfg.dimension.size() as u64))
|
||||
// Ensure runs are reproducible.
|
||||
.rng(rng.gen::<TestRng>())
|
||||
.using_step(move |node| {
|
||||
println!("Constructing new dynamic honey badger node #{}", node.id);
|
||||
DynamicHoneyBadger::builder()
|
||||
|
|
|
@ -0,0 +1,7 @@
|
|||
# Seeds for failure cases proptest has generated in the past. It is
|
||||
# automatically read and these particular cases re-run before any
|
||||
# novel cases are generated.
|
||||
#
|
||||
# It is recommended to check this file in to source control so that
|
||||
# everyone who runs the test benefits from these saved cases.
|
||||
xs 2984819682 1621205622 3953858650 3756562836 # shrinks to seed = [724246048, 1190443809, 4113437293, 1855000933], ops = [Simplify, Complicate]
|
|
@ -2,128 +2,219 @@ extern crate failure;
|
|||
extern crate hbbft;
|
||||
#[macro_use]
|
||||
extern crate proptest;
|
||||
extern crate integer_sqrt;
|
||||
extern crate rand;
|
||||
extern crate rand_core;
|
||||
extern crate threshold_crypto;
|
||||
|
||||
pub mod net;
|
||||
|
||||
use proptest::strategy::ValueTree;
|
||||
use proptest::test_runner::TestRunner;
|
||||
use proptest::arbitrary::any;
|
||||
use proptest::prelude::RngCore;
|
||||
use proptest::strategy::{Strategy, ValueTree};
|
||||
use rand::{Rng as Rng4, SeedableRng as SeedableRng4};
|
||||
|
||||
use net::proptest::{NetworkDimension, NetworkDimensionTree};
|
||||
use net::proptest::{max_sum, NetworkDimension, NetworkDimensionTree};
|
||||
|
||||
/// Checks the `check_sanity` function with various inputs.
|
||||
#[test]
|
||||
fn check_sanity_works() {
|
||||
assert!(NetworkDimension::new(3, 0).is_bft());
|
||||
assert!(NetworkDimension::new(4, 0).is_bft());
|
||||
assert!(NetworkDimension::new(5, 0).is_bft());
|
||||
assert!(NetworkDimension::new(6, 0).is_bft());
|
||||
assert!(!NetworkDimension::new(3, 1).is_bft());
|
||||
assert!(NetworkDimension::new(4, 1).is_bft());
|
||||
assert!(NetworkDimension::new(5, 1).is_bft());
|
||||
assert!(NetworkDimension::new(6, 1).is_bft());
|
||||
assert!(NetworkDimension::new(16, 3).is_bft());
|
||||
assert!(NetworkDimension::new(17, 3).is_bft());
|
||||
assert!(NetworkDimension::new(18, 3).is_bft());
|
||||
assert!(NetworkDimension::new(19, 3).is_bft());
|
||||
assert!(NetworkDimension::new(16, 5).is_bft());
|
||||
assert!(NetworkDimension::new(17, 5).is_bft());
|
||||
assert!(NetworkDimension::new(18, 5).is_bft());
|
||||
assert!(NetworkDimension::new(19, 5).is_bft());
|
||||
assert!(!NetworkDimension::new(16, 6).is_bft());
|
||||
assert!(!NetworkDimension::new(17, 6).is_bft());
|
||||
assert!(!NetworkDimension::new(18, 6).is_bft());
|
||||
assert!(NetworkDimension::new(19, 6).is_bft());
|
||||
assert!(!NetworkDimension::new(19, 19).is_bft());
|
||||
assert!(!NetworkDimension::new(19, 21).is_bft());
|
||||
struct RngAdapter4To5<T>(pub T);
|
||||
|
||||
// Edge cases:
|
||||
assert!(NetworkDimension::new(1, 0).is_bft());
|
||||
assert!(!NetworkDimension::new(0, 0).is_bft());
|
||||
assert!(!NetworkDimension::new(1, 1).is_bft());
|
||||
}
|
||||
|
||||
proptest!{
|
||||
/// Ensure that `.average_higher()` produces valid new dimensions.
|
||||
#[test]
|
||||
fn average_higher_is_bft(size in 4..40usize) {
|
||||
let mut faulty: usize = size/3;
|
||||
if faulty > 0 {
|
||||
faulty -= 1;
|
||||
}
|
||||
|
||||
let high = NetworkDimension::new(size, faulty);
|
||||
let low = NetworkDimension::new(size/4, faulty/12);
|
||||
|
||||
println!("high: {:?}, low: {:?}", high, low);
|
||||
assert!(high.is_bft());
|
||||
assert!(low.is_bft());
|
||||
|
||||
let average_higher = low.average_higher(high);
|
||||
println!("average_higher: {:?}", average_higher);
|
||||
assert!(average_higher.is_bft());
|
||||
impl<T> Rng4 for RngAdapter4To5<T>
|
||||
where
|
||||
T: Rng4,
|
||||
{
|
||||
#[inline]
|
||||
fn next_u32(&mut self) -> u32 {
|
||||
self.0.next_u32()
|
||||
}
|
||||
}
|
||||
|
||||
/// Ensure `.average_higher()` works for edge cases.
|
||||
#[test]
|
||||
fn average_higher_handles_edge_cases() {
|
||||
let high = NetworkDimension::new(1, 0);
|
||||
let low = NetworkDimension::new(1, 0);
|
||||
let average_higher = low.average_higher(high);
|
||||
assert!(average_higher.is_bft());
|
||||
impl<T> RngCore for RngAdapter4To5<T>
|
||||
where
|
||||
T: Rng4,
|
||||
{
|
||||
#[inline]
|
||||
fn next_u32(&mut self) -> u32 {
|
||||
self.0.next_u32()
|
||||
}
|
||||
|
||||
let high = NetworkDimension::new(10, 0);
|
||||
let low = NetworkDimension::new(10, 0);
|
||||
let average_higher = low.average_higher(high);
|
||||
assert!(average_higher.is_bft());
|
||||
#[inline]
|
||||
fn next_u64(&mut self) -> u64 {
|
||||
self.0.next_u64()
|
||||
}
|
||||
|
||||
let high = NetworkDimension::new(10, 3);
|
||||
let low = NetworkDimension::new(10, 3);
|
||||
let average_higher = low.average_higher(high);
|
||||
assert!(average_higher.is_bft());
|
||||
#[inline]
|
||||
fn fill_bytes(&mut self, bytes: &mut [u8]) {
|
||||
self.0.fill_bytes(bytes);
|
||||
}
|
||||
|
||||
let high = NetworkDimension::new(11, 3);
|
||||
let low = NetworkDimension::new(10, 3);
|
||||
let average_higher = low.average_higher(high);
|
||||
assert!(average_higher.is_bft());
|
||||
#[inline]
|
||||
fn try_fill_bytes(&mut self, bytes: &mut [u8]) -> Result<(), rand_core::Error> {
|
||||
self.0.fill_bytes(bytes);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
proptest!{
|
||||
/// Ensures all generated network dimensions are actually sane.
|
||||
#[test]
|
||||
fn generated_network_dimensions_are_sane(nt in NetworkDimension::range(1, 400)) {
|
||||
assert!(nt.is_bft());
|
||||
fn generated_network_dimensions_are_sane(_nt in NetworkDimension::range(1, 400)) {
|
||||
// Nothing to do here, assert already in `NetworkDimension::new`.
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
enum Op {
|
||||
Simplify,
|
||||
Complicate,
|
||||
}
|
||||
|
||||
fn any_op() -> impl Strategy<Value = Op> {
|
||||
any::<bool>().prop_map(|v| if v { Op::Simplify } else { Op::Complicate })
|
||||
}
|
||||
|
||||
proptest!{
|
||||
/// Verifies generated network dimensions can be grown and shrunk multiple times.
|
||||
#[test]
|
||||
fn network_dimensions_shrink_and_grow(
|
||||
// dim in NetworkDimension::range(1, 400).no_shrink(),
|
||||
seed in any::<[u32; 4]>().no_shrink(),
|
||||
// num_ops in 10..10000,
|
||||
ops in proptest::collection::vec(any_op(), 1..100)
|
||||
) {
|
||||
let mut rng5 = RngAdapter4To5(rand::XorShiftRng::from_seed(seed));
|
||||
|
||||
let mut tree = NetworkDimensionTree::gen(&mut rng5, 1, 40);
|
||||
println!("Start: {:?}", tree);
|
||||
|
||||
for op in ops {
|
||||
println!("Op: {:?}", op);
|
||||
match op {
|
||||
Op::Simplify => tree.simplify(),
|
||||
Op::Complicate => tree.complicate(),
|
||||
};
|
||||
println!("Result: {:?}", tree);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Verifies generated network dimensions can be grown and shrunk multiple times.
|
||||
#[test]
|
||||
fn network_dimensions_shrink_and_grow() {
|
||||
let mut runner = TestRunner::new(Default::default());
|
||||
fn network_succ_works() {
|
||||
let expected = (&[
|
||||
(1, 0),
|
||||
(2, 0),
|
||||
(3, 0),
|
||||
(4, 0),
|
||||
(4, 1),
|
||||
(5, 0),
|
||||
(5, 1),
|
||||
(6, 0),
|
||||
(6, 1),
|
||||
(7, 0),
|
||||
(7, 1),
|
||||
(7, 2),
|
||||
(8, 0),
|
||||
(8, 1),
|
||||
(8, 2),
|
||||
(9, 0),
|
||||
(9, 1),
|
||||
(9, 2),
|
||||
(10, 0),
|
||||
(10, 1),
|
||||
(10, 2),
|
||||
(10, 3),
|
||||
])
|
||||
.iter()
|
||||
.map(|&(n, f)| NetworkDimension::new(n, f));
|
||||
|
||||
let mut tree = NetworkDimensionTree::gen(runner.rng(), 1, 40);
|
||||
assert!(tree.current().is_bft());
|
||||
let mut dim = NetworkDimension::new(1, 0);
|
||||
|
||||
// We complicate and simplify a few times.
|
||||
for _ in 0..10 {
|
||||
tree.complicate();
|
||||
assert!(tree.current().is_bft());
|
||||
}
|
||||
|
||||
for _ in 0..20 {
|
||||
tree.simplify();
|
||||
assert!(tree.current().is_bft());
|
||||
}
|
||||
|
||||
for _ in 0..10 {
|
||||
tree.complicate();
|
||||
assert!(tree.current().is_bft());
|
||||
}
|
||||
|
||||
for _ in 0..10 {
|
||||
tree.simplify();
|
||||
assert!(tree.current().is_bft());
|
||||
for exp in expected {
|
||||
assert_eq!(dim, exp);
|
||||
dim = dim.succ();
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_max_sum() {
|
||||
assert_eq!(max_sum(0), 0);
|
||||
assert_eq!(max_sum(1), 1);
|
||||
assert_eq!(max_sum(2), 1);
|
||||
assert_eq!(max_sum(3), 2);
|
||||
assert_eq!(max_sum(4), 2);
|
||||
assert_eq!(max_sum(5), 2);
|
||||
assert_eq!(max_sum(6), 3);
|
||||
assert_eq!(max_sum(7), 3);
|
||||
assert_eq!(max_sum(8), 3);
|
||||
assert_eq!(max_sum(9), 3);
|
||||
assert_eq!(max_sum(10), 4);
|
||||
assert_eq!(max_sum(5049), 99);
|
||||
assert_eq!(max_sum(5050), 100);
|
||||
assert_eq!(max_sum(5051), 100);
|
||||
assert_eq!(max_sum(5150), 100);
|
||||
assert_eq!(max_sum(5151), 101);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn network_to_u32_is_correct() {
|
||||
assert_eq!(u32::from(NetworkDimension::new(1, 0)), 0u32);
|
||||
assert_eq!(u32::from(NetworkDimension::new(2, 0)), 1u32);
|
||||
assert_eq!(u32::from(NetworkDimension::new(3, 0)), 2u32);
|
||||
assert_eq!(u32::from(NetworkDimension::new(4, 0)), 3u32);
|
||||
assert_eq!(u32::from(NetworkDimension::new(4, 1)), 4u32);
|
||||
assert_eq!(u32::from(NetworkDimension::new(5, 0)), 5u32);
|
||||
assert_eq!(u32::from(NetworkDimension::new(5, 1)), 6u32);
|
||||
assert_eq!(u32::from(NetworkDimension::new(6, 0)), 7u32);
|
||||
assert_eq!(u32::from(NetworkDimension::new(6, 1)), 8u32);
|
||||
assert_eq!(u32::from(NetworkDimension::new(7, 0)), 9u32);
|
||||
assert_eq!(u32::from(NetworkDimension::new(7, 1)), 10u32);
|
||||
assert_eq!(u32::from(NetworkDimension::new(7, 2)), 11u32);
|
||||
assert_eq!(u32::from(NetworkDimension::new(8, 0)), 12u32);
|
||||
assert_eq!(u32::from(NetworkDimension::new(8, 1)), 13u32);
|
||||
assert_eq!(u32::from(NetworkDimension::new(8, 2)), 14u32);
|
||||
assert_eq!(u32::from(NetworkDimension::new(9, 0)), 15u32);
|
||||
assert_eq!(u32::from(NetworkDimension::new(9, 1)), 16u32);
|
||||
assert_eq!(u32::from(NetworkDimension::new(9, 2)), 17u32);
|
||||
assert_eq!(u32::from(NetworkDimension::new(10, 0)), 18u32);
|
||||
assert_eq!(u32::from(NetworkDimension::new(10, 1)), 19u32);
|
||||
assert_eq!(u32::from(NetworkDimension::new(10, 2)), 20u32);
|
||||
assert_eq!(u32::from(NetworkDimension::new(10, 3)), 21u32);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn network_from_u32_is_correct() {
|
||||
assert_eq!(NetworkDimension::new(1, 0), NetworkDimension::from(0u32));
|
||||
assert_eq!(NetworkDimension::new(2, 0), NetworkDimension::from(1u32));
|
||||
assert_eq!(NetworkDimension::new(3, 0), NetworkDimension::from(2u32));
|
||||
assert_eq!(NetworkDimension::new(4, 0), NetworkDimension::from(3u32));
|
||||
assert_eq!(NetworkDimension::new(4, 1), NetworkDimension::from(4u32));
|
||||
assert_eq!(NetworkDimension::new(5, 0), NetworkDimension::from(5u32));
|
||||
assert_eq!(NetworkDimension::new(5, 1), NetworkDimension::from(6u32));
|
||||
assert_eq!(NetworkDimension::new(6, 0), NetworkDimension::from(7u32));
|
||||
assert_eq!(NetworkDimension::new(6, 1), NetworkDimension::from(8u32));
|
||||
assert_eq!(NetworkDimension::new(7, 0), NetworkDimension::from(9u32));
|
||||
assert_eq!(NetworkDimension::new(7, 1), NetworkDimension::from(10u32));
|
||||
assert_eq!(NetworkDimension::new(7, 2), NetworkDimension::from(11u32));
|
||||
assert_eq!(NetworkDimension::new(8, 0), NetworkDimension::from(12u32));
|
||||
assert_eq!(NetworkDimension::new(8, 1), NetworkDimension::from(13u32));
|
||||
assert_eq!(NetworkDimension::new(8, 2), NetworkDimension::from(14u32));
|
||||
assert_eq!(NetworkDimension::new(9, 0), NetworkDimension::from(15u32));
|
||||
assert_eq!(NetworkDimension::new(9, 1), NetworkDimension::from(16u32));
|
||||
assert_eq!(NetworkDimension::new(9, 2), NetworkDimension::from(17u32));
|
||||
assert_eq!(NetworkDimension::new(10, 0), NetworkDimension::from(18u32));
|
||||
assert_eq!(NetworkDimension::new(10, 1), NetworkDimension::from(19u32));
|
||||
assert_eq!(NetworkDimension::new(10, 2), NetworkDimension::from(20u32));
|
||||
assert_eq!(NetworkDimension::new(10, 3), NetworkDimension::from(21u32));
|
||||
assert_eq!(NetworkDimension::new(11, 0), NetworkDimension::from(22u32));
|
||||
assert_eq!(NetworkDimension::new(11, 1), NetworkDimension::from(23u32));
|
||||
assert_eq!(NetworkDimension::new(11, 2), NetworkDimension::from(24u32));
|
||||
assert_eq!(NetworkDimension::new(11, 3), NetworkDimension::from(25u32));
|
||||
assert_eq!(NetworkDimension::new(12, 0), NetworkDimension::from(26u32));
|
||||
assert_eq!(NetworkDimension::new(12, 1), NetworkDimension::from(27u32));
|
||||
assert_eq!(NetworkDimension::new(12, 2), NetworkDimension::from(28u32));
|
||||
assert_eq!(NetworkDimension::new(12, 3), NetworkDimension::from(29u32));
|
||||
assert_eq!(NetworkDimension::new(13, 0), NetworkDimension::from(30u32));
|
||||
assert_eq!(NetworkDimension::new(13, 1), NetworkDimension::from(31u32));
|
||||
assert_eq!(NetworkDimension::new(13, 2), NetworkDimension::from(32u32));
|
||||
assert_eq!(NetworkDimension::new(13, 3), NetworkDimension::from(33u32));
|
||||
assert_eq!(NetworkDimension::new(13, 4), NetworkDimension::from(34u32));
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue