Skip to content

Commit 4b182d2

Browse files
authored
known_good.json maybe use "version" instead of "hash" (#471)
1 parent 3815e8b commit 4b182d2

4 files changed

Lines changed: 195 additions & 20 deletions

File tree

docs/internals/extensions/source_code_linker.md

Lines changed: 35 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ The feature is split into **two layers**:
1212

1313
1. **Bazel pre-processing (before Sphinx runs)**
1414
Generates and aggregates JSON caches containing the raw `source_code_link` findings (and, if available, repository metadata like `repo_name/hash/url`).
15+
Note that "hash" might also be a "version" tag.
1516

1617
2. **Sphinx extension (during the Sphinx build)**
1718
Reads the aggregated JSON, validates and adapts it, and finally injects the links into Sphinx needs in the final layout (**RepoSourceLink**).
@@ -36,6 +37,7 @@ one JSON cache per repository.
3637
It also adds metadata to each needlink that is needed in further steps.
3738

3839
Example of requirement tags:
40+
3941
```python
4042
# Choose one or the other, both mentioned here to avoid detection
4143
# req-Id/req-traceability: <NEED_ID>
@@ -83,20 +85,23 @@ This step also fills in url & hash if there is a known_good_json provided (e.g.
8385

8486
(repo-metadata-rules)=
8587
#### Repo metadata rules
88+
8689
Here are some basic rules regarding the MetaData information
8790

8891
In a combo build, a known_good_json **must** be provided.
8992
If known_good_json is found then the hash & url will be filled for each need by the script.
9093

9194
Combo build:
92-
- `repo_name`: repository name (parsed from filepath in step 1)
93-
- `hash`: revision/commit hash (as provided by the known_good_json)
94-
- `url`: repository remote URL (as provided by the known_good_json)
95+
96+
- `repo_name`: repository name (parsed from filepath in step 1)
97+
- `hash`: revision/commit hash (as provided by the known_good_json)
98+
- `url`: repository remote URL (as provided by the known_good_json)
9599

96100
Local build:
97-
- `repo_name`: 'local_repo'
98-
- `hash`: will be empty at this point, and later filled via parsing the git commands
99-
- `url`: will be empty at this point, and later filled via parsing the git commands
101+
102+
- `repo_name`: 'local_repo'
103+
- `hash`: will be empty at this point, and later filled via parsing the git commands
104+
- `url`: will be empty at this point, and later filled via parsing the git commands
100105

101106
---
102107

@@ -140,6 +145,7 @@ def test_feature():
140145
...
141146

142147
```
148+
143149
> Note: If you use the decorator, it will check that you have specified a docstring inside the function.
144150
145151
#### Data Flow
@@ -153,7 +159,7 @@ def test_feature():
153159
- Result (e.g. passed, failed, skipped)
154160
- Result text (if failed/skipped will check if message was attached to it)
155161
- Verifications (`PartiallyVerifies`, `FullyVerifies`)
156-
- Also parses metadata according to the [metadata rules](#repo-metadata-rules)
162+
- Also parses metadata according to the [metadata rules](#repo-metadata-rules)
157163

158164
- Cases without metadata are logged out as info (not errors).
159165
- Test cases with metadata are converted into:
@@ -168,7 +174,9 @@ def test_feature():
168174
- Warns on missing need IDs.
169175

170176
#### Example JSON Cache (DataFromTestCase)
177+
171178
The DataFromTestCase depicts the information gathered about one testcase.
179+
172180
```json
173181
[
174182
{
@@ -246,6 +254,7 @@ Instead of repeating `repo_name/hash/url` for every single link entry, the final
246254
- All links belonging to that repository are stored beneath it
247255

248256
This somewhat looks like this:
257+
249258
```{code-block} json
250259
[
251260
{
@@ -296,20 +305,22 @@ During the Sphinx build process, the extension applies the computed links to nee
296305
## Known Limitations
297306

298307
### General
299-
- In combo builds, known_good_json is required.
300-
- inefficiencies in creating the links and saving the JSON caches
301-
- Not compatible with **Esbonio/Live_preview**
308+
309+
- In combo builds, known_good_json is required.
310+
- inefficiencies in creating the links and saving the JSON caches
311+
- Not compatible with **Esbonio/Live_preview**
302312

303313
### Codelinks
304-
- GitHub links may 404 if the commit isn’t pushed
305-
- Tags must match exactly (e.g. #<!-- comment prevents parsing this occurance --> req-Id)
306-
- `source_code_link` isn’t visible until the full Sphinx build is completed
314+
315+
- GitHub links may 404 if the commit isn’t pushed
316+
- Tags must match exactly (e.g. #<!-- comment prevents parsing this occurance --> req-Id)
317+
- `source_code_link` isn’t visible until the full Sphinx build is completed
307318

308319
### TestLink
309320

310-
- GitHub links may 404 if the commit isn’t pushed
311-
- XML structure must be followed exactly (e.g. `properties & attributes`)
312-
- Relies on test to be executed first
321+
- GitHub links may 404 if the commit isn’t pushed
322+
- XML structure must be followed exactly (e.g. `properties & attributes`)
323+
- Relies on test to be executed first
313324

314325
---
315326

@@ -332,16 +343,19 @@ rm -rf _build/
332343
## Internal Overview
333344

334345
The bazel part:
335-
```
346+
347+
```text
336348
scripts_bazel/
337349
├── BUILD # Declare libraries and filegroups needed for bazel
338350
├── generate_sourcelinks_cli.py # Bazel step 1 => Parses sourcefiles for tags
339351
├── merge_sourcelinks.py
340352
└── tests
341353
│ └── ...
342354
```
355+
343356
The Sphinx extension
344-
```
357+
358+
```text
345359
score_source_code_linker/
346360
├── __init__.py # Main Sphinx extension; combines CodeLinks + TestLinks
347361
├── generate_source_code_links_json.py # Most functionality moved to 'scripts_bazel/generate_sourcelinks_cli'
@@ -356,6 +370,7 @@ score_source_code_linker/
356370
```
357371

358372
---
373+
359374
## Clearing Cache Manually
360375

361376
To clear the build cache, run:
@@ -364,7 +379,8 @@ To clear the build cache, run:
364379
rm -rf _build/
365380
```
366381

367-
## Examples:
382+
## Examples
383+
368384
To see working examples for CodeLinks & TestLinks, take a look at the Docs-As-Code documentation.
369385

370386
[Example CodeLink](https://eclipse-score.github.io/docs-as-code/main/requirements/requirements.html#tool_req__docs_common_attr_id_scheme)

src/extensions/score_source_code_linker/helpers.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -107,5 +107,10 @@ def parse_info_from_known_good(
107107
for category in kg_json["modules"].values():
108108
if repo_name in category:
109109
m = category[repo_name]
110-
return (m["hash"], m["repo"].removesuffix(".git"))
110+
hash_or_version = m.get("hash") or m.get("version")
111+
if hash_or_version is None:
112+
raise KeyError(
113+
f"Module {repo_name} has neither 'hash' nor 'version' key."
114+
)
115+
return (hash_or_version, m["repo"].removesuffix(".git"))
111116
raise KeyError(f"Module {repo_name} not found in known_good_json.")

src/extensions/score_source_code_linker/tests/test_helpers.py

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -189,6 +189,76 @@ def test_parse_info_from_known_good_empty_repo_dict_in_json(tmp_path: Path):
189189
parse_info_from_known_good(json_file, "any_repo")
190190

191191

192+
VALID_KNOWN_GOOD_WITH_VERSION = {
193+
"modules": {
194+
"target_sw": {
195+
"score_baselibs": {
196+
"repo": "https://github.com/eclipse-score/baselibs.git",
197+
"version": "v1.2.3",
198+
},
199+
},
200+
"tooling": {
201+
"score_docs_as_code": {
202+
"repo": "https://github.com/eclipse-score/docs-as-code.git",
203+
"version": "4.3-alpha",
204+
}
205+
},
206+
}
207+
}
208+
209+
210+
@pytest.fixture
211+
def known_good_json_with_version(tmp_path: Path):
212+
"""Providing a known_good.json file that uses 'version' instead of 'hash'."""
213+
json_file = tmp_path / "known_good_version.json"
214+
_ = json_file.write_text(json.dumps(VALID_KNOWN_GOOD_WITH_VERSION))
215+
return json_file
216+
217+
218+
def test_parse_info_from_known_good_with_version(known_good_json_with_version: Path):
219+
"""Test that 'version' is accepted as a fallback when 'hash' is absent."""
220+
hash_result, repo_result = parse_info_from_known_good(
221+
known_good_json_with_version, "score_baselibs"
222+
)
223+
224+
assert hash_result == "v1.2.3"
225+
assert repo_result == "https://github.com/eclipse-score/baselibs"
226+
227+
228+
def test_parse_info_from_known_good_with_version_different_category(
229+
known_good_json_with_version: Path,
230+
):
231+
"""Test that 'version' works for a module in a different category."""
232+
hash_result, repo_result = parse_info_from_known_good(
233+
known_good_json_with_version, "score_docs_as_code"
234+
)
235+
236+
assert hash_result == "4.3-alpha"
237+
assert repo_result == "https://github.com/eclipse-score/docs-as-code"
238+
239+
240+
def test_parse_info_from_known_good_neither_hash_nor_version(tmp_path: Path):
241+
"""Test that KeyError is raised when neither 'hash' nor 'version' is present."""
242+
json_file = tmp_path / "broken.json"
243+
_ = json_file.write_text(
244+
json.dumps(
245+
{
246+
"modules": {
247+
"target_sw": {
248+
"score_baselibs": {
249+
"repo": "https://github.com/eclipse-score/baselibs.git",
250+
}
251+
}
252+
}
253+
}
254+
)
255+
)
256+
257+
msg = "score_baselibs has neither 'hash' nor 'version'"
258+
with pytest.raises(KeyError, match=msg):
259+
parse_info_from_known_good(json_file, "score_baselibs")
260+
261+
192262
# Tests for get_github_link_from_json
193263
def test_get_github_link_from_json_happy_path():
194264
"""

src/extensions/score_source_code_linker/tests/test_xml_parser.py

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@
2121
Once we enable those we will need to change the tests
2222
"""
2323

24+
import json
25+
import os
2426
import xml.etree.ElementTree as ET
2527
from collections.abc import Callable
2628
from pathlib import Path
@@ -382,3 +384,85 @@ def test_clean_test_file_name_empty_path_raises_error():
382384
ValueError, match="Filepath does not have 'bazel-testlogs' nor 'tests-report'"
383385
):
384386
xml_parser.clean_test_file_name(raw_path)
387+
388+
389+
# ╭──────────────────────────────────────────────────────────╮
390+
# │ Tests for get_metadata_from_test_path │
391+
# ╰──────────────────────────────────────────────────────────╯
392+
393+
_KNOWN_GOOD_WITH_HASH = {
394+
"modules": {
395+
"tooling": {
396+
"score_docs_as_code": {
397+
"repo": "https://github.com/eclipse-score/docs-as-code.git",
398+
"hash": "abc123hashvalue",
399+
}
400+
}
401+
}
402+
}
403+
404+
_KNOWN_GOOD_WITH_VERSION = {
405+
"modules": {
406+
"tooling": {
407+
"score_docs_as_code": {
408+
"repo": "https://github.com/eclipse-score/docs-as-code.git",
409+
"version": "v2.1.0",
410+
}
411+
}
412+
}
413+
}
414+
415+
_COMBO_TEST_PATH = Path(
416+
"/root/bazel-testlogs/external/score_docs_as_code+/src/ext/score_foo/test.xml"
417+
)
418+
419+
420+
def test_get_metadata_from_test_path_local():
421+
"""Local builds produce empty hash/url without reading known_good.json."""
422+
local_path = Path(
423+
"/home/root/docs-as-code/bazel-testlogs/src/extensions/foo/test.xml"
424+
)
425+
md = xml_parser.get_metadata_from_test_path(local_path)
426+
assert md["repo_name"] == "local_repo"
427+
assert md["hash"] == ""
428+
assert md["url"] == ""
429+
430+
431+
def test_get_metadata_from_test_path_combo_with_hash(tmp_path: Path):
432+
"""Combo builds with 'hash' in known_good.json populate metadata correctly."""
433+
json_file = tmp_path / "known_good.json"
434+
json_file.write_text(json.dumps(_KNOWN_GOOD_WITH_HASH))
435+
436+
old = os.environ.get("KNOWN_GOOD_JSON")
437+
try:
438+
os.environ["KNOWN_GOOD_JSON"] = str(json_file)
439+
md = xml_parser.get_metadata_from_test_path(_COMBO_TEST_PATH)
440+
finally:
441+
if old is None:
442+
os.environ.pop("KNOWN_GOOD_JSON", None)
443+
else:
444+
os.environ["KNOWN_GOOD_JSON"] = old
445+
446+
assert md["repo_name"] == "score_docs_as_code"
447+
assert md["hash"] == "abc123hashvalue"
448+
assert md["url"] == "https://github.com/eclipse-score/docs-as-code"
449+
450+
451+
def test_get_metadata_from_test_path_combo_with_version(tmp_path: Path):
452+
"""Combo builds with 'version' in known_good.json populate metadata correctly."""
453+
json_file = tmp_path / "known_good.json"
454+
json_file.write_text(json.dumps(_KNOWN_GOOD_WITH_VERSION))
455+
456+
old = os.environ.get("KNOWN_GOOD_JSON")
457+
try:
458+
os.environ["KNOWN_GOOD_JSON"] = str(json_file)
459+
md = xml_parser.get_metadata_from_test_path(_COMBO_TEST_PATH)
460+
finally:
461+
if old is None:
462+
os.environ.pop("KNOWN_GOOD_JSON", None)
463+
else:
464+
os.environ["KNOWN_GOOD_JSON"] = old
465+
466+
assert md["repo_name"] == "score_docs_as_code"
467+
assert md["hash"] == "v2.1.0"
468+
assert md["url"] == "https://github.com/eclipse-score/docs-as-code"

0 commit comments

Comments
 (0)