1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
//! Dependency-free, compile-time SHA-256 node identities.
//! 零依赖、编译期计算的 SHA-256 节点身份。
use std::fmt;
use std::str::FromStr;
/// Version of the identity input and persisted catalog formats.
/// 身份输入与持久化目录格式的版本。
pub const IDENTITY_SCHEMA: &str = "3";
#[path = "node_id.rs"]
mod node_id;
pub use node_id::*;
#[path = "sha256.rs"]
mod sha256;
pub use sha256::*;
#[path = "path_text.rs"]
mod path_text;
pub use path_text::*;
#[cfg(test)]
mod tests {
use super::{
NodeId, ROOT_NODE_ID, last_path_segment, manifest_relative_source, root_node_id,
sha256_hex, strip_path_prefix, strip_prefix,
};
const ABC: NodeId = NodeId::from_bytes(b"abc");
const MULTI_BLOCK: NodeId =
NodeId::from_bytes(b"abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq");
/// The derived source must equal what the build step used to inject, so
/// identities survive the switch from injection to derivation.
/// 推导出的 source 必须等于构建步骤原先注入的值,身份才不受切换影响。
#[test]
fn manifest_relative_source_matches_the_injected_form() {
assert_eq!(
manifest_relative_source(
"/home/me/proj",
"/home/me/proj/src/control/object/button/button.rs"
),
"control/object/button/button.rs"
);
// A manifest path that itself contains `src` must not confuse the strip.
// manifest 路径自身含 `src` 时不能被剥错。
assert_eq!(
manifest_relative_source("/home/me/src/proj", "/home/me/src/proj/src/a/b.rs"),
"a/b.rs"
);
}
/// Declarations outside `src/` keep a usable identity instead of panicking.
/// 不在 `src/` 下的声明保留一个可用的身份,而不是 panic。
#[test]
fn manifest_relative_source_falls_back_outside_src() {
assert_eq!(
manifest_relative_source("/home/me/proj", "/home/me/proj/tests/probe.rs"),
"tests/probe.rs"
);
// Not under the manifest at all: keep the original value.
// 完全不在 manifest 下:保留原值。
assert_eq!(
manifest_relative_source("/home/me/proj", "/elsewhere/face.rs"),
"/elsewhere/face.rs"
);
assert_eq!(manifest_relative_source("/home/me/proj", ""), "");
}
/// Windows reports the manifest directory with backslashes while the build
/// step writes `#[path]` with forward slashes. An exact comparison would
/// leave the absolute path as the identity there, so the two forms must
/// agree on one relative value.
/// Windows 以反斜杠给出 manifest 目录,而构建步骤用正斜杠写 `#[path]`。
/// 精确比较会让身份退化成绝对路径,因此两种形式必须归一到同一个相对值。
#[test]
fn manifest_relative_source_normalizes_windows_separators() {
assert_eq!(
manifest_relative_source(r"C:\proj", "C:/proj/src/control/object/button/button.rs"),
"control/object/button/button.rs"
);
// A backslash-separated file path behaves the same.
// 反斜杠分隔的文件路径同样处理。
assert_eq!(
manifest_relative_source(r"C:\proj", r"C:\proj\src\control\control.rs"),
r"control\control.rs"
);
// A trailing separator on the manifest does not change the result.
// manifest 末尾多一个分隔符不影响结果。
assert_eq!(
manifest_relative_source("C:/proj/", "C:/proj/src/a.rs"),
"a.rs"
);
}
#[test]
fn strip_path_prefix_stops_at_component_boundaries() {
assert_eq!(
strip_path_prefix("/work/app/x.rs", "/work/app"),
Some("/x.rs")
);
assert_eq!(
strip_path_prefix("/work/application/x.rs", "/work/app"),
None
);
assert_eq!(strip_path_prefix("C:/app2/a.rs", "C:/app"), None);
assert_eq!(strip_path_prefix("/work/app", "/work/app"), Some(""));
}
#[test]
fn strip_path_prefix_treats_both_separators_alike() {
assert_eq!(strip_path_prefix("C:/a/b", r"C:\a"), Some("/b"));
assert_eq!(strip_path_prefix(r"C:\a\b", "C:/a"), Some(r"\b"));
assert_eq!(strip_path_prefix("C:/a/b", "D:/a"), None);
}
#[test]
fn last_path_segment_reads_the_module_name() {
assert_eq!(last_path_segment("myproj::control::control"), "control");
assert_eq!(
last_path_segment("myproj::control::object::button"),
"button"
);
// No separator at all is the whole value; a trailing separator is empty.
// 没有分隔符时返回原值;尾随分隔符返回空串。
assert_eq!(last_path_segment("button"), "button");
assert_eq!(last_path_segment("myproj::"), "");
}
#[test]
fn strip_prefix_reports_matches_honestly() {
assert_eq!(strip_prefix("/a/b/c", "/a"), Some("/b/c"));
assert_eq!(strip_prefix("/a/b/c", "/a/b/c"), Some(""));
assert_eq!(strip_prefix("/a/b/c", "/x"), None);
// A longer prefix than the value is not a match.
// 前缀比原值更长时不算匹配。
assert_eq!(strip_prefix("/a", "/a/b"), None);
}
#[test]
fn matches_the_standard_vector() {
assert_eq!(ABC.to_string(), "ba7816bf8f01cfea414140de5dae2223");
assert_eq!(ABC.to_string().parse(), Ok(ABC));
assert_eq!(MULTI_BLOCK.to_string(), "248d6a61d20638b8e5c026930c3e6039");
}
#[test]
fn path_is_part_of_the_identity() {
assert_ne!(
NodeId::from_path("control/object/button/button.rs", "Button"),
NodeId::from_path(
"engine/role/style/object/composite_style/object/button/button.rs",
"Button",
)
);
}
/// A face must keep one identity on every platform, so the two path
/// separators hash to the same bytes.
/// 注册面在每个平台上必须保持同一个身份,因此两种路径分隔符哈希为相同字节。
#[test]
fn path_identities_fold_platform_separators() {
assert_eq!(
NodeId::from_namespaced_path("app", "control\\control.rs", "Control"),
NodeId::from_namespaced_path("app", "control/control.rs", "Control")
);
assert_eq!(
NodeId::from_path("a\\b.rs", "A"),
NodeId::from_path("a/b.rs", "A")
);
// Folding must not merge genuinely different paths.
// 折叠不能把真正不同的路径混为一谈。
assert_ne!(
NodeId::from_path("a/b.rs", "A"),
NodeId::from_path("ab.rs", "A")
);
}
#[test]
fn path_components_are_unambiguous() {
assert_ne!(
NodeId::from_path("ab", "c"),
NodeId::from_path("a", "bc"),
"the path/name separator must be part of the identity input"
);
}
#[test]
fn namespace_changes_node_identity() {
assert_ne!(
NodeId::from_namespaced_path("library-a", "src/button.rs", "Button"),
NodeId::from_namespaced_path("library-b", "src/button.rs", "Button")
);
}
#[test]
fn namespaced_roots_are_distinct_and_legacy_root_remains_stable() {
assert_ne!(root_node_id("library-a"), root_node_id("library-b"));
assert_ne!(ROOT_NODE_ID, root_node_id("library-a"));
}
/// Identities are persisted: `.nichlink/external-grafts/<selector>/graft.plan`
/// stores a `NodeId` and parses it back, and a mismatch only warns. These
/// literals are therefore a COMPATIBILITY PIN, not a snapshot. Relational
/// assertions cannot replace them — swapping the two digest inputs in
/// `from_path`, or toggling its `separator` flag, moves both sides of an
/// `assert_eq!`/`assert_ne!` together and passes. Only a literal catches it.
/// Do not edit a literal to make this test green unless the identity format
/// is being deliberately migrated and every recorded plan is re-derived.
/// 身份会被持久化:`.nichlink/external-grafts/<selector>/graft.plan` 存有
/// `NodeId` 并在读回时解析,不匹配只会给出警告。因此这些字面量是兼容性钉,而不是
/// 快照。关系型断言无法取代它们——交换 `from_path` 的两个摘要输入,或翻转它的
/// `separator` 标志,会让 `assert_eq!`/`assert_ne!` 的两侧一起移动并通过。只有
/// 字面量才能发现。除非是有意迁移身份格式并重新推导每一份已记录的 plan,否则不要
/// 为了让本测试变绿而改字面量。
#[test]
fn pinned_identities_are_an_on_disk_compatibility_contract() {
let path_id = NodeId::from_path("control/control.rs", "Control");
let namespaced_id = NodeId::from_namespaced_path("app", "control/control.rs", "Control");
// The Windows spelling must fold onto the same identity as its slash
// twin, so it is pinned to the same literal rather than a second value.
// Windows 写法必须折叠到与其正斜杠孪生相同的身份,因此钉的是同一个字面量,
// 而不是第二个值。
let namespaced_windows_id =
NodeId::from_namespaced_path("app", "control\\control.rs", "Control");
assert_eq!(namespaced_windows_id, namespaced_id);
assert_eq!(
[
path_id.to_string(),
namespaced_id.to_string(),
namespaced_windows_id.to_string(),
ROOT_NODE_ID.to_string(),
],
[
// `NodeId::from_path("control/control.rs", "Control")`
"b7f058b83908f5b3603201bf030674c7",
// `NodeId::from_namespaced_path("app", "control/control.rs", "Control")`
"3ba6f15f11b62ea433f6f16acea3512a",
// the same call with `control\control.rs`
"3ba6f15f11b62ea433f6f16acea3512a",
// `ROOT_NODE_ID` = `from_path("<root>", "root")`
"33515c0bbbf5082cb760eca7c48ff9b6",
]
);
}
#[test]
fn sha256_matches_standard_vector() {
assert_eq!(
sha256_hex(b"abc"),
"ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad"
);
}
}