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:
Marc Brinkmann 2018-10-04 15:52:24 +02:00 committed by GitHub
parent 79e5ef19fd
commit 6f0b53436f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 335 additions and 184 deletions

View File

@ -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"

View File

@ -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())
// ...
}
```

View File

@ -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;

View File

@ -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,61 +35,68 @@ 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.
#[inline]
pub fn range(min_size: u16, max_size: u16) -> NetworkDimensionStrategy {
NetworkDimensionStrategy { min_size, max_size }
}
/// Creates a proptest strategy to create network dimensions within a certain range.
pub fn range(min_size: usize, max_size: usize) -> 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)
}
}
@ -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 {

View File

@ -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()

View File

@ -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]

View File

@ -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));
}