Skip to content

Commit f2e8bcf

Browse files
author
Arnaud Riess
committed
Merge branch 'feat/generate-sn6-schema-validation' of https://github.com/arnoox/score-docs-as-code into feat/generate-sn6-schema-validation
2 parents 31c472c + 7220fe9 commit f2e8bcf

11 files changed

Lines changed: 696 additions & 3641 deletions

File tree

.github/workflows/test.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ jobs:
3636
- name: Run test targets
3737
run: |
3838
bazel run --lockfile_mode=error //:ide_support
39-
bazel test --lockfile_mode=error //src/... //score_pytest/...
39+
bazel test --lockfile_mode=error //...
4040
4141
- name: Prepare bundled consumer report
4242
if: always()

.pre-commit-config.yaml

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,15 @@ repos:
2727
- id: check-added-large-files
2828
args: ["--maxkb=150"]
2929

30+
- repo: local
31+
hooks:
32+
- id: bazel-mod-tidy
33+
name: format Bazel module files
34+
entry: bazel mod tidy
35+
language: system
36+
pass_filenames: false
37+
files: '(^|/)(MODULE\.bazel|.*\.bzl|BUILD(\.bazel)?)$'
38+
3039
- repo: https://github.com/rhysd/actionlint
3140
rev: v1.7.11
3241
hooks:

MODULE.bazel

Lines changed: 1 addition & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -69,14 +69,4 @@ http_file(
6969
urls = ["https://github.com/withered-magic/starpls/releases/download/v0.1.21/starpls-linux-amd64"],
7070
)
7171

72-
# Checker rule for CopyRight checks/fixes
73-
74-
# docs dependency
75-
# Note: requirements were last aligned with 1.2.0,
76-
# the switch to 1.3.1 is purely to drop the dependency on docs-as-code 1.x.
77-
bazel_dep(name = "score_process", version = "1.5.1")
78-
git_override(
79-
module_name = "score_process",
80-
commit = "4c3a16f03c2834b25a10f39542cbe81a8dec4e8a",
81-
remote = "https://github.com/eclipse-score/process_description.git",
82-
)
72+
bazel_dep(name = "score_process", version = "1.5.4")

MODULE.bazel.lock

Lines changed: 256 additions & 3552 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

docs.bzl

Lines changed: 22 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -63,8 +63,8 @@ def _rewrite_needs_json_to_sourcelinks(labels):
6363
s = str(x)
6464
if s.endswith("//:needs_json"):
6565
out.append(s.replace("//:needs_json", "//:sourcelinks_json"))
66-
else:
67-
out.append(s)
66+
#Items which do not end up with '//:needs_json' shall not be appended to 'out'.
67+
#They are treated separately and are not related to source code linking.
6868
return out
6969

7070
def _merge_sourcelinks(name, sourcelinks, known_good = None):
@@ -156,22 +156,28 @@ def docs(source_dir = "docs", data = [], deps = [], scan_code = [], known_good =
156156
deps = deps,
157157
)
158158

159+
# If the source directory is the root (".") we must omit it, otherwise:
160+
# > invalid glob pattern './**/*.png': segment '.' not permitted
161+
if source_dir == ".":
162+
source_prefix = ""
163+
else:
164+
source_prefix = source_dir + "/"
165+
159166
native.filegroup(
160167
name = "docs_sources",
161168
srcs = native.glob([
162-
source_dir + "/**/*.png",
163-
source_dir + "/**/*.svg",
164-
source_dir + "/**/*.md",
165-
source_dir + "/**/*.rst",
166-
source_dir + "/**/*.html",
167-
source_dir + "/**/*.css",
168-
source_dir + "/**/*.puml",
169-
source_dir + "/**/*.need",
170-
source_dir + "/**/*.yaml",
171-
source_dir + "/**/*.json",
172-
source_dir + "/**/*.csv",
173-
source_dir + "/**/*.inc",
174-
"more_docs/**/*.rst",
169+
source_prefix + "**/*.png",
170+
source_prefix + "**/*.svg",
171+
source_prefix + "**/*.md",
172+
source_prefix + "**/*.rst",
173+
source_prefix + "**/*.html",
174+
source_prefix + "**/*.css",
175+
source_prefix + "**/*.puml",
176+
source_prefix + "**/*.need",
177+
source_prefix + "**/*.yaml",
178+
source_prefix + "**/*.json",
179+
source_prefix + "**/*.csv",
180+
source_prefix + "**/*.inc",
175181
], allow_empty = True),
176182
visibility = ["//visibility:public"],
177183
)
@@ -279,7 +285,7 @@ def docs(source_dir = "docs", data = [], deps = [], scan_code = [], known_good =
279285
sphinx_docs(
280286
name = "needs_json",
281287
srcs = [":docs_sources"],
282-
config = ":" + source_dir + "/conf.py",
288+
config = ":" + source_prefix + "conf.py",
283289
extra_opts = [
284290
"-W",
285291
"--keep-going",

docs/how-to/other_modules.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ A minimal example (add or extend the existing `bazel_deps` stanza):
3535

3636
.. code-block:: starlark
3737
38-
bazel_dep(name = "score_process", version = "1.3.0")
38+
bazel_dep(name = "score_process", version = "1.5.3")
3939
4040
2) Extend your `docs` rule so Sphinx picks up the other module's inventory
4141
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

docs/how-to/setup.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ designed to enhance documentation capabilities in S-CORE.
2323
Add the module to your `MODULE.bazel` file:
2424

2525
```starlark
26-
bazel_dep(name = "score_docs_as_code", version = "2.0.3")
26+
bazel_dep(name = "score_docs_as_code", version = "4.0.1")
2727
```
2828

2929
And make sure to also add the S-core Bazel registry to your `.bazelrc` file

scripts_bazel/merge_sourcelinks.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,6 @@ def main():
3939
)
4040
_ = parser.add_argument(
4141
"--known_good",
42-
required=True,
4342
help="Path to a required 'known good' JSON file (provided by Bazel).",
4443
)
4544
_ = parser.add_argument(

scripts_bazel/tests/generate_sourcelinks_cli_test.py

Lines changed: 152 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -11,17 +11,63 @@
1111
# SPDX-License-Identifier: Apache-2.0
1212
# *******************************************************************************
1313

14+
# ╓ ╖
15+
# ║ Some parts are generated by Gemini ║
16+
# ╙ ╜
17+
1418
"""Tests for generate_sourcelinks_cli.py"""
1519

1620
import json
17-
import subprocess
1821
import sys
1922
from pathlib import Path
2023

24+
import pytest
25+
26+
import scripts_bazel.generate_sourcelinks_cli
27+
from src.extensions.score_source_code_linker.needlinks import is_metadata
28+
2129
_MY_PATH = Path(__file__).parent
2230

2331

24-
def test_generate_sourcelinks_cli_basic(tmp_path: Path) -> None:
32+
def assert_json_internal_types(input: list[dict[str, str | int]]):
33+
for entry in input:
34+
assert "file" in entry
35+
assert "line" in entry
36+
assert "tag" in entry
37+
assert "need" in entry
38+
assert "full_line" in entry
39+
40+
assert isinstance(entry["file"], str)
41+
assert isinstance(entry["line"], int)
42+
assert isinstance(entry["tag"], str)
43+
assert isinstance(entry["need"], str)
44+
assert isinstance(entry["full_line"], str)
45+
46+
47+
# Unit test generated by Gemini.
48+
@pytest.mark.parametrize(
49+
"input_path, expected_output",
50+
[
51+
# Case 1: Path starts with "external/" and has a project name
52+
(Path("external/score_docs_as_code+/docs/index.md"), Path("docs/index.md")),
53+
# Case 2: Path does NOT start with "external/"
54+
(Path("src/main.py"), Path("src/main.py")),
55+
# Case 3: Path has "external" elsewhere in the string (should not be removed)
56+
(Path("my_external_data/file.txt"), Path("my_external_data/file.txt")),
57+
# Case 4: Deeply nested path inside an external prefix
58+
(Path("external/repo/subfolder/file.py"), Path("subfolder/file.py")),
59+
# Case 5: Path is exactly "external/" (edge case, returns empty path based on split logic)
60+
(Path("external/"), Path("external")),
61+
],
62+
)
63+
def test_clean_external_prefix(input_path: Path, expected_output: Path):
64+
output = scripts_bazel.generate_sourcelinks_cli.clean_external_prefix(input_path)
65+
assert output == expected_output
66+
67+
68+
def test_generate_sourcelinks_cli_basic(
69+
tmp_path: Path, monkeypatch: pytest.MonkeyPatch
70+
) -> None:
2571
"""Test basic functionality of generate_sourcelinks_cli."""
2672
# Create a test source file with a traceability tag
2773
test_file = tmp_path / "test_source.py"
@@ -36,39 +82,116 @@ def some_function():
3682

3783
output_file = tmp_path / "output.json"
3884

39-
# Execute the script
40-
result = subprocess.run(
41-
[
42-
sys.executable,
43-
_MY_PATH.parent / "generate_sourcelinks_cli.py",
44-
"--output",
45-
str(output_file),
46-
str(test_file),
47-
],
48-
)
49-
50-
assert result.returncode == 0
51-
assert output_file.exists()
85+
test_args: list[Path | str] = [
86+
_MY_PATH.parent
87+
/ "generate_sourcelinks_cli.py", # sys.argv[0] is always the script name
88+
"--output",
89+
str(output_file),
90+
str(test_file),
91+
]
92+
monkeypatch.setattr(sys, "argv", test_args)
93+
result = scripts_bazel.generate_sourcelinks_cli.main()
94+
assert result == 0
5295

5396
# Check the output content
5497
with open(output_file) as f:
5598
data: list[dict[str, str | int]] = json.load(f)
5699
assert isinstance(data, list)
57-
assert len(data) > 0
100+
# The first dictionary has to be metadata
101+
assert len(data) == 2
102+
assert is_metadata(data[0])
103+
assert data[0]["repo_name"] == "local_repo"
104+
# hash & url can not be set in this script therefore HAVE to be empty
105+
assert data[0]["hash"] == ""
106+
assert data[0]["url"] == ""
58107

59108
# Verify schema of each entry
60-
for entry in data:
61-
assert "file" in entry
62-
assert "line" in entry
63-
assert "tag" in entry
64-
assert "need" in entry
65-
assert "full_line" in entry
109+
assert_json_internal_types(data[1:])
66110

67-
# Verify types
68-
assert isinstance(entry["file"], str)
69-
assert isinstance(entry["line"], int)
70-
assert isinstance(entry["tag"], str)
71-
assert isinstance(entry["need"], str)
72-
assert isinstance(entry["full_line"], str)
111+
assert data[1]["need"] == "tool_req__docs_arch_types"
73112

74-
assert any(entry["need"] == "tool_req__docs_arch_types" for entry in data)
113+
114+
def test_generate_sourcelinks_cli_parse_external_module(
115+
tmp_path: Path, monkeypatch: pytest.MonkeyPatch
116+
):
117+
# 1. Create the 'external' directory inside the temp path
118+
external_root = tmp_path / "external"
119+
external_root.mkdir()
120+
121+
# 2. Create your file inside that 'external' directory
122+
test_file = external_root / "score_baselibs+" / "src" / "source_file1.py"
123+
test_file.parent.mkdir(parents=True)
124+
test_file.write_text("content")
125+
126+
# 3. Create the path relative to tmp_path so it starts with 'external/'
127+
# Use .relative_to(tmp_path) to get 'external/score_docs_as_code+/...'
128+
test_file.write_text(
129+
"""
130+
# Some code here
131+
# req-Id: tool_req__docs_arch_types
132+
def some_function():
133+
pass
134+
"""
135+
)
136+
output_file = tmp_path / "output.json"
137+
monkeypatch.chdir(tmp_path)
138+
relative_test_file = test_file.relative_to(tmp_path)
139+
test_args: list[Path | str] = [
140+
_MY_PATH.parent
141+
/ "generate_sourcelinks_cli.py", # sys.argv[0] is always the script name
142+
"--output",
143+
str(output_file),
144+
str(relative_test_file),
145+
]
146+
monkeypatch.setattr(sys, "argv", test_args)
147+
result = scripts_bazel.generate_sourcelinks_cli.main()
148+
assert result == 0
149+
with open(output_file) as f:
150+
data: list[dict[str, str | int]] = json.load(f)
151+
assert isinstance(data, list)
152+
assert len(data) == 2
153+
# The first dictionary has to be metadata
154+
assert is_metadata(data[0])
155+
assert data[0]["repo_name"] == "score_baselibs"
156+
# hash & url can not be set in this script therefore HAVE to be empty
157+
assert data[0]["hash"] == ""
158+
assert data[0]["url"] == ""
159+
160+
# Verify schema of each entry
161+
assert_json_internal_types(data[1:])
162+
163+
164+
def test_generate_sourcelinks_cli_file_not_exists(
165+
tmp_path: Path, monkeypatch: pytest.MonkeyPatch
166+
):
167+
external_root = tmp_path / "external"
168+
external_root.mkdir()
169+
170+
# 2. Create your file inside that 'external' directory
171+
test_file = external_root / "score_baselibs+" / "src" / "source_file1.py"
172+
test_file.parent.mkdir(parents=True)
173+
test_file.write_text("content")
174+
175+
# 3. Create the path relative to tmp_path so it starts with 'external/'
176+
# Use .relative_to(tmp_path) to get 'external/score_docs_as_code+/...'
177+
test_file.write_text(
178+
"""
179+
# Some code here
180+
# req-Id: tool_req__docs_arch_types
181+
def some_function():
182+
pass
183+
"""
184+
)
185+
output_file = tmp_path / "output.json"
186+
# BY not changing directory (like above) we can FORCE the file to not exists
187+
relative_test_file = test_file.relative_to(tmp_path)
188+
test_args: list[Path | str] = [
189+
_MY_PATH.parent
190+
/ "generate_sourcelinks_cli.py", # sys.argv[0] is always the script name
191+
"--output",
192+
str(output_file),
193+
str(relative_test_file),
194+
]
195+
monkeypatch.setattr(sys, "argv", test_args)
196+
with pytest.raises(AssertionError):
197+
scripts_bazel.generate_sourcelinks_cli.main()

0 commit comments

Comments
 (0)