use super::{
has_unprefixed_function_call, innermost_namespaced_call, metered_unprefixed_call_arguments,
};
use crate::budget::{ExecutionBudget, Meter};
fn metered_calls<'a>(source: &'a str, name: &str) -> Vec<(usize, &'a str)> {
let mut meter = Meter::new(
ExecutionBudget {
source_bytes: usize::MAX,
external_documents: usize::MAX,
recursion_depth: usize::MAX,
xpath_evaluations: usize::MAX,
xpath_operations: usize::MAX,
extension_operations: usize::MAX,
pattern_evaluations: usize::MAX,
template_applications: usize::MAX,
sort_comparisons: usize::MAX,
key_entries: usize::MAX,
result_nodes: usize::MAX,
serialized_bytes: usize::MAX,
messages: usize::MAX,
owned_bytes: usize::MAX,
},
0,
)
.expect("test meter");
let (calls, reservation) = metered_unprefixed_call_arguments(source, name, &mut meter)
.expect("call scan fits the test budget");
meter.release_owned_bytes(reservation);
calls
}
#[test]
fn namespaced_call_discovery_is_iterative_at_extreme_depth() {
let source = format!("{}1{}", "x:f(".repeat(1_000), ")".repeat(1_000));
let call = innermost_namespaced_call(
&source,
&[("x".into(), "urn:test".into())],
|namespace, local| namespace == "urn:test" && local == "f",
)
.expect("nested call is discovered");
assert_eq!(&source[call.start..call.end], "x:f(1)");
}
#[test]
fn namespaced_call_discovery_scans_mixed_nesting_once() {
let source =
format!("plain({}x:target(')'))", "x:outer(plain(".repeat(1_000)) + &"))".repeat(1_000);
let call = innermost_namespaced_call(
&source,
&[("x".into(), "urn:test".into())],
|namespace, _| namespace == "urn:test",
)
.expect("innermost accepted call is discovered");
assert_eq!(&source[call.start..call.end], "x:target(')')");
}
#[test]
fn namespaced_call_discovery_accepts_complete_ncname_grammar() {
let prefix = "\u{200c}";
let source = format!("{prefix}:function()");
let call = innermost_namespaced_call(
&source,
&[(prefix.into(), "urn:test".into())],
|namespace, local| namespace == "urn:test" && local == "function",
)
.expect("valid Unicode-prefixed call is discovered");
assert_eq!(&source[call.start..call.end], source);
}
#[test]
fn unprefixed_call_discovery_excludes_qualified_names() {
assert!(metered_calls("x:key()", "key").is_empty());
assert_eq!(metered_calls("key()", "key").len(), 1);
}
#[test]
fn function_arguments_preserve_non_xpath_whitespace() {
let calls = metered_calls("key(\u{a0}'value')", "key");
assert_eq!(calls[0].1, "\u{a0}'value'");
}
#[test]
fn unprefixed_call_detection_ignores_literals_and_qualified_names() {
assert!(has_unprefixed_function_call("current ()/item", "current"));
assert!(!has_unprefixed_function_call("'current()'", "current"));
assert!(!has_unprefixed_function_call("x:current()", "current"));
assert!(!has_unprefixed_function_call("current(", "current"));
assert!(has_unprefixed_function_call(
"current(('current())'))",
"current"
));
}
#[test]
fn unprefixed_boolean_detection_does_not_retain_matches() {
let source = "ordinary(),".repeat(100_000) + "current()";
assert!(has_unprefixed_function_call(&source, "current"));
}
#[test]
fn unprefixed_call_discovery_is_linear_at_extreme_depth() {
let source = format!("{}1{}", "key(".repeat(1_000), ")".repeat(1_000));
let calls = metered_calls(&source, "key");
assert_eq!(calls.len(), 1_000);
assert_eq!(calls[0].0, 0);
let innermost = calls.last().expect("nested expression has calls");
assert_eq!(innermost.1, "1");
}
#[test]
fn key_call_scan_reserves_temporary_stack_before_growing() {
let mut budget = ExecutionBudget {
source_bytes: 0,
external_documents: 0,
recursion_depth: 0,
xpath_evaluations: 0,
xpath_operations: 0,
extension_operations: 0,
pattern_evaluations: 0,
template_applications: 0,
sort_comparisons: 0,
key_entries: 0,
result_nodes: 0,
serialized_bytes: 0,
messages: 0,
owned_bytes: 0,
};
let mut meter = Meter::new(budget, 0).expect("zero-size source");
assert!(metered_unprefixed_call_arguments("key('name', /)", "key", &mut meter).is_err());
budget.owned_bytes = 1024;
let mut meter = Meter::new(budget, 0).expect("meter");
let (calls, reservation) =
metered_unprefixed_call_arguments("key('name', /)", "key", &mut meter)
.expect("sufficient budget");
assert_eq!(calls, [(0, "'name', /")]);
meter.release_owned_bytes(reservation);
}