use super::{Directory, fixture}; use dever::compiler::{build, project::Project}; mod async_stream; mod channel; mod group; mod liveness; mod parallel; mod select; #[test] fn asynchronous_clauses_keep_nullable_payloads_axes_and_failure_cleanup() { let directory = Directory::new(); let project = Project::load(&fixture().with_file_name("task-clauses.dever")).unwrap(); let program = project.check().unwrap(); let binary = build::build( &program, &project.sources, "verify", &directory.0.join("clauses"), ) .unwrap(); let result = std::process::Command::new(binary).output().unwrap(); assert!( result.status.success(), "{}", String::from_utf8_lossy(&result.stderr) ); assert_eq!(result.stdout, b"true\n"); } #[test] fn native_tasks_transfer_values_wait_cancel_and_preserve_error_origins() { let directory = Directory::new(); let project = Project::load(&fixture().with_file_name("task.dever")).unwrap(); let program = project.check().unwrap(); for (entry, expected) in [ ("verify", None), ("sleepFailure", Some("SleepResult.Failed")), ("faultEntry", Some("integer arithmetic fault")), ] { let binary = build::build(&program, &project.sources, entry, &directory.0.join(entry)).unwrap(); let result = std::process::Command::new(binary).output().unwrap(); if let Some(message) = expected { assert!(!result.status.success()); assert!(result.stdout.is_empty()); let error = String::from_utf8(result.stderr).unwrap(); assert!(error.contains(message), "{error}"); if entry == "faultEntry" { let source = &project.sources[0]; let (line, column) = source.position(source.text.rfind("value + 1").unwrap()); assert!( error.contains(&format!("task.dever:{line}:{column}:")), "{error}" ); } else { assert!(error.contains("lib/dever/time.dever:"), "{error}"); } } else { assert!( result.status.success(), "{}", String::from_utf8_lossy(&result.stderr) ); assert_eq!(result.stdout, b"true\n"); } } } #[test] fn asynchronous_commands_keep_typed_inputs_outputs_and_synchronous_peers() { let directory = Directory::new(); let mut project = Project::load(&fixture().with_file_name("application")).unwrap(); let source = project .sources .iter_mut() .find(|source| source.path.ends_with("greeting/app.dever")) .unwrap(); let text = source .text .replace( "message = utility.decorate(domain.greet(name))", "dever.task.sleep(0)\n message = utility.decorate(domain.greet(name))", ) .replace( "{ first = value\n second = value + 1 }", "{ dever.task.sleep(0)\n first = value\n second = value + 1 }", ); *source = dever::syntax::source::Source::new(source.id, source.path.clone(), text); let program = project.check().unwrap(); let binary = build::build( &program, &project.sources, "main", &directory.0.join("commands"), ) .unwrap(); for (command, input, expected) in [ ("greet", r#"{"name":"雪"}"#, r#""[Hello, 雪]""#), ("pair", r#"{"value":41}"#, r#"{"first":41,"second":42}"#), ("double", r#"{"value":21}"#, "42"), ] { let result = std::process::Command::new(&binary) .args([&format!("demo.greeting.{command}"), input]) .output() .unwrap(); assert!( result.status.success(), "{}", String::from_utf8_lossy(&result.stderr) ); assert_eq!( String::from_utf8(result.stdout).unwrap(), format!("{{\"code\":0,\"message\":\"ok\",\"data\":{expected}}}\n") ); } } #[test] fn leaf_entry_preserves_business_failure_origin() { let directory = Directory::new(); let mut project = Project::load(&fixture().with_file_name("task.dever")).unwrap(); let source = &project.sources[0]; let text = format!( "{}\nasyncFailure() () {{ dever.task.sleep(-1) }}\n", source.text ); project.sources[0] = dever::syntax::source::Source::new(source.id, source.path.clone(), text); let program = project.check().unwrap(); let binary = build::build( &program, &project.sources, "asyncFailure", &directory.0.join("failure"), ) .unwrap(); let result = std::process::Command::new(binary).output().unwrap(); assert!(!result.status.success()); assert!(result.stdout.is_empty()); let source = project .sources .iter() .find(|source| source.path.ends_with("lib/dever/task.dever")) .unwrap(); let offset = source .text .find("fail(dever.time.SleepResult.Failed(message))") .unwrap() + "fail(".len(); let (line, column) = source.position(offset); let error = String::from_utf8(result.stderr).unwrap(); assert!(error.contains("SleepResult.Failed"), "{error}"); assert!( error.contains(&format!("{}:{line}:{column}:", source.path.display())), "{error}" ); } #[test] fn actual_entries_only_create_task_scopes_when_they_schedule() { let directory = Directory::new(); let mut project = Project::load(&fixture().with_file_name("task-performance.dever")).unwrap(); let cases = [ ("syncEntry", "answer = total([1, 2])", false, false, "3\n"), ( "leafEntry", "answer = asyncTotal([1, 2], false)", true, false, "3\n", ), ("forwardEntry", "answer = leafEntry()", true, false, "3\n"), ( "scheduledEntry", "answer = asyncPair([1, 2])", true, true, "6\n", ), ( "blockingEntry", "answer = blockingSum([1, 2])", true, true, "3\n", ), ("cancelEntry", "answer = cancel()", true, true, "1\n"), ]; let mut text = project.sources[0].text.clone(); for (name, body, _, _, _) in cases { text.push_str(&format!("{name}() (answer: Int) {{ {body} }}\n")); } let source = &project.sources[0]; project.sources[0] = dever::syntax::source::Source::new(source.id, source.path.clone(), text); let program = project.check().unwrap(); for (entry, _, suspend, schedule, expected) in cases { let signature = &program .functions .iter() .find(|function| function.signature.name == entry) .unwrap() .signature; assert_eq!( (signature.effects.suspend, signature.effects.schedule), (suspend, schedule), "{entry}" ); let binary = build::build(&program, &project.sources, entry, &directory.0.join(entry)).unwrap(); let output = std::process::Command::new(binary).output().unwrap(); assert!( output.status.success(), "{entry}: {}", String::from_utf8_lossy(&output.stderr) ); assert_eq!(output.stdout, expected.as_bytes(), "{entry}"); } } #[test] fn pending_tasks_keep_cleanup_on_each_failure_exit() { let directory = Directory::new(); let mut project = Project::load(&fixture().with_file_name("task.dever")).unwrap(); let source = &project.sources[0]; let mut text = source.text.clone(); for code in [ "explicitFailure() () { pending = run(dever.task.sleep(60000))\n fail(dever.time.SleepResult.Failed(\"explicit\")) }", "explicitVerify() (answer: Bool) { answer = failed(result(explicitFailure())) }", "arithmeticSleep(value: Int) () { next = value + 1\n dever.task.sleep(next) }", "capturedFault(value: Int) (answer: dever.time.SleepResult) { pending = run(dever.task.sleep(60000))\n answer = result(arithmeticSleep(value))\n stop(pending) }", "captureFaultEntry() (answer: dever.time.SleepResult) { answer = capturedFault(9223372036854775807) }", "sumWithPending(values: List) (answer: Int) { pending = run(dever.task.sleep(60000))\n answer = sum(values)\n stop(pending) }", "sumCleanupEntry() (answer: Int) { answer = sumWithPending([9223372036854775807, 1]) }", "mapWithPending(key: Text) (answer: Map) { pending = run(dever.task.sleep(60000))\n answer = { key = 1\n \"same\" = 2 }\n stop(pending) }", "mapCleanupEntry() (answer: Map) { answer = mapWithPending(\"same\") }", ] { text.push_str(&format!("{code}\n")); } project.sources[0] = dever::syntax::source::Source::new(source.id, source.path.clone(), text); let program = project.check().unwrap(); for (entry, failure) in [ ("explicitVerify", None), ( "captureFaultEntry", Some(("value + 1", "integer arithmetic fault")), ), ("sumCleanupEntry", Some(("sum(values)", "integer overflow"))), ( "mapCleanupEntry", Some(("{ key = 1", "duplicate Map literal key")), ), ] { let binary = build::build(&program, &project.sources, entry, &directory.0.join(entry)).unwrap(); let result = std::process::Command::new(binary).output().unwrap(); if let Some((needle, message)) = failure { assert!(!result.status.success()); assert!(result.stdout.is_empty()); let source = &project.sources[0]; let (line, column) = source.position(source.text.rfind(needle).unwrap()); let error = String::from_utf8(result.stderr).unwrap(); assert!( error.contains(&format!("task.dever:{line}:{column}:")), "{error}" ); assert!(error.contains(message), "{error}"); } else { assert!( result.status.success(), "{}", String::from_utf8_lossy(&result.stderr) ); assert_eq!(result.stdout, b"true\n"); } } }