use crate::{
features::{OUTPUT},
low_level_analysis::{
self,
types::{BigOIteratorAlgorithmType, ConstantSetIteratorAlgorithmMeasurements, SetResizingIteratorAlgorithmMeasurements,
BigOAlgorithmAnalysis, BigOTimeMeasurements, BigOSpaceMeasurements,
SetResizingIteratorAlgorithmPassesInfo, ConstantSetIteratorAlgorithmPassesInfo, BigOAlgorithmComplexity},
},
runners::common::*,
};
use std::{
ops::Range,
time::{Instant},
io::{self, Write},
{error::Error, fmt},
fmt::{Display, Formatter},
collections::BTreeMap,
};
pub fn test_crud_algorithms<ResetClosure: Fn(u32) -> u32 + Sync,
CreateClosure: Fn(u32) -> u32 + Sync,
ReadClosure: Fn(u32) -> u32 + Sync,
UpdateClosure: Fn(u32) -> u32 + Sync,
DeleteClosure: Fn(u32) -> u32 + Sync>
(crud_name: &str, max_retry_attempts: u32,
reset_fn: ResetClosure,
create_fn: CreateClosure, expected_create_time_complexity: BigOAlgorithmComplexity, expected_create_space_complexity: BigOAlgorithmComplexity,
read_fn: ReadClosure, expected_read_time_complexity: BigOAlgorithmComplexity, expected_read_space_complexity: BigOAlgorithmComplexity,
update_fn: UpdateClosure, expected_update_time_complexity: BigOAlgorithmComplexity, expected_update_space_complexity: BigOAlgorithmComplexity,
delete_fn: DeleteClosure, expected_delete_time_complexity: BigOAlgorithmComplexity, expected_delete_space_complexity: BigOAlgorithmComplexity,
warmup_percentage: u32, create_iterations_per_pass: u32, read_iterations_per_pass: u32, update_iterations_per_pass: u32, delete_iterations_per_pass: u32,
create_threads: u32, read_threads: u32, update_threads: u32, delete_threads: u32)
where PassResult: Copy {
fn adapt(attempt: u32, iterations_per_pass: u32) -> u32 {
let factor = 10-(((attempt % 15)/3)*2); match attempt {
0 => iterations_per_pass,
_ => match (attempt-1) % 3 {
0 => iterations_per_pass / factor,
1 => iterations_per_pass - (iterations_per_pass / factor),
2 => iterations_per_pass + (iterations_per_pass / factor),
_ => panic!("fix this match")
}
}
}
let mut collected_errors = Vec::<CRUDComplexityAnalysisError>::with_capacity(max_retry_attempts as usize);
for attempt in 0..max_retry_attempts {
let adapted_create_iterations_per_pass = adapt(attempt, create_iterations_per_pass);
let adapted_read_iterations_per_pass = adapt(attempt, read_iterations_per_pass);
let adapted_update_iterations_per_pass = adapt(attempt, update_iterations_per_pass);
let adapted_delete_iterations_per_pass = adapt(attempt, delete_iterations_per_pass);
let crud_analysis = internal_analyse_crud_algorithms(crud_name, &reset_fn,
&create_fn, expected_create_time_complexity, expected_create_space_complexity,
&read_fn, expected_read_time_complexity, expected_read_space_complexity,
&update_fn, expected_update_time_complexity, expected_update_space_complexity,
&delete_fn, expected_delete_time_complexity, expected_delete_space_complexity,
warmup_percentage, adapted_create_iterations_per_pass, adapted_read_iterations_per_pass, adapted_update_iterations_per_pass, adapted_delete_iterations_per_pass,
create_threads, read_threads, update_threads, delete_threads);
if crud_analysis.is_err() {
let crud_analysis_error = crud_analysis.err().unwrap();
if crud_analysis_error.failed_analysis == "Time" {
if attempt < max_retry_attempts-1 {
collected_errors.push(crud_analysis_error);
OUTPUT(&format!("\nAttempt {} failed. Resetting before retrying", attempt+1));
reset_fn(100); OUTPUT("...\n");
continue;
} else {
let unique_failed_operations_count = collected_errors.iter()
.rfold(BTreeMap::<String, u32>::new(), |mut acc, collected_error| {
let key = format!("{} with {:?}", collected_error.failed_operation, collected_error.failed_complexity);
let op_count = acc.get_mut(&key);
match op_count {
Some(count) => *count += 1,
None => {
acc.insert(key, 1);
},
};
acc
});
let previous_errors = unique_failed_operations_count.iter()
.rfold(String::new(), |mut acc, failed_operation_count_item| {
let operation = failed_operation_count_item.0;
let count = failed_operation_count_item.1;
acc.push_str(&format!(" - {} ({} time{})\n", operation, count, if *count == 1 {""} else {"s"}));
acc
});
panic!("After {} attempts, gave up retrying: {}.\n\
Previous attempts failed at:\n\
{}", max_retry_attempts, crud_analysis_error, previous_errors);
}
} else {
panic!("At attempt #{}, SPACE complexity mismatch: {}\n", attempt+1, crud_analysis_error);
}
}
break;
}
}
pub fn analyse_crud_algorithms<ResetClosure: Fn(u32) -> u32 + Sync,
CreateClosure: Fn(u32) -> u32 + Sync,
ReadClosure: Fn(u32) -> u32 + Sync,
UpdateClosure: Fn(u32) -> u32 + Sync,
DeleteClosure: Fn(u32) -> u32 + Sync>
(crud_name: &str,
reset_fn: ResetClosure,
create_fn: CreateClosure,
read_fn: ReadClosure,
update_fn: UpdateClosure,
delete_fn: DeleteClosure,
warmup_percentage: u32, create_iterations_per_pass: u32, read_iterations_per_pass: u32, update_iterations_per_pass: u32, delete_iterations_per_pass: u32,
create_threads: u32, read_threads: u32, update_threads: u32, delete_threads: u32)
-> (Option< BigOAlgorithmAnalysis<SetResizingIteratorAlgorithmMeasurements> >, // create analysis
Option< BigOAlgorithmAnalysis<ConstantSetIteratorAlgorithmMeasurements> >, // read analysis
Option< BigOAlgorithmAnalysis<ConstantSetIteratorAlgorithmMeasurements> >, // update analysis
Option< BigOAlgorithmAnalysis<SetResizingIteratorAlgorithmMeasurements> >, // delete analysis
String) where PassResult: Copy {
internal_analyse_crud_algorithms(crud_name, reset_fn,
create_fn, BigOAlgorithmComplexity::WorseThanExponential, BigOAlgorithmComplexity::WorseThanExponential,
read_fn, BigOAlgorithmComplexity::WorseThanExponential, BigOAlgorithmComplexity::WorseThanExponential,
update_fn, BigOAlgorithmComplexity::WorseThanExponential, BigOAlgorithmComplexity::WorseThanExponential,
delete_fn, BigOAlgorithmComplexity::WorseThanExponential, BigOAlgorithmComplexity::WorseThanExponential,
warmup_percentage, create_iterations_per_pass, read_iterations_per_pass, update_iterations_per_pass, delete_iterations_per_pass,
create_threads, read_threads, update_threads, delete_threads).unwrap()
}
#[derive(Debug)]
struct CRUDComplexityAnalysisError {
pub failed_operation: String,
pub failed_analysis: String,
pub failed_complexity: BigOAlgorithmComplexity,
pub failed_assertion_msg: String,
#[allow(dead_code)]
pub partial_report: String,
}
impl Display for CRUDComplexityAnalysisError {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
write!(f, "CRUD complexity analysis assertion failed: {}", self.failed_assertion_msg)
}
}
impl Error for CRUDComplexityAnalysisError {}
fn internal_analyse_crud_algorithms<ResetClosure: Fn(u32) -> u32 + Sync,
CreateClosure: Fn(u32) -> u32 + Sync,
ReadClosure: Fn(u32) -> u32 + Sync,
UpdateClosure: Fn(u32) -> u32 + Sync,
DeleteClosure: Fn(u32) -> u32 + Sync>
(crud_name: &str,
reset_fn: ResetClosure,
create_fn: CreateClosure, expected_create_time_complexity: BigOAlgorithmComplexity, expected_create_space_complexity: BigOAlgorithmComplexity,
read_fn: ReadClosure, expected_read_time_complexity: BigOAlgorithmComplexity, expected_read_space_complexity: BigOAlgorithmComplexity,
update_fn: UpdateClosure, expected_update_time_complexity: BigOAlgorithmComplexity, expected_update_space_complexity: BigOAlgorithmComplexity,
delete_fn: DeleteClosure, expected_delete_time_complexity: BigOAlgorithmComplexity, expected_delete_space_complexity: BigOAlgorithmComplexity,
warmup_percentage: u32, create_iterations_per_pass: u32, read_iterations_per_pass: u32, update_iterations_per_pass: u32, delete_iterations_per_pass: u32,
create_threads: u32, read_threads: u32, update_threads: u32, delete_threads: u32)
-> Result<(Option< BigOAlgorithmAnalysis<SetResizingIteratorAlgorithmMeasurements> >, // create analysis
Option< BigOAlgorithmAnalysis<ConstantSetIteratorAlgorithmMeasurements> >, // read analysis
Option< BigOAlgorithmAnalysis<ConstantSetIteratorAlgorithmMeasurements> >, // update analysis
Option< BigOAlgorithmAnalysis<SetResizingIteratorAlgorithmMeasurements> >, // delete analysis
String), CRUDComplexityAnalysisError>
where PassResult: Copy {
let mut full_report = String::with_capacity(2048);
let mut _output = |msg: &str| {
full_report.push_str(msg);
OUTPUT(msg);
};
let mut create_passes_results = [PassResult::default(); NUMBER_OF_PASSES as usize];
let mut read_passes_results = [PassResult::default(); NUMBER_OF_PASSES as usize];
let mut update_passes_results = [PassResult::default(); NUMBER_OF_PASSES as usize];
let mut delete_passes_results = [PassResult::default(); NUMBER_OF_PASSES as usize];
const NUMBER_OF_PASSES: u32 = 2;
let mut r: u32 = 0;
fn calc_regular_cru_range(iterations_per_pass: u32, pass_number: u32) -> Range<u32> { iterations_per_pass * pass_number .. iterations_per_pass * (pass_number + 1) }
fn calc_regular_d_range(iterations_per_pass: u32, pass_number: u32) -> Range<u32> { iterations_per_pass * (pass_number + 1) .. iterations_per_pass * pass_number }
macro_rules! run_constant_set_pass {
($pass_number: expr, $operation_name: literal, $suffix: expr, $passes_results: ident,
$algorithm_closure: ident, $expected_time_complexity: ident, $expected_space_complexity: ident,
$number_of_iterations_per_pass: expr, $number_of_threads: ident) => {
if $number_of_iterations_per_pass > 0 {
let (pass_result, pass_r) = run_iterator_pass_verbosely(&format!("{}: ", $operation_name.to_ascii_lowercase()), $suffix,
&$algorithm_closure, &BigOIteratorAlgorithmType::SetResizing,
calc_regular_cru_range($number_of_iterations_per_pass, $pass_number),
$number_of_threads, &mut _output);
$passes_results[$pass_number as usize] = pass_result;
r ^= pass_r;
if $pass_number == NUMBER_OF_PASSES-1 {
let measurements = ConstantSetIteratorAlgorithmMeasurements {
measurement_name: $operation_name,
passes_info: ConstantSetIteratorAlgorithmPassesInfo {
pass_1_set_size: create_iterations_per_pass,
pass_2_set_size: create_iterations_per_pass * 2,
repetitions: $number_of_iterations_per_pass,
},
time_measurements: BigOTimeMeasurements {
pass_1_measurements: $passes_results[0].time_measurements,
pass_2_measurements: $passes_results[1].time_measurements,
},
space_measurements: BigOSpaceMeasurements {
pass_1_measurements: $passes_results[0].space_measurements,
pass_2_measurements: $passes_results[1].space_measurements,
},
pass1_measurements: $crate::low_level_analysis::types::BigOPassMeasurements {
time_measurements: $passes_results[0].time_measurements,
space_measurements: $passes_results[0].space_measurements,
custom_measurements: vec![],
},
pass2_measurements: $crate::low_level_analysis::types::BigOPassMeasurements {
time_measurements: $passes_results[1].time_measurements,
space_measurements: $passes_results[1].space_measurements,
custom_measurements: vec![],
},
};
let time_complexity = low_level_analysis::time_analysis:: analyse_time_complexity_for_constant_set_iterator_algorithm(&measurements.passes_info, &measurements.time_measurements);
let space_complexity = low_level_analysis::space_analysis::analyse_space_complexity_for_constant_set_iterator_algorithm(&measurements.passes_info, &measurements.space_measurements);
yield_analysis_or_return_with_error!($operation_name, measurements, $expected_time_complexity, $expected_space_complexity, time_complexity, space_complexity)
} else {
None
}
} else {
None
}
}
}
macro_rules! run_set_resizing_pass {
($pass_number: expr, $operation_name: literal, $suffix: ident, $result_prefix_closure: expr,
$passes_results: ident, $range_fn: ident, $last_pass_number: expr,
$algorithm_closure: ident, $expected_time_complexity: ident, $expected_space_complexity: ident,
$number_of_iterations_per_pass: expr, $number_of_threads: ident) => {
if $number_of_iterations_per_pass > 0 {
let (pass_result, pass_r) = run_iterator_pass_verbosely(&$result_prefix_closure($pass_number, $operation_name), $suffix,
&$algorithm_closure, &BigOIteratorAlgorithmType::SetResizing,
$range_fn($number_of_iterations_per_pass, $pass_number),
$number_of_threads, &mut _output);
$passes_results[$pass_number as usize] = pass_result;
r ^= pass_r;
if $pass_number == $last_pass_number {
let measurements = SetResizingIteratorAlgorithmMeasurements {
measurement_name: $operation_name,
passes_info: SetResizingIteratorAlgorithmPassesInfo {
delta_set_size: $number_of_iterations_per_pass,
},
time_measurements: BigOTimeMeasurements {
pass_1_measurements: $passes_results[0].time_measurements,
pass_2_measurements: $passes_results[1].time_measurements,
},
space_measurements: BigOSpaceMeasurements {
pass_1_measurements: $passes_results[0].space_measurements,
pass_2_measurements: $passes_results[1].space_measurements,
},
};
let time_complexity = low_level_analysis::time_analysis:: analyse_time_complexity_for_set_resizing_iterator_algorithm(&measurements.passes_info, &measurements.time_measurements);
let space_complexity = low_level_analysis::space_analysis::analyse_space_complexity_for_set_resizing_iterator_algorithm(&measurements.passes_info, &measurements.space_measurements);
yield_analysis_or_return_with_error!($operation_name, measurements, $expected_time_complexity, $expected_space_complexity, time_complexity, space_complexity)
} else {
None
}
} else {
None
}
}
}
macro_rules! yield_analysis_or_return_with_error {
($operation_name: literal, $measurements: ident,
$expected_time_complexity: ident, $expected_space_complexity: ident,
$observed_time_complexity: ident, $observed_space_complexity: ident) => {
if $observed_time_complexity as u32 > $expected_time_complexity as u32 {
_output(&format!(" ** Aborted due to TIME complexity mismatch on '{}' operation: maximum: {:?}, measured: {:?}\n\n", $operation_name, $expected_time_complexity, $observed_time_complexity));
return Err(CRUDComplexityAnalysisError {
failed_operation: $operation_name.to_string(),
failed_analysis: "Time".to_owned(),
failed_complexity: $observed_time_complexity,
failed_assertion_msg: format!("'{}' algorithm was expected to match a maximum TIME complexity of '{:?}', but '{:?}' was measured", $operation_name, $expected_time_complexity, $observed_time_complexity),
partial_report: full_report,
});
} else if $observed_space_complexity as u32 > $expected_space_complexity as u32 {
_output(&format!(" ** Aborted due to SPACE complexity mismatch on '{}' operation: maximum: {:?}, measured: {:?}\n\n", $operation_name, $expected_space_complexity, $observed_space_complexity));
return Err(CRUDComplexityAnalysisError {
failed_operation: $operation_name.to_string(),
failed_analysis: "Space".to_owned(),
failed_complexity: $observed_space_complexity,
failed_assertion_msg: format!("'{}' algorithm was expected to match a maximum SPACE complexity of '{:?}', but '{:?}' was measured", $operation_name, $expected_space_complexity, $observed_space_complexity),
partial_report: full_report,
});
} else {
Some(BigOAlgorithmAnalysis {
algorithm_measurements: $measurements,
$observed_time_complexity,
$observed_space_complexity,
})
}
}
}
macro_rules! run_create_pass {
($pass_number: expr) => {{
let suffix = if read_iterations_per_pass > 0 || update_iterations_per_pass > 0 {", "} else {""};
run_set_resizing_pass!($pass_number, "Create", suffix, |_pass_number: u32, pass_name: &str| format!("{}: ", pass_name.to_ascii_lowercase()),
create_passes_results, calc_regular_cru_range, NUMBER_OF_PASSES-1,
create_fn, expected_create_time_complexity, expected_create_space_complexity,
create_iterations_per_pass, create_threads)
}}
}
macro_rules! run_read_pass {
($pass_number: expr) => {{
let suffix = if update_iterations_per_pass > 0 {", "} else {""};
run_constant_set_pass!($pass_number, "Read", suffix, read_passes_results, read_fn, expected_read_time_complexity, expected_read_space_complexity, read_iterations_per_pass, read_threads)
}}
}
macro_rules! run_update_pass {
($pass_number: expr) => {{
let suffix = "";
run_constant_set_pass!($pass_number, "Update", suffix, update_passes_results, update_fn, expected_update_time_complexity, expected_update_space_complexity, update_iterations_per_pass, update_threads)
}}
}
macro_rules! run_delete_pass {
($pass_number: expr) => {{
let suffix = "";
run_set_resizing_pass!($pass_number, "Delete", suffix,
|pass_number: u32, _pass_name: &str|
if pass_number == NUMBER_OF_PASSES-1 {
"2nd: "
} else {
"; 1st: "
},
delete_passes_results, calc_regular_d_range, 0,
delete_fn, expected_delete_time_complexity, expected_delete_space_complexity,
delete_iterations_per_pass, delete_threads)
}}
}
_output(&format!("{} CRUD Algorithm Complexity Analysis:\n ", crud_name));
if warmup_percentage > 0 {
let calc_warmup_cru_range = |iterations_per_pass| 0 .. iterations_per_pass * warmup_percentage / 100;
let calc_warmup_d_range = |iterations_per_pass| iterations_per_pass * warmup_percentage / 100 .. 0;
let warmup_start = Instant::now();
_output("warming up [");
io::stdout().flush().unwrap();
if create_iterations_per_pass > 0 {
_output("C");
let (_elapse, warmup_r) = run_iterator_pass(&create_fn, &BigOIteratorAlgorithmType::SetResizing, calc_warmup_cru_range(create_iterations_per_pass), create_threads);
r ^= warmup_r;
}
if read_iterations_per_pass > 0 {
_output("R");
let (_elapse, warmup_r) = run_iterator_pass(&read_fn, &BigOIteratorAlgorithmType::ConstantSet, calc_warmup_cru_range(read_iterations_per_pass), read_threads);
r ^= warmup_r;
}
if update_iterations_per_pass > 0 {
_output("U");
let (_elapse, warmup_r) = run_iterator_pass(&update_fn, &BigOIteratorAlgorithmType::ConstantSet, calc_warmup_cru_range(update_iterations_per_pass), update_threads);
r ^= warmup_r;
}
if delete_iterations_per_pass > 0 {
_output("D");
let (_elapse, warmup_r) = run_iterator_pass(&delete_fn, &BigOIteratorAlgorithmType::SetResizing, calc_warmup_d_range(delete_iterations_per_pass), delete_threads);
r ^= warmup_r;
}
_output("] ");
reset_fn(create_iterations_per_pass * warmup_percentage / 100);
let warmup_end = Instant::now();
let warmup_elapsed = warmup_end.duration_since(warmup_start);
_output(&format!("{:?}, ", warmup_elapsed));
}
_output("First Pass (");
run_create_pass!(0);
run_read_pass!(0);
run_update_pass!(0);
_output("); Second Pass (");
let create_analysis = run_create_pass!(1);
let read_analysis = run_read_pass!(1);
let update_analysis = run_update_pass!(1);
_output("):\n\n");
if create_iterations_per_pass > 0 {
_output(&format!("{}\n\n", create_analysis.as_ref().unwrap()));
}
if read_iterations_per_pass > 0 {
_output(&format!("{}\n\n", read_analysis.as_ref().unwrap()));
}
if update_iterations_per_pass > 0 {
_output(&format!("{}\n\n", update_analysis.as_ref().unwrap()));
}
let delete_analysis;
if delete_iterations_per_pass > 0 {
_output("Delete Passes (");
run_delete_pass!(1);
delete_analysis = run_delete_pass!(0);
_output(&format!(") r={}:\n", r));
_output(&format!("{}\n\n", delete_analysis.as_ref().unwrap()));
} else {
delete_analysis = None;
}
Ok( (create_analysis, read_analysis, update_analysis, delete_analysis, full_report) )
}
#[cfg(test)]
mod tests {
use super::*;
use crate:: {
low_level_analysis::types::BigOAlgorithmMeasurements,
};
use std::{
collections::HashMap,
sync::atomic::{Ordering, AtomicU32},
};
use serial_test::serial;
#[test]
#[serial] fn analyse_crud_algorithm_output_check() {
let iterations_per_pass = 100000;
fn assert_contains_algorithm_report<T: BigOAlgorithmMeasurements>(report: &str, algorithm_analysis: Option<BigOAlgorithmAnalysis<T>>, algorithm_name: &str) {
assert!(report.contains(&algorithm_analysis.unwrap().to_string()), "couldn't find '{}' report analysis on the full report", algorithm_name);
}
fn assert_passes_progress(report: &str, warmup: bool, create: bool, read: bool, update: bool, delete: bool) {
if warmup {
let warmup_announcement = format!("warming up [{}{}{}{}] ",
if create {"C"} else {""},
if read {"R"} else {""},
if update {"U"} else {""},
if delete {"D"} else {""});
assert!(report.contains(&warmup_announcement), "'Warmup' announcement was not properly issued -- no '{}' announcement was found on the full report", warmup_announcement);
assert!(report.contains(", First Pass ("), "'warming up' & 'First Pass' announcements seem not to be in sync -- they used to be separated by a comma when the former one is present");
} else {
assert!(!report.contains("warming up "), "'Warmup' announcement was present on the full report, even when it wasn't requested");
}
if create {
let first_pass_announcement = "First Pass (create: ";
let second_pass_announcement = "Second Pass (create: ";
assert!(report.contains(first_pass_announcement), "'First Pass' announcement of the 'create' step seems wrong");
assert!(report.contains(second_pass_announcement), "'Second Pass' announcement of the 'create' step seems wrong");
} else {
assert!(!report.contains("create: "), "'create' step announcement was present on the full report, even when that step wasn't requested");
}
if read {
let first_pass_announcement = if create {"b, read: "} else {"First Pass (read: "};
let second_pass_announcement = if create {"b, read: "} else {"Second Pass (read: "};
assert!(report.contains(first_pass_announcement), "'First Pass' announcement of the 'read' step seems wrong when 'create' is {}present", if create {""} else {"not "});
assert!(report.contains(second_pass_announcement), "'Second Pass' announcement of the 'read' step seems wrong when 'create' is {}present", if create {""} else {"not "});
} else {
assert!(!report.contains("read: "), "'read' step announcement was present on the full report, even when that step wasn't requested");
}
if update {
let first_pass_announcement = if create || read {"b, update: "} else {"First Pass (update: "};
let second_pass_announcement = if create || read {"b, update: "} else {"Second Pass (update: "};
assert!(report.contains(first_pass_announcement), "'First Pass' announcement of the 'update' step seems wrong when 'create'/'read' are {}present", if create || read {""} else {"not "});
assert!(report.contains(second_pass_announcement), "'Second Pass' announcement of the 'update' step seems wrong when 'create'/'read' are {}present", if create || read {""} else {"not "});
} else {
assert!(!report.contains("update: "), "'update' step announcement was present on the full report, even when that step wasn't requested");
}
let delete_passes_announcement = "Delete Passes (";
if delete {
assert!(report.contains(delete_passes_announcement), "'Delete' announcement was not properly issued -- no '{}' announcement was found on the full report", delete_passes_announcement);
} else {
assert!(!report.contains(delete_passes_announcement), "'Delete' announcement was present on the full report, even when it wasn't requested");
}
assert!(!report.contains(",); Second Pass ("), "comma handling at the end of the 'First Pass' announcement is wrong");
assert!(!report.contains(" ); Second Pass ("), "space handling at end end of the 'First Pass' announcement is wrong");
assert!(report.contains("b):\n"), "comma / space handling at the end of the 'Second Pass' announcement seems wrong");
}
let (create_analysis,
read_analysis,
update_analysis,
delete_analysis,
report) = analyse_crud_algorithms("MyContainer",
|n| (n+1)/(n+1),
|n| (n+1)/(n+1),
|n| (n+1)/(n+1),
|n| (n+1)/(n+1),
|n| (n+1)/(n+1),
iterations_per_pass /100,
iterations_per_pass, iterations_per_pass, iterations_per_pass, iterations_per_pass,
1, 1, 1, 1);
assert!(report.contains("MyContainer"), "CRUD name not present on the full report");
assert_passes_progress(&report, true, true, true, true, true);
assert_contains_algorithm_report(&report, create_analysis, "Create");
assert_contains_algorithm_report(&report, read_analysis, "Read");
assert_contains_algorithm_report(&report, update_analysis, "Update");
assert_contains_algorithm_report(&report, delete_analysis, "Delete");
let (_create_analysis,
_read_analysis,
_update_analysis,
_delete_analysis,
report) = analyse_crud_algorithms("MyContainer",
|_n| panic!("'reset_fn' should not be called if there is no warmup taking place"),
|n| (n+1)/(n+1),
|n| (n+1)/(n+1),
|n| (n+1)/(n+1),
|n| (n+1)/(n+1),
0, iterations_per_pass, iterations_per_pass, iterations_per_pass, iterations_per_pass,
1, 1, 1, 1);
assert_passes_progress(&report, false, true, true, true, true);
let (_create_analysis,
_read_analysis,
_update_analysis,
delete_analysis,
report) = analyse_crud_algorithms("MyContainer",
|_n| panic!("'reset_fn' should not be called if there is no warmup taking place"),
|n| (n+1)/(n+1),
&|n| (n+1)/(n+1),
|n| (n+1)/(n+1),
|_n| panic!("'delete_fn' should not be called if there is no warmup taking place"),
0, iterations_per_pass, iterations_per_pass, iterations_per_pass, 0,
1, 1, 1, 0);
assert_passes_progress(&report, false, true, true, true, false);
assert!(delete_analysis.is_none(), "No Delete Complexity Analysis should have been made");
let (_create_analysis,
_read_analysis,
_update_analysis,
_delete_analysis,
report) = analyse_crud_algorithms("MyContainer",
|_n| panic!("'reset_fn' should not be called if there is no warmup taking place"),
|n| (n+1)/(n+1),
&|_n| panic!("'read_fn' should not be called if there is no warmup taking place"),
|_n| panic!("'update_fn' should not be called if there is no warmup taking place"),
|_n| panic!("'delete_fn' should not be called if there is no warmup taking place"),
0, iterations_per_pass, 0, 0, 0,
1, 1, 1, 1);
assert_passes_progress(&report, false, true, false, false, false);
}
#[test]
#[serial]
fn thread_chunk_division() {
let iterations_per_pass = 1000;
for n_threads in [1,2,4,5,10] {
let map_locker = parking_lot::RwLock::new(HashMap::<u32, u32>::with_capacity(2 * iterations_per_pass as usize));
let max_length = AtomicU32::new(0);
analyse_crud_algorithms("thread_chunk_division",
|_n| {0},
|n| {
let mut map = map_locker.write();
map.insert(n, n);
if map.len() as u32 > max_length.load(Ordering::Relaxed) {
max_length.store(map.len() as u32, Ordering::Relaxed);
}
max_length.load(Ordering::Relaxed)
},
|_n| {0},
|_n| {0},
|n| {
let mut map = map_locker.write();
assert_eq!(map.remove(&n), Some(n), "missing element #{} when deleting for n_threads {}", n, n_threads);
map.len() as u32
},
0, iterations_per_pass, 0, 0, iterations_per_pass,
n_threads, n_threads, n_threads, n_threads);
let map = map_locker.read();
assert_eq!(iterations_per_pass *2, max_length.load(Ordering::Relaxed), "failed to insert records when testing for n_threads {}", n_threads);
assert_eq!(0, map.len(), "failed to delete records when testing for n_threads {}", n_threads);
}
}
}