[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"