use std::collections::{hash_map::DefaultHasher, HashMap};
use std::hash::{Hash, Hasher};
use std::path::PathBuf;
use std::str::FromStr;
use std::sync::Arc;
use rstest::fixture;
use crate::constants;
use crate::node;
use crate::runtime::debug::show_term;
use crate::runtime::{
self, Rollback, Runtime, StatementInfo, U128_NONE, U64_NONE,
};
use kindelia_common::{Name, U120};
use kindelia_lang::{ast, parser};
pub fn init_runtime(path: &PathBuf) -> runtime::Runtime {
let genesis_stmts =
parser::parse_code(constants::GENESIS_CODE).expect("Genesis code parses.");
runtime::init_runtime(path.clone(), &genesis_stmts)
}
pub type Validator =
(&'static str, fn(u64, &ast::Term, &mut runtime::Runtime) -> bool);
pub fn are_all_elemenets_equal<E: PartialEq>(vec: &[E]) -> bool {
if vec.len() == 0 {
return true;
}
let last_value = &vec[0];
for value in vec.iter().skip(1) {
if *value != vec[0] {
return false;
}
}
true
}
pub fn view_rollback_ticks(rt: &Runtime) -> String {
fn view_rollback_ticks_go(
rt: &Runtime,
back: &Arc<Rollback>,
) -> Vec<Option<u64>> {
match &**back {
Rollback::Nil => return Vec::new(),
Rollback::Cons { keep, head, tail, life } => {
let mut vec = view_rollback_ticks_go(&rt, tail);
let tick = rt.get_heap(*head).tick;
vec.push(Some(tick));
return vec;
}
}
}
let back = rt.get_back();
let ticks = view_rollback_ticks_go(rt, &back);
let elems = ticks
.iter()
.rev()
.map(|x| {
if let Some(x) = x {
format!("{}", if *x != U64_NONE { *x } else { 0 })
} else {
"___________".to_string()
}
})
.collect::<Vec<String>>()
.join(", ");
return format!("[{}]", elems);
}
#[derive(Eq, PartialEq, Debug, Clone)]
pub struct RuntimeStateTest {
checksum: u64,
mana: u64,
size: u64,
}
impl RuntimeStateTest {
pub fn new(fn_names: &[&str], rt: &mut Runtime) -> RuntimeStateTest {
RuntimeStateTest {
checksum: test_heap_checksum(&fn_names, rt),
mana: rt.get_mana(),
size: rt.get_size(),
}
}
}
pub fn test_heap_checksum(fn_names: &[&str], rt: &mut Runtime) -> u64 {
let fn_ids =
fn_names.iter().map(|x| Name::from_str(x).unwrap()).collect::<Vec<Name>>();
let mut hasher = DefaultHasher::new();
for fn_id in fn_ids {
let term_lnk = rt.read_disk(fn_id.into());
if let Some(term_lnk) = term_lnk {
let term_lnk = show_term(rt, term_lnk, None);
term_lnk.hash(&mut hasher);
}
}
let res = hasher.finish();
res
}
pub fn rollback(
rt: &mut Runtime,
tick: u64,
pre_code: Option<&str>,
code: Option<&str>,
validators: &[Validator],
) {
debug_assert!(tick < rt.get_tick());
rt.rollback(tick);
if rt.get_tick() == 0 {
if let Some(pre_code) = pre_code {
rt.run_statements_from_code(pre_code, true, true);
}
}
let tick_diff = tick - rt.get_tick();
for _ in 0..tick_diff {
if let Some(code) = code {
rt.run_statements_from_code(code, true, true);
}
validate_and_tick(rt, validators);
}
}
pub fn advance(
rt: &mut Runtime,
tick: u64,
code: Option<&str>,
validators: &[Validator],
) {
debug_assert!(tick >= rt.get_tick());
let current_tick = rt.get_tick();
for _ in current_tick..tick {
if let Some(code) = code {
rt.run_statements_from_code(code, true, true);
}
validate_and_tick(rt, validators);
}
}
pub fn rollback_simple(
pre_code: &str,
code: &str,
fn_names: &[&str],
total_tick: u128,
rollback_tick: u64,
validators: &[Validator],
dir_path: &PathBuf,
) -> bool {
let mut rt = init_runtime(dir_path);
let mut old_state = RuntimeStateTest::new(fn_names, &mut rt);
rt.run_statements_from_code(pre_code, true, true);
for _ in 0..total_tick {
rt.run_statements_from_code(code, true, true);
validate_and_tick(&mut rt, validators);
if rt.get_tick() == rollback_tick {
old_state = RuntimeStateTest::new(fn_names, &mut rt);
}
}
rt.rollback(rollback_tick);
if rt.get_tick() == 0 {
rt.run_statements_from_code(pre_code, true, true);
}
let tick_diff = rollback_tick - rt.get_tick();
for _ in 0..tick_diff {
rt.run_statements_from_code(code, true, true);
validate_and_tick(&mut rt, validators);
}
let new_state = RuntimeStateTest::new(fn_names, &mut rt);
old_state == new_state
}
pub fn rollback_path(
pre_code: &str,
code: &str,
fn_names: &[&str],
path: &[u64],
validators: &[Validator],
dir_path: &PathBuf,
) -> bool {
let mut states_store: HashMap<u64, Vec<RuntimeStateTest>> = HashMap::new();
let mut insert_state = |rt: &mut Runtime| {
let state = RuntimeStateTest::new(fn_names, rt);
let vec = states_store.get_mut(&rt.get_tick());
if let Some(vec) = vec {
vec.push(state);
} else {
states_store.insert(rt.get_tick(), vec![state]);
}
};
let mut rt = init_runtime(dir_path);
rt.run_statements_from_code(pre_code, true, true);
for tick in path {
let tick = *tick;
if tick < rt.get_tick() {
rollback(&mut rt, tick, Some(pre_code), Some(code), validators);
} else {
advance(&mut rt, tick, Some(code), validators);
}
insert_state(&mut rt);
}
states_store.values().all(|vec| are_all_elemenets_equal(vec))
}
pub fn run_term_and<A>(term: &ast::Term, action: A)
where
A: Fn(&ast::Term),
{
let temp_dir = temp_dir();
let mut rt = init_runtime(&temp_dir.path);
let term = ast::Term::Fun {
name: "Done".try_into().unwrap(),
args: [term.clone()].to_vec(),
};
let stmt = ast::Statement::Run { expr: term, sign: None };
let result = rt.run_statement(&stmt, false, true, None).unwrap();
if let StatementInfo::Run { done_term, .. } = result {
action(&done_term)
}
}
pub fn run_term_from_code_and<A>(code: &str, action: A)
where
A: Fn(&ast::Term),
{
let (_, term) = parser::parse_term(code).unwrap();
run_term_and(&term, action)
}
fn validate<P: Fn(u64, &ast::Term, &mut runtime::Runtime) -> bool>(
rt: &mut Runtime,
name: &str,
predicate: P,
) {
let tick = rt.get_tick();
let name = Name::from_str(name).unwrap();
let name = U120::from(name);
let state = rt.read_disk(name).unwrap();
let state = runtime::readback_term(rt, state, None).unwrap();
assert!(predicate(tick, &state, rt))
}
fn validate_and_tick(rt: &mut Runtime, validators: &[Validator]) {
rt.open();
eprintln!("tick: {}", rt.get_tick());
for (fn_name, predicate) in validators {
validate(rt, fn_name, predicate)
}
rt.commit();
}
pub struct TempPath {
pub path: PathBuf,
}
impl Drop for TempPath {
fn drop(&mut self) {
if self.path.is_file() {
if let Err(e) = std::fs::remove_file(&self.path) {
eprintln!("Error removing temp file {}: {}", self.path.display(), e);
};
if let Some(parent) = self.path.parent() {
if let Err(e) = std::fs::remove_dir_all(&parent) {
eprintln!(
"Error removing temp dir of file {}: {}",
self.path.display(),
e
);
}
}
} else {
if let Err(e) = std::fs::remove_dir_all(&self.path) {
eprintln!("Error removing temp dir {}: {}", self.path.display(), e);
}
}
}
}
#[fixture]
pub fn temp_dir() -> TempPath {
let path =
std::env::temp_dir().join(format!("kindelia.{:x}", fastrand::u128(..)));
let temp_dir = TempPath { path };
temp_dir
}
#[fixture]
pub fn temp_file() -> TempPath {
let path = std::env::temp_dir()
.join(format!("kindelia.{:x}", fastrand::u128(..)))
.join(format!("kindelia.{:x}.txt", fastrand::u128(..)));
std::fs::create_dir_all(&path.parent().unwrap()).unwrap();
std::fs::write(&path, "").unwrap();
let temp_file = TempPath { path };
temp_file
}