Skip to content

Commit 7b27586

Browse files
committed
Test things
1 parent 07394af commit 7b27586

6 files changed

Lines changed: 167 additions & 17 deletions

File tree

src/blueapi/cli/cli.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@
3939
from blueapi.service.model import DeviceResponse, PlanResponse, SourceInfo, TaskRequest
4040
from blueapi.worker import ProgressEvent, WorkerEvent
4141

42+
from . import stubgen
4243
from .scratch import setup_scratch
4344
from .updates import CliEventRenderer
4445

@@ -167,7 +168,6 @@ def generate_stubs(obj: dict, target: Path):
167168

168169
config: ApplicationConfig = obj["config"]
169170
bc = BlueapiClient.from_config(config)
170-
from . import stubgen
171171

172172
stubgen.generate_stubs(Path(target), list(bc.plans), list(bc.devices))
173173

src/blueapi/client/cache.py

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -22,10 +22,7 @@ class PlanCache:
2222
"""
2323

2424
def __init__(self, runner: PlanRunner, plans: list[PlanModel]):
25-
self._cache = {
26-
model.name: Plan(name=model.name, model=model, runner=runner)
27-
for model in plans
28-
}
25+
self._cache = {model.name: Plan(model=model, runner=runner) for model in plans}
2926
for name, plan in self._cache.items():
3027
if name.startswith("_"):
3128
continue
@@ -56,12 +53,12 @@ class Plan:
5653
5754
blueapi generate-stubs /tmp/blueapi-stubs
5855
uv add --editable /tmp/blueapi-stubs
56+
5957
"""
6058

6159
model: PlanModel
6260

63-
def __init__(self, name, model: PlanModel, runner: PlanRunner):
64-
self.name = name
61+
def __init__(self, model: PlanModel, runner: PlanRunner):
6562
self.model = model
6663
self._runner = runner
6764
self.__doc__ = model.description
@@ -72,6 +69,10 @@ def __call__(self, *args, **kwargs) -> WorkerEvent:
7269
"""
7370
return self._runner(self.name, self._build_args(*args, **kwargs))
7471

72+
@property
73+
def name(self) -> str:
74+
return self.model.name
75+
7576
@property
7677
def help_text(self) -> str:
7778
return self.model.description or f"Plan {self!r}"

src/blueapi/stubs/templates/cache_template.pyi.jinja

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -30,16 +30,18 @@ class PlanCache:
3030
{{ item.docs | docstring | indent(8) }}
3131
"""
3232
...
33-
{% endfor -%}
33+
{%- endfor %}
3434
### End
3535

3636

3737
class Plan:
3838
model: PlanModel
39-
name: str
40-
def __init__(self, name, model: PlanModel, runner: PlanRunner) -> None: ...
39+
def __init__(self, model: PlanModel, runner: PlanRunner) -> None: ...
4140
def __call__(self, *args, **kwargs): # -> None:
4241
...
42+
43+
@property
44+
def name(self) -> str: ...
4345
@property
4446
def help_text(self) -> str: ...
4547
@property

tests/unit_tests/cli/test_stubgen.py

Lines changed: 137 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,18 @@
1+
from io import StringIO
2+
from textwrap import dedent
13
from types import FunctionType
4+
from unittest.mock import Mock
25

36
import pytest
47

5-
from blueapi.cli.stubgen import _docstring, _type_string
8+
from blueapi.cli.stubgen import (
9+
_docstring,
10+
_type_string,
11+
generate_stubs,
12+
render_stub_file,
13+
)
14+
from blueapi.client.cache import DeviceRef, Plan
15+
from blueapi.service.model import DeviceModel, PlanModel
616

717

818
def single_line():
@@ -76,3 +86,129 @@ def test_docstring_filter(input: FunctionType, expected: str):
7686
)
7787
def test_type_string(typ: dict, expected: str):
7888
assert _type_string(typ) == expected
89+
90+
91+
def test_render_empty():
92+
output = StringIO()
93+
94+
render_stub_file(output, [], [])
95+
plan_text, device_text = _extract_rendered(output)
96+
97+
assert plan_text == ""
98+
assert device_text == ""
99+
100+
101+
FOO = PlanModel(name="empty", description="Doc string for empty", schema={})
102+
103+
BAR = PlanModel(
104+
name="two_args",
105+
description="Doc string for two_args",
106+
schema={
107+
"properties": {
108+
"one": {"type": "integer"},
109+
"two": {"type": "string"},
110+
},
111+
"required": ["one"],
112+
},
113+
)
114+
115+
116+
def test_render_empty_plan_function():
117+
output = StringIO()
118+
plans = [Plan(model=FOO, runner=Mock())]
119+
render_stub_file(output, plans, [])
120+
plan_text, device_text = _extract_rendered(output)
121+
122+
assert device_text == ""
123+
124+
assert (
125+
plan_text
126+
== """\
127+
def empty(self,
128+
) -> WorkerEvent:
129+
\"""
130+
Doc string for empty
131+
\"""
132+
...\n"""
133+
)
134+
135+
136+
def test_render_multiple_plan_functions():
137+
output = StringIO()
138+
runner = Mock()
139+
plans = [Plan(FOO, runner), Plan(BAR, runner)]
140+
render_stub_file(output, plans, [])
141+
plan_text, device_text = _extract_rendered(output)
142+
assert device_text == ""
143+
144+
assert (
145+
plan_text
146+
== """\
147+
def empty(self,
148+
) -> WorkerEvent:
149+
\"""
150+
Doc string for empty
151+
\"""
152+
...
153+
def two_args(self,
154+
one: int,
155+
two: str | None = None,
156+
) -> WorkerEvent:
157+
\"""
158+
Doc string for two_args
159+
\"""
160+
...\n"""
161+
)
162+
163+
164+
def test_device_fields():
165+
output = StringIO()
166+
cache = Mock()
167+
devices = [
168+
DeviceRef("one", cache, DeviceModel(name="one", protocols=[])),
169+
DeviceRef("two", cache, DeviceModel(name="two", protocols=[])),
170+
]
171+
render_stub_file(output, [], devices)
172+
173+
plan_text, device_text = _extract_rendered(output)
174+
assert plan_text == ""
175+
assert device_text == " one: DeviceRef\n two: DeviceRef\n"
176+
177+
178+
def test_package_creation(tmp_path):
179+
generate_stubs(tmp_path / "blueapi-stubs", [], [])
180+
with open(tmp_path / "blueapi-stubs" / "pyproject.toml") as pyproj:
181+
assert pyproj.read().startswith(
182+
dedent("""
183+
[project]
184+
name = "blueapi-stubs"
185+
version = "0.1.0"
186+
""")
187+
)
188+
with open(
189+
tmp_path / "blueapi-stubs" / "src" / "blueapi-stubs" / "py.typed"
190+
) as typed:
191+
assert typed.read() == "partial\n"
192+
193+
assert (
194+
tmp_path / "blueapi-stubs" / "src" / "blueapi-stubs" / "client" / "cache.pyi"
195+
).exists()
196+
197+
198+
def _extract_rendered(src: StringIO) -> tuple[str, str]:
199+
src.seek(0)
200+
_read_until_line(src, "### Generated plans")
201+
plan_text = _read_until_line(src, "### End")
202+
_read_until_line(src, "### Generated devices")
203+
device_text = _read_until_line(src, "### End")
204+
return plan_text, device_text
205+
206+
207+
def _read_until_line(src: StringIO, match: str) -> str:
208+
text = ""
209+
for line in src:
210+
if line.startswith(match):
211+
break
212+
text += line
213+
214+
return text

tests/unit_tests/client/test_client.py

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -689,13 +689,12 @@ def test_device_ignores_underscores():
689689

690690

691691
def test_plan_help_text():
692-
plan = Plan("foo", PlanModel(name="foo", description="help for foo"), Mock())
692+
plan = Plan(PlanModel(name="foo", description="help for foo"), Mock())
693693
assert plan.help_text == "help for foo"
694694

695695

696696
def test_plan_fallback_help_text():
697697
plan = Plan(
698-
"foo",
699698
PlanModel(
700699
name="foo",
701700
schema={"properties": {"one": {}, "two": {}}, "required": ["one"]},
@@ -707,7 +706,6 @@ def test_plan_fallback_help_text():
707706

708707
def test_plan_properties():
709708
plan = Plan(
710-
"foo",
711709
PlanModel(
712710
name="foo",
713711
schema={"properties": {"one": {}, "two": {}}, "required": ["one"]},
@@ -721,7 +719,7 @@ def test_plan_properties():
721719

722720
def test_plan_empty_fallback_help_text():
723721
plan = Plan(
724-
"foo", PlanModel(name="foo", schema={"properties": {}, "required": []}), Mock()
722+
PlanModel(name="foo", schema={"properties": {}, "required": []}), Mock()
725723
)
726724
assert plan.help_text == "Plan foo()"
727725

@@ -741,7 +739,7 @@ def test_plan_empty_fallback_help_text():
741739
)
742740
def test_plan_param_mapping(args, kwargs, params):
743741
runner = Mock()
744-
plan = Plan(FULL_PLAN.name, FULL_PLAN, runner)
742+
plan = Plan(FULL_PLAN, runner)
745743

746744
plan(*args, **kwargs)
747745
runner.assert_called_once_with("foobar", params)
@@ -765,7 +763,6 @@ def test_plan_param_mapping(args, kwargs, params):
765763
def test_plan_invalid_param_mapping(args, kwargs, msg):
766764
runner = Mock(spec=Callable)
767765
plan = Plan(
768-
FULL_PLAN.name,
769766
FULL_PLAN,
770767
runner,
771768
)

tests/unit_tests/test_cli.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1329,3 +1329,17 @@ def test_config_schema(
13291329
stream.write.assert_called()
13301330
else:
13311331
assert json.loads(result.output) == expected
1332+
pass
1333+
1334+
1335+
@patch("blueapi.client.client.BlueapiClient.from_config")
1336+
@patch("blueapi.cli.cli.stubgen")
1337+
def test_genstubs(
1338+
stubgen,
1339+
client,
1340+
runner: CliRunner,
1341+
):
1342+
runner.invoke(main, ["generate-stubs", "/path/to/stub_dir"])
1343+
stubgen.generate_stubs.assert_called_once_with(
1344+
Path("/path/to/stub_dir"), list(client().plans), list(client().devices)
1345+
)

0 commit comments

Comments
 (0)