返回 CodeWhale
tests.rs
根目录 / crates / tui / src / tools / pdf / tests.rs
1 use super::*;
2 use crate::tools::spec::{ToolExecutionOutcome, ToolTerminalStatus};
3
4 #[tokio::test]
5 async fn missing_binary_maps_to_bounded_failed_machine_contract() {
6 let temporary = tempfile::tempdir().expect("tempdir");
7 let missing = temporary.path().join("definitely-not-pdftotext");
8 let input = temporary.path().join("input.pdf");
9 std::fs::write(&input, b"%PDF-1.7\n%%EOF").expect("fixture");
10 let error = extract_path(
11 &input,
12 None,
13 PdfTextCommand::test(missing.as_os_str(), Duration::from_secs(1), None),
14 )
15 .await
16 .expect_err("missing binary");
17 assert_eq!(error, PdfTextError::BinaryUnavailable);
18
19 let error = into_tool_error(error);
20 let payload = match &error {
21 ToolError::NotAvailable { message } => {
22 assert!(message.len() < 512, "{message}");
23 serde_json::from_str::<serde_json::Value>(message).expect("JSON failure payload")
24 }
25 other => panic!("unexpected mapped error: {other:?}"),
26 };
27 assert_eq!(payload["type"], "binary_unavailable");
28 assert_eq!(payload["binary"], "pdftotext");
29 assert_eq!(
30 ToolExecutionOutcome::from_legacy(Err(error)).status,
31 ToolTerminalStatus::Failed
32 );
33 }
34
35 #[cfg(unix)]
36 fn executable_script(contents: &str) -> (tempfile::TempDir, std::path::PathBuf) {
37 use std::os::unix::fs::PermissionsExt;
38
39 let temporary = tempfile::tempdir().expect("tempdir");
40 let binary = temporary.path().join("fake-pdftotext");
41 std::fs::write(&binary, contents).expect("fake binary");
42 let mut permissions = std::fs::metadata(&binary).expect("metadata").permissions();
43 permissions.set_mode(0o700);
44 std::fs::set_permissions(&binary, permissions).expect("executable");
45 (temporary, binary)
46 }
47
48 #[cfg(unix)]
49 #[tokio::test]
50 async fn shared_adapter_forwards_page_window_and_returns_stdout() {
51 let (temporary, binary) = executable_script(
52 "#!/bin/sh\nprintf 'args:%s\\n' \"$*\"\nprintf 'page one\\fpage two\\n'\n",
53 );
54 // Success-path budget, not a tightness proof. Under a loaded cargo-test
55 // process a 1s spawn of `#!/bin/sh` timed out as TimedOut (#5355). The
56 // neighboring test still uses 50ms to prove the timeout path.
57 let request = PdfTextCommand::test(binary.as_os_str(), Duration::from_secs(10), None);
58 let input = temporary.path().join("input with spaces.pdf");
59 std::fs::write(&input, b"fixture bytes").expect("fixture");
60 let text = extract_path(&input, Some((2, 4)), request)
61 .await
62 .expect("fake extraction");
63 assert!(text.contains("-layout -f 2 -l 4"), "{text}");
64 assert!(text.contains(input.to_string_lossy().as_ref()), "{text}");
65 assert!(text.ends_with("page one\u{c}page two\n"), "{text:?}");
66
67 let staged = extract_bytes(b"fetched fixture bytes", request)
68 .await
69 .expect("fake fetched extraction");
70 assert!(staged.contains("-layout"), "{staged}");
71 assert!(staged.ends_with("page one\u{c}page two\n"), "{staged:?}");
72 }
73
74 #[cfg(unix)]
75 #[tokio::test]
76 async fn child_execution_is_timeout_and_cancellation_bounded() {
77 let (temporary, binary) = executable_script("#!/bin/sh\nexec sleep 10\n");
78 let input = temporary.path().join("input.pdf");
79 std::fs::write(&input, b"fixture bytes").expect("fixture");
80 let started = std::time::Instant::now();
81 let error = extract_path(
82 &input,
83 None,
84 PdfTextCommand::test(binary.as_os_str(), Duration::from_millis(50), None),
85 )
86 .await
87 .expect_err("timeout");
88 assert_eq!(error, PdfTextError::TimedOut);
89 assert!(started.elapsed() < Duration::from_secs(2));
90
91 let cancel = CancellationToken::new();
92 let cancel_after_spawn = cancel.clone();
93 tokio::spawn(async move {
94 tokio::time::sleep(Duration::from_millis(50)).await;
95 cancel_after_spawn.cancel();
96 });
97 let started = std::time::Instant::now();
98 let error = extract_path(
99 &input,
100 None,
101 PdfTextCommand::test(binary.as_os_str(), Duration::from_secs(10), Some(&cancel)),
102 )
103 .await
104 .expect_err("cancelled");
105 assert_eq!(error, PdfTextError::Cancelled);
106 assert!(started.elapsed() < Duration::from_secs(2));
107 }
108
109 #[tokio::test]
110 async fn bounded_reader_drains_but_retains_only_the_prefix() {
111 let output = read_bounded(&b"0123456789"[..], 4).await.expect("read");
112 assert_eq!(output.bytes, b"0123");
113 assert!(output.truncated);
114 }
115
116 #[test]
117 fn stderr_sanitizer_removes_terminal_control_bytes() {
118 assert_eq!(sanitized_text(b"bad\x1b[31m\0\nnext"), "bad�[31m�\nnext");
119 }
120
120 lines RUST