126 lines
4.1 KiB
Rust
126 lines
4.1 KiB
Rust
use super::signature;
|
|
use dever::compiler::{check, model::Program};
|
|
use dever::syntax::{diagnostic::Diagnostic, source::Source};
|
|
|
|
fn packages(files: &[(&str, &str)]) -> Result<Program, Diagnostic> {
|
|
let mut modules = Vec::new();
|
|
let mut identities = Vec::new();
|
|
for (index, (identity, text)) in files.iter().enumerate() {
|
|
let path = if *identity == "dever.io" {
|
|
"lib/dever/io.dever"
|
|
} else {
|
|
"case.dever"
|
|
};
|
|
modules.push(dever::syntax::parse(&Source::new(
|
|
index,
|
|
path,
|
|
(*text).into(),
|
|
))?);
|
|
identities.push((*identity).into());
|
|
}
|
|
check::check(modules, &identities)
|
|
}
|
|
|
|
const IO: &str = include_str!("../../../../lib/dever/io.dever");
|
|
const PORT: &str = "open(path: Text) (file: dever.system.File) fails dever.io.OpenResult";
|
|
const ADAPTER: &str = "port.open(path: Text) (file: dever.system.File) { file = open(path) }\nopen(path: Text) (file: dever.system.File) { file = dever.io.open(path) }";
|
|
|
|
#[test]
|
|
fn boundary_retains_real_effects_without_permitting_unmediated_io() {
|
|
let app = "open(path: Text) (file: dever.system.File) { file = port.open(path) }";
|
|
let program = packages(&[
|
|
("shop.files.app", app),
|
|
("shop.files.port", PORT),
|
|
("shop.files.adapter", ADAPTER),
|
|
("dever.io", IO),
|
|
])
|
|
.unwrap();
|
|
let effects = signature(&program, "open").effects;
|
|
assert!(effects.file && effects.blocking && !effects.unmediated_io);
|
|
for body in [
|
|
"file = dever.io.open(path)",
|
|
"old = port.open(path)\nfile = dever.io.open(path)",
|
|
"files = each(dever.io.open, [path])\nfile = port.open(path)",
|
|
"old = result(dever.io.open(path))\nfile = port.open(path)",
|
|
] {
|
|
let app = format!("open(path: Text) (file: dever.system.File) {{ {body} }}");
|
|
assert!(
|
|
packages(&[
|
|
("shop.files.app", &app),
|
|
("shop.files.port", PORT),
|
|
("shop.files.adapter", ADAPTER),
|
|
("dever.io", IO)
|
|
])
|
|
.is_err(),
|
|
"{body}"
|
|
);
|
|
}
|
|
let pure = app.replace(" {", " pure {");
|
|
assert_eq!(
|
|
packages(&[
|
|
("shop.files.app", &pure),
|
|
("shop.files.port", PORT),
|
|
("shop.files.adapter", ADAPTER),
|
|
("dever.io", IO)
|
|
])
|
|
.unwrap_err()
|
|
.code,
|
|
"C013"
|
|
);
|
|
let scheduled = app.replace("port.open(path)", "blocking(port.open(path))");
|
|
assert!(
|
|
packages(&[
|
|
("shop.files.app", &scheduled),
|
|
("shop.files.port", PORT),
|
|
("shop.files.adapter", ADAPTER),
|
|
("dever.io", IO)
|
|
])
|
|
.is_ok()
|
|
);
|
|
let asynchronous = app.replace(
|
|
"file = port.open(path)",
|
|
"other = blocking(port.open(path))\nfile = port.open(path)",
|
|
);
|
|
assert_eq!(
|
|
packages(&[
|
|
("shop.files.app", &asynchronous),
|
|
("shop.files.port", PORT),
|
|
("shop.files.adapter", ADAPTER),
|
|
("dever.io", IO)
|
|
])
|
|
.unwrap_err()
|
|
.code,
|
|
"C013"
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn selection_cannot_hide_blocking_candidate_effects() {
|
|
let app = "open(path: Text) (file: dever.system.File) { file = port.open(path) }";
|
|
let worker =
|
|
"port.open(path: Text) (file: dever.system.File) { file = blocking(dever.io.open(path)) }";
|
|
assert_eq!(
|
|
packages(&[
|
|
("shop.files.app", app),
|
|
("shop.files.port", PORT),
|
|
("shop.files.adapter.inline", ADAPTER),
|
|
("shop.files.adapter.worker", worker),
|
|
("dever.io", IO),
|
|
])
|
|
.unwrap_err()
|
|
.code,
|
|
"C013"
|
|
);
|
|
let program = packages(&[
|
|
("shop.files.app", app),
|
|
("shop.files.port", PORT),
|
|
("shop.files.adapter.first", worker),
|
|
("shop.files.adapter.second", worker),
|
|
("dever.io", IO),
|
|
])
|
|
.unwrap();
|
|
let effects = signature(&program, "open").effects;
|
|
assert!(effects.file && effects.suspend && effects.configuration);
|
|
assert!(!effects.blocking && !effects.unmediated_io);
|
|
}
|