[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 },
91 ),
92 (
93 TEST_DIR / "data" / "oneline_comment_default",
94 [
95 TEST_DIR / "data" / "oneline_comment_default" / "default_oneliners.c",
96 ],
97 ONELINE_COMMENT_STYLE_DEFAULT,
98 {
99 "num_src_files": 1,
100 "num_uncached_files": 1,
101 "num_cached_files": 0,
102 "num_comments": 5,
103 "num_oneline_warnings": 1,
104 },
105 ),
106 (
107 TEST_DIR / "data" / "rust",
108 [
109 TEST_DIR / "data" / "rust" / "demo.rs",
110 ],
111 ONELINE_COMMENT_STYLE_DEFAULT,
112 {
113 "num_src_files": 1,
114 "num_uncached_files": 1,
115 "num_cached_files": 0,
116 "num_comments": 6,
117 "num_oneline_warnings": 0,
118 },
119 ),
120 (
121 TEST_DIR / "data" / "jsonc",
122 [
123 TEST_DIR / "data" / "jsonc" / "demo.jsonc",
124 ],
125 ONELINE_COMMENT_STYLE_DEFAULT,
126 {
127 "num_src_files": 1,
128 "num_uncached_files": 1,
129 "num_cached_files": 0,
130 "num_comments": 4,
131 "num_oneline_warnings": 0,
132 "comment_type": CommentType.jsonc,
133 },
134 ),
135 ],
136)
137def test_analyse_oneline_needs(
138 tmp_path, src_dir, src_paths, oneline_comment_style, result
139):
140 src_analyse_config = SourceAnalyseConfig(
141 src_files=src_paths,
142 src_dir=src_dir,
143 get_need_id_refs=False,
144 get_oneline_needs=True,
145 get_rst=False,
146 oneline_comment_style=oneline_comment_style,
147 comment_type=result.get("comment_type", CommentType.cpp),
148 )
149 src_analyse = SourceAnalyse(src_analyse_config)
150 src_analyse.run()
151
152 assert len(src_analyse.src_files) == result["num_src_files"]
153 assert len(src_analyse.oneline_warnings) == result["num_oneline_warnings"]
154
155 cnt_comments = 0
156 for src_file in src_analyse.src_files:
157 cnt_comments += len(src_file.src_comments)
158 assert cnt_comments == result["num_comments"]
159
160
161def test_explicit_git_root_configuration(tmp_path):
162 """Test that explicit git_root configuration is used instead of auto-detection."""
163 # Create a fake git repo structure in tmp_path
164 fake_git_root = tmp_path / "fake_repo"
165 fake_git_root.mkdir()
166 (fake_git_root / ".git").mkdir()
167
168 # Create a minimal .git/config with remote URL
169 git_config = fake_git_root / ".git" / "config"
170 git_config.write_text(
171 '[remote "origin"]\n url = https://github.com/test/repo.git\n'
172 )
173
174 # Create HEAD file pointing to a branch ref
175 git_head = fake_git_root / ".git" / "HEAD"
176 git_head.write_text("ref: refs/heads/main\n")
177
178 # Create the refs/heads/main file with the commit hash
179 refs_dir = fake_git_root / ".git" / "refs" / "heads"
180 refs_dir.mkdir(parents=True)
181 (refs_dir / "main").write_text("abc123def456\n")
182
183 # Create source file in a deeply nested location
184 src_dir = tmp_path / "deeply" / "nested" / "src"
185 src_dir.mkdir(parents=True)
186 src_file = src_dir / "test.c"
187 src_file.write_text("// @Test, TEST_1\nvoid test() {}\n")
188
189 # Configure with explicit git_root
190 src_analyse_config = SourceAnalyseConfig(
191 src_files=[src_file],
192 src_dir=src_dir,
193 get_need_id_refs=False,
194 get_oneline_needs=True,
195 get_rst=False,
196 git_root=fake_git_root,
197 )
198
199 src_analyse = SourceAnalyse(src_analyse_config)
200
201 # Verify the explicit git_root was used
202 assert src_analyse.git_root == fake_git_root.resolve()
203 assert src_analyse.git_remote_url == "https://github.com/test/repo.git"
204 assert src_analyse.git_commit_rev == "abc123def456"
205
206
207def test_git_root_auto_detection_when_not_configured(tmp_path):
208 """Test that git_root is auto-detected when not explicitly configured."""
209 src_dir = TEST_DIR / "data" / "dcdc"
210 src_paths = [src_dir / "charge" / "demo_1.cpp"]
211
212 # Don't set git_root - it should auto-detect
213 src_analyse_config = SourceAnalyseConfig(
214 src_files=src_paths,
215 src_dir=src_dir,
216 get_need_id_refs=False,
217 get_oneline_needs=True,
218 get_rst=False,
219 # git_root is not set, so auto-detection should be used
220 )
221
222 src_analyse = SourceAnalyse(src_analyse_config)
223
224 # The test is running inside a git repo, so git_root should be detected
225 # We just verify it's not None (since this test runs in the sphinx-codelinks repo)
226 assert src_analyse.git_root is not None
227 assert (src_analyse.git_root / ".git").exists()
228
229
230def test_oneline_parser_warnings_are_collected(tmp_path):
231 """Test that oneline parser warnings are collected for later output."""
232 src_dir = TEST_DIR / "data" / "oneline_comment_default"
233 src_paths = [src_dir / "default_oneliners.c"]
234 src_analyse_config = SourceAnalyseConfig(
235 src_files=src_paths,
236 src_dir=src_dir,
237 get_need_id_refs=False,
238 get_oneline_needs=True,
239 get_rst=False,
240 oneline_comment_style=ONELINE_COMMENT_STYLE_DEFAULT,
241 )
242 src_analyse = SourceAnalyse(src_analyse_config)
243 src_analyse.run()
244
245 # Verify that warnings were collected
246 assert len(src_analyse.oneline_warnings) == 1
247 warning = src_analyse.oneline_warnings[0]
248 assert "too_many_fields" in warning.sub_type
249 assert warning.lineno == 17
250
251
252def test_count_pluralizes_nouns() -> None:
253 assert _count(0, "file") == "0 files"
254 assert _count(1, "file") == "1 file"
255 assert _count(2, "marker") == "2 markers"
256 assert _count(1, "marked-rst block") == "1 marked-rst block"