graphic design tool
git clone https://git.lucas.co/cce-designer.git
tests/user_paths.rs (5K)
1 //! The crate's own source, checked for paths that belong to the user.
2 //!
3 //! An integration test on purpose, for the reason `doc_claims.rs` gives about
4 //! its own scans: anything under `src/` is counted by them, and a scanner
5 //! living there is the first thing it finds. From out here the needles can be
6 //! written plainly, because this file is not in what it reads.
7 //!
8 //! Deliberately MIRRORED per crate rather than shared from a helper: each is
9 //! its own git repository that must build standalone, and this needs nothing
10 //! but std. Same call `doc_claims.rs` makes.
11
12 pub mod user_paths {
13 use std::path::{Path, PathBuf};
14
15 /// Every `.rs` file under `src/`, as (path, contents).
16 fn sources(root: &Path) -> Vec<(PathBuf, String)> {
17 fn walk(dir: &Path, out: &mut Vec<(PathBuf, String)>) {
18 let Ok(entries) = std::fs::read_dir(dir) else { return };
19 for e in entries.flatten() {
20 let p = e.path();
21 if p.is_dir() {
22 walk(&p, out);
23 } else if p.extension().and_then(|x| x.to_str()) == Some("rs") {
24 if let Ok(s) = std::fs::read_to_string(&p) {
25 out.push((p, s));
26 }
27 }
28 }
29 }
30 let mut v = Vec::new();
31 walk(&root.join("src"), &mut v);
32 v
33 }
34
35 /// Prose says what the code used to do, so only code counts.
36 fn is_comment(line: &str) -> bool {
37 let t = line.trim_start();
38 t.starts_with("//") || t.starts_with("*")
39 }
40
41 /// `cargo test` must not touch anything the user owns. Two shapes of that
42 /// shipped from this crate, and this is the scan that would have caught
43 /// either the day it was written.
44 ///
45 /// **A config path built by hand.** `DesignSettings::file_path` spelled
46 /// out `$HOME/.config/cce/cce-designer/state.kdl`. `State::new` loads the
47 /// bundled project, whose meta subnets overwrite the live viewport flags,
48 /// so every test that reached `save_settings` wrote the bundled project's
49 /// show_grid / show_cube / show_origin over the user's own. The suite
50 /// reset three of their toggles on every run and stayed green. The path
51 /// goes through `cce_ui::config::cce_config_dir()` now, which is what the
52 /// `cfg(test)` redirect keys off — so hand-assembling one out of
53 /// `.config` is precisely the regression to refuse, and going through the
54 /// toolkit is the only way in.
55 ///
56 /// **A scratch path shared with the rest of the machine.** Two tests kept
57 /// their files at fixed `/tmp` names. /tmp is one namespace shared by
58 /// every user — the point `../cce-compositor/WORKSPACE.md` makes about
59 /// `cce_runtime_dir` — so a fixed name belongs to whoever ran first and
60 /// the sticky bit denies it to everyone else; nearer to hand, two
61 /// checkouts running their suites at once shared one directory. Scoping
62 /// by pid is the crate's own convention, followed at every other site.
63 ///
64 /// The rule is on `temp_dir()` rather than on tests alone because a fixed
65 /// /tmp path is no better in shipped code; if one ever needs an unscoped
66 /// name it should earn a line here saying why.
67 #[test]
68 fn no_source_builds_a_path_the_user_owns() {
69 let root = Path::new(env!("CARGO_MANIFEST_DIR"));
70 let files = sources(root);
71 assert!(!files.is_empty(), "found no sources under src/ — the walk is broken");
72
73 let mut bad: Vec<String> = Vec::new();
74 for (path, src) in &files {
75 let name = path.strip_prefix(root).unwrap_or(path).display();
76 let lines: Vec<&str> = src.lines().collect();
77 for (i, line) in lines.iter().enumerate() {
78 if is_comment(line) {
79 continue;
80 }
81 if line.contains("\".config\"") || line.contains("/.config") {
82 bad.push(format!(
83 "{name}:{}: builds a config path by hand — go through \
84 cce_ui::config::cce_config_dir(), which is what the \
85 cfg(test) redirect keys off:\n {}",
86 i + 1,
87 line.trim()
88 ));
89 }
90 // The call may wrap, so the scoping can be a line or two down.
91 if line.contains("temp_dir()") {
92 let window = lines[i..lines.len().min(i + 3)].join(" ");
93 if !window.contains("process::id()") {
94 bad.push(format!(
95 "{name}:{}: a scratch path shared with every other \
96 user and every concurrent run — scope it with \
97 std::process::id():\n {}",
98 i + 1,
99 line.trim()
100 ));
101 }
102 }
103 }
104 }
105 assert!(bad.is_empty(), "source builds paths the user owns:\n {}", bad.join("\n "));
106 }
107 }