[docs] 1# @Test suite for source code analysis and marker extraction, TEST_ANA_1, test, [IMPL_LNK_1, IMPL_ONE_1, IMPL_MRST_1]
2import json
3from pathlib import Path
4
5import pytest
6
7from sphinx_codelinks.analyse.analyse import SourceAnalyse, _count
8from sphinx_codelinks.config import SourceAnalyseConfig
9from sphinx_codelinks.source_discover.config import CommentType
10from tests.conftest import (
11 ONELINE_COMMENT_STYLE,
12 ONELINE_COMMENT_STYLE_DEFAULT,
13 TEST_DIR,
14)
15
16TEST_DATA_DIR = Path(__file__).parent.parent / "tests" / "data"
17
18
19@pytest.mark.parametrize(
20 ("src_dir", "src_paths"),
21 [
22 (
23 TEST_DATA_DIR,
24 [
25 TEST_DATA_DIR / "oneline_comment_default" / "default_oneliners.c",
26 TEST_DATA_DIR / "need_id_refs" / "dummy_1.cpp",
27 TEST_DATA_DIR / "marked_rst" / "dummy_1.cpp",
28 ],
29 )
30 ],
31)
32def test_analyse(src_dir, src_paths, tmp_path, snapshot_marks):
33 src_analyse_config = SourceAnalyseConfig(
34 src_files=src_paths,
35 src_dir=src_dir,
36 get_need_id_refs=True,
37 get_oneline_needs=True,
38 get_rst=True,
39 )
40
41 analyse = SourceAnalyse(src_analyse_config)
42 analyse.git_remote_url = None
43 analyse.git_commit_rev = None
44 analyse.run()
45 analyse.dump_marked_content(tmp_path)
46
47 dumped_content = tmp_path / "marked_content.json"
48 with dumped_content.open("r") as f:
49 marked_content = json.load(f)
50 # normalize filepath
51 for obj in marked_content:
52 obj["filepath"] = (
53 Path(obj["filepath"]).relative_to(src_analyse_config.src_dir)
54 ).as_posix()
55 assert marked_content == snapshot_marks
56
57
58@pytest.mark.parametrize(
59 "src_dir, src_paths , oneline_comment_style, result",
60 [
61 (
62 TEST_DIR / "data" / "dcdc",
63 [
64 TEST_DIR / "data" / "dcdc" / "charge" / "demo_1.cpp",
65 TEST_DIR / "data" / "dcdc" / "charge" / "demo_2.cpp",
66 TEST_DIR / "data" / "dcdc" / "discharge" / "demo_3.cpp",
67 TEST_DIR / "data" / "dcdc" / "supercharge.cpp",
68 ],
69 ONELINE_COMMENT_STYLE,
70 {
71 "num_src_files": 4,
72 "num_uncached_files": 4,
73 "num_cached_files": 0,
74 "num_comments": 29,
75 "num_oneline_warnings": 0,
76 },
77 ),
78 (
79 TEST_DIR / "data" / "oneline_comment_basic",
80 [
81 TEST_DIR / "data" / "oneline_comment_basic" / "basic_oneliners.c",
82 ],
83 ONELINE_COMMENT_STYLE,
84 {
85 "num_src_files": 1,
86 "num_uncached_files": 1,
87 "num_cached_files": 0,
88 "num_comments": 14,
89 "num_oneline_warnings": 0,
90 "warnings_path_exists": True,
91 },
92 ),
93 (
94 TEST_DIR / "data" / "oneline_comment_default",
95 [
96 TEST_DIR / "data" / "oneline_comment_default" / "default_oneliners.c",
97 ],
98 ONELINE_COMMENT_STYLE_DEFAULT,
99 {
100 "num_src_files": 1,
101 "num_uncached_files": 1,
102 "num_cached_files": 0,
103 "num_comments": 5,
104 "num_oneline_warnings": 1,
105 "warnings_path_exists": True,
106 },
107 ),
108 (
109 TEST_DIR / "data" / "rust",
110 [
111 TEST_DIR / "data" / "rust" / "demo.rs",
112 ],
113 ONELINE_COMMENT_STYLE_DEFAULT,
114 {
115 "num_src_files": 1,
116 "num_uncached_files": 1,
117 "num_cached_files": 0,
118 "num_comments": 6,
119 "num_oneline_warnings": 0,
120 },
121 ),
122 (
123 TEST_DIR / "data" / "jsonc",
124 [
125 TEST_DIR / "data" / "jsonc" / "demo.jsonc",
126 ],
127 ONELINE_COMMENT_STYLE_DEFAULT,
128 {
129 "num_src_files": 1,
130 "num_uncached_files": 1,
131 "num_cached_files": 0,
132 "num_comments": 4,
133 "num_oneline_warnings": 0,
134 "comment_type": CommentType.jsonc,
135 },
136 ),
137 ],
138)
139def test_analyse_oneline_needs(
140 tmp_path, src_dir, src_paths, oneline_comment_style, result
141):
142 src_analyse_config = SourceAnalyseConfig(
143 src_files=src_paths,
144 src_dir=src_dir,
145 get_need_id_refs=False,
146 get_oneline_needs=True,
147 get_rst=False,
148 oneline_comment_style=oneline_comment_style,
149 comment_type=result.get("comment_type", CommentType.cpp),
150 )
151 src_analyse = SourceAnalyse(src_analyse_config)
152 src_analyse.run()
153
154 assert len(src_analyse.src_files) == result["num_src_files"]
155 assert len(src_analyse.oneline_warnings) == result["num_oneline_warnings"]
156
157 cnt_comments = 0
158 for src_file in src_analyse.src_files:
159 cnt_comments += len(src_file.src_comments)
160 assert cnt_comments == result["num_comments"]
161
162
163def test_explicit_git_root_configuration(tmp_path):
164 """Test that explicit git_root configuration is used instead of auto-detection."""
165 # Create a fake git repo structure in tmp_path
166 fake_git_root = tmp_path / "fake_repo"
167 fake_git_root.mkdir()
168 (fake_git_root / ".git").mkdir()
169
170 # Create a minimal .git/config with remote URL
171 git_config = fake_git_root / ".git" / "config"
172 git_config.write_text(
173 '[remote "origin"]\n url = https://github.com/test/repo.git\n'
174 )
175
176 # Create HEAD file pointing to a branch ref
177 git_head = fake_git_root / ".git" / "HEAD"
178 git_head.write_text("ref: refs/heads/main\n")
179
180 # Create the refs/heads/main file with the commit hash
181 refs_dir = fake_git_root / ".git" / "refs" / "heads"
182 refs_dir.mkdir(parents=True)
183 (refs_dir / "main").write_text("abc123def456\n")
184
185 # Create source file in a deeply nested location
186 src_dir = tmp_path / "deeply" / "nested" / "src"
187 src_dir.mkdir(parents=True)
188 src_file = src_dir / "test.c"
189 src_file.write_text("// @Test, TEST_1\nvoid test() {}\n")
190
191 # Configure with explicit git_root
192 src_analyse_config = SourceAnalyseConfig(
193 src_files=[src_file],
194 src_dir=src_dir,
195 get_need_id_refs=False,
196 get_oneline_needs=True,
197 get_rst=False,
198 git_root=fake_git_root,
199 )
200
201 src_analyse = SourceAnalyse(src_analyse_config)
202
203 # Verify the explicit git_root was used
204 assert src_analyse.git_root == fake_git_root.resolve()
205 assert src_analyse.git_remote_url == "https://github.com/test/repo.git"
206 assert src_analyse.git_commit_rev == "abc123def456"
207
208
209def test_git_root_auto_detection_when_not_configured(tmp_path):
210 """Test that git_root is auto-detected when not explicitly configured."""
211 src_dir = TEST_DIR / "data" / "dcdc"
212 src_paths = [src_dir / "charge" / "demo_1.cpp"]
213
214 # Don't set git_root - it should auto-detect
215 src_analyse_config = SourceAnalyseConfig(
216 src_files=src_paths,
217 src_dir=src_dir,
218 get_need_id_refs=False,
219 get_oneline_needs=True,
220 get_rst=False,
221 # git_root is not set, so auto-detection should be used
222 )
223
224 src_analyse = SourceAnalyse(src_analyse_config)
225
226 # The test is running inside a git repo, so git_root should be detected
227 # We just verify it's not None (since this test runs in the sphinx-codelinks repo)
228 assert src_analyse.git_root is not None
229 assert (src_analyse.git_root / ".git").exists()
230
231
232def test_oneline_parser_warnings_are_collected(tmp_path):
233 """Test that oneline parser warnings are collected for later output."""
234 src_dir = TEST_DIR / "data" / "oneline_comment_default"
235 src_paths = [src_dir / "default_oneliners.c"]
236 src_analyse_config = SourceAnalyseConfig(
237 src_files=src_paths,
238 src_dir=src_dir,
239 get_need_id_refs=False,
240 get_oneline_needs=True,
241 get_rst=False,
242 oneline_comment_style=ONELINE_COMMENT_STYLE_DEFAULT,
243 )
244 src_analyse = SourceAnalyse(src_analyse_config)
245 src_analyse.run()
246
247 # Verify that warnings were collected
248 assert len(src_analyse.oneline_warnings) == 1
249 warning = src_analyse.oneline_warnings[0]
250 assert "too_many_fields" in warning.sub_type
251 assert warning.lineno == 17
252
253
254def test_count_pluralizes_nouns() -> None:
255 assert _count(0, "file") == "0 files"
256 assert _count(1, "file") == "1 file"
257 assert _count(2, "marker") == "2 markers"
258 assert _count(1, "marked-rst block") == "1 marked-rst block"