160 lines
4.8 KiB
Rust
160 lines
4.8 KiB
Rust
//! `toolkit read_file` and `toolkit write_file`, run as `brokerd` runs them. Do not edit.
|
|
|
|
mod support;
|
|
|
|
use std::os::unix::fs::PermissionsExt;
|
|
|
|
use proto::tools::{ReadFileArgs, WriteFileArgs};
|
|
use support::{TempDir, json, toolkit};
|
|
use toolkit::files::MAX_READ;
|
|
|
|
fn read(path: &str) -> support::Ran {
|
|
toolkit(
|
|
&["read_file"],
|
|
&json(&ReadFileArgs {
|
|
path: path.to_string(),
|
|
}),
|
|
)
|
|
}
|
|
|
|
fn write(path: &str, content: &str) -> support::Ran {
|
|
let args = WriteFileArgs {
|
|
path: path.to_string(),
|
|
content: content.to_string(),
|
|
};
|
|
toolkit(&["write_file"], &json(&args))
|
|
}
|
|
|
|
#[test]
|
|
fn a_file_is_read_exactly() {
|
|
let dir = TempDir::new("read");
|
|
let text = "line one\nline two, no newline at the end: ✓";
|
|
std::fs::write(dir.at("a.md"), text).unwrap();
|
|
let ran = read(&dir.at("a.md"));
|
|
assert_eq!((ran.code, ran.stdout.as_str()), (0, text));
|
|
assert_eq!(ran.stderr, "");
|
|
}
|
|
|
|
#[test]
|
|
fn an_empty_file_is_empty_output() {
|
|
let dir = TempDir::new("read-empty");
|
|
std::fs::write(dir.at("e"), "").unwrap();
|
|
let ran = read(&dir.at("e"));
|
|
assert_eq!((ran.code, ran.stdout.as_str()), (0, ""));
|
|
}
|
|
|
|
#[test]
|
|
fn what_cannot_be_read_is_exit_1_with_one_line_for_the_model() {
|
|
let dir = TempDir::new("read-bad");
|
|
std::fs::write(dir.at("bin"), [0xff, 0xfe, 0x00]).unwrap();
|
|
std::fs::write(dir.at("big"), vec![b'a'; MAX_READ + 1]).unwrap();
|
|
std::fs::write(dir.at("exact"), vec![b'a'; MAX_READ]).unwrap();
|
|
let cases = [
|
|
(dir.at("missing"), "no such file"),
|
|
(dir.at(""), "is a directory"),
|
|
(dir.at("bin"), "not UTF-8 text"),
|
|
(dir.at("big"), "larger than 1048576 bytes"),
|
|
];
|
|
for (path, why) in cases {
|
|
let ran = read(&path);
|
|
assert_eq!(ran.code, 1, "{path}");
|
|
assert_eq!(ran.stdout, format!("read_file: {path}: {why}"));
|
|
}
|
|
let ran = read(&dir.at("exact"));
|
|
assert_eq!(
|
|
(ran.code, ran.stdout.len()),
|
|
(0, MAX_READ),
|
|
"exactly the limit is fine"
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn an_unreadable_file_names_the_error() {
|
|
if is_root() {
|
|
return; // root reads anything
|
|
}
|
|
let dir = TempDir::new("read-perm");
|
|
std::fs::write(dir.at("secret"), "x").unwrap();
|
|
std::fs::set_permissions(dir.at("secret"), std::fs::Permissions::from_mode(0o000)).unwrap();
|
|
let ran = read(&dir.at("secret"));
|
|
assert_eq!(ran.code, 1);
|
|
assert!(
|
|
ran.stdout
|
|
.starts_with(&format!("read_file: {}: ", dir.at("secret"))),
|
|
"{}",
|
|
ran.stdout
|
|
);
|
|
assert!(ran.stdout.contains("ermission denied"), "{}", ran.stdout);
|
|
}
|
|
|
|
#[test]
|
|
fn a_file_is_written_created_or_replaced() {
|
|
let dir = TempDir::new("write");
|
|
let ran = write(&dir.at("new.md"), "hello ✓\n");
|
|
assert_eq!(ran.code, 0);
|
|
assert_eq!(
|
|
ran.stdout,
|
|
format!("wrote 10 bytes to {}", dir.at("new.md"))
|
|
);
|
|
assert_eq!(
|
|
std::fs::read_to_string(dir.at("new.md")).unwrap(),
|
|
"hello ✓\n"
|
|
);
|
|
let ran = write(&dir.at("new.md"), "");
|
|
assert_eq!(ran.stdout, format!("wrote 0 bytes to {}", dir.at("new.md")));
|
|
assert_eq!(std::fs::read_to_string(dir.at("new.md")).unwrap(), "");
|
|
}
|
|
|
|
#[test]
|
|
fn what_cannot_be_written_is_exit_1() {
|
|
let dir = TempDir::new("write-bad");
|
|
std::fs::create_dir(dir.at("sub")).unwrap();
|
|
let cases = [
|
|
(dir.at("nope/a.md"), "the directory does not exist"),
|
|
(dir.at("sub"), "is a directory"),
|
|
];
|
|
for (path, why) in cases {
|
|
let ran = write(&path, "x");
|
|
assert_eq!(ran.code, 1, "{path}");
|
|
assert_eq!(ran.stdout, format!("write_file: {path}: {why}"));
|
|
}
|
|
assert!(!dir.path().join("nope").exists(), "no directory is created");
|
|
}
|
|
|
|
#[test]
|
|
fn misuse_is_exit_2_with_nothing_on_standard_output() {
|
|
let long = vec![b' '; toolkit::input::MAX_INPUT + 1];
|
|
let cases: [(&[&str], &[u8], &str); 7] = [
|
|
(
|
|
&["read_file"],
|
|
br#"{"path":"/a","mode":1}"#,
|
|
"the arguments do not parse",
|
|
),
|
|
(&["read_file"], b"", "the arguments do not parse"),
|
|
(
|
|
&["write_file"],
|
|
br#"{"path":"/a"}"#,
|
|
"the arguments do not parse",
|
|
),
|
|
(&["read_file"], &[0xff, 0xfe], "not UTF-8"),
|
|
(&["read_file"], &long, "larger than 2097152 bytes"),
|
|
(&["format_disk"], b"{}", "unknown tool"),
|
|
(&[], b"{}", "unknown tool"),
|
|
];
|
|
for (args, input, why) in cases {
|
|
let ran = toolkit(args, input);
|
|
assert_eq!(ran.code, 2, "{args:?}");
|
|
assert_eq!(
|
|
ran.stdout, "",
|
|
"{args:?}: the model sees nothing of a misuse"
|
|
);
|
|
assert!(ran.stderr.contains(why), "{args:?}: {}", ran.stderr);
|
|
}
|
|
}
|
|
|
|
fn is_root() -> bool {
|
|
std::fs::read_to_string("/proc/self/status")
|
|
.map(|s| s.lines().any(|l| l.starts_with("Uid:\t0\t")))
|
|
.unwrap_or(false)
|
|
}
|