Public dry run dryrun-f1e9b7b784b4

Source commit: f1e9b7b784b4a4dd28799719681535e2a4ef44c9
This commit is contained in:
Michel Paulissen 2026-07-03 18:54:07 +02:00
parent 20c72e6066
commit 8c0c336ae9
5 changed files with 1829 additions and 20 deletions

File diff suppressed because it is too large Load diff

View file

@ -305,7 +305,7 @@ fn launch_threads(entry: &str) -> BTreeMap<i64, VirtualThread> {
thread(MAIN_THREAD, "main", &format!("{entry} virtual process"), 12),
thread(LINUX_THREAD, "compile-linux", "compile linux", 42),
thread(WINDOWS_THREAD, "compile-windows", "compile windows", 52),
thread(PACKAGE_THREAD, "package", "package artifacts", 64),
thread(PACKAGE_THREAD, "package-release", "package artifacts", 64),
]
.into_iter()
.map(|thread| (thread.id, thread))
@ -1428,6 +1428,7 @@ fn parse_let_binding_name(line: &str) -> Option<String> {
#[derive(Clone, Debug, PartialEq, Eq)]
enum SourceLocalRuntimeValue {
SourceSnapshot(String),
TaskHandle {
task: TaskId,
thread_id: i64,
@ -1441,6 +1442,9 @@ enum SourceLocalRuntimeValue {
impl SourceLocalRuntimeValue {
fn display(&self, _state: &AdapterState) -> String {
match self {
SourceLocalRuntimeValue::SourceSnapshot(digest) => {
format!("SourceSnapshot {{ digest = \"{digest}\" }}")
}
SourceLocalRuntimeValue::TaskHandle {
task,
thread_id,
@ -1462,7 +1466,18 @@ fn infer_disasmer_source_local_value(
statement: &str,
runtime_values: &BTreeMap<String, SourceLocalRuntimeValue>,
) -> Option<SourceLocalRuntimeValue> {
if let Some(task_function) = extract_call_argument(statement, "disasmer::spawn::task(") {
if statement.contains("prepare_source_snapshot()") {
return Some(SourceLocalRuntimeValue::SourceSnapshot(
source_snapshot_digest_from_project(state).unwrap_or_else(|| {
format!(
"source://local-checkout/{}",
project_snapshot_suffix(&state.project)
)
}),
));
}
if let Some(task_function) = extract_spawn_task_function(statement) {
let task_name = extract_string_argument(statement, ".name(");
let spawned_thread =
thread_for_spawn_statement(state, &task_function, task_name.as_deref())?;
@ -1498,12 +1513,83 @@ fn infer_disasmer_source_local_value(
None
}
fn extract_call_argument(statement: &str, marker: &str) -> Option<String> {
fn extract_spawn_task_function(statement: &str) -> Option<String> {
if let Some(args) = extract_call_arguments(statement, "disasmer::spawn::task(") {
return args.first().cloned();
}
let args = extract_call_arguments(statement, "disasmer::spawn::task_with_arg(")?;
args.get(1).cloned()
}
fn source_snapshot_digest_from_project(state: &AdapterState) -> Option<String> {
let source =
fs::read_to_string(resolve_source_path(&state.project, &state.source_path)).ok()?;
let digest_marker = source.find("digest:")?;
let after_digest = &source[digest_marker..];
let first_quote = after_digest.find('"')? + 1;
let rest = &after_digest[first_quote..];
let end_quote = rest.find('"')?;
Some(rest[..end_quote].to_owned())
}
fn extract_call_arguments(statement: &str, marker: &str) -> Option<Vec<String>> {
let start = statement.find(marker)? + marker.len();
let rest = &statement[start..];
let end = rest.find(')')?;
let value = rest[..end].trim();
(!value.is_empty()).then_some(value.to_owned())
let mut args = Vec::new();
let mut current = String::new();
let mut depth = 0_i32;
let mut in_string = false;
let mut escaped = false;
for ch in rest.chars() {
if in_string {
current.push(ch);
if escaped {
escaped = false;
} else if ch == '\\' {
escaped = true;
} else if ch == '"' {
in_string = false;
}
continue;
}
match ch {
'"' => {
in_string = true;
current.push(ch);
}
'(' | '[' | '{' => {
depth += 1;
current.push(ch);
}
')' => {
if depth == 0 {
let value = current.trim();
if !value.is_empty() {
args.push(value.to_owned());
}
return (!args.is_empty()).then_some(args);
}
depth -= 1;
current.push(ch);
}
']' | '}' => {
depth -= 1;
current.push(ch);
}
',' if depth == 0 => {
let value = current.trim();
if !value.is_empty() {
args.push(value.to_owned());
}
current.clear();
}
_ => current.push(ch),
}
}
None
}
fn extract_string_argument(statement: &str, marker: &str) -> Option<String> {
@ -1518,9 +1604,15 @@ fn extract_env_name(statement: &str) -> Option<String> {
if statement.contains("windows_env") || statement.contains("env!(\"windows\")") {
return Some("windows".to_owned());
}
if statement.contains("linux_command_env") || statement.contains("env!(\"linux-command\")") {
return Some("linux-command".to_owned());
}
if statement.contains("linux_env") || statement.contains("env!(\"linux\")") {
return Some("linux".to_owned());
}
if statement.contains("coordinator_env") || statement.contains("env!(\"coordinator\")") {
return Some("coordinator".to_owned());
}
None
}
@ -1552,25 +1644,27 @@ fn thread_for_spawn_statement<'a>(
}
}
let normalized = task_function.replace('_', "-");
state
.threads
.values()
.find(|thread| thread.task.as_str() == normalized)
state.threads.values().find(|thread| {
thread.task.as_str() == normalized
|| (thread.id == PACKAGE_THREAD && normalized == "package-release")
})
}
fn task_environment(thread: &VirtualThread) -> &'static str {
match thread.id {
WINDOWS_THREAD => "windows",
MAIN_THREAD => "coordinator",
PACKAGE_THREAD => "coordinator",
_ => "linux",
}
}
fn task_arguments_value(thread: &VirtualThread) -> String {
if thread.task.as_str() == "task_add_one" {
"[input: i32]".to_owned()
} else {
"[]".to_owned()
match thread.task.as_str() {
"compile-linux" => "[source: SourceSnapshot]".to_owned(),
"package-release" => "[inputs: Vec<Artifact>]".to_owned(),
"task_add_one" => "[input: i32]".to_owned(),
_ => "[]".to_owned(),
}
}
@ -2224,6 +2318,7 @@ fn read_message<R: BufRead>(reader: &mut R) -> Result<Option<Value>> {
#[cfg(test)]
mod tests {
use std::fs;
use std::io::Cursor;
use serde_json::json;
@ -2349,7 +2444,7 @@ mod tests {
let error = freeze_all(&mut state, LINUX_THREAD, None).unwrap_err();
assert_eq!(error.thread_id, PACKAGE_THREAD);
assert_eq!(error.task, TaskId::from("package"));
assert_eq!(error.task, TaskId::from("package-release"));
assert!(error.message().contains("could not freeze"));
assert_eq!(state.epoch, 0);
assert!(state
@ -2541,6 +2636,119 @@ mod tests {
}));
}
#[test]
fn source_locals_infer_task_with_arg_values_from_runtime_state() {
let project =
std::env::temp_dir().join(format!("disasmer-dap-task-with-arg-{}", std::process::id()));
let src = project.join("src");
fs::create_dir_all(&src).unwrap();
fs::write(
src.join("main.rs"),
r#"use disasmer::{Artifact, EnvRef, SourceSnapshot};
async fn build_release() -> Result<(), disasmer::TaskArgError> {
let source = prepare_source_snapshot();
let compile = disasmer::spawn::task_with_arg(source.clone(), compile_linux)
.name("compile linux")
.env(linux_command_env())
.start()
.await?;
let compile_thread = compile.virtual_thread_id();
let linux_artifact = compile.join().await;
let package = disasmer::spawn::task_with_arg(vec![linux_artifact.clone()], package_release)
.name("package release")
.env(coordinator_env())
.start()
.await?;
let package_thread = package.virtual_thread_id();
let release_artifact = package.join().await;
Ok(())
}
fn prepare_source_snapshot() -> SourceSnapshot {
SourceSnapshot {
digest: "source://coordinator/quick-test-checkout".to_owned(),
}
}
fn linux_command_env() -> EnvRef {
disasmer::env!("linux-command")
}
fn coordinator_env() -> EnvRef {
disasmer::env!("coordinator")
}
fn compile_linux(source: SourceSnapshot) -> Artifact {
Artifact { id: source.digest }
}
fn package_release(inputs: Vec<Artifact>) -> Artifact {
inputs.into_iter().next().unwrap()
}
"#,
)
.unwrap();
let mut state = AdapterState::default();
state.project = project.to_string_lossy().into_owned();
state.source_path = "src/main.rs".to_owned();
state.threads.get_mut(&MAIN_THREAD).unwrap().line = 23;
let thread = state.threads[&MAIN_THREAD].clone();
let locals = variables_response(&state, thread.locals_ref);
let locals = locals["variables"].as_array().unwrap();
assert!(locals.iter().any(|variable| {
variable["name"] == "source"
&& variable["value"]
.as_str()
.is_some_and(|value| value.contains("source://coordinator/quick-test-checkout"))
}));
assert!(locals.iter().any(|variable| {
variable["name"] == "compile"
&& variable["value"].as_str().is_some_and(|value| {
value.contains("TaskHandle")
&& value.contains("compile-linux")
&& value.contains("virtual_thread_id = 2")
&& value.contains("linux-command")
})
}));
assert!(locals
.iter()
.any(|variable| variable["name"] == "compile_thread" && variable["value"] == "2"));
assert!(locals.iter().any(|variable| {
variable["name"] == "linux_artifact"
&& variable["value"]
.as_str()
.is_some_and(|value| value.contains("Artifact"))
}));
assert!(locals.iter().any(|variable| {
variable["name"] == "package"
&& variable["value"].as_str().is_some_and(|value| {
value.contains("TaskHandle")
&& value.contains("package-release")
&& value.contains("virtual_thread_id = 4")
&& value.contains("coordinator")
})
}));
assert!(locals
.iter()
.any(|variable| variable["name"] == "package_thread" && variable["value"] == "4"));
assert!(locals.iter().any(|variable| {
variable["name"] == "release_artifact"
&& variable["value"]
.as_str()
.is_some_and(|value| value.contains("Artifact"))
}));
let _ = fs::remove_dir_all(project);
}
#[test]
fn wasm_frame_locals_expose_wasmtime_runtime_values() {
let mut state = AdapterState::default();