From 535f6bf79b8fbbc9fb748660546c94efbe7ae576 Mon Sep 17 00:00:00 2001 From: Deukhoofd Date: Sat, 18 May 2024 19:24:47 +0200 Subject: [PATCH] Unit tests for basic Rune registration --- .../wrappers/dynamic_data/executing_move.rs | 48 +- .../rune/wrappers/mod.rs | 481 +++++++++++++++++- tests/integration.rs | 77 +-- 3 files changed, 512 insertions(+), 94 deletions(-) diff --git a/src/script_implementations/rune/wrappers/dynamic_data/executing_move.rs b/src/script_implementations/rune/wrappers/dynamic_data/executing_move.rs index 139c6d7..11ee3ea 100644 --- a/src/script_implementations/rune/wrappers/dynamic_data/executing_move.rs +++ b/src/script_implementations/rune/wrappers/dynamic_data/executing_move.rs @@ -1,6 +1,8 @@ -use crate::dynamic_data::ExecutingMove; +use crate::dynamic_data::{ExecutingMove, HitData}; +use crate::script_implementations::rune::wrappers::dynamic_data::pokemon::RunePokemon; +use crate::script_implementations::rune::wrappers::dynamic_data::resolve_script_data; use crate::script_implementations::rune::wrappers::{impl_rune_wrapper, RuneWrapper}; -use rune::runtime::{AnyObj, Shared}; +use rune::runtime::{AnyObj, Object, Shared}; use rune::Any; use std::sync::Arc; @@ -9,6 +11,9 @@ pub fn register(module: &mut rune::Module) -> anyhow::Result<()> { module.function_meta(RuneExecutingMove::target_count)?; module.function_meta(RuneExecutingMove::number_of_hits)?; module.function_meta(RuneExecutingMove::user)?; + module.function_meta(RuneExecutingMove::chosen_move)?; + module.function_meta(RuneExecutingMove::use_move)?; + module.function_meta(RuneExecutingMove::script)?; Ok(()) } @@ -25,4 +30,43 @@ impl RuneExecutingMove { fn number_of_hits(&self) -> u8 { self.0.number_of_hits() } #[rune::function] fn user(&self) -> Shared { self.0.user().wrap() } + + #[rune::function] + fn chosen_move(&self) -> Shared { self.0.chosen_move().wrap() } + + #[rune::function] + fn use_move(&self) -> Shared { self.0.use_move().wrap() } + + #[rune::function] + fn script(&self) -> Option> { resolve_script_data(self.0.script()) } + + #[rune::function] + fn get_hit_data(&self, for_target: RunePokemon, hit: u8) -> anyhow::Result> { + self.0.get_hit_data(&for_target.0, hit).map(|hit_data| hit_data.wrap()) + } +} + +#[derive(Debug, Clone, Any)] +pub struct RuneHitData(pub Arc); + +impl_rune_wrapper!(&Arc, RuneHitData); + +impl RuneHitData { + #[rune::function] + fn is_critical(&self) -> bool { self.0.is_critical() } + + #[rune::function] + fn base_power(&self) -> u8 { self.0.base_power() } + + #[rune::function] + fn effectiveness(&self) -> f32 { self.0.effectiveness() } + + #[rune::function] + fn damage(&self) -> u32 { self.0.damage() } + + #[rune::function] + fn move_type(&self) -> u8 { u8::from(self.0.move_type()) } + + #[rune::function] + fn fail(&self) { self.0.fail() } } diff --git a/src/script_implementations/rune/wrappers/mod.rs b/src/script_implementations/rune/wrappers/mod.rs index c368f61..09bf8d0 100644 --- a/src/script_implementations/rune/wrappers/mod.rs +++ b/src/script_implementations/rune/wrappers/mod.rs @@ -1,3 +1,4 @@ +use rune::alloc::fmt::TryWrite; use rune::runtime::{AnyObj, Protocol, Shared, VmResult}; use rune::{Any, Value}; use std::num::Saturating; @@ -21,16 +22,17 @@ pub fn module() -> anyhow::Result { module.associated_function(Protocol::MUL_ASSIGN, RuneValueIntWrapper::mul_assign)?; module.associated_function(Protocol::PARTIAL_EQ, RuneValueIntWrapper::partial_eq)?; module.associated_function(Protocol::EQ, RuneValueIntWrapper::eq)?; - module.associated_function(Protocol::STRING_DISPLAY, RuneValueIntWrapper::string_display)?; + module.function_meta(RuneValueIntWrapper::string_display)?; module.ty::()?; module.function_meta(RuneValueBoolWrapper::rn_as_bool)?; module.function_meta(RuneValueBoolWrapper::set_value)?; + module.associated_function(Protocol::PARTIAL_EQ, RuneValueBoolWrapper::eq)?; module.associated_function(Protocol::EQ, RuneValueBoolWrapper::eq)?; - module.associated_function(Protocol::STRING_DISPLAY, RuneValueBoolWrapper::string_display)?; + module.function_meta(RuneValueBoolWrapper::string_display)?; module.ty::()?; - module.associated_function(Protocol::STRING_DISPLAY, RuneStringKey::string_display)?; + module.function_meta(RuneStringKey::string_display)?; parameters::register(&mut module)?; dynamic_data::register(&mut module)?; @@ -103,36 +105,76 @@ impl RuneValueIntWrapper { fn sub_assign(&mut self, other: i64) { self.value -= other; } - fn div_assign(&mut self, other: i64) { self.value /= other; } + fn div_assign(&mut self, other: Value) { + match other { + Value::Integer(other) => self.value /= other, + Value::Float(other) => self.div_assign_f64(other), + _ => (), + } + } - fn mul_assign(&mut self, other: i64) { self.value *= other; } + fn div_assign_f64(&mut self, other: f64) { + let v = (self.value.0 as f64 / other).floor(); + if v > i64::MAX as f64 { + self.value = Saturating(i64::MAX); + } else if v < i64::MIN as f64 { + self.value = Saturating(i64::MIN); + } else { + self.value = Saturating(v as i64); + } + } + + fn mul_assign(&mut self, other: Value) { + match other { + Value::Integer(other) => self.value *= other, + Value::Float(other) => self.mul_assign_f64(other), + _ => (), + } + } + + fn mul_assign_f64(&mut self, other: f64) { + let v = (self.value.0 as f64 * other).floor(); + if v > i64::MAX as f64 { + self.value = Saturating(i64::MAX); + } else if v < i64::MIN as f64 { + self.value = Saturating(i64::MIN); + } else { + self.value = Saturating(v as i64); + } + } fn partial_eq(&self, b: i64) -> VmResult { VmResult::Ok(self.value.0 == b) } fn eq(&self, b: i64) -> VmResult { VmResult::Ok(self.value.0 == b) } - fn string_display(&self) -> VmResult { VmResult::Ok(format!("{}", self.value.0)) } + #[rune::function(instance, protocol = STRING_DISPLAY)] + fn string_display(&self, f: &mut rune::runtime::Formatter) -> VmResult<()> { + rune::vm_write!(f, "{}", self.value.0); + VmResult::Ok(()) + } } #[derive(Any, Clone)] -struct RuneValueBoolWrapper { - value: bool, -} +struct RuneValueBoolWrapper(bool); impl RuneValueBoolWrapper { - pub fn new(value: bool) -> Self { Self { value } } + pub fn new(value: bool) -> Self { Self(value) } - pub fn as_bool(&self) -> bool { self.value } + pub fn as_bool(&self) -> bool { self.0 } #[rune::function(path = RuneValueIntWrapper::as_bool)] - fn rn_as_bool(&self) -> bool { self.value } + fn rn_as_bool(&self) -> bool { self.0 } #[rune::function] - fn set_value(&mut self, value: bool) { self.value = value; } + fn set_value(&mut self, value: bool) { self.0 = value; } - fn string_display(&self) -> VmResult { VmResult::Ok(format!("{}", self.value)) } + #[rune::function(instance, protocol = STRING_DISPLAY)] + fn string_display(&self, f: &mut rune::runtime::Formatter) -> VmResult<()> { + rune::vm_write!(f, "{}", self.0); + VmResult::Ok(()) + } - fn eq(&self, b: bool) -> VmResult { VmResult::Ok(self.value == b) } + fn eq(&self, b: bool) -> VmResult { VmResult::Ok(self.0 == b) } } #[derive(Any, Clone, Debug)] @@ -141,9 +183,416 @@ pub(super) struct RuneStringKey(pub StringKey); impl RuneStringKey { pub fn new(value: StringKey) -> Self { Self(value) } - fn string_display(&self) -> VmResult { VmResult::Ok(format!("{}", self.0)) } + #[rune::function(instance, protocol = STRING_DISPLAY)] + fn string_display(&self, f: &mut rune::runtime::Formatter) -> VmResult<()> { + rune::vm_write!(f, "{}", self.0); + VmResult::Ok(()) + } } impl RuneWrapper for &StringKey { fn wrap(self) -> Shared { Shared::new(AnyObj::new(RuneStringKey(self.clone())).unwrap()).unwrap() } } + +#[cfg(test)] +mod test { + use super::*; + use rune::{Context, Diagnostics, Options, Source, Vm}; + use std::sync::Arc; + + pub fn setup_script(script: &str) -> Vm { + let mut context = Context::with_default_modules().unwrap(); + context.install(module().unwrap()).unwrap(); + + let mut sources = rune::Sources::new(); + sources.insert(Source::memory(script).unwrap()).unwrap(); + let mut diagnostics = Diagnostics::new(); + + let mut options = Options::default(); + options.debug_info(true); + options.memoize_instance_fn(true); + options.macros(true); + options.bytecode(true); + + let unit = rune::prepare(&mut sources) + .with_context(&context) + .with_diagnostics(&mut diagnostics) + .with_options(&options) + .build() + .unwrap(); + if !diagnostics.is_empty() { + panic!("Diagnostics: {:?}", diagnostics); + } + + Vm::new(Arc::new(context.runtime().unwrap()), Arc::new(unit)) + } + + macro_rules! execute_vm { + ($vm:expr, $func:expr, $val:expr) => {{ + let args = vec![$val.clone()]; + $vm.execute([$func], args).unwrap().complete().into_result().unwrap() + }}; + } + + #[test] + fn test_int_wrapper_set_value() { + let mut vm = setup_script( + r#" + pub fn test_int(a) { + a.set_value(5); + } + "#, + ); + let val = wrap_int_reference(10).unwrap(); + execute_vm!(vm, "test_int", val); + let v = get_int_reference_value(val).unwrap(); + assert_eq!(v, 5); + } + + #[test] + fn test_int_wrapper_as_int() { + let mut vm = setup_script( + r#" + pub fn test_int(a) { + a.as_int() + } + "#, + ); + let val = wrap_int_reference(10).unwrap(); + let res = execute_vm!(vm, "test_int", val); + let res = res.as_integer().unwrap(); + assert_eq!(res, 10); + } + + #[test] + fn test_int_wrapper_add() { + let mut vm = setup_script( + r#" + pub fn test_int(a) { + a += 5; + } + "#, + ); + let val = wrap_int_reference(10).unwrap(); + execute_vm!(vm, "test_int", val); + let v = get_int_reference_value(val).unwrap(); + assert_eq!(v, 15); + } + + #[test] + fn test_int_wrapper_add_overflow() { + let mut vm = setup_script( + r#" + pub fn test_int(a) { + a += 5; + } + "#, + ); + let val = wrap_int_reference(i64::MAX).unwrap(); + execute_vm!(vm, "test_int", val); + let v = get_int_reference_value(val).unwrap(); + assert_eq!(v, i64::MAX); + } + + #[test] + fn test_int_wrapper_sub() { + let mut vm = setup_script( + r#" + pub fn test_int(a) { + a -= 5; + } + "#, + ); + let val = wrap_int_reference(10).unwrap(); + execute_vm!(vm, "test_int", val); + let v = get_int_reference_value(val).unwrap(); + assert_eq!(v, 5); + } + + #[test] + fn test_int_wrapper_underflow() { + let mut vm = setup_script( + r#" + pub fn test_int(a) { + a -= 5; + } + "#, + ); + let val = wrap_int_reference(i64::MIN).unwrap(); + execute_vm!(vm, "test_int", val); + let v = get_int_reference_value(val).unwrap(); + assert_eq!(v, i64::MIN); + } + + #[test] + fn test_int_wrapper_mul() { + let mut vm = setup_script( + r#" + pub fn test_int(a) { + a *= 5; + } + "#, + ); + let val = wrap_int_reference(10).unwrap(); + execute_vm!(vm, "test_int", val); + let v = get_int_reference_value(val).unwrap(); + assert_eq!(v, 50); + } + + #[test] + fn test_int_wrapper_mul_float() { + let mut vm = setup_script( + r#" + pub fn test_int(a) { + a *= 0.5; + } + "#, + ); + let val = wrap_int_reference(10).unwrap(); + execute_vm!(vm, "test_int", val); + let v = get_int_reference_value(val).unwrap(); + assert_eq!(v, 5); + } + + #[test] + fn test_int_wrapper_mul_overflow() { + let mut vm = setup_script( + r#" + pub fn test_int(a) { + a *= 5; + } + "#, + ); + let val = wrap_int_reference(i64::MAX).unwrap(); + execute_vm!(vm, "test_int", val); + let v = get_int_reference_value(val).unwrap(); + assert_eq!(v, i64::MAX); + } + + #[test] + fn test_int_wrapper_mul_float_overflow() { + let mut vm = setup_script( + r#" + pub fn test_int(a) { + a *= 10.0; + } + "#, + ); + let val = wrap_int_reference(i64::MAX).unwrap(); + execute_vm!(vm, "test_int", val); + let v = get_int_reference_value(val).unwrap(); + assert_eq!(v, i64::MAX); + } + + #[test] + fn test_int_wrapper_mul_float_underflow() { + let mut vm = setup_script( + r#" + pub fn test_int(a) { + a *= 10.0; + } + "#, + ); + let val = wrap_int_reference(i64::MIN).unwrap(); + execute_vm!(vm, "test_int", val); + let v = get_int_reference_value(val).unwrap(); + assert_eq!(v, i64::MIN); + } + + #[test] + fn test_int_wrapper_div() { + let mut vm = setup_script( + r#" + pub fn test_int(a) { + a /= 5; + } + "#, + ); + let val = wrap_int_reference(10).unwrap(); + execute_vm!(vm, "test_int", val); + let v = get_int_reference_value(val).unwrap(); + assert_eq!(v, 2); + } + + #[test] + fn test_int_wrapper_div_float() { + let mut vm = setup_script( + r#" + pub fn test_int(a) { + a /= 0.5; + } + "#, + ); + let val = wrap_int_reference(10).unwrap(); + execute_vm!(vm, "test_int", val); + let v = get_int_reference_value(val).unwrap(); + assert_eq!(v, 20); + } + + #[test] + fn test_int_wrapper_div_float_overflow() { + let mut vm = setup_script( + r#" + pub fn test_int(a) { + a /= 0.0001; + } + "#, + ); + let val = wrap_int_reference(i64::MAX).unwrap(); + execute_vm!(vm, "test_int", val); + let v = get_int_reference_value(val).unwrap(); + assert_eq!(v, i64::MAX); + } + + #[test] + fn test_int_wrapper_div_float_underflow() { + let mut vm = setup_script( + r#" + pub fn test_int(a) { + a /= 0.0001; + } + "#, + ); + let val = wrap_int_reference(i64::MIN).unwrap(); + execute_vm!(vm, "test_int", val); + let v = get_int_reference_value(val).unwrap(); + assert_eq!(v, i64::MIN); + } + + #[test] + fn test_int_wrapper_eq() { + let mut vm = setup_script( + r#" + pub fn test_int(a) { + a == 5 + } + "#, + ); + let val = wrap_int_reference(5).unwrap(); + let res = execute_vm!(vm, "test_int", val); + let res = res.as_bool().unwrap(); + assert_eq!(res, true); + } + + #[test] + fn test_int_wrapper_ineq() { + let mut vm = setup_script( + r#" + pub fn test_int(a) { + a == 5 + } + "#, + ); + let val = wrap_int_reference(6).unwrap(); + let res = execute_vm!(vm, "test_int", val); + let res = res.as_bool().unwrap(); + assert_eq!(res, false); + } + + #[test] + fn test_int_wrapper_string_display() { + let mut vm = setup_script( + r#" + pub fn test_int(a) { + `${a}` + } + "#, + ); + let val = wrap_int_reference(5).unwrap(); + let res = execute_vm!(vm, "test_int", val); + let res = res.into_string().unwrap().take().unwrap().into_std(); + assert_eq!(res, "5"); + } + + #[test] + fn test_bool_wrapper_set_value() { + let mut vm = setup_script( + r#" + pub fn test_bool(a) { + a.set_value(true); + } + "#, + ); + let val = wrap_bool_reference(false).unwrap(); + execute_vm!(vm, "test_bool", val); + let v = get_bool_reference_value(val).unwrap(); + assert_eq!(v, true); + } + + #[test] + fn test_bool_wrapper_as_bool() { + let mut vm = setup_script( + r#" + pub fn test_bool(a) { + a.as_bool() + } + "#, + ); + let val = wrap_bool_reference(true).unwrap(); + let res = execute_vm!(vm, "test_bool", val); + let res = res.as_bool().unwrap(); + assert_eq!(res, true); + } + + #[test] + fn test_bool_wrapper_eq() { + let mut vm = setup_script( + r#" + pub fn test_bool(a) { + a == true + } + "#, + ); + let val = wrap_bool_reference(true).unwrap(); + let res = execute_vm!(vm, "test_bool", val); + let res = res.as_bool().unwrap(); + assert_eq!(res, true); + } + + #[test] + fn test_bool_wrapper_ineq() { + let mut vm = setup_script( + r#" + pub fn test_bool(a) { + a == true + } + "#, + ); + let val = wrap_bool_reference(false).unwrap(); + let res = execute_vm!(vm, "test_bool", val); + let res = res.as_bool().unwrap(); + assert_eq!(res, false); + } + + #[test] + fn test_bool_wrapper_string_display() { + let mut vm = setup_script( + r#" + pub fn test_bool(a) { + `${a}` + } + "#, + ); + let val = wrap_bool_reference(true).unwrap(); + let res = execute_vm!(vm, "test_bool", val); + let res = res.into_string().unwrap().take().unwrap().into_std(); + assert_eq!(res, "true"); + } + + #[test] + fn test_string_key_wrapper_string_display() { + let mut vm = setup_script( + r#" + pub fn test_string_key(a) { + `${a}` + } + "#, + ); + let val = RuneStringKey::new(StringKey::from("test")); + let val = Shared::new(AnyObj::new(val).unwrap()).unwrap(); + let arg = Value::Any(val); + let res = execute_vm!(vm, "test_string_key", arg); + let res = res.into_string().unwrap().take().unwrap().into_std(); + assert_eq!(res, "test"); + } +} diff --git a/tests/integration.rs b/tests/integration.rs index c748e40..5d6ef8e 100755 --- a/tests/integration.rs +++ b/tests/integration.rs @@ -5,10 +5,7 @@ use std::sync::{Arc, LazyLock}; -use pkmn_lib::dynamic_data::{ - Battle, BattleParty, DamageSource, DynamicLibrary, ExecutingMove, MoveChoice, PassChoice, PokemonBuilder, - PokemonParty, ScriptCategory, ScriptContainer, ScriptOwnerData, TurnChoice, VolatileScriptsOwner, -}; +use pkmn_lib::dynamic_data::{DynamicLibrary, PassChoice, PokemonBuilder, ScriptCategory, ScriptOwnerData, TurnChoice}; use crate::common::library_loader; @@ -106,75 +103,3 @@ fn load_non_existing_wasm_script() { assert!(script.is_none()); } - -/// Assurance has the interesting properties that it creates a special data script internally, and -/// deletes that data script through the get_owner functionality. -#[test] -fn validate_assurance() { - let lib = get_library(); - let p1 = PokemonBuilder::new(lib.clone(), "charizard".into(), 100) - .learn_move("assurance".into()) - .build() - .unwrap(); - let p2 = PokemonBuilder::new(lib.clone(), "venusaur".into(), 100) - .build() - .unwrap(); - let party1 = Arc::new( - BattleParty::new( - Arc::new(PokemonParty::new_from_vec(vec![Some(p1.clone())])), - vec![(0, 0)], - ) - .unwrap(), - ); - let party2 = Arc::new( - BattleParty::new( - Arc::new(PokemonParty::new_from_vec(vec![Some(p2.clone())])), - vec![(1, 0)], - ) - .unwrap(), - ); - - let battle = Battle::new(lib.clone(), vec![party1, party2], false, 2, 1, None); - - battle.sides()[0].set_pokemon(0, Some(p1.clone())).unwrap(); - battle.sides()[1].set_pokemon(0, Some(p2.clone())).unwrap(); - - let script = lib - .load_script( - ScriptOwnerData::None, - ScriptCategory::Move, - &"double_power_if_target_damaged_in_turn".into(), - ) - .unwrap() - .unwrap(); - - let mv = p1.learned_moves().read()[0].as_ref().unwrap().clone(); - let choice = Arc::new(TurnChoice::Move(MoveChoice::new(p1.clone(), mv.clone(), 1, 0))); - script.on_before_turn(&choice).unwrap(); - assert!(battle.sides()[1].has_volatile_script(&"double_power_if_target_damaged_in_turn_data".into())); - - let executing_move = Arc::new(ExecutingMove::new( - vec![], - 1, - p1, - mv.clone(), - mv.move_data().clone(), - ScriptContainer::default(), - )); - let mut v = 20_u8; - script.change_base_power(&executing_move, &p2, 0, &mut v).unwrap(); - assert_eq!(v, 20_u8); - - let s = battle.sides()[1].get_volatile_script(&"double_power_if_target_damaged_in_turn_data".into()); - let binding = s.as_ref().unwrap().get().unwrap().read(); - let data_script = binding.as_ref().unwrap(); - - data_script.on_damage(&p2, DamageSource::Misc, 100, 50).unwrap(); - - let mut v = 20_u8; - script.change_base_power(&executing_move, &p2, 0, &mut v).unwrap(); - assert_eq!(v, 40_u8); - - data_script.on_end_turn().unwrap(); - assert!(!battle.sides()[1].has_volatile_script(&"double_power_if_target_damaged_in_turn_data".into())); -}