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