Skip to content

Commit

Permalink
feat(test-data-generation): generate deck configuration protocols (#1…
Browse files Browse the repository at this point in the history
…5111)

# Overview

Generate Python protocols from Hypothesis-generated Deck Configurations.

Closes [RQA-2617](https://opentrons.atlassian.net/browse/RQA-2617
)

# Test Plan

- [x] Run analysis on a few of the generated Python protocols and ensure
they are valid Python

# Changelog

- Create `ast_helpers.py` which is an abstraction layer that handles
creating [Python ast
nodes](https://docs.python.org/3.10/library/ast.html#node-classes).
- Create `generation_phases` dir. Logic inside the directory, utilizes
`ast_helpers.py` to map the test data logic to ast nodes
- Create `setup_phase.py` which defines the boilerplate code that every
protocol needs: importing the opentrons, package, creating requirements
dictionary, creating run function
- Create `load_phase.py` which evaluated the Deck Configuration and
generates the `load_*` calls to load in labware, modules, and fixtures
to the protocol
- Create `call_phase.py` which makes calls to loaded entities to ensure
analysis recognizes them as being on the deck

- Create `python_protocol_generator.py` which calls `generate_ast` on
all of the `ast_helpers` and wires up the created nodes. Also handles
calling into the `astor` package to generate the python code
- Added different Makefile targets to run generated tests in a more
verbose mode

# Review requests

Is there a better way, than the `phase` logic, to handle figuring out
what python statements to generate?

# Risk assessment

Low


[RQA-2617]:
https://opentrons.atlassian.net/browse/RQA-2617?atlOrigin=eyJpIjoiNWRkNTljNzYxNjVmNDY3MDlhMDU5Y2ZhYzA5YTRkZjUiLCJwIjoiZ2l0aHViLWNvbS1KU1cifQ
  • Loading branch information
DerekMaggio authored May 10, 2024
1 parent 0db2732 commit 8f9a8cc
Show file tree
Hide file tree
Showing 16 changed files with 914 additions and 156 deletions.
17 changes: 12 additions & 5 deletions test-data-generation/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -27,11 +27,18 @@ wheel:
$(python) setup.py $(wheel_opts) bdist_wheel
rm -rf build

.PHONY: test
test:
$(pytest) tests \
.PHONY: debug-test
debug-test:
$(pytest) ./tests \
-vvv \
-s \
--hypothesis-show-statistics \
--hypothesis-verbosity=normal \
--hypothesis-explain \
-vvv
--hypothesis-profile=dev


.PHONY: test
test:
$(pytest) ./tests \
--hypothesis-explain \
--hypothesis-profile=ci
6 changes: 4 additions & 2 deletions test-data-generation/Pipfile
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@ url = "https://pypi.org/simple"
verify_ssl = true

[packages]
pytest = "==7.4.3"
pytest = "==7.4.4"
pytest-asyncio = "~=0.23.0"
black = "==23.11.0"
mypy = "==1.7.1"
flake8 = "==7.0.0"
Expand All @@ -13,8 +14,9 @@ flake8-docstrings = "~=1.7.0"
flake8-noqa = "~=1.4.0"
hypothesis = "==6.96.1"
opentrons-shared-data = {file = "../shared-data/python", editable = true}
opentrons = { editable = true, path = "../api"}
test-data-generation = {file = ".", editable = true}

astor = "0.8.1"

[requires]
python_version = "3.10"
127 changes: 115 additions & 12 deletions test-data-generation/Pipfile.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
Expand Up @@ -111,14 +111,14 @@ def __str__(self) -> str:
return f"{(self.row + self.col).center(self.contents.longest_string())}{self.contents}"

@property
def __label(self) -> SlotName:
def label(self) -> SlotName:
"""Return the slot label."""
return typing.cast(SlotName, f"{self.row}{self.col}")

@property
def slot_label_string(self) -> str:
"""Return the slot label."""
return f"{self.__label.center(self.contents.longest_string())}"
return f"{self.label.center(self.contents.longest_string())}"

@property
def contents_string(self) -> str:
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
"""Test data generation for deck configuration tests."""
import typing
from hypothesis import assume, strategies as st
from test_data_generation.deck_configuration.datashapes import (
DeckConfiguration,
PossibleSlotContents as PSC,
)

from test_data_generation.deck_configuration.strategy.helper_strategies import (
a_deck_by_columns,
)

DeckConfigurationStrategy = typing.Callable[..., st.SearchStrategy[DeckConfiguration]]


@st.composite
def a_deck_configuration_with_invalid_fixture_in_col_2(
draw: st.DrawFn,
) -> DeckConfiguration:
"""Generate a deck with an invalid fixture in column 2."""
POSSIBLE_FIXTURES = [
PSC.LABWARE_SLOT,
PSC.TEMPERATURE_MODULE,
PSC.HEATER_SHAKER_MODULE,
PSC.TRASH_BIN,
PSC.MAGNETIC_BLOCK_MODULE,
]
INVALID_FIXTURES = [
PSC.HEATER_SHAKER_MODULE,
PSC.TRASH_BIN,
PSC.TEMPERATURE_MODULE,
]

deck = draw(a_deck_by_columns(col_2_contents=POSSIBLE_FIXTURES))

num_invalid_fixtures = len(
[
True
for slot in deck.column_by_number("2").slots
if slot.contents.is_one_of(INVALID_FIXTURES)
]
)
assume(num_invalid_fixtures > 0)

return deck


DECK_CONFIGURATION_STRATEGIES: typing.Dict[str, DeckConfigurationStrategy] = {
f.__name__: f
for f in [
a_deck_configuration_with_invalid_fixture_in_col_2,
]
}

This file was deleted.

Loading

0 comments on commit 8f9a8cc

Please sign in to comment.