diff --git a/e2e-tests/tests/fixtures/rust_global_program/src/main.rs b/e2e-tests/tests/fixtures/rust_global_program/src/main.rs index f28182f4..2326e250 100644 --- a/e2e-tests/tests/fixtures/rust_global_program/src/main.rs +++ b/e2e-tests/tests/fixtures/rust_global_program/src/main.rs @@ -2,6 +2,7 @@ #![allow(static_mut_refs)] use std::{ + borrow::Cow, cell::{Cell, RefCell}, collections::{BTreeMap, BTreeSet, HashMap, HashSet, VecDeque}, ffi::{CString, OsString}, @@ -32,10 +33,14 @@ pub static mut G_VEC_VEC_STRING: Vec> = Vec::new(); pub static mut G_EMPTY_VEC: Vec = Vec::new(); pub static mut G_VEC_UNIT: Vec<()> = Vec::new(); pub static mut G_VEC_DEQUE_I32: VecDeque = VecDeque::new(); +pub static mut G_VEC_DEQUE_STRING: VecDeque = VecDeque::new(); pub static mut G_VEC_DEQUE_UNIT: VecDeque<()> = VecDeque::new(); pub static mut G_SLICE_I32: &[i32] = &[]; +pub static mut G_SLICE_STRING: &[String] = &[]; pub static mut G_MUT_SLICE_U16: &mut [u16] = &mut []; pub static mut G_EMPTY_SLICE: &[i32] = &[]; +pub static mut G_COW_STR_BORROWED: Cow<'static, str> = Cow::Borrowed(""); +pub static mut G_COW_STR_OWNED: Cow<'static, str> = Cow::Borrowed(""); pub static G_NONZERO_U32: NonZeroU32 = NonZeroU32::new(7).unwrap(); pub static G_NONZERO_I32: NonZeroI32 = NonZeroI32::new(-9).unwrap(); pub static G_NONZERO_U128: NonZeroU128 = @@ -232,8 +237,10 @@ pub static mut G_AGGREGATE_REPR_C: AggregateReprC = AggregateReprC { pub static mut G_OPTION_STRING: Option = None; pub static mut G_OPTION_STRING_NONE: Option = None; +pub static mut G_OPTION_VEC_STRING: Option> = None; pub static mut G_RESULT_RECORD_OK: Result = Err(String::new()); pub static mut G_RESULT_RECORD_ERR: Result = Err(String::new()); +pub static mut G_RESULT_RECORDS_OK: Result, String> = Err(String::new()); pub enum ManyStringVariants { V00(String), @@ -559,20 +566,21 @@ pub mod math { } } -fn wrapped_vec_deque() -> VecDeque { - let mut deque = VecDeque::with_capacity(4); - let filler_len = deque.capacity() - 2; +fn wrapped_vec_deque(filler: T, values: [T; N]) -> VecDeque { + assert!(N > 1); + let mut values = values.into_iter(); + let first = values.next().expect("wrapped deque values are nonempty"); + let mut deque = VecDeque::with_capacity(N); + let filler_len = deque.capacity() - 1; for _ in 0..filler_len { - deque.push_back(-1); + deque.push_back(filler.clone()); } - deque.push_back(10); - deque.push_back(20); + deque.push_back(first); for _ in 0..filler_len { deque.pop_front(); } - deque.push_back(30); - deque.push_back(40); + deque.extend(values); deque } @@ -651,10 +659,14 @@ fn touch_globals() -> i32 { + G_EMPTY_VEC.len() as i64 + G_VEC_UNIT.len() as i64 + G_VEC_DEQUE_I32.len() as i64 + + G_VEC_DEQUE_STRING.len() as i64 + G_VEC_DEQUE_UNIT.len() as i64 + G_SLICE_I32.len() as i64 + + G_SLICE_STRING.len() as i64 + G_MUT_SLICE_U16.len() as i64 + G_EMPTY_SLICE.len() as i64 + + G_COW_STR_BORROWED.len() as i64 + + G_COW_STR_OWNED.len() as i64 + G_NONZERO_U32.get() as i64 + G_NONZERO_I32.get() as i64 + G_NONZERO_U128.get() as i64 @@ -679,6 +691,10 @@ fn touch_globals() -> i32 { .as_ref() .map(String::len) .unwrap_or(0) as i64 + + G_OPTION_VEC_STRING + .as_ref() + .map(Vec::len) + .unwrap_or(0) as i64 + match &G_RESULT_RECORD_OK { Ok(record) => record.title.len() as i64 + record.count as i64, Err(error) => error.len() as i64, @@ -687,6 +703,13 @@ fn touch_globals() -> i32 { Ok(record) => record.title.len() as i64 + record.count as i64, Err(error) => error.len() as i64, } + + match &G_RESULT_RECORDS_OK { + Ok(records) => records + .iter() + .map(|record| record.title.len() as i64 + record.count as i64) + .sum(), + Err(error) => error.len() as i64, + } + match &G_MANY_STRING_VARIANT { ManyStringVariants::V00(value) | ManyStringVariants::V01(value) @@ -747,9 +770,28 @@ fn main() { ]; G_VEC_UNIT = vec![(); 3]; G_VEC_DEQUE_I32 = VecDeque::from([10, 20, 30, 40]); + let string_deque = wrapped_vec_deque( + String::new(), + [ + String::from("deque alpha"), + String::from("deque beta"), + String::from("deque gamma"), + ], + ); + assert!(!string_deque.as_slices().1.is_empty()); + G_VEC_DEQUE_STRING = string_deque; G_VEC_DEQUE_UNIT = VecDeque::from([(), (), ()]); G_SLICE_I32 = Box::leak(vec![7, -8, 9].into_boxed_slice()); + G_SLICE_STRING = Box::leak( + vec![ + String::from("slice alpha"), + String::from("slice beta"), + ] + .into_boxed_slice(), + ); G_MUT_SLICE_U16 = Box::leak(vec![1000, 2000, 65535].into_boxed_slice()); + G_COW_STR_BORROWED = Cow::Borrowed("borrowed cow"); + G_COW_STR_OWNED = Cow::Owned(String::from("owned cow")); G_CELL_STRING.set(String::from("cell nested")); *G_REF_CELL_STRING.get_mut() = String::from("refcell nested"); G_AGGREGATE_RECORD = AggregateRecord { @@ -784,11 +826,25 @@ fn main() { }; G_OPTION_STRING = Some(String::from("optional value")); G_OPTION_STRING_NONE = None; + G_OPTION_VEC_STRING = Some(vec![ + String::from("option alpha"), + String::from("option beta"), + ]); G_RESULT_RECORD_OK = Ok(AggregateRecord { title: String::from("result record"), count: 31, }); G_RESULT_RECORD_ERR = Err(String::from("result error")); + G_RESULT_RECORDS_OK = Ok(vec![ + AggregateRecord { + title: String::from("result one"), + count: 37, + }, + AggregateRecord { + title: String::from("result two"), + count: 41, + }, + ]); G_MANY_STRING_VARIANT = ManyStringVariants::V07(String::from("shared variant budget")); } @@ -811,7 +867,7 @@ fn main() { CString::new("boxed c").unwrap().into_boxed_c_str(), ) as i64; acc += math::observe_vec_deque( - wrapped_vec_deque(), + wrapped_vec_deque(-1, [10, 20, 30, 40]), VecDeque::from([7_u16, 8, 9]), VecDeque::new(), ) as i64; diff --git a/e2e-tests/tests/rust_script_execution.rs b/e2e-tests/tests/rust_script_execution.rs index 4251ea6a..470a3a9b 100644 --- a/e2e-tests/tests/rust_script_execution.rs +++ b/e2e-tests/tests/rust_script_execution.rs @@ -231,6 +231,26 @@ async fn test_rust_nested_value_plans_recurse_into_semantic_children() -> anyhow ghostscope_dwarf::ValuePresentation::Utf8String ); + for name in ["G_VEC_DEQUE_STRING", "G_SLICE_STRING"] { + let plan = nested_plan(name)?; + assert!( + matches!( + &plan.presentation, + ghostscope_dwarf::ValuePresentation::Sequence { .. } + ), + "{name} must preserve its sequence presentation: {plan:#?}" + ); + let Some(ghostscope_dwarf::ValueNestedPlan::Sequence { element }) = plan.nested.as_ref() + else { + anyhow::bail!("expected nested sequence element plan for {name}: {plan:#?}") + }; + assert_eq!( + element.presentation, + ghostscope_dwarf::ValuePresentation::Utf8String, + "{name} must recurse into its String elements" + ); + } + let c_strings = nested_plan("G_VEC_C_STRING")?; let Some(ghostscope_dwarf::ValueNestedPlan::Sequence { element }) = c_strings.nested.as_ref() else { @@ -371,6 +391,69 @@ async fn test_rust_nested_value_plans_recurse_into_semantic_children() -> anyhow ); } + let option_vector = nested_plan("G_OPTION_VEC_STRING")?; + let Some(ghostscope_dwarf::ValueNestedPlan::Variant { fields }) = option_vector.nested.as_ref() + else { + anyhow::bail!("expected Option> variant plan: {option_vector:#?}") + }; + let vector = fields + .iter() + .find_map(|field| match field.value.nested.as_ref() { + Some(ghostscope_dwarf::ValueNestedPlan::Sequence { element }) => Some(element), + _ => None, + }) + .ok_or_else(|| { + anyhow::anyhow!("expected Option> sequence payload: {option_vector:#?}") + })?; + assert_eq!( + vector.presentation, + ghostscope_dwarf::ValuePresentation::Utf8String + ); + + let result_records = nested_plan("G_RESULT_RECORDS_OK")?; + let Some(ghostscope_dwarf::ValueNestedPlan::Variant { fields }) = + result_records.nested.as_ref() + else { + anyhow::bail!( + "expected Result, String> variant plan: {result_records:#?}" + ) + }; + let records = fields + .iter() + .find_map(|field| match field.value.nested.as_ref() { + Some(ghostscope_dwarf::ValueNestedPlan::Sequence { element }) => Some(element), + _ => None, + }) + .ok_or_else(|| { + anyhow::anyhow!( + "expected Result, String> sequence payload: \ + {result_records:#?}" + ) + })?; + assert!( + matches!( + records.nested.as_ref(), + Some(ghostscope_dwarf::ValueNestedPlan::ProjectedView { .. }) + ) && contains_utf8_string(records), + "Result, String> must recurse into record fields: \ + {result_records:#?}" + ); + + for name in ["G_COW_STR_BORROWED", "G_COW_STR_OWNED"] { + let plan = nested_plan(name)?; + let Some(ghostscope_dwarf::ValueNestedPlan::Variant { fields }) = plan.nested.as_ref() + else { + anyhow::bail!("expected Cow variant plan for {name}: {plan:#?}") + }; + assert_eq!(fields.len(), 2, "{name} must plan both Cow variants"); + assert!( + fields.iter().all(|field| { + field.value.presentation == ghostscope_dwarf::ValuePresentation::Utf8String + }), + "{name} must recurse into both Cow string payloads: {plan:#?}" + ); + } + let many_variants = nested_plan("G_MANY_STRING_VARIANT")?; let Some(ghostscope_dwarf::ValueNestedPlan::Variant { fields }) = many_variants.nested.as_ref() else { @@ -1759,6 +1842,10 @@ trace do_stuff { print "RENUM_OPTION:{}:{}", G_OPTION_STRING, G_OPTION_STRING_NONE; print "RENUM_RESULT:{}:{}", G_RESULT_RECORD_OK, G_RESULT_RECORD_ERR; print "RENUM_MANY:{}", G_MANY_STRING_VARIANT; + print "RCOMPOSE_SEQUENCE:{}:{}", G_VEC_DEQUE_STRING, G_SLICE_STRING; + print "RCOMPOSE_OPTION:{}", G_OPTION_VEC_STRING; + print "RCOMPOSE_RESULT:{}", G_RESULT_RECORDS_OK; + print "RCOMPOSE_COW:{}:{}", G_COW_STR_BORROWED, G_COW_STR_OWNED; } trace observe_nested_owners { print "RNESTED_OWNER:{}:{}", rc, arc; @@ -1861,6 +1948,40 @@ trace observe_nested_owners { }), "Expected shared enum payload budget output: {stdout}" ); + assert!( + stdout.lines().any(|line| { + line.contains("RCOMPOSE_SEQUENCE:") + && line.contains(r#"["deque alpha", "deque beta", "deque gamma"]"#) + && line.contains(r#"["slice alpha", "slice beta"]"#) + }), + "Expected nested sequence composition output: {stdout}" + ); + assert!( + stdout.lines().any(|line| { + line.contains("RCOMPOSE_OPTION:") + && line.contains(r#"::Some(["option alpha", "option beta"])"#) + }), + "Expected Option> semantic output: {stdout}" + ); + assert!( + stdout.lines().any(|line| { + line.contains("RCOMPOSE_RESULT:") + && line.contains("::Ok([") + && line.contains(r#"title: "result one""#) + && line.contains("count: 37") + && line.contains(r#"title: "result two""#) + && line.contains("count: 41") + }), + "Expected Result, String> semantic output: {stdout}" + ); + assert!( + stdout.lines().any(|line| { + line.contains("RCOMPOSE_COW:") + && line.contains(r#"::Borrowed("borrowed cow")"#) + && line.contains(r#"::Owned("owned cow")"#) + }), + "Expected Cow semantic output: {stdout}" + ); assert!( stdout.lines().any(|line| { line.contains("RNESTED_OWNER:Rc(strong=1, weak=0)")