Skip to content

Commit 7273aa6

Browse files
test(robot-server): Test compatibility with databases created in v6.0 (#11448)
1 parent 2b6f6f1 commit 7273aa6

File tree

12 files changed

+10577
-9
lines changed

12 files changed

+10577
-9
lines changed

robot-server/.flake8

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,3 +35,7 @@ per-file-ignores =
3535
tests/service/*:ANN,D
3636
tests/conftest.py:ANN,D
3737
tests/test_util.py:ANN,D
38+
39+
# Ignore Python Protocol API files that are used as opaque test input.
40+
extend-exclude =
41+
tests/integration/persistence_snapshots/

robot-server/mypy.ini

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,22 @@
22
plugins = pydantic.mypy, decoy.mypy, sqlalchemy.ext.mypy.plugin
33
show_error_codes = True
44
strict = True
5-
# TODO(mc, 2021-09-12): remove these exclusions
6-
exclude = tests/(robot|service/labware|service/legacy|service/notifications|service/pipette_offset|service/session|service/tip_length)
5+
6+
# `(?x)` for a verbose regex (so it ignores whitespace and can have comments).
7+
exclude = (?x)(
8+
tests/(
9+
# TODO(mc, 2021-09-12): remove these exclusions
10+
robot
11+
| service/labware
12+
| service/legacy
13+
| service/notifications
14+
| service/pipette_offset
15+
| service/session
16+
| service/tip_length
17+
)
18+
# Ignore Python Protocol API files that are used as opaque test input.
19+
| tests/integration/persistence_snapshots
20+
)
721

822
[pydantic-mypy]
923
init_forbid_extra = True

robot-server/tests/integration/dev_server.py

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
from __future__ import annotations
2+
from pathlib import Path
23
import subprocess
34
import signal
45
import sys
@@ -8,10 +9,16 @@
89

910

1011
class DevServer:
11-
def __init__(self, port: str = "31950") -> None:
12+
def __init__(
13+
self, port: str = "31950", persistence_directory: Optional[Path] = None
14+
) -> None:
1215
"""Initialize a dev server."""
1316
self.server_temp_directory: str = tempfile.mkdtemp()
14-
self.persistence_directory: str = tempfile.mkdtemp()
17+
self.persistence_directory: Path = (
18+
persistence_directory
19+
if persistence_directory is not None
20+
else Path(tempfile.mkdtemp())
21+
)
1522
self.port: str = port
1623

1724
def __enter__(self) -> DevServer:
@@ -52,7 +59,9 @@ def start(self) -> None:
5259
env={
5360
"OT_ROBOT_SERVER_DOT_ENV_PATH": "dev.env",
5461
"OT_API_CONFIG_DIR": self.server_temp_directory,
55-
"OT_ROBOT_SERVER_persistence_directory": self.persistence_directory,
62+
"OT_ROBOT_SERVER_persistence_directory": str(
63+
self.persistence_directory
64+
),
5665
},
5766
stdin=subprocess.DEVNULL,
5867
# The server will log to its stdout or stderr.
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
from pathlib import Path
2+
from typing import AsyncGenerator, Generator
3+
4+
import pytest
5+
6+
from tests.integration.dev_server import DevServer
7+
from tests.integration.robot_client import RobotClient
8+
9+
10+
# FIXME(mm, 2022-09-09): This assumes the current working directory is
11+
# the root project directory. See Jira RSS-104.
12+
_OLDER_PERSISTENCE_DIR = Path("tests/integration/persistence_snapshots/v6.0.1")
13+
14+
_EXPECTED_PROTOCOL_COUNT = 4
15+
_EXPECTED_RUN_COUNT = 5
16+
17+
18+
# Module-scope to avoid the overhead of restarting the server between test functions.
19+
# This relies on the test functions only reading, never writing.
20+
@pytest.fixture(scope="module")
21+
def dev_server() -> Generator[DevServer, None, None]:
22+
port = "15555"
23+
with DevServer(
24+
port=port,
25+
persistence_directory=_OLDER_PERSISTENCE_DIR,
26+
) as server:
27+
server.start()
28+
yield server
29+
30+
31+
@pytest.fixture
32+
async def robot_client(dev_server: DevServer) -> AsyncGenerator[RobotClient, None]:
33+
"""Return a client to talk to a server that's using an old persistence dir."""
34+
async with RobotClient.make(
35+
host="http://localhost", port=dev_server.port, version="*"
36+
) as robot_client:
37+
assert (
38+
await robot_client.wait_until_alive()
39+
), "Dev Robot never became available."
40+
yield robot_client
41+
42+
43+
async def test_protocols_and_analyses_available_from_older_persistence_dir(
44+
robot_client: RobotClient,
45+
) -> None:
46+
all_protocols = (await robot_client.get_protocols()).json()
47+
all_protocol_ids = [p["id"] for p in all_protocols["data"]]
48+
49+
assert len(all_protocol_ids) == _EXPECTED_PROTOCOL_COUNT
50+
51+
for protocol_id in all_protocol_ids:
52+
protocol = (await robot_client.get_protocol(protocol_id=protocol_id)).json()
53+
54+
analysis_ids = [s["id"] for s in protocol["data"]["analysisSummaries"]]
55+
assert len(analysis_ids) >= 1
56+
for analysis_id in analysis_ids:
57+
await robot_client.get_analysis(
58+
protocol_id=protocol_id, analysis_id=analysis_id
59+
)
60+
61+
62+
async def test_runs_available_from_older_persistence_dir(
63+
robot_client: RobotClient,
64+
) -> None:
65+
all_runs = (await robot_client.get_runs()).json()
66+
all_run_ids = [r["id"] for r in all_runs["data"]]
67+
68+
assert len(all_run_ids) == _EXPECTED_RUN_COUNT
69+
70+
for run_id in all_run_ids:
71+
await robot_client.get_run(run_id=run_id)
72+
73+
all_command_summaries = (
74+
await robot_client.get_run_commands(
75+
run_id=run_id,
76+
page_length=999999, # Big enough to include all commands.
77+
)
78+
).json()
79+
assert len(all_command_summaries["data"]) > 0
80+
81+
# Ideally, we would also fetch full commands via
82+
# `GET /runs/{run_id}/commands/{command_id}`.
83+
# We skip it for performance. On my machine, it would take ~7 seconds.
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
# Overview
2+
3+
This directory has examples of what a real robot might have in its `robot-server` persistence directory. (See the environment variable `OT_ROBOT_SERVER_persistence_directory` for background.)
4+
5+
These help with testing schema migration and backwards compatibility.
6+
7+
# Snapshot notes
8+
9+
## v6.0.1
10+
11+
This snapshot comes from a v6.0.1 dev server (run with `make -C robot-server dev`).
12+
13+
It includes these protocols, which were uploaded by manually issuing HTTP `POST` requests:
14+
15+
* [simpleV6.json](https://github.com/Opentrons/opentrons/blob/4f9c72ab076692a377afc7245e857385935763a8/shared-data/protocol/fixtures/6/simpleV6.json)
16+
* [multipleTipracksWithTC.json](https://github.com/Opentrons/opentrons/blob/4f9c72ab076692a377afc7245e857385935763a8/shared-data/protocol/fixtures/6/multipleTipracksWithTC.json)
17+
* [tempAndMagModuleCommands.json](https://github.com/Opentrons/opentrons/blob/4f9c72ab076692a377afc7245e857385935763a8/shared-data/protocol/fixtures/6/tempAndMagModuleCommands.json)
18+
* [swift_smoke.py](https://github.com/Opentrons/opentrons/blob/4f9c72ab076692a377afc7245e857385935763a8/g-code-testing/g_code_test_data/protocol/protocols/slow/swift_smoke.py)
19+
20+
The JSON protocols were chosen to cover a wide breadth of Protocol Engine commands.
21+
22+
Each protocol has one completed analysis and one successful run. multipleTipracksWithTC.json also has one failed run from a mismatched pipette error.

0 commit comments

Comments
 (0)