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