Path:
tests/unit/strictdoc/backend/sdoc_source_code/readers/test_reader_rust.py
Lines:
269
Non-empty lines:
213
Non-empty lines covered with requirements:
213 / 213 (100.0%)
Functions:
27
Functions covered by requirements:
27 / 27 (100.0%)
1
"""2
@relation(SDOC-SRS-142, scope=file)3
"""4
5
from pathlib import Path
6
7
from strictdoc.backend.sdoc_source_code.models.source_file_info import (
8
SourceFileTraceabilityInfo,
9
)10
from strictdoc.backend.sdoc_source_code.reader_rust import (
11
SourceFileTraceabilityReader_Rust,
12
rust_canonical_crate_segments,
13
rust_crate_root_and_name,
14
rust_module_segments_within_crate,
15
)16
17
18
def _read(source: bytes, file_path: str) -> SourceFileTraceabilityInfo:
19
return SourceFileTraceabilityReader_Rust().read(source, file_path=file_path)
20
21
22
def _function_named(info: SourceFileTraceabilityInfo, name: str):
23
matches = [function for function in info.functions if function.name == name]
24
assert len(matches) == 1, (
25
f"expected exactly one function named {name!r}, got "
26
f"{[function.name for function in info.functions]}"
27
)28
return matches[0]
29
30
31
def _crate(root: Path, package_name: str) -> Path:
32
root.mkdir(parents=True, exist_ok=True)
33
(root / "Cargo.toml").write_text(
34
f'[package]\nname = "{package_name}"\n'
35
'version = "0.1.0"\nedition = "2024"\n',
36
encoding="utf-8",
37
)38
return root
39
40
41
def _read_one(file_path: str, source_body: bytes, fn_name: str):
42
source = b"/// @relation(REQ-1, scope=function)\n" + source_body
43
info = _read(source, file_path=file_path)
44
return _function_named(info, fn_name)
45
46
47
def test_module_segments_within_crate_mapping():
48
f = rust_module_segments_within_crate
49
assert f(()) == []
50
assert f(("src", "lib.rs")) == []
51
assert f(("src", "main.rs")) == []
52
assert f(("src", "model.rs")) == ["model"]
53
assert f(("src", "model", "mod.rs")) == ["model"]
54
assert f(("src", "a", "b", "c.rs")) == ["a", "b", "c"]
55
assert f(("src", "model", "tests.rs")) == ["model", "tests"]
56
assert f(("tests", "it.rs")) == ["it"]
57
assert f(("tests", "it", "helper.rs")) == ["it", "helper"]
58
assert f(("tests", "it", "main.rs")) == ["it"]
59
60
61
def test_crate_root_and_name_finds_enclosing_package(tmp_path):
62
_crate(tmp_path, "my_crate")
63
found = rust_crate_root_and_name(str(tmp_path / "src"))
64
assert found == (str(tmp_path), "my_crate")
65
66
67
def test_crate_root_and_name_keeps_hyphenated_package_name(tmp_path):
68
_crate(tmp_path, "weird-pkg-name")
69
found = rust_crate_root_and_name(str(tmp_path / "src"))
70
assert found == (str(tmp_path), "weird-pkg-name")
71
72
73
def test_crate_root_and_name_skips_workspace_only_manifest(tmp_path):
74
(tmp_path / "Cargo.toml").write_text(
75
'[workspace]\nmembers = ["crates/foo"]\n', encoding="utf-8"
76
)77
assert rust_crate_root_and_name(str(tmp_path / "src")) is None
78
79
80
def test_crate_root_and_name_returns_none_without_manifest(tmp_path):
81
assert rust_crate_root_and_name(str(tmp_path / "src")) is None
82
83
84
def test_canonical_segments_crate_qualified(tmp_path):
85
_crate(tmp_path, "my_crate")
86
assert rust_canonical_crate_segments(str(tmp_path / "src" / "lib.rs")) == [
87
"my_crate"88
]89
assert rust_canonical_crate_segments(
90
str(tmp_path / "src" / "a" / "b" / "c.rs")
91
) == ["my_crate", "a", "b", "c"]
92
93
94
def test_canonical_segments_fall_back_to_stem_without_cargo(tmp_path):
95
flat = tmp_path / "forward_relations.rs"
96
assert rust_canonical_crate_segments(str(flat)) == ["forward_relations"]
97
98
99
def test_canonical_segments_none_path():
100
assert rust_canonical_crate_segments(None) == []
101
102
103
def test_empty_file():
104
info = _read(b"", file_path="src/lib.rs")
105
assert isinstance(info, SourceFileTraceabilityInfo)
106
assert len(info.markers) == 0
107
108
109
def test_relation_marker_is_attached_to_function(tmp_path):
110
_crate(tmp_path, "my_crate")
111
source = b"""\
112
/// @relation(REQ-1, scope=function)113
pub fn add(a: i32, b: i32) -> i32 {114
a + b115
}116
"""117
info = _read(source, file_path=str(tmp_path / "src" / "lib.rs"))
118
function = _function_named(info, "my_crate::add")
119
assert function.markers[0].reqs == ["REQ-1"]
120
121
122
def test_function_range_folds_in_leading_attributes_and_doc_comment():
123
source = b"""\
124
#[cfg(test)]125
mod tests {126
/// @relation(REQ-1, scope=function)127
#[test]128
fn add_works() {129
assert_eq!(2 + 2, 4);130
}131
}132
"""133
info = _read(source, file_path="lib.rs")
134
# doc comment = 3, #[test] = 4, fn = 5, closing brace = 7.135
fn = _function_named(info, "lib::tests::add_works")
136
assert fn.line_begin == 3
137
assert fn.line_end == 7
138
module = _function_named(info, "lib::tests")
139
assert module.line_begin == 1
140
141
142
def test_canonical_path_crate_root_lib(tmp_path):
143
_crate(tmp_path, "my_crate")
144
fn = _read_one(
145
str(tmp_path / "src" / "lib.rs"), b"pub fn add() {}\n", "my_crate::add"
146
)147
assert fn.markers[0].reqs == ["REQ-1"]
148
149
150
def test_canonical_path_crate_root_main(tmp_path):
151
_crate(tmp_path, "my_crate")
152
_read_one(
153
str(tmp_path / "src" / "main.rs"),
154
b"pub fn helper() {}\n",
155
"my_crate::helper",
156
)157
158
159
def test_canonical_path_top_level_module_file(tmp_path):
160
_crate(tmp_path, "my_crate")
161
_read_one(
162
str(tmp_path / "src" / "model.rs"),
163
b"pub fn parse() {}\n",
164
"my_crate::model::parse",
165
)166
167
168
def test_canonical_path_nested_module_file(tmp_path):
169
_crate(tmp_path, "my_crate")
170
_read_one(
171
str(tmp_path / "src" / "a" / "b" / "c.rs"),
172
b"pub fn run() {}\n",
173
"my_crate::a::b::c::run",
174
)175
176
177
def test_canonical_path_mod_rs_uses_directory_name(tmp_path):
178
_crate(tmp_path, "my_crate")
179
_read_one(
180
str(tmp_path / "src" / "model" / "mod.rs"),
181
b"pub fn run() {}\n",
182
"my_crate::model::run",
183
)184
185
186
def test_canonical_path_submodule_file(tmp_path):
187
_crate(tmp_path, "my_crate")
188
_read_one(
189
str(tmp_path / "src" / "model" / "tests.rs"),
190
b"pub fn round_trips() {}\n",
191
"my_crate::model::tests::round_trips",
192
)193
194
195
def test_canonical_path_inline_mod_nesting(tmp_path):
196
_crate(tmp_path, "my_crate")
197
source = b"""\
198
mod tests {199
/// @relation(REQ-3, scope=function)200
pub fn it_works() {}
201
}202
"""203
info = _read(source, file_path=str(tmp_path / "src" / "element_id.rs"))
204
fn = _function_named(info, "my_crate::element_id::tests::it_works")
205
assert fn.markers[0].reqs == ["REQ-3"]
206
207
208
def test_canonical_path_integration_test_root(tmp_path):
209
_crate(tmp_path, "my_crate")
210
_read_one(
211
str(tmp_path / "tests" / "it.rs"),
212
b"fn it_runs() {}\n",
213
"my_crate::it::it_runs",
214
)215
216
217
def test_canonical_path_integration_test_submodule(tmp_path):
218
_crate(tmp_path, "my_crate")
219
_read_one(
220
str(tmp_path / "tests" / "it" / "helper.rs"),
221
b"fn helps() {}\n",
222
"my_crate::it::helper::helps",
223
)224
225
226
def test_canonical_path_non_cargo_flat_file_uses_stem(tmp_path):
227
fn = _read_one(
228
str(tmp_path / "forward_relations.rs"),
229
b"pub fn parse() {}\n",
230
"forward_relations::parse",
231
)232
assert fn.markers[0].reqs == ["REQ-1"]
233
234
235
def test_canonical_path_non_cargo_nested_modules(tmp_path):
236
source = b"""\
237
mod foo_module {238
mod foo {239
/// @relation(REQ-2, scope=function)240
pub fn bar() {}
241
}242
}243
"""244
info = _read(source, file_path=str(tmp_path / "forward_relations.rs"))
245
fn = _function_named(info, "forward_relations::foo_module::foo::bar")
246
assert fn.markers[0].reqs == ["REQ-2"]
247
248
249
def test_canonical_path_workspace_members_stay_distinct(tmp_path):
250
(tmp_path / "Cargo.toml").write_text(
251
'[workspace]\nmembers = ["crates/foo", "crates/bar"]\n',
252
encoding="utf-8",
253
)254
_crate(tmp_path / "crates" / "foo", "foo-crate")
255
_crate(tmp_path / "crates" / "bar", "bar-crate")
256
body = b"""\
257
mod tests {258
/// @relation(REQ-1, scope=function)259
pub fn common_test() {}
260
}261
"""262
foo = _read(
263
body, file_path=str(tmp_path / "crates" / "foo" / "src" / "lib.rs")
264
)265
bar = _read(
266
body, file_path=str(tmp_path / "crates" / "bar" / "src" / "lib.rs")
267
)268
_function_named(foo, "foo-crate::tests::common_test")
269
_function_named(bar, "bar-crate::tests::common_test")