Skip to content

Commit e3bc460

Browse files
Mike ProsserMike Prosser
authored andcommitted
misc feedback and FakePythonPanelService
1 parent 74847dc commit e3bc460

File tree

9 files changed

+126
-52
lines changed

9 files changed

+126
-52
lines changed

CONTRIBUTING.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ See [GitHub's official documentation](https://help.github.com/articles/using-pul
2222
# Getting Started
2323

2424
This is the command to generate the files in /src/ni/pythonpanel/v1/:
25-
`poetry run python -m grpc_tools.protoc --proto_path=protos --python_out=src/ --grpc_python_out=src/ --plugin=protoc-gen-mypy=.venv\Scripts\protoc-gen-mypy.exe --mypy_out=src/ --mypy_grpc_out=src/ ni/pythonpanel/v1/python_panel_service.proto`
25+
`poetry run python -m grpc_tools.protoc --proto_path=protos --python_out=src/ --grpc_python_out=src/ --mypy_out=src/ --mypy_grpc_out=src/ ni/pythonpanel/v1/python_panel_service.proto`
2626

2727
# Testing
2828

poetry.lock

Lines changed: 26 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pyproject.toml

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ ni-measurement-plugin-sdk = {version=">=2.3"}
1515
[tool.poetry.group.dev.dependencies]
1616
grpc-stubs = "^1.53"
1717
types-protobuf = ">=4.21"
18+
pyupgrade = "^3.19.1"
1819

1920
[tool.poetry.group.lint.dependencies]
2021
bandit = { version = ">=1.7", extras = ["toml"] }
@@ -41,6 +42,9 @@ build-backend = "poetry.core.masonry.api"
4142
[tool.ni-python-styleguide]
4243
extend_exclude = ".tox,docs,src/ni/pythonpanel/v1"
4344

45+
[tool.black]
46+
extend-exclude = '\.tox/|docs/|src/ni/pythonpanel/v1/'
47+
4448
[tool.mypy]
4549
files = "examples/,src/nipanel/,tests/"
4650
namespace_packages = true
@@ -60,9 +64,4 @@ skips = [
6064

6165
[tool.pytest.ini_options]
6266
addopts = "--doctest-modules --strict-markers"
63-
testpaths = ["src/nipanel", "tests"]
64-
65-
[tool.black]
66-
extend-exclude = '''
67-
/src/ni/pythonpanel/v1/
68-
'''
67+
testpaths = ["src/nipanel", "tests"]

src/nipanel/_panel.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
import sys
44
from abc import ABC, abstractmethod
55
from types import TracebackType
6-
from typing import TYPE_CHECKING, Optional, Type
6+
from typing import TYPE_CHECKING
77

88
from ni_measurement_plugin_sdk_service.discovery import DiscoveryClient
99

@@ -48,10 +48,10 @@ def __enter__(self) -> Self:
4848

4949
def __exit__(
5050
self,
51-
exctype: Optional[Type[BaseException]],
52-
excinst: Optional[BaseException],
53-
exctb: Optional[TracebackType],
54-
) -> Optional[bool]:
51+
exctype: type[BaseException] | None,
52+
excinst: BaseException | None,
53+
exctb: TracebackType | None,
54+
) -> bool | None:
5555
"""Exit the runtime context related to this object."""
5656
self.disconnect()
5757
return None

tests/conftest.py

Lines changed: 24 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,37 +1,42 @@
11
"""Fixtures for testing."""
22

3+
from collections.abc import Generator
34
from concurrent import futures
4-
from typing import Any, Generator
55

66
import grpc
77
import pytest
88
from ni.pythonpanel.v1.python_panel_service_pb2_grpc import (
99
PythonPanelServiceStub,
10-
add_PythonPanelServiceServicer_to_server,
1110
)
1211

13-
from tests.utils._fake_python_panel_servicer import FakePythonPanelServicer
12+
from tests.utils._fake_python_panel_service import FakePythonPanelService
1413

1514

1615
@pytest.fixture
17-
def fake_python_panel_service() -> Generator[tuple[FakePythonPanelServicer, int], Any, None]:
16+
def fake_python_panel_service() -> Generator[FakePythonPanelService]:
1817
"""Fixture to create a FakePythonPanelServicer for testing."""
19-
thread_pool = futures.ThreadPoolExecutor(max_workers=10)
20-
server = grpc.server(thread_pool)
21-
servicer = FakePythonPanelServicer()
22-
add_PythonPanelServiceServicer_to_server(servicer, server)
23-
port = server.add_insecure_port("[::]:0")
24-
server.start()
25-
yield servicer, port
26-
server.stop(None)
18+
with futures.ThreadPoolExecutor(max_workers=10) as thread_pool:
19+
service = FakePythonPanelService()
20+
service.start(thread_pool)
21+
yield service
22+
service.stop()
2723

2824

2925
@pytest.fixture
30-
def fake_python_panel_service_stub(
31-
fake_python_panel_service: tuple[FakePythonPanelServicer, int],
32-
) -> Generator[PythonPanelServiceStub, Any, None]:
33-
"""Fixture to attach a PythonPanelSericeStub to a FakePythonPanelService."""
34-
_, port = fake_python_panel_service
35-
channel = grpc.insecure_channel(f"localhost:{port}")
36-
yield PythonPanelServiceStub(channel)
26+
def grpc_channel_to_fake_panel_service(
27+
fake_python_panel_service: FakePythonPanelService,
28+
) -> Generator[grpc.Channel]:
29+
"""Fixture to get a channel to the FakePythonPanelService."""
30+
service = fake_python_panel_service
31+
channel = grpc.insecure_channel(f"localhost:{service.port}")
32+
yield channel
3733
channel.close()
34+
35+
36+
@pytest.fixture
37+
def python_panel_service_stub(
38+
grpc_channel_to_fake_panel_service: grpc.Channel,
39+
) -> Generator[PythonPanelServiceStub]:
40+
"""Fixture to get a PythonPanelServiceStub, attached to a FakePythonPanelService."""
41+
channel = grpc_channel_to_fake_panel_service
42+
yield PythonPanelServiceStub(channel)
Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
from google.protobuf.any_pb2 import Any
2+
from google.protobuf.wrappers_pb2 import StringValue
23
from ni.pythonpanel.v1.python_panel_service_pb2 import (
34
ConnectRequest,
45
DisconnectRequest,
@@ -8,37 +9,38 @@
89
from ni.pythonpanel.v1.python_panel_service_pb2_grpc import PythonPanelServiceStub
910

1011

11-
def test___connect___gets_response(fake_python_panel_service_stub: PythonPanelServiceStub) -> None:
12+
def test___connect___gets_response(python_panel_service_stub: PythonPanelServiceStub) -> None:
1213
request = ConnectRequest(panel_id="test_panel", panel_uri="path/to/panel")
13-
response = fake_python_panel_service_stub.Connect(request)
14+
response = python_panel_service_stub.Connect(request)
1415

1516
assert response is not None # Ensure response is returned
1617

1718

1819
def test___disconnect___gets_response(
19-
fake_python_panel_service_stub: PythonPanelServiceStub,
20+
python_panel_service_stub: PythonPanelServiceStub,
2021
) -> None:
2122
request = DisconnectRequest(panel_id="test_panel")
22-
response = fake_python_panel_service_stub.Disconnect(request)
23+
response = python_panel_service_stub.Disconnect(request)
2324

2425
assert response is not None # Ensure response is returned
2526

2627

2728
def test___get_value___gets_response(
28-
fake_python_panel_service_stub: PythonPanelServiceStub,
29+
python_panel_service_stub: PythonPanelServiceStub,
2930
) -> None:
3031
request = GetValueRequest(panel_id="test_panel", value_id="test_value")
31-
response = fake_python_panel_service_stub.GetValue(request)
32+
response = python_panel_service_stub.GetValue(request)
3233

3334
assert response is not None # Ensure response is returned
3435
assert isinstance(response.value, Any) # Ensure the value is of type `Any`
3536

3637

3738
def test___set_value___gets_response(
38-
fake_python_panel_service_stub: PythonPanelServiceStub,
39+
python_panel_service_stub: PythonPanelServiceStub,
3940
) -> None:
40-
test_value = Any(value=b"test_data")
41+
test_value = Any()
42+
test_value.Pack(StringValue(value="test_value"))
4143
request = SetValueRequest(panel_id="test_panel", value_id="test_value", value=test_value)
42-
response = fake_python_panel_service_stub.SetValue(request)
44+
response = python_panel_service_stub.SetValue(request)
4345

4446
assert response is not None # Ensure response is returned

tests/unit/test_panel.py

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
from tests.utils._fake_python_panel_servicer import FakePythonPanelServicer
1+
from tests.utils._fake_python_panel_service import FakePythonPanelService
22
from tests.utils._port_panel import PortPanel
33

44

@@ -9,10 +9,10 @@ def test___panel___has_panel_id_and_panel_uri() -> None:
99

1010

1111
def test___connected_panel___set_value___gets_same_value(
12-
fake_python_panel_service: tuple[FakePythonPanelServicer, int],
12+
fake_python_panel_service: FakePythonPanelService,
1313
) -> None:
14-
_, port = fake_python_panel_service
15-
panel = PortPanel(port, "my_panel", "path/to/script")
14+
service = fake_python_panel_service
15+
panel = PortPanel(service.port, "my_panel", "path/to/script")
1616
panel.connect()
1717

1818
panel.set_value("test_id", "test_value")
@@ -23,24 +23,24 @@ def test___connected_panel___set_value___gets_same_value(
2323

2424

2525
def test___with_panel___set_value___gets_same_value(
26-
fake_python_panel_service: tuple[FakePythonPanelServicer, int],
26+
fake_python_panel_service: FakePythonPanelService,
2727
) -> None:
28-
_, port = fake_python_panel_service
29-
with PortPanel(port, "my_panel", "path/to/script") as panel:
28+
service = fake_python_panel_service
29+
with PortPanel(service.port, "my_panel", "path/to/script") as panel:
3030
panel.set_value("test_id", "test_value")
3131

3232
# TODO: AB#3095681 - change asserted value to test_value
3333
assert panel.get_value("test_id") == "placeholder value"
3434

3535

3636
def test___first_connect_fails___connect___gets_value(
37-
fake_python_panel_service: tuple[FakePythonPanelServicer, int],
37+
fake_python_panel_service: FakePythonPanelService,
3838
) -> None:
3939
"""Test that panel.connect() will automatically retry once."""
40-
servicer, port = fake_python_panel_service
40+
service = fake_python_panel_service
4141
# Simulate a failure on the first connect attempt
42-
servicer.fail_next_connect()
43-
panel = PortPanel(port, "my_panel", "path/to/script")
42+
service.servicer.fail_next_connect()
43+
panel = PortPanel(service.port, "my_panel", "path/to/script")
4444

4545
panel.connect()
4646

tests/utils/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
"""Test Utilities for the Panel."""
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
from concurrent import futures
2+
3+
import grpc
4+
from ni.pythonpanel.v1.python_panel_service_pb2_grpc import (
5+
add_PythonPanelServiceServicer_to_server,
6+
)
7+
8+
from tests.utils._fake_python_panel_servicer import FakePythonPanelServicer
9+
10+
11+
class FakePythonPanelService:
12+
"""Encapsulates a fake PythonPanelService with a gRPC server for testing."""
13+
14+
_server: grpc.Server
15+
_port: int
16+
_servicer: FakePythonPanelServicer
17+
18+
def __init__(self) -> None:
19+
"""Initialize the fake PythonPanelService."""
20+
self._servicer = FakePythonPanelServicer()
21+
22+
def start(self, thread_pool: futures.ThreadPoolExecutor) -> None:
23+
"""Start the gRPC server and return the port it is bound to."""
24+
self._server = grpc.server(thread_pool)
25+
add_PythonPanelServiceServicer_to_server(self._servicer, self._server)
26+
self._port = self._server.add_insecure_port("[::1]:0")
27+
self._server.start()
28+
29+
def stop(self) -> None:
30+
"""Stop the gRPC server."""
31+
if self._server:
32+
self._server.stop(None)
33+
34+
@property
35+
def servicer(self) -> FakePythonPanelServicer:
36+
"""Get the servicer instance."""
37+
return self._servicer
38+
39+
@property
40+
def port(self) -> int:
41+
"""Get the port the server is bound to."""
42+
return self._port

0 commit comments

Comments
 (0)