use super::{Directory, fixture}; use dever::compiler::{build, project::Project}; use dever_runtime::io::File; mod stream; #[test] fn file_aliases_share_cursor_and_close_without_truncating_existing_data() { let directory = Directory::new(); let path = directory.0.join("file"); let name = path.to_str().unwrap(); let file = File::create(name).unwrap(); file.write(b"abcde").unwrap(); assert!(File::create(name).is_err()); assert_eq!(std::fs::read(&path).unwrap(), b"abcde"); file.close().unwrap(); let file = File::open(name).unwrap(); let alias = file.clone(); assert_eq!(file.read(2).unwrap().unwrap(), b"ab"); assert_eq!(alias.read(2).unwrap().unwrap(), b"cd"); for limit in [0, -1, i64::MAX] { assert!(alias.read(limit).is_err()); } assert_eq!(file.read(20).unwrap().unwrap(), b"e"); assert_eq!(file.read(2).unwrap(), None); alias.close().unwrap(); assert_eq!(file.read(2).unwrap_err(), "file is closed"); assert_eq!(file.write(b"").unwrap_err(), "file is closed"); assert_eq!(file.close().unwrap_err(), "file is closed"); } #[cfg(target_os = "linux")] #[test] fn last_file_alias_releases_descriptor_on_success_and_error() { let directory = Directory::new(); let path = directory.0.join("drop"); std::fs::write(&path, b"test").unwrap(); let open_count = || { std::fs::read_dir("/proc/self/fd") .unwrap() .filter_map(Result::ok) .filter(|entry| std::fs::read_link(entry.path()).ok().as_ref() == Some(&path)) .count() }; for _ in 0..32 { let alias = { let file = File::open(path.to_str().unwrap()).unwrap(); let alias = file.clone(); assert_eq!(open_count(), 1); alias }; assert_eq!(open_count(), 1); drop(alias); assert_eq!(open_count(), 0); let failure = || -> Result<(), dever_runtime::io::Error> { let file = File::open(path.to_str().unwrap())?; let alias = file.clone(); alias.read(0)?; Ok(()) }; assert!(failure().is_err()); assert_eq!(open_count(), 0); } } #[test] fn native_file_values_preserve_aliases_and_nominal_failures() { let directory = Directory::new(); std::fs::write(directory.0.join("input"), b"abcde").unwrap(); let project = Project::load(&fixture().with_file_name("io.dever")).unwrap(); let program = project.check().unwrap(); for (entry, expected) in [ ("verify", None), ("missing", Some("OpenResult.Failed")), ("invalid", Some("ReadResult.Failed")), ] { let binary = build::build(&program, &project.sources, entry, &directory.0.join(entry)).unwrap(); let output = std::process::Command::new(binary) .current_dir(&directory.0) .output() .unwrap(); if let Some(message) = expected { assert!(!output.status.success()); assert!(output.stdout.is_empty()); let error = String::from_utf8(output.stderr).unwrap(); assert!( error.contains(message) && error.contains("lib/dever/io.dever:"), "{error}" ); } else { assert!( output.status.success(), "{}", String::from_utf8_lossy(&output.stderr) ); assert_eq!(output.stdout, b"hello\ntrue\n"); assert_eq!( std::fs::read(directory.0.join("created")).unwrap(), b"hello" ); } } #[cfg(target_os = "linux")] { let binary = build::build( &program, &project.sources, "print", &directory.0.join("print"), ) .unwrap(); let output = std::process::Command::new(binary) .stdout( std::fs::OpenOptions::new() .write(true) .open("/dev/full") .unwrap(), ) .output() .unwrap(); assert!(!output.status.success()); let error = String::from_utf8(output.stderr).unwrap(); assert!( error.contains("standard output write failed") && error.contains("lib/dever/io.dever:"), "{error}" ); } }