Files
wordcraft/apps/wordcraft-cli/tests/non_utf8_args.rs
T
3cc0c16302 A file name that isn't valid Unicode no longer crashes wordcraft-cli or the app (#1440) (#1444)
* A file name that isn't valid Unicode no longer crashes wordcraft-cli or the app (#1440)

Both binaries read std::env::args(), which panics on a non-Unicode
argument such as a Latin-1 file name. The CLI now reads args_os(): the
file a command opens keeps its real name and opens (convert, info, text,
inspect, render, zotero, run --file), and any other such argument is an
error naming it, exit 1. The app leaves such a file out with a warning,
as it does a file that fails to open, since file.open takes its path as
text; a --control port that isn't valid Unicode exits 2.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* The app opens a file whose name isn't valid Unicode too (#1440)

file.open takes its path as text but also accepts the file's bytes
under a name, as dropped files use. The app reads such a file, opens
its bytes under the lossy name and keeps the real path on the session,
so Save writes back to it. A unit test covers the open and the save.

The CLI test now checks that every command in OPENS_FILE reaches its
open with the file, and no longer pins `pos` counting a `--page` value.
ROADMAP says which saves go back to the file.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* #1440: skip non-UTF-8 file name tests on macOS

APFS refuses file names that aren't valid UTF-8, so the fixtures fail
with "Illegal byte sequence" there, and the app-start test keeps running
because macOS opens a window without a display variable. The unit test
for the CLI's argument checks and the control-port test still run.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-10-14 19:32:48 -04:00

77 lines
3.4 KiB
Rust

//! A file name that isn't valid Unicode (`caf\xe9.txt`, Latin-1) never panics the CLI (#1440): the
//! file a command opens opens, and any other such argument is an error naming it. Not on macOS:
//! APFS refuses file names that aren't valid UTF-8, so the fixture can't be made there.
#![cfg(all(unix, not(target_os = "macos")))]
use std::ffi::OsString;
use std::os::unix::ffi::OsStringExt;
use std::path::PathBuf;
use std::process::{Command, Output};
fn cli(args: &[OsString]) -> Output {
Command::new(env!("CARGO_BIN_EXE_wordcraft-cli")).args(args).output().unwrap()
}
/// A temp folder holding `caf\xe9.txt`, whose text is "Bonjour".
fn folder(name: &str) -> (PathBuf, OsString) {
let dir = std::env::temp_dir().join(format!("wc-cli-non-utf8-{name}-{}", std::process::id()));
let _ = std::fs::remove_dir_all(&dir);
std::fs::create_dir_all(&dir).unwrap();
let file = dir.join(OsString::from_vec(b"caf\xe9.txt".to_vec()));
std::fs::write(&file, "Bonjour\n").unwrap();
(dir, file.into_os_string())
}
fn ok(out: &Output) -> String {
let stderr = String::from_utf8_lossy(&out.stderr);
assert!(out.status.success(), "status {:?}, stderr: {stderr}", out.status);
String::from_utf8_lossy(&out.stdout).into_owned()
}
#[test]
fn the_file_a_command_opens_may_have_any_name() {
let (dir, file) = folder("open");
assert_eq!(ok(&cli(&["text".into(), file.clone()])).trim(), "Bonjour");
assert!(ok(&cli(&["info".into(), file.clone()])).contains("\"words\""));
assert!(ok(&cli(&["inspect".into(), file.clone()])).contains("Bonjour"));
let md = dir.join("out.md");
ok(&cli(&["convert".into(), file.clone(), md.clone().into()]));
assert!(std::fs::read_to_string(&md).unwrap().contains("Bonjour"));
let saved = dir.join("run.txt");
ok(&cli(&["run".into(), "--file".into(), file.clone(), "--save".into(), saved.clone().into()]));
assert!(std::fs::read_to_string(&saved).unwrap().contains("Bonjour"));
// Saving without a path writes back to the file under its real name, not a lossy one.
let mut args: Vec<OsString> = vec!["run".into(), "--file".into(), file.clone()];
for cmd in ["caret.docEnd", "text.insert={\"text\":\" encore\"}", "file.save"] {
args.extend(["--cmd".into(), cmd.into()]);
}
ok(&cli(&args));
assert!(std::fs::read_to_string(&file).unwrap().contains("encore"));
assert_eq!(std::fs::read_dir(&dir).unwrap().count(), 3, "a second file was written");
let _ = std::fs::remove_dir_all(dir);
}
#[test]
fn any_other_such_argument_is_an_error_naming_it() {
let (dir, file) = folder("error");
let out_name = dir.join(OsString::from_vec(b"caf\xe9.md".to_vec())).into_os_string();
let cmd = OsString::from_vec(b"text.insert={\"text\":\"\xe9\"}".to_vec());
for (args, n) in [
(vec!["convert".into(), file.clone(), out_name.clone()], 3),
(vec!["run".into(), "--file".into(), file.clone(), "--cmd".into(), cmd], 5),
(vec![OsString::from_vec(b"t\xe9xt".to_vec()), file.clone()], 1),
] {
let out = cli(&args);
let stderr = String::from_utf8_lossy(&out.stderr);
let shown = args[n - 1].to_string_lossy();
assert!(shown.contains('\u{FFFD}'));
assert_eq!(out.status.code(), Some(1), "{args:?}: stderr: {stderr}");
assert_eq!(stderr, format!("wordcraft-cli: argument {n} is not valid Unicode: {shown}\n"), "{args:?}");
}
assert!(!std::path::Path::new(&out_name).exists());
let _ = std::fs::remove_dir_all(dir);
}