Skip to content

Commit 08c049b

Browse files
authored
feat(g-code-testing): Add functionality to run HTTP requests through G-Code Parser (RET-148, RET-149, RET-151) (#8316)
* Filter out __init__.py files * feat(g-code-testing): Add robot HTTP support to G-Code Project * Convert ProtocolRunner into an ABC called GCodeEngine * In ABC define an abstract `run` method * Rename all references * Create ProtocolGCodeEngine which runs protocols * Create HTTPGCodeEngine which runs HTTP requests * Push name change * Create first configuration * Rename references in README * Fix import paths in README * Formatting * Change folder name * Turn into package * Add new test data * Add configuration file to define different test data * Update cli.py to work with configuration file * Fix JSON file * Update Makefile * Formatting * Use hardcoded versions * Fix tests * Fix variable name * Refactor from_config_file to add dependency injection for testing * Also renamed classes to not start with `Test` because it was messing up pytest * Add testing * Formatting * Linting * Add Github Action * Split up GCodeTestData Break out GCodeTestData into HTTPTestData and ProtocolTestData. Did this so parameters on each class can be more specific than just path. * Update GCodeEngine to account for TestData breakout Instead of subclassing and having a single method just have more specialized methods on GCodeEngine. Pass HTTPTestData or ProtocolTestData classes itself instead of parameter from class * Change from JSON to Py. Update config files Changed json file to py file so for the HTTP types I can specify paths to the main function Changed all HTTP configs to inherit from HTTPBase and defined a main function * Forgot to ignore imports from robot_server * Update tests * Update to account for TestData breakout * Fix whitespace
1 parent 152a487 commit 08c049b

32 files changed

+568
-85
lines changed
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
# This workflow runs test and lint on branch pushes that touch the
2+
# notify-server project or its dependencies
3+
4+
name: 'G-Code Testing Lint & Test'
5+
6+
on:
7+
# Most of the time, we run on pull requests, which lets us handle external PRs
8+
push:
9+
paths:
10+
- 'Makefile'
11+
- 'g-code-testing/**'
12+
- '.github/workflows/g-code-testing-lint-test.yaml'
13+
- '.github/actions/python/**'
14+
tags-ignore:
15+
- '*'
16+
pull_request:
17+
paths:
18+
- 'Makefile'
19+
- 'g-code-testing/**'
20+
workflow_dispatch:
21+
22+
defaults:
23+
run:
24+
shell: bash
25+
26+
jobs:
27+
lint-test:
28+
name: 'g-code-testing package linting and tests'
29+
runs-on: 'ubuntu-18.04'
30+
steps:
31+
- uses: 'actions/checkout@v2'
32+
- uses: 'actions/setup-node@v1'
33+
with:
34+
node-version: '12'
35+
- uses: 'actions/setup-python@v2'
36+
with:
37+
python-version: '3.7'
38+
39+
- uses: './.github/actions/python/setup'
40+
with:
41+
project: 'g-code-testing'
42+
- name: Lint
43+
run: make -C g-code-testing lint
44+
- name: Test
45+
run: make -C g-code-testing test
46+
- name: 'Upload coverage report'
47+
uses: 'codecov/codecov-action@v2'
48+
with:
49+
files: ./g-code-testing/coverage.xml
50+
flags: g-code-testing

g-code-testing/Pipfile

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,10 @@ robot-server = {editable = true, path = "./../robot-server"}
1010
opentrons-shared-data = {editable = true, path = "../shared-data/python"}
1111
g-code-testing = {editable = true, path = "."}
1212
anyio = "==3.3.0"
13-
13+
pydantic = "==1.4"
1414

1515
[dev-packages]
16-
diff-match-patch = "*"
16+
diff-match-patch = "==20200713"
1717
pytest = "~=6.1"
1818
pytest-aiohttp = "==0.3.0"
1919
pytest-cov = "==2.10.1"
@@ -27,6 +27,5 @@ flake8-noqa = "~=1.1.0"
2727
black = "==21.7b0"
2828
decoy = "~=1.6.7"
2929

30-
3130
[requires]
3231
python_version = "3.7"

g-code-testing/Pipfile.lock

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

g-code-testing/README.md

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,8 @@ Framework supporting the following:
1717

1818
### G-Code Parser
1919

20-
Using the `ProtocolRunner` you can run a Python protocol file against emulation.
21-
The `ProtocolRunner` instance will return a `GCodeProgram` instance
20+
Using the `GCodeEngine` you can run a Python protocol file against emulation.
21+
The `GCodeEngine` instance will return a `GCodeProgram` instance
2222

2323
**Example:**
2424

@@ -29,16 +29,15 @@ against emulation.
2929
In "Concise" format store parsed G-Code to /where/I/want/to/store/my/output.txt
3030
"""
3131
import os.path
32-
from protocol_runner import ProtocolRunner
33-
from g_code_program.supported_text_modes \
34-
import SupportedTextModes
32+
from g_code_parsing.g_code_engine import HTTPGCodeEngine
33+
from g_code_parsing.g_code_program.supported_text_modes import SupportedTextModes
3534
from opentrons.hardware_control.emulation.settings import Settings
3635

3736
PROTOCOL_PATH = os.path.join('my', 'absolute', 'path', 'to', 'my', 'protocol.py')
3837
OUTPUT_PATH = os.path.join('where', 'I', 'want', 'to', 'store', 'my', 'output.txt')
3938

4039
settings = Settings() # Using default settings defined in class
41-
with ProtocolRunner(settings).run_protocol(PROTOCOL_PATH) as program:
40+
with HTTPGCodeEngine(settings).run_protocol(PROTOCOL_PATH) as program:
4241
program.save_text_explanation_to_file(OUTPUT_PATH, SupportedTextModes.CONCISE)
4342
```
4443

@@ -54,7 +53,7 @@ them in HTML format.
5453
Compare 2 files and save the diff to a file
5554
"""
5655
import os.path
57-
from g_code_differ import GCodeDiffer
56+
from g_code_parsing.g_code_differ import GCodeDiffer
5857
FILE_1_PATH = os.path.join('tmp', 'file_1.txt')
5958
FILE_2_PATH = os.path.join('tmp', 'file_2.txt')
6059
HTML_PATH = os.path.join('home', 'derek_maggio', 'Desktop', 'my_diff.html')

g-code-testing/g_code_parsing.mk

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ g-code-run:
5252
--text-mode '$(text_mode)' \
5353
--left-pipette '$(left_pipette)' \
5454
--right-pipette '$(right_pipette)' \
55-
$(protocol_path) \
55+
$(configuration_name) \
5656
2> /dev/null
5757

5858
master_file = /tmp/master_file.txt # File pulled from S3
@@ -99,7 +99,7 @@ g-code-smoothie-protocol-run:
9999
@$(MAKE) --no-print-directory g-code-run \
100100
left_pipette='{"model": "p20_single_v2.0", "id": "P20SV202020070101"}' \
101101
right_pipette='{"model": "p20_single_v2.0", "id": "P20SV202020070101"}' \
102-
protocol_path='protocols/smoothie_protocol.py' \
102+
configuration_name='protocol_smoothie' \
103103
> $(file_under_test)
104104

105105
# g-code-smoothie-protocol-run
@@ -135,7 +135,7 @@ g-code-2-modules-1s-1m-v2-protocol-run:
135135
@$(MAKE) --no-print-directory g-code-run \
136136
left_pipette='{"model": "p300_single_v2.1", "id": "P20SV202020070101"}' \
137137
right_pipette='{"model": "p20_multi_v2.1", "id": "P20SV202020070101"}' \
138-
protocol_path='protocols/2_modules_1s_1m_v2.py' \
138+
configuration_name='protocol_2_modules' \
139139
> $(file_under_test)
140140

141141
# g-code-2-modules-1s-1m-v2-protocol-diff
@@ -170,7 +170,7 @@ g-code-swift-turbo-protocol-run:
170170
@$(MAKE) --no-print-directory g-code-run \
171171
left_pipette='{"model": "p20_single_v2.0", "id": "P20SV202020070101"}' \
172172
right_pipette='{"model": "p300_multi_v2.1", "id": "P20SV202020070101"}' \
173-
protocol_path='protocols/swift_turbo.py' \
173+
configuration_name='protocol_swift_turbo' \
174174
> $(file_under_test)
175175

176176
# g-code-swift-turbo-protocol-diff
@@ -205,7 +205,7 @@ g-code-swift-smoke-protocol-run:
205205
@$(MAKE) --no-print-directory g-code-run \
206206
left_pipette='{"model": "p20_single_v2.0", "id": "P20SV202020070101"}' \
207207
right_pipette='{"model": "p300_multi_v2.1", "id": "P20SV202020070101"}' \
208-
protocol_path='protocols/swift_smoke.py' \
208+
configuration_name='protocol_swift_smoke' \
209209
> $(file_under_test)
210210

211211
# g-code-swift-smoke-protocol-diff
@@ -240,7 +240,7 @@ g-code-2-single-channel-v2-protocol-run:
240240
@$(MAKE) --no-print-directory g-code-run \
241241
left_pipette='{"model": "p20_single_v2.0", "id": "P20SV202020070101"}' \
242242
right_pipette='{"model": "p300_single_v2.1", "id": "P20SV202020070101"}' \
243-
protocol_path='protocols/2_single_channel_v2.py' \
243+
configuration_name='protocol_2_single_channel' \
244244
> $(file_under_test)
245245

246246
# g-code-2-single-channel-v2-protocol-diff

g-code-testing/g_code_parsing/cli.py

Lines changed: 32 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
import abc
22
import argparse
33
import json
4-
import os
54
from typing import Union, Dict, Any, List
65

76
import sys
@@ -18,14 +17,18 @@
1817
from g_code_parsing.g_code_program.supported_text_modes import (
1918
SupportedTextModes,
2019
)
21-
from g_code_parsing.protocol_runner import ProtocolRunner
22-
from g_code_parsing.utils import get_configuration_dir
20+
from g_code_parsing.g_code_engine import GCodeEngine
21+
from g_code_parsing.g_code_test_data import (
22+
GCodeTestFile,
23+
ProtocolTestData,
24+
HTTPTestData,
25+
)
26+
from g_code_test_data.configurations import CONFIGURATIONS
2327

2428

2529
class CLICommand(abc.ABC):
2630
"""ABC which all CLI command classes should inherit from"""
2731

28-
CONFIGURATION_DIR_LOCATION = get_configuration_dir()
2932
able_to_respond_with_error_code = False
3033
respond_with_error_code = False
3134
error_message = "Error message not defined"
@@ -40,7 +43,7 @@ def execute(self) -> str:
4043
class RunCommand(CLICommand):
4144
"""The "run" command of the CLI."""
4245

43-
configuration_path: str
46+
configuration_name: str
4447
text_mode: str
4548
left_pipette_config: PipetteSettings
4649
right_pipette_config: PipetteSettings
@@ -55,11 +58,22 @@ def execute(self) -> str:
5558
left=self.left_pipette_config, right=self.right_pipette_config
5659
)
5760
settings = Settings(smoothie=smoothie_settings, host=self.host)
58-
file_path = os.path.join(
59-
self.CONFIGURATION_DIR_LOCATION, self.configuration_path
60-
)
61-
with ProtocolRunner(settings).run_protocol(file_path) as program:
62-
return program.get_text_explanation(self.text_mode)
61+
# Ignoring because mypy is throwing an error saying that configs
62+
# is of type List[BaseModel] when it for sure isn't
63+
configuration = GCodeTestFile(
64+
configs=CONFIGURATIONS # type: ignore
65+
).get_by_name(self.configuration_name)
66+
67+
engine = GCodeEngine(settings)
68+
69+
if isinstance(configuration, ProtocolTestData):
70+
with engine.run_protocol(configuration) as program:
71+
text = program.get_text_explanation(self.text_mode)
72+
elif isinstance(configuration, HTTPTestData):
73+
with engine.run_http(configuration) as program:
74+
text = program.get_text_explanation(self.text_mode)
75+
76+
return text
6377

6478

6579
@dataclass
@@ -99,16 +113,11 @@ def execute(self) -> str:
99113

100114
class ConfigurationCommand(CLICommand):
101115
def execute(self) -> str:
102-
paths = [
103-
os.path.join(root, name).replace(self.CONFIGURATION_DIR_LOCATION + "/", "")
104-
for root, dirs, files in os.walk(
105-
self.CONFIGURATION_DIR_LOCATION, topdown=False
106-
)
107-
for name in files
108-
]
109-
110-
path_string = "\n".join(paths)
111-
116+
# Ignoring because mypy is throwing an error saying that configs
117+
# is of type List[BaseModel] when it for sure isn't
118+
path_string = "\n".join(
119+
GCodeTestFile(configs=CONFIGURATIONS).names # type: ignore
120+
)
112121
return f"Runnable Configurations:\n{path_string}"
113122

114123

@@ -122,7 +131,7 @@ class GCodeCLI:
122131
COMMAND_KEY = "command"
123132

124133
RUN_PROTOCOL_COMMAND = "run"
125-
CONFIGURATION_PATH = "configuration_path"
134+
CONFIGURATION_NAME = "configuration_name"
126135
TEXT_MODE_KEY_NAME = "text_mode"
127136
LEFT_PIPETTE_KEY_NAME = "left_pipette"
128137
RIGHT_PIPETTE_KEY_NAME = "right_pipette"
@@ -170,7 +179,7 @@ def to_command(cls, processed_args) -> CLICommand:
170179
command: Union[RunCommand, DiffCommand, ConfigurationCommand]
171180
if cls.RUN_PROTOCOL_COMMAND == passed_command_name:
172181
command = RunCommand(
173-
configuration_path=processed_args[cls.CONFIGURATION_PATH],
182+
configuration_name=processed_args[cls.CONFIGURATION_NAME],
174183
text_mode=processed_args[cls.TEXT_MODE_KEY_NAME],
175184
left_pipette_config=processed_args[cls.LEFT_PIPETTE_KEY_NAME],
176185
right_pipette_config=processed_args[cls.RIGHT_PIPETTE_KEY_NAME],
@@ -236,7 +245,7 @@ def parser(cls) -> argparse.ArgumentParser:
236245
formatter_class=argparse.RawTextHelpFormatter,
237246
)
238247
run_parser.add_argument(
239-
"configuration_path", type=str, help="Path to configuration you want to run"
248+
"configuration_name", type=str, help="Name of configuration you want to run"
240249
)
241250
run_parser.add_argument(
242251
"--text-mode",

g-code-testing/g_code_parsing/errors.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,3 +43,10 @@ def __init__(self, invalid_g_code) -> None:
4343
f"a polling command"
4444
)
4545
self.invalid_g_code = invalid_g_code
46+
47+
48+
class ConfigurationNotFoundError(ValueError):
49+
def __init__(self, configuration_name) -> None:
50+
51+
super().__init__(f'Configuration "{configuration_name}" not found')
52+
self.configuration_name = configuration_name

0 commit comments

Comments
 (0)