8000 Improve testing harness to allow tests on real devices (#197) · akshat-ja/python-kasa@1803a83 · GitHub
[go: up one dir, main page]

Skip to content

Commit 1803a83

Browse files
authored
Improve testing harness to allow tests on real devices (python-kasa#197)
* test_cli: provide return values to patched objects to avoid warning about non-awaited calls * test_cli: restore alias after testing * smartstrip: remove internal update() calls for turn_{on,off}, set_led * Make sure power is always a float * Fix discovery tests * Make tests runnable on real devices * Add a note about running tests on a real device * test_strip: run update against the parent device
1 parent b088596 commit 1803a83

14 files changed

+70
-24
lines changed

README.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,15 @@ This will make sure that the checks are passing when you do a commit.
9494

9595
You can also execute the checks by running either `tox -e lint` to only do the linting checks, or `tox` to also execute the tests.
9696

97+
### Running tests
98+
99+
You can run tests on the library by executing `pytest` in the source directory.
100+
This will run the tests against contributed example responses, but you can also execute the tests against a real device:
101+
```
102+
pytest --ip <address>
103+
```
104+
Note that this will perform state changes on the device.
105+
97106
### Analyzing network captures
98107

99108
The simplest way to add support for a new device or to improve existing ones is to capture traffic between the mobile app and the device.

kasa/cli.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -247,6 +247,7 @@ async def alias(dev, new_alias, index):
247247
if new_alias is not None:
248248
click.echo(f"Setting alias to {new_alias}")
249249
click.echo(await dev.set_alias(new_alias))
250+
await dev.update()
250251

251252
click.echo(f"Alias: {dev.alias}")
252253
if dev.is_strip:

kasa/smartdevice.py

Lines changed: 3 additions & 2 deletions
10000
Original file line numberDiff line numberDiff line change
@@ -603,7 +603,7 @@ async def current_consumption(self) -> float:
603603
raise SmartDeviceException("Device has no emeter")
604604

605605
response = EmeterStatus(await self.get_emeter_realtime())
606-
return response["power"]
606+
return float(response["power"])
607607

608608
async def reboot(self, delay: int = 1) -> None:
609609
"""Reboot the device.
@@ -658,7 +658,8 @@ def state_information(self) -> Dict[str, Any]:
658658
def device_id(self) -> str:
659659
"""Return unique ID for the device.
660660
661-
This is the MAC address of the device.
661+
If not overridden, this is the MAC address of the device.
662+
Individual sockets on strips will override this.
662663
"""
663664
return self.mac
664665

kasa/smartstrip.py

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -100,12 +100,10 @@ async def update(self):
100100
async def turn_on(self, **kwargs):
101101
"""Turn the strip on."""
102102
await self._query_helper("system", "set_relay_state", {"state": 1})
103-
await self.update()
104103

105104
async def turn_off(self, **kwargs):
106105
"""Turn the strip off."""
107106
await self._query_helper("system", "set_relay_state", {"state": 0})
108-
await self.update()
109107

110108
@property # type: ignore
111109
@requires_update
@@ -126,7 +124,6 @@ def led(self) -> bool:
126124
async def set_led(self, state: bool):
127125
"""Set the state of the led (night mode)."""
128126
await self._query_helper("system", "set_led_off", {"off": int(not state)})
129-
await self.update()
130127

131128
@property # type: ignore
132129
@requires_update

kasa/tests/conftest.py

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -147,7 +147,7 @@ def get_device_for_file(file):
147147
with open(p) as f:
148148
sysinfo = json.load(f)
149149
model = basename(file)
150-
p = device_for_file(model)(host="123.123.123.123")
150+
p = device_for_file(model)(host="127.0.0.123")
151151
p.protocol = FakeTransportProtocol(sysinfo)
152152
asyncio.run(p.update())
153153
return p
@@ -168,21 +168,29 @@ def dev(request):
168168
asyncio.run(d.update())
169169
if d.model in file:
170170
return d
171-
raise Exception("Unable to find type for %s" % ip)
171+
else:
172+
pytest.skip(f"skipping file {file}")
172173

173174
return get_device_for_file(file)
174175

175176

176177
def pytest_addoption(parser):
177-
parser.addoption("--ip", action="store", default=None, help="run against device")
178+
parser.addoption(
179+
"--ip", action="store", default=None, help="run against device on given ip"
180+
)
178181

179182

180183
def pytest_collection_modifyitems(config, items):
181184
if not config.getoption("--ip"):
182185
print("Testing against fixtures.")
183-
return
184186
else:
185187
print("Running against ip %s" % config.getoption("--ip"))
188+
requires_dummy = pytest.mark.skip(
189+
reason="test requires to be run against dummy data"
190+
)
191+
for item in items:
192+
if "requires_dummy" in item.keywords:
193+
item.add_marker(requires_dummy)
186194

187195

188196
# allow mocks to be awaited

kasa/tests/newfakes.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -124,7 +124,7 @@ def lb_dev_state(x):
124124
"dft_on_state": Optional(
125125
{
126126
"brightness": All(int, Range(min=0, max=100)),
127-
"color_temp": All(int, Range(min=2000, max=9000)),
127+
"color_temp": All(int, Range(min=0, max=9000)),
128128
"hue": All(int, Range(min=0, max=255)),
129129
"mode": str,
130130
"saturation": All(int, Range(min=0, max=255)),

kasa/tests/test_bulb.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@ async def test_hsv(dev, turn_on):
6666

6767
await dev.set_hsv(hue=1, saturation=1, value=1)
6868

69+
await dev.update()
6970
hue, saturation, brightness = dev.hsv
7071
assert hue == 1
7172
assert saturation == 1
@@ -134,6 +135,7 @@ async def test_variable_temp_state_information(dev):
134135
async def test_try_set_colortemp(dev, turn_on):
135136
await handle_turn_on(dev, turn_on)
136137
await dev.set_color_temp(2700)
138+
await dev.update()
137139
assert dev.color_temp == 2700
138140

139141

@@ -179,9 +181,11 @@ async def test_dimmable_brightness(dev, turn_on):
179181
assert dev.is_dimmable
180182

181183
await dev.set_brightness(50)
184+
await dev.update()
182185
assert dev.brightness == 50
183186

184187
await dev.set_brightness(10)
188+
await dev.update()
185189
assert dev.brightness == 10
186190

187191
with pytest.raises(ValueError):

kasa/tests/test_cli.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ async def test_state(dev, turn_on):
1818
await handle_turn_on(dev, turn_on)
1919
runner = CliRunner()
2020
res = await runner.invoke(state, obj=dev)
21-
print(res.output)
21+
await dev.update()
2222

2323
if dev.is_on:
2424
assert "Device state: ON" in res.output
@@ -32,13 +32,17 @@ async def test_alias(dev):
3232
res = await runner.invoke(alias, obj=dev)
3333
assert f"Alias: {dev.alias}" in res.output
3434

35+
old_alias = dev.alias
36+
3537
new_alias = "new alias"
3638
res = await runner.invoke(alias, [new_alias], obj=dev)
3739
assert f"Setting alias to {new_alias}" in res.output
3840

3941
res = await runner.invoke(alias, obj=dev)
4042
assert f"Alias: {new_alias}" in res.output
4143

44+
await dev.set_alias(old_alias)
45+
4246

4347
async def test_raw_command(dev):
4448
runner = CliRunner()
@@ -63,11 +67,13 @@ async def test_emeter(dev: SmartDevice, mocker):
6367
assert "== Emeter ==" in res.output
6468

6569
monthly = mocker.patch.object(dev, "get_emeter_monthly")
70+
monthly.return_value = []
6671
res = await runner.invoke(emeter, ["--year", "1900"], obj=dev)
6772
assert "For year" in res.output
6873
monthly.assert_called()
6974

7075
daily = mocker.patch.object(dev, "get_emeter_daily")
76+
daily.return_value = []
7177
res = await runner.invoke(emeter, ["--month", "1900-12"], obj=dev)
7278
assert "For month" in res.output
7379
daily.assert_called()

kasa/tests/test_discovery.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,14 +8,14 @@
88

99
@plug
1010
async def test_type_detection_plug(dev: SmartDevice):
11-
d = Discover._get_device_class(dev.protocol.discovery_data)("localhost")
11+
d = Discover._get_device_class(dev._last_update)("localhost")
1212
assert d.is_plug
1313
assert d.device_type == DeviceType.Plug
1414

1515

1616
@bulb
1717
async def test_type_detection_bulb(dev: SmartDevice):
18-
d = Discover._get_device_class(dev.protocol.discovery_data)("localhost")
18+
d = Discover._get_device_class(dev._last_update)("localhost")
1919
# TODO: light_strip is a special case for now to force bulb tests on it
2020
if not d.is_light_strip:
2121
assert d.is_bulb
@@ -24,21 +24,21 @@ async def test_type_detection_bulb(dev: SmartDevice):
2424

2525
@strip
2626
async def test_type_detection_strip(dev: SmartDevice):
27-
d = Discover._get_device_class(dev.protocol.discovery_data)("localhost")
27+
d = Discover._get_device_class(dev._last_update)("localhost")
2828
assert d.is_strip
2929
assert d.device_type == DeviceType.Strip
3030

3131

3232
@dimmer
3333
async def test_type_detection_dimmer(dev: SmartDevice):
34-
d = Discover._get_device_class(dev.protocol.discovery_data)("localhost")
34+
d = Discover._get_device_class(dev._last_update)("localhost")
3535
assert d.is_dimmer
3636
assert d.device_type == DeviceType.Dimmer
3737

3838

3939
@lightstrip
4040
async def test_type_detection_lightstrip(dev: SmartDevice):
41-
d = Discover._get_device_class(dev.protocol.discovery_data)("localhost")
41+
d = Discover._get_device_class(dev._last_update)("localhost")
4242
assert d.is_light_strip
4343
assert d.device_type == DeviceType.LightStrip
4444

kasa/tests/test_emeter.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ async def test_get_emeter_realtime(dev):
3232

3333

3434
@has_emeter
35+
@pytest.mark.requires_dummy
3536
async def test_get_emeter_daily(dev):
3637
if dev.is_strip:
3738
pytest.skip("Disabled for strips temporarily")
@@ -54,6 +55,7 @@ async def test_get_emeter_daily(dev):
5455

5556

5657
@has_emeter
58+
@pytest.mark.requires_dummy
5759
async def test_get_emeter_monthly(dev):
5860
if dev.is_strip:
5961
pytest.skip("Disabled for strips temporarily")

0 commit comments

Comments
 (0)
0