use schemars::JsonSchema;
use serde::Deserialize;
fn default_host() -> String {
"localhost".to_string()
}
const fn default_port() -> u16 {
5005
}
const fn default_max_frames() -> usize {
20
}
const fn default_true() -> bool {
true
}
const fn default_max_result_length() -> usize {
2000
}
const fn default_limit() -> usize {
40
}
const fn default_class_limit() -> usize {
100
}
const fn default_source_context() -> usize {
20
}
const fn default_source_max_lines() -> usize {
400
}
const fn default_trace_limit() -> usize {
50
}
const fn default_event_limit() -> usize {
1
}
const fn default_max_depth() -> usize {
2
}
const fn default_max_children() -> usize {
16
}
const fn default_trace_frames() -> usize {
crate::handlers::DEFAULT_TRACE_FRAMES
}
const fn default_trace_max_length() -> Option<usize> {
None
}
const fn default_dump_frames() -> usize {
8
}
const fn default_max_suspend_ms() -> u64 {
crate::handlers::DEFAULT_MAX_SUSPEND_MS
}
pub fn parse_thread_id(s: Option<&str>) -> Option<u64> {
s.and_then(|s| u64::from_str_radix(s.trim_start_matches("0x"), 16).ok())
}
pub fn parse<T: serde::de::DeserializeOwned>(args: &serde_json::Value) -> Result<T, String> {
let mut v = if args.is_null() { serde_json::Value::Object(serde_json::Map::new()) } else { args.clone() };
if let Some(object) = v.as_object_mut() {
object.remove(SESSION_ID_ARG);
}
serde_json::from_value(v).map_err(|e| format!("Invalid arguments: {e}"))
}
pub const SESSION_ID_ARG: &str = "session_id";
#[derive(Debug, Deserialize, JsonSchema)]
#[serde(deny_unknown_fields)]
pub struct NoArgs {}
#[derive(Debug, Deserialize, JsonSchema)]
#[serde(deny_unknown_fields)]
pub struct LaunchArgs {
#[serde(default)]
pub main_class: Option<String>,
#[serde(default)]
pub jar: Option<String>,
#[serde(default)]
pub classpath: Option<Vec<String>>,
#[serde(default)]
pub jvm_args: Option<Vec<String>>,
#[serde(default)]
pub args: Option<Vec<String>>,
#[serde(default)]
pub working_dir: Option<String>,
#[serde(default = "default_true")]
pub suspend: bool,
#[serde(default)]
pub port: u16,
#[serde(default)]
pub java_home: Option<String>,
#[serde(default)]
pub detach_on_disconnect: bool,
#[serde(default)]
pub read_only: bool,
#[serde(default)]
pub source_roots: Option<Vec<String>>,
#[serde(default)]
pub class_roots: Option<Vec<String>>,
#[serde(default)]
pub trace_expr: Option<TraceExprs>,
}
#[derive(Debug, Clone, Deserialize, JsonSchema)]
#[serde(untagged)]
pub enum ClassPatterns {
One(String),
Many(Vec<String>),
}
#[derive(Debug, Clone, Deserialize, JsonSchema)]
#[serde(untagged)]
pub enum TraceExprs {
One(String),
Many(Vec<String>),
}
impl TraceExprs {
pub fn into_vec(self) -> Vec<String> {
match self {
Self::One(s) => vec![s],
Self::Many(v) => v,
}
.into_iter()
.map(|s| s.trim().to_string())
.filter(|s| !s.is_empty())
.collect()
}
}
pub fn trace_exprs(arg: Option<TraceExprs>) -> Vec<String> {
arg.map(TraceExprs::into_vec).unwrap_or_default()
}
impl ClassPatterns {
pub fn list(&self) -> Vec<String> {
let raw: &[String] = match self {
Self::One(s) => std::slice::from_ref(s),
Self::Many(v) => v.as_slice(),
};
let mut out: Vec<String> = Vec::with_capacity(raw.len());
for p in raw {
let t = p.trim();
if !t.is_empty() && !out.iter().any(|q| q == t) {
out.push(t.to_string());
}
}
out
}
}
pub const DEFAULT_MAX_CLASSES: usize = 20;
pub const MAX_CLASSES_CEILING: usize = 200;
const fn default_max_classes() -> usize {
DEFAULT_MAX_CLASSES
}
#[derive(Debug, Deserialize, JsonSchema)]
#[serde(deny_unknown_fields)]
pub struct AttachArgs {
#[serde(default = "default_host")]
pub host: String,
#[serde(default = "default_port")]
pub port: u16,
#[serde(default)]
pub read_only: bool,
#[serde(default)]
pub source_roots: Option<Vec<String>>,
#[serde(default)]
pub class_roots: Option<Vec<String>>,
#[serde(default)]
pub trace_expr: Option<TraceExprs>,
}
#[derive(Debug, Deserialize, JsonSchema)]
#[serde(deny_unknown_fields)]
pub struct SetBreakpointArgs {
pub class_pattern: ClassPatterns,
#[serde(default)]
pub line: Option<i32>,
#[serde(default)]
pub method: Option<String>,
#[serde(default)]
pub hit_count: Option<i32>,
#[serde(default)]
pub instance_id: Option<String>,
#[serde(default)]
pub thread_id: Option<String>,
#[serde(default)]
pub condition: Option<String>,
#[serde(default)]
pub trace: bool,
#[serde(default)]
pub trace_expr: Option<TraceExprs>,
#[serde(default)]
pub trace_max_hits: Option<u32>,
#[serde(default = "default_trace_frames")]
pub trace_frames: usize,
#[serde(default = "default_trace_max_length")]
pub trace_max_length: Option<usize>,
#[serde(default = "default_max_classes")]
pub max_classes: usize,
}
#[derive(Debug, Deserialize, JsonSchema)]
#[serde(deny_unknown_fields)]
pub struct ToggleBreakpointArgs {
pub breakpoint_id: String,
#[serde(default)]
pub enabled: Option<bool>,
}
#[derive(Debug, Deserialize, JsonSchema)]
#[serde(deny_unknown_fields)]
pub struct GetTracesArgs {
#[serde(default = "default_trace_limit")]
pub limit: usize,
#[serde(default)]
pub clear: bool,
#[serde(default)]
pub bp_id: Option<String>,
#[serde(default)]
pub class_filter: Option<String>,
#[serde(default)]
pub since: Option<u64>,
}
#[derive(Debug, Deserialize, JsonSchema)]
#[serde(deny_unknown_fields)]
pub struct GetLastEventArgs {
#[serde(default = "default_event_limit")]
pub limit: usize,
#[serde(default)]
pub drain: bool,
}
#[derive(Debug, Deserialize, JsonSchema)]
#[serde(deny_unknown_fields)]
pub struct ClearBreakpointArgs {
pub breakpoint_id: String,
}
#[derive(Debug, Deserialize, JsonSchema)]
#[serde(deny_unknown_fields)]
pub struct ListStopPointsArgs {
#[serde(default)]
pub export: bool,
}
#[derive(Debug, Deserialize, JsonSchema)]
#[serde(deny_unknown_fields)]
pub struct ArmStopPointsArgs {
pub set: String,
}
#[derive(Debug, Deserialize, JsonSchema)]
#[serde(deny_unknown_fields)]
pub struct StepArgs {
#[serde(default)]
pub thread_id: Option<String>,
#[serde(default)]
pub exclude_classes: Option<Vec<String>>,
#[serde(default)]
pub only_classes: Option<Vec<String>>,
}
#[derive(Debug, Deserialize, JsonSchema)]
#[serde(deny_unknown_fields)]
pub struct SuspendThreadArgs {
pub thread_id: String,
}
#[derive(Debug, Deserialize, JsonSchema)]
#[serde(deny_unknown_fields)]
pub struct ResumeThreadArgs {
#[serde(default)]
pub thread_id: Option<String>,
}
#[derive(Debug, Deserialize, JsonSchema)]
#[serde(deny_unknown_fields)]
pub struct GetStackArgs {
#[serde(default)]
pub thread_id: Option<String>,
#[serde(default = "default_max_frames")]
pub max_frames: usize,
#[serde(default = "default_true")]
pub include_variables: bool,
#[serde(default)]
pub package_filter: Option<String>,
#[serde(default)]
pub expand_objects: bool,
#[serde(default = "default_max_depth")]
pub max_depth: usize,
#[serde(default = "default_max_children")]
pub max_children: usize,
}
#[derive(Debug, Deserialize, JsonSchema)]
#[serde(deny_unknown_fields)]
pub struct EvaluateArgs {
pub expression: String,
#[serde(default)]
pub thread_id: Option<String>,
#[serde(default)]
pub frame_index: usize,
#[serde(default = "default_max_result_length")]
pub max_result_length: usize,
#[serde(default)]
pub expand_objects: bool,
#[serde(default = "default_max_depth")]
pub max_depth: usize,
#[serde(default = "default_max_children")]
pub max_children: usize,
#[serde(default)]
pub force_initialize: bool,
}
#[derive(Debug, Deserialize, JsonSchema)]
#[serde(deny_unknown_fields)]
pub struct EvaluateChainArgs {
pub expression: String,
#[serde(default)]
pub thread_id: Option<String>,
#[serde(default)]
pub frame_index: usize,
#[serde(default = "default_max_result_length")]
pub max_result_length: usize,
#[serde(default)]
pub force_initialize: bool,
}
#[derive(Debug, Deserialize, JsonSchema)]
#[serde(deny_unknown_fields)]
pub struct ListThreadsArgs {
#[serde(default)]
pub name_filter: Option<String>,
#[serde(default)]
pub only_suspended: bool,
#[serde(default = "default_limit")]
pub limit: usize,
}
#[derive(Debug, Deserialize, JsonSchema)]
#[serde(deny_unknown_fields)]
pub struct ListClassesArgs {
#[serde(default)]
pub filter: Option<String>,
#[serde(default = "default_class_limit")]
pub limit: usize,
#[serde(default)]
pub include_arrays: bool,
}
#[derive(Debug, Deserialize, JsonSchema)]
#[serde(deny_unknown_fields)]
pub struct ListMethodsArgs {
pub class_name: String,
#[serde(default)]
pub name_filter: Option<String>,
#[serde(default)]
pub inherited: bool,
#[serde(default = "default_limit")]
pub limit: usize,
}
#[derive(Debug, Deserialize, JsonSchema)]
#[serde(deny_unknown_fields)]
pub struct ListFieldsArgs {
pub class_name: String,
#[serde(default)]
pub name_filter: Option<String>,
#[serde(default)]
pub inherited: bool,
#[serde(default = "default_limit")]
pub limit: usize,
}
const fn default_max_instances() -> i32 {
10
}
#[derive(Debug, Deserialize, JsonSchema)]
#[serde(deny_unknown_fields)]
pub struct ListInstancesArgs {
pub class_names: Vec<String>,
#[serde(default = "default_max_instances")]
pub max_instances: i32,
#[serde(default)]
pub counts_only: bool,
}
const fn default_max_query_rows() -> usize {
20
}
#[derive(Debug, Deserialize, JsonSchema)]
#[serde(deny_unknown_fields)]
pub struct RunNamedQueryArgs {
pub query_name: String,
#[serde(default)]
pub parameters: Option<std::collections::BTreeMap<String, serde_json::Value>>,
#[serde(default)]
pub positional_parameters: Option<Vec<serde_json::Value>>,
#[serde(default)]
pub parameter_expressions: Option<std::collections::BTreeMap<String, String>>,
#[serde(default)]
pub entity_manager: Option<String>,
#[serde(default = "default_max_query_rows")]
pub max_rows: usize,
#[serde(default)]
pub max_fetch: Option<usize>,
#[serde(default = "default_max_result_length")]
pub max_result_length: usize,
#[serde(default)]
pub allow_flush: bool,
#[serde(default)]
pub thread_id: Option<String>,
#[serde(default)]
pub frame_index: usize,
}
#[derive(Debug, Deserialize, JsonSchema)]
#[serde(deny_unknown_fields)]
pub struct SourceArgs {
pub class_name: String,
#[serde(default)]
pub line: Option<i32>,
#[serde(default = "default_source_context")]
pub context: usize,
#[serde(default)]
pub whole_file: bool,
#[serde(default = "default_source_max_lines")]
pub max_lines: usize,
#[serde(default)]
pub source_roots: Option<Vec<String>>,
#[serde(default)]
pub class_roots: Option<Vec<String>>,
}
#[allow(clippy::struct_excessive_bools)]
#[derive(Debug, Deserialize, JsonSchema)]
#[serde(deny_unknown_fields)]
pub struct ThreadDumpArgs {
#[serde(default)]
pub name_filter: Option<String>,
#[serde(default)]
pub only_suspended: bool,
#[serde(default = "default_limit")]
pub limit: usize,
#[serde(default = "default_dump_frames")]
pub max_frames: usize,
#[serde(default)]
pub package_filter: Option<String>,
#[serde(default = "default_true")]
pub monitors: bool,
#[serde(default)]
pub monitors_only: bool,
#[serde(default = "default_max_suspend_ms")]
pub max_suspend_ms: u64,
#[serde(default)]
pub suspend: bool,
}
#[derive(Debug, Deserialize, JsonSchema)]
#[serde(deny_unknown_fields)]
pub struct SetValueArgs {
#[serde(alias = "name")]
pub target: String,
pub value: String,
#[serde(default)]
pub thread_id: Option<String>,
#[serde(default)]
pub frame_index: usize,
}
#[derive(Debug, Deserialize, JsonSchema)]
#[serde(deny_unknown_fields)]
pub struct SetExceptionBreakpointArgs {
#[serde(default)]
pub class_pattern: Option<ClassPatterns>,
#[serde(default = "default_true")]
pub caught: bool,
#[serde(default = "default_true")]
pub uncaught: bool,
#[serde(default)]
pub hit_count: Option<i32>,
#[serde(default)]
pub instance_id: Option<String>,
#[serde(default)]
pub condition: Option<String>,
#[serde(default)]
pub trace: bool,
#[serde(default)]
pub trace_expr: Option<TraceExprs>,
#[serde(default)]
pub trace_max_hits: Option<u32>,
#[serde(default = "default_trace_frames")]
pub trace_frames: usize,
#[serde(default = "default_trace_max_length")]
pub trace_max_length: Option<usize>,
#[serde(default)]
pub thread_id: Option<String>,
#[serde(default = "default_max_classes")]
pub max_classes: usize,
}
#[derive(Debug, Deserialize, JsonSchema)]
#[serde(deny_unknown_fields)]
pub struct SetWatchpointArgs {
pub class_name: ClassPatterns,
pub field_name: String,
#[serde(default = "default_true")]
pub modify: bool,
#[serde(default)]
pub access: bool,
#[serde(default)]
pub hit_count: Option<i32>,
#[serde(default)]
pub instance_id: Option<String>,
#[serde(default)]
pub condition: Option<String>,
#[serde(default)]
pub trace: bool,
#[serde(default)]
pub trace_expr: Option<TraceExprs>,
#[serde(default)]
pub trace_max_hits: Option<u32>,
#[serde(default = "default_trace_frames")]
pub trace_frames: usize,
#[serde(default = "default_trace_max_length")]
pub trace_max_length: Option<usize>,
#[serde(default)]
pub thread_id: Option<String>,
#[serde(default = "default_max_classes")]
pub max_classes: usize,
}
#[derive(Debug, Deserialize, JsonSchema)]
#[serde(deny_unknown_fields)]
pub struct SetMethodBreakpointArgs {
pub class_pattern: ClassPatterns,
#[serde(default)]
pub method: Option<String>,
#[serde(default)]
pub hit_count: Option<i32>,
#[serde(default)]
pub instance_id: Option<String>,
#[serde(default)]
pub exclude_classes: Option<Vec<String>>,
#[serde(default)]
pub condition: Option<String>,
#[serde(default = "default_true")]
pub trace: bool,
#[serde(default)]
pub trace_expr: Option<TraceExprs>,
#[serde(default)]
pub trace_max_hits: Option<u32>,
#[serde(default = "default_trace_frames")]
pub trace_frames: usize,
#[serde(default = "default_trace_max_length")]
pub trace_max_length: Option<usize>,
#[serde(default)]
pub thread_id: Option<String>,
}
#[derive(Debug, Deserialize, JsonSchema)]
#[serde(deny_unknown_fields)]
pub struct SetMonitorStopArgs {
#[serde(default)]
pub kinds: Option<Vec<String>>,
#[serde(default)]
pub thread_id: Option<String>,
#[serde(default)]
pub monitor_class: Option<String>,
#[serde(default)]
pub instance_id: Option<String>,
#[serde(default)]
pub min_duration_ms: Option<u64>,
#[serde(default)]
pub hit_count: Option<i32>,
#[serde(default = "default_true")]
pub trace: bool,
#[serde(default)]
pub trace_expr: Option<TraceExprs>,
#[serde(default)]
pub trace_max_hits: Option<u32>,
#[serde(default = "default_trace_frames")]
pub trace_frames: usize,
#[serde(default = "default_trace_max_length")]
pub trace_max_length: Option<usize>,
}
#[derive(Debug, Deserialize, JsonSchema)]
#[serde(deny_unknown_fields)]
pub struct ForceReturnArgs {
#[serde(default)]
pub value: Option<String>,
#[serde(default)]
pub thread_id: Option<String>,
}
#[derive(Debug, Deserialize, JsonSchema)]
#[serde(deny_unknown_fields)]
pub struct ReloadClassArgs {
pub class_name: String,
#[serde(default)]
pub class_file: Option<String>,
#[serde(default)]
pub class_roots: Option<Vec<String>>,
#[serde(default)]
pub dry_run: bool,
#[serde(default)]
pub thread_id: Option<String>,
}
#[derive(Debug, Deserialize, JsonSchema)]
#[serde(deny_unknown_fields)]
pub struct CheckStaleArgs {
pub class_name: String,
#[serde(default)]
pub class_file: Option<String>,
#[serde(default)]
pub class_roots: Option<Vec<String>>,
#[serde(default = "default_limit")]
pub limit: usize,
#[serde(default)]
pub bytecode: bool,
}
#[derive(Debug, Deserialize, JsonSchema)]
#[serde(deny_unknown_fields)]
pub struct PopFrameArgs {
#[serde(default)]
pub frame: usize,
#[serde(default)]
pub thread_id: Option<String>,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn all_schemas_generate() {
let schemas = [
serde_json::to_value(schemars::schema_for!(AttachArgs)).unwrap(),
serde_json::to_value(schemars::schema_for!(SetBreakpointArgs)).unwrap(),
serde_json::to_value(schemars::schema_for!(ClearBreakpointArgs)).unwrap(),
serde_json::to_value(schemars::schema_for!(StepArgs)).unwrap(),
serde_json::to_value(schemars::schema_for!(GetStackArgs)).unwrap(),
serde_json::to_value(schemars::schema_for!(EvaluateArgs)).unwrap(),
serde_json::to_value(schemars::schema_for!(ListThreadsArgs)).unwrap(),
serde_json::to_value(schemars::schema_for!(SetValueArgs)).unwrap(),
serde_json::to_value(schemars::schema_for!(SetExceptionBreakpointArgs)).unwrap(),
serde_json::to_value(schemars::schema_for!(SetWatchpointArgs)).unwrap(),
serde_json::to_value(schemars::schema_for!(ForceReturnArgs)).unwrap(),
serde_json::to_value(schemars::schema_for!(GetTracesArgs)).unwrap(),
serde_json::to_value(schemars::schema_for!(GetLastEventArgs)).unwrap(),
serde_json::to_value(schemars::schema_for!(ToggleBreakpointArgs)).unwrap(),
serde_json::to_value(schemars::schema_for!(ThreadDumpArgs)).unwrap(),
serde_json::to_value(schemars::schema_for!(SetMethodBreakpointArgs)).unwrap(),
serde_json::to_value(schemars::schema_for!(ReloadClassArgs)).unwrap(),
serde_json::to_value(schemars::schema_for!(PopFrameArgs)).unwrap(),
serde_json::to_value(schemars::schema_for!(LaunchArgs)).unwrap(),
];
for s in schemas {
assert_eq!(s.get("type").and_then(|t| t.as_str()), Some("object"));
}
}
#[test]
fn launch_defaults_to_suspended_and_to_owning_the_process() {
let a: LaunchArgs = serde_json::from_value(serde_json::json!({"main_class": "M"})).unwrap();
assert!(a.suspend, "suspend=y is the point of launching rather than attaching");
assert!(!a.detach_on_disconnect, "the JVM we started dies with the session unless asked otherwise");
assert_eq!(a.port, 0, "0 means pick a free port");
assert!(!a.read_only);
}
#[test]
fn the_two_arming_tools_that_default_to_tracing_are_the_two_that_pick_their_own_site() {
let mexit: SetMethodBreakpointArgs =
serde_json::from_value(serde_json::json!({"class_pattern": "com.example.Order"})).unwrap();
assert!(
mexit.trace,
"set_method_exit_stop traces by default: a suspending exit on a hot method freezes a shared JVM"
);
let mon: SetMonitorStopArgs = serde_json::from_value(serde_json::json!({})).unwrap();
assert!(
mon.trace,
"set_monitor_stop traces by default: contention is not a site the caller chose, so a suspending \
arm freezes the VM at a location nobody picked"
);
}
#[test]
fn the_three_arming_tools_whose_site_the_caller_names_default_to_suspending() {
let line: SetBreakpointArgs =
serde_json::from_value(serde_json::json!({"class_pattern": "com.example.Order", "line": 1}))
.unwrap();
assert!(!line.trace, "set_line_stop suspends by default: the caller named the line");
let exc: SetExceptionBreakpointArgs = serde_json::from_value(serde_json::json!({})).unwrap();
assert!(!exc.trace, "set_exception_stop suspends by default: the caller named the exception");
let field: SetWatchpointArgs = serde_json::from_value(
serde_json::json!({"class_name": "com.example.Order", "field_name": "id"}),
)
.unwrap();
assert!(!field.trace, "set_field_stop suspends by default: the caller named the field");
}
#[test]
fn a_class_argument_deserialises_from_a_string_or_a_list() {
let one: SetBreakpointArgs =
serde_json::from_value(serde_json::json!({"class_pattern": "com.example.Order", "line": 1}))
.unwrap();
assert_eq!(one.class_pattern.list(), vec!["com.example.Order".to_string()]);
let many: SetBreakpointArgs = serde_json::from_value(serde_json::json!({
"class_pattern": ["com.example.Order", "com.example.*Repo"],
"method": "handle"
}))
.unwrap();
assert_eq!(
many.class_pattern.list(),
vec!["com.example.Order".to_string(), "com.example.*Repo".to_string()]
);
let exc: SetExceptionBreakpointArgs =
serde_json::from_value(serde_json::json!({"class_pattern": ["A", "B"]})).unwrap();
assert_eq!(exc.class_pattern.unwrap().list().len(), 2);
let watch: SetWatchpointArgs =
serde_json::from_value(serde_json::json!({"class_name": ["A", "B"], "field_name": "f"})).unwrap();
assert_eq!(watch.class_name.list().len(), 2);
let mexit: SetMethodBreakpointArgs =
serde_json::from_value(serde_json::json!({"class_pattern": ["*.A", "*.B"]})).unwrap();
assert_eq!(mexit.class_pattern.list().len(), 2);
}
#[test]
fn a_class_list_is_trimmed_and_deduped_in_order() {
let a: SetBreakpointArgs = serde_json::from_value(serde_json::json!({
"class_pattern": [" com.example.B ", "com.example.A", "", "com.example.B", " "],
"method": "handle"
}))
.unwrap();
assert_eq!(
a.class_pattern.list(),
vec!["com.example.B".to_string(), "com.example.A".to_string()],
"order is the caller's, duplicates and blanks are gone"
);
}
#[test]
fn max_classes_defaults_to_twenty_and_is_documented() {
let bp: SetBreakpointArgs =
serde_json::from_value(serde_json::json!({"class_pattern": "C", "line": 1})).unwrap();
assert_eq!(bp.max_classes, DEFAULT_MAX_CLASSES);
assert_eq!(DEFAULT_MAX_CLASSES, 20);
assert_eq!(MAX_CLASSES_CEILING, 200);
let asked: SetBreakpointArgs = serde_json::from_value(
serde_json::json!({"class_pattern": "C*", "method": "m", "max_classes": 5}),
)
.unwrap();
assert_eq!(asked.max_classes, 5);
}
#[test]
fn expansion_defaults_match_the_documented_values() {
let ev: EvaluateArgs = serde_json::from_value(serde_json::json!({"expression": "x"})).unwrap();
assert!(!ev.expand_objects, "expansion must be opt-in: it invokes methods in the debuggee");
assert_eq!(ev.max_depth, 2);
assert_eq!(ev.max_children, 16);
let st: GetStackArgs = serde_json::from_value(serde_json::json!({})).unwrap();
assert!(!st.expand_objects);
assert_eq!(st.max_depth, 2);
assert_eq!(st.max_children, 16);
}
#[test]
fn trace_frames_defaults_are_shared_and_documented() {
let bp: SetBreakpointArgs =
serde_json::from_value(serde_json::json!({"class_pattern": "C", "line": 1})).unwrap();
let exc: SetExceptionBreakpointArgs = serde_json::from_value(serde_json::json!({})).unwrap();
let watch: SetWatchpointArgs =
serde_json::from_value(serde_json::json!({"class_name": "C", "field_name": "f"})).unwrap();
assert_eq!(bp.trace_frames, 3);
assert_eq!(exc.trace_frames, bp.trace_frames);
assert_eq!(watch.trace_frames, bp.trace_frames);
let off: SetBreakpointArgs =
serde_json::from_value(serde_json::json!({"class_pattern": "C", "line": 1, "trace_frames": 0}))
.unwrap();
assert_eq!(off.trace_frames, 0);
}
#[test]
fn trace_max_length_defaults_to_unset_on_all_four_arming_tools() {
let bp: SetBreakpointArgs =
serde_json::from_value(serde_json::json!({"class_pattern": "C", "line": 1})).unwrap();
let exc: SetExceptionBreakpointArgs = serde_json::from_value(serde_json::json!({})).unwrap();
let watch: SetWatchpointArgs =
serde_json::from_value(serde_json::json!({"class_name": "C", "field_name": "f"})).unwrap();
let mexit: SetMethodBreakpointArgs =
serde_json::from_value(serde_json::json!({"class_pattern": "C"})).unwrap();
assert_eq!(bp.trace_max_length, None, "unset must not be a number");
assert_eq!(exc.trace_max_length, bp.trace_max_length);
assert_eq!(watch.trace_max_length, bp.trace_max_length);
assert_eq!(mexit.trace_max_length, bp.trace_max_length);
let raised: SetBreakpointArgs = serde_json::from_value(
serde_json::json!({"class_pattern": "C", "line": 1, "trace_max_length": 3000}),
)
.unwrap();
assert_eq!(raised.trace_max_length, Some(3000));
let zero: SetBreakpointArgs = serde_json::from_value(
serde_json::json!({"class_pattern": "C", "line": 1, "trace_max_length": 0}),
)
.unwrap();
assert_eq!(zero.trace_max_length, Some(0));
}
#[test]
fn get_last_event_defaults_to_the_newest_event_only_and_keeps_it() {
let a: GetLastEventArgs = serde_json::from_value(serde_json::json!({})).unwrap();
assert_eq!(a.limit, 1);
assert!(!a.drain);
}
#[test]
fn set_value_accepts_target_and_legacy_name() {
let by_target: SetValueArgs = serde_json::from_value(
serde_json::json!({"target": "ConfigDefaultUtils.dsInfra", "value": "DEV"}),
)
.unwrap();
assert_eq!(by_target.target, "ConfigDefaultUtils.dsInfra");
let by_name: SetValueArgs =
serde_json::from_value(serde_json::json!({"name": "counter", "value": "5"})).unwrap();
assert_eq!(by_name.target, "counter");
}
}