Skip to content

Commit a1b0aea

Browse files
committed
Add readme test to examples
Signed-off-by: Fynn Schmitt-Ulms <[email protected]>
1 parent deafbd9 commit a1b0aea

File tree

2 files changed

+83
-1
lines changed

2 files changed

+83
-1
lines changed

tests/examples/test_readmes.py

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
from tests.examples.utils import ReadMe
2+
3+
from pathlib import Path
4+
5+
import pytest
6+
7+
8+
@pytest.mark.example
9+
@pytest.mark.parametrize(
10+
"subdir",
11+
[
12+
"quantization_2of4_sparse_w4a16",
13+
"quantization_kv_cache",
14+
"quantization_w4a16",
15+
"quantization_w8a8_fp8",
16+
"quantization_w8a8_int8",
17+
"quantizing_moe",
18+
],
19+
)
20+
def test_readmes(subdir):
21+
path = Path("examples") / subdir / "README.md"
22+
23+
readme = ReadMe(path)
24+
25+
cmd = readme.get_code_block_content(position=1, lang="bash").split()
26+
27+
assert cmd[0] in ["python", "python3"]
28+
29+
script_path = Path("examples") / subdir / cmd[1]
30+
31+
assert script_path.is_file(), f"Could not find script at {script_path}"

tests/examples/utils.py

Lines changed: 52 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
1-
from typing import Union
1+
import re
2+
from typing import Union, Optional
3+
from pathlib import Path
24

35
import pytest
6+
from bs4 import BeautifulSoup, ResultSet, Tag
7+
from cmarkgfm import github_flavored_markdown_to_html as gfm_to_html
48

59

610
def requires_gpu_count(num_required_gpus: int) -> pytest.MarkDecorator:
@@ -37,3 +41,50 @@ def requires_gpu_mem(required_amount: Union[int, float]) -> pytest.MarkDecorator
3741
f"{actual_vram:.1f} GiB GPU memory found"
3842
)
3943
return pytest.mark.skipif(required_amount > actual_vram, reason=reason)
44+
45+
46+
class ReadMe:
47+
"""
48+
Class representing a README (Markdown) file with methods to expedite common usage.
49+
"""
50+
51+
def __init__(self, path: Path) -> None:
52+
self.path = path
53+
self.content = self.path.expanduser().read_text(encoding="utf-8")
54+
self.__normalize_code_fence_lang()
55+
self.html = gfm_to_html(self.content)
56+
self.soup = BeautifulSoup(self.html, "html.parser")
57+
58+
def __normalize_code_fence_lang(self):
59+
"""
60+
Perform limited normalization on the code language of code blocks to maintain
61+
consistency and simplicity with locating them.
62+
"""
63+
self.content = re.sub(r"```(shell|bash|sh)\b", "```shell", self.content)
64+
65+
def get_code_blocks(self, *, lang: Optional[str] = None) -> ResultSet[Tag]:
66+
"""
67+
Get all code blocks with language `lang`, or all code blocks if `lang` is None
68+
(default).
69+
:param lang: language of code block to filter by
70+
:return: code block `Tag`s found in README
71+
"""
72+
lang = "shell" if lang == "bash" else lang
73+
selector = f'pre[lang="{lang}"] > code' if lang else "pre > code"
74+
tags = self.soup.select(selector)
75+
return tags
76+
77+
def get_code_block_content(
78+
self, *, position: int, lang: Optional[str] = None
79+
) -> str:
80+
"""
81+
Get contents of code block at specified position (starting with 0). Optionally
82+
pass a language specifier, `lang`, to only look at code blocks highlighted for
83+
that language (happens prior to indexing).
84+
:param position: position of code block to get (starting at 0)
85+
:param lang: language of code block to filter by
86+
:return: content of the code block
87+
"""
88+
code_blocks = self.get_code_blocks(lang=lang)
89+
code = code_blocks[position].text.strip()
90+
return code

0 commit comments

Comments
 (0)