piphi-runtime-testkit-python 0.1.2__py3-none-any.whl → 0.1.4__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- piphi_runtime_testkit_python/__init__.py +8 -0
- piphi_runtime_testkit_python/assertions.py +10 -5
- piphi_runtime_testkit_python/behavior_contract.py +268 -0
- piphi_runtime_testkit_python/mock_core.py +30 -3
- {piphi_runtime_testkit_python-0.1.2.dist-info → piphi_runtime_testkit_python-0.1.4.dist-info}/METADATA +23 -1
- piphi_runtime_testkit_python-0.1.4.dist-info/RECORD +11 -0
- {piphi_runtime_testkit_python-0.1.2.dist-info → piphi_runtime_testkit_python-0.1.4.dist-info}/WHEEL +1 -1
- piphi_runtime_testkit_python-0.1.2.dist-info/RECORD +0 -10
- {piphi_runtime_testkit_python-0.1.2.dist-info → piphi_runtime_testkit_python-0.1.4.dist-info}/entry_points.txt +0 -0
- {piphi_runtime_testkit_python-0.1.2.dist-info → piphi_runtime_testkit_python-0.1.4.dist-info}/licenses/LICENSE +0 -0
|
@@ -7,17 +7,25 @@ from .builders import (
|
|
|
7
7
|
build_core_config_row,
|
|
8
8
|
build_runtime_headers,
|
|
9
9
|
)
|
|
10
|
+
from .behavior_contract import (
|
|
11
|
+
BehaviorTelemetryIssue,
|
|
12
|
+
assert_behavior_conditions_match_telemetry,
|
|
13
|
+
find_behavior_telemetry_issues,
|
|
14
|
+
)
|
|
10
15
|
from .mock_core import CapturedRequest, MockCoreServer, RUNTIME_CONFIG_FETCH_PATH
|
|
11
16
|
|
|
12
17
|
__all__ = [
|
|
13
18
|
"CapturedRequest",
|
|
19
|
+
"BehaviorTelemetryIssue",
|
|
14
20
|
"MockCoreServer",
|
|
15
21
|
"RUNTIME_CONFIG_FETCH_PATH",
|
|
16
22
|
"assert_entities_response",
|
|
23
|
+
"assert_behavior_conditions_match_telemetry",
|
|
17
24
|
"assert_event_sent",
|
|
18
25
|
"assert_telemetry_sent",
|
|
19
26
|
"build_config_payload",
|
|
20
27
|
"build_config_snapshot",
|
|
21
28
|
"build_core_config_row",
|
|
22
29
|
"build_runtime_headers",
|
|
30
|
+
"find_behavior_telemetry_issues",
|
|
23
31
|
]
|
|
@@ -79,10 +79,10 @@ def _find_request(
|
|
|
79
79
|
if body_event_type is None and isinstance(body, dict):
|
|
80
80
|
body_event_type = body.get("type")
|
|
81
81
|
if device_id is not None:
|
|
82
|
-
if isinstance(body, dict) and body
|
|
82
|
+
if isinstance(body, dict) and _pick_alias(body, "device_id", "deviceId") != device_id:
|
|
83
83
|
continue
|
|
84
84
|
if config_id is not None:
|
|
85
|
-
if isinstance(body, dict) and body
|
|
85
|
+
if isinstance(body, dict) and _pick_alias(body, "config_id", "configId") != config_id:
|
|
86
86
|
continue
|
|
87
87
|
if event_type is not None:
|
|
88
88
|
if body_event_type != event_type:
|
|
@@ -95,18 +95,23 @@ def assert_telemetry_sent(
|
|
|
95
95
|
mock_core: MockCoreServer,
|
|
96
96
|
*,
|
|
97
97
|
device_id: str | None = None,
|
|
98
|
+
config_id: str | None = None,
|
|
98
99
|
) -> CapturedRequest:
|
|
99
100
|
"""Assert that at least one telemetry request was captured."""
|
|
100
101
|
|
|
101
102
|
if not mock_core.telemetry_requests:
|
|
102
103
|
raise AssertionError("Expected telemetry to be sent to mock Core, but no telemetry requests were captured.")
|
|
103
104
|
|
|
104
|
-
if device_id is None:
|
|
105
|
+
if device_id is None and config_id is None:
|
|
105
106
|
return mock_core.telemetry_requests[-1]
|
|
106
107
|
|
|
107
|
-
request = _find_request(mock_core.telemetry_requests, device_id=device_id)
|
|
108
|
+
request = _find_request(mock_core.telemetry_requests, device_id=device_id, config_id=config_id)
|
|
108
109
|
if request is None:
|
|
109
|
-
raise AssertionError(
|
|
110
|
+
raise AssertionError(
|
|
111
|
+
"Expected telemetry matching filters, but none were captured. "
|
|
112
|
+
f"device_id={device_id!r} config_id={config_id!r} "
|
|
113
|
+
f"captured_device_ids={mock_core.captured_telemetry_device_ids()!r}"
|
|
114
|
+
)
|
|
110
115
|
return request
|
|
111
116
|
|
|
112
117
|
|
|
@@ -0,0 +1,268 @@
|
|
|
1
|
+
"""Conformance checks between automation behaviors and emitted telemetry."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from collections.abc import Iterable, Mapping
|
|
6
|
+
from dataclasses import dataclass
|
|
7
|
+
from typing import Any
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
@dataclass(frozen=True)
|
|
11
|
+
class BehaviorTelemetryIssue:
|
|
12
|
+
"""One behavior condition that cannot be evaluated from runtime telemetry."""
|
|
13
|
+
|
|
14
|
+
device_id: str
|
|
15
|
+
condition_id: str
|
|
16
|
+
field: str
|
|
17
|
+
aliases: tuple[str, ...]
|
|
18
|
+
reason: str
|
|
19
|
+
template_id: str | None = None
|
|
20
|
+
|
|
21
|
+
def describe(self) -> str:
|
|
22
|
+
owner = (
|
|
23
|
+
f"template {self.template_id!r}"
|
|
24
|
+
if self.template_id
|
|
25
|
+
else f"device {self.device_id!r}"
|
|
26
|
+
)
|
|
27
|
+
candidates = ", ".join(repr(item) for item in (self.field, *self.aliases))
|
|
28
|
+
return (
|
|
29
|
+
f"{owner} condition {self.condition_id!r}: {self.reason}; "
|
|
30
|
+
f"expected one of [{candidates}]"
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def _as_mapping(value: Any) -> Mapping[str, Any]:
|
|
35
|
+
return value if isinstance(value, Mapping) else {}
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def _as_list(value: Any) -> list[Any]:
|
|
39
|
+
return list(value) if isinstance(value, (list, tuple)) else []
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def _metric_samples(
|
|
43
|
+
telemetry_samples: Iterable[Mapping[str, Any]] | Mapping[str, Any],
|
|
44
|
+
) -> list[Mapping[str, Any]]:
|
|
45
|
+
raw_samples = (
|
|
46
|
+
[telemetry_samples]
|
|
47
|
+
if isinstance(telemetry_samples, Mapping)
|
|
48
|
+
else list(telemetry_samples)
|
|
49
|
+
)
|
|
50
|
+
samples: list[Mapping[str, Any]] = []
|
|
51
|
+
for sample in raw_samples:
|
|
52
|
+
payload = _as_mapping(sample)
|
|
53
|
+
metrics = payload.get("metrics")
|
|
54
|
+
samples.append(_as_mapping(metrics) if isinstance(metrics, Mapping) else payload)
|
|
55
|
+
return samples
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def _field_aliases(runtime: Mapping[str, Any]) -> tuple[str, ...]:
|
|
59
|
+
raw_aliases = runtime.get("fieldAliases", runtime.get("field_aliases"))
|
|
60
|
+
return tuple(
|
|
61
|
+
dict.fromkeys(
|
|
62
|
+
str(alias or "").strip()
|
|
63
|
+
for alias in _as_list(raw_aliases)
|
|
64
|
+
if str(alias or "").strip()
|
|
65
|
+
)
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def _value_matches_type(value: Any, condition_type: str) -> bool:
|
|
70
|
+
normalized_type = str(condition_type or "").strip().lower()
|
|
71
|
+
if not normalized_type:
|
|
72
|
+
return True
|
|
73
|
+
if normalized_type == "boolean":
|
|
74
|
+
return isinstance(value, bool)
|
|
75
|
+
if normalized_type == "number":
|
|
76
|
+
return isinstance(value, (int, float)) and not isinstance(value, bool)
|
|
77
|
+
if normalized_type in {"text", "enum"}:
|
|
78
|
+
return isinstance(value, str)
|
|
79
|
+
return True
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def _condition_issue(
|
|
83
|
+
condition: Mapping[str, Any],
|
|
84
|
+
*,
|
|
85
|
+
device_id: str,
|
|
86
|
+
samples: list[Mapping[str, Any]],
|
|
87
|
+
dynamic_condition_ids: set[str],
|
|
88
|
+
template_id: str | None = None,
|
|
89
|
+
) -> BehaviorTelemetryIssue | None:
|
|
90
|
+
condition_id = str(condition.get("id") or condition.get("field") or "condition").strip()
|
|
91
|
+
runtime = _as_mapping(condition.get("runtime"))
|
|
92
|
+
source = str(runtime.get("source") or condition.get("source") or "state").strip().lower()
|
|
93
|
+
if source not in {"", "state"}:
|
|
94
|
+
return None
|
|
95
|
+
|
|
96
|
+
if condition_id in dynamic_condition_ids:
|
|
97
|
+
return None
|
|
98
|
+
|
|
99
|
+
dynamic_field_param = str(
|
|
100
|
+
runtime.get("fieldFromParam") or runtime.get("field_from_param") or ""
|
|
101
|
+
).strip()
|
|
102
|
+
if dynamic_field_param:
|
|
103
|
+
return BehaviorTelemetryIssue(
|
|
104
|
+
device_id=device_id,
|
|
105
|
+
condition_id=condition_id,
|
|
106
|
+
field=dynamic_field_param,
|
|
107
|
+
aliases=(),
|
|
108
|
+
reason="dynamic condition needs an explicit test exemption",
|
|
109
|
+
template_id=template_id,
|
|
110
|
+
)
|
|
111
|
+
|
|
112
|
+
field = str(runtime.get("field") or condition.get("field") or "").strip()
|
|
113
|
+
if not field:
|
|
114
|
+
return BehaviorTelemetryIssue(
|
|
115
|
+
device_id=device_id,
|
|
116
|
+
condition_id=condition_id,
|
|
117
|
+
field="<missing>",
|
|
118
|
+
aliases=(),
|
|
119
|
+
reason="state-backed condition does not declare a runtime field",
|
|
120
|
+
template_id=template_id,
|
|
121
|
+
)
|
|
122
|
+
|
|
123
|
+
aliases = _field_aliases(runtime)
|
|
124
|
+
candidates = (field, *aliases)
|
|
125
|
+
observed = [
|
|
126
|
+
sample[candidate]
|
|
127
|
+
for sample in samples
|
|
128
|
+
for candidate in candidates
|
|
129
|
+
if candidate in sample
|
|
130
|
+
]
|
|
131
|
+
if not observed:
|
|
132
|
+
return BehaviorTelemetryIssue(
|
|
133
|
+
device_id=device_id,
|
|
134
|
+
condition_id=condition_id,
|
|
135
|
+
field=field,
|
|
136
|
+
aliases=aliases,
|
|
137
|
+
reason="field was not emitted by any representative telemetry sample",
|
|
138
|
+
template_id=template_id,
|
|
139
|
+
)
|
|
140
|
+
|
|
141
|
+
condition_type = str(condition.get("type") or runtime.get("type") or "").strip()
|
|
142
|
+
if condition_type and not any(
|
|
143
|
+
_value_matches_type(value, condition_type) for value in observed
|
|
144
|
+
):
|
|
145
|
+
return BehaviorTelemetryIssue(
|
|
146
|
+
device_id=device_id,
|
|
147
|
+
condition_id=condition_id,
|
|
148
|
+
field=field,
|
|
149
|
+
aliases=aliases,
|
|
150
|
+
reason=f"emitted value does not match declared type {condition_type!r}",
|
|
151
|
+
template_id=template_id,
|
|
152
|
+
)
|
|
153
|
+
return None
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
def _condition_leaves(node: Any) -> Iterable[Mapping[str, Any]]:
|
|
157
|
+
current = _as_mapping(node)
|
|
158
|
+
if not current:
|
|
159
|
+
return
|
|
160
|
+
children = _as_list(current.get("children"))
|
|
161
|
+
if children:
|
|
162
|
+
for child in children:
|
|
163
|
+
yield from _condition_leaves(child)
|
|
164
|
+
return
|
|
165
|
+
yield current
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
def find_behavior_telemetry_issues(
|
|
169
|
+
behaviors: Mapping[str, Any],
|
|
170
|
+
telemetry_samples: Iterable[Mapping[str, Any]] | Mapping[str, Any],
|
|
171
|
+
*,
|
|
172
|
+
dynamic_condition_ids: Iterable[str] = (),
|
|
173
|
+
) -> list[BehaviorTelemetryIssue]:
|
|
174
|
+
"""Return conditions that representative runtime telemetry cannot evaluate.
|
|
175
|
+
|
|
176
|
+
``telemetry_samples`` may contain raw metric mappings or Core telemetry
|
|
177
|
+
payloads with a ``metrics`` object. Parameter-driven dynamic conditions must
|
|
178
|
+
be named explicitly in ``dynamic_condition_ids`` and tested separately.
|
|
179
|
+
"""
|
|
180
|
+
|
|
181
|
+
samples = _metric_samples(telemetry_samples)
|
|
182
|
+
dynamic_ids = {
|
|
183
|
+
str(condition_id or "").strip()
|
|
184
|
+
for condition_id in dynamic_condition_ids
|
|
185
|
+
if str(condition_id or "").strip()
|
|
186
|
+
}
|
|
187
|
+
devices = [
|
|
188
|
+
_as_mapping(device)
|
|
189
|
+
for device in _as_list(behaviors.get("devices"))
|
|
190
|
+
if isinstance(device, Mapping)
|
|
191
|
+
]
|
|
192
|
+
devices_by_id = {
|
|
193
|
+
str(device.get("id") or "").strip(): device
|
|
194
|
+
for device in devices
|
|
195
|
+
if str(device.get("id") or "").strip()
|
|
196
|
+
}
|
|
197
|
+
options_by_device = {
|
|
198
|
+
device_id: {
|
|
199
|
+
str(condition.get("id") or "").strip(): condition
|
|
200
|
+
for condition in _as_list(device.get("conditions"))
|
|
201
|
+
if isinstance(condition, Mapping)
|
|
202
|
+
}
|
|
203
|
+
for device_id, device in devices_by_id.items()
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
issues: list[BehaviorTelemetryIssue] = []
|
|
207
|
+
for device_id, device in devices_by_id.items():
|
|
208
|
+
for raw_condition in _as_list(device.get("conditions")):
|
|
209
|
+
condition = _as_mapping(raw_condition)
|
|
210
|
+
issue = _condition_issue(
|
|
211
|
+
condition,
|
|
212
|
+
device_id=device_id,
|
|
213
|
+
samples=samples,
|
|
214
|
+
dynamic_condition_ids=dynamic_ids,
|
|
215
|
+
)
|
|
216
|
+
if issue:
|
|
217
|
+
issues.append(issue)
|
|
218
|
+
|
|
219
|
+
for raw_template in _as_list(behaviors.get("templates")):
|
|
220
|
+
template = _as_mapping(raw_template)
|
|
221
|
+
template_id = str(template.get("id") or "template").strip()
|
|
222
|
+
device_id = str(
|
|
223
|
+
template.get("deviceKey") or template.get("device_key") or ""
|
|
224
|
+
).strip()
|
|
225
|
+
condition_options = options_by_device.get(device_id, {})
|
|
226
|
+
config = _as_mapping(template.get("config"))
|
|
227
|
+
condition_tree = config.get("conditionTree", config.get("condition_tree"))
|
|
228
|
+
for leaf in _condition_leaves(condition_tree):
|
|
229
|
+
option_key = str(
|
|
230
|
+
_as_mapping(leaf.get("sourceRef")).get("optionKey")
|
|
231
|
+
or _as_mapping(leaf.get("source_ref")).get("option_key")
|
|
232
|
+
or leaf.get("id")
|
|
233
|
+
or ""
|
|
234
|
+
).strip()
|
|
235
|
+
option = condition_options.get(option_key, {})
|
|
236
|
+
merged = {**option, **leaf}
|
|
237
|
+
if option.get("runtime") and not leaf.get("runtime"):
|
|
238
|
+
merged["runtime"] = option["runtime"]
|
|
239
|
+
issue = _condition_issue(
|
|
240
|
+
merged,
|
|
241
|
+
device_id=device_id,
|
|
242
|
+
samples=samples,
|
|
243
|
+
dynamic_condition_ids=dynamic_ids,
|
|
244
|
+
template_id=template_id,
|
|
245
|
+
)
|
|
246
|
+
if issue:
|
|
247
|
+
issues.append(issue)
|
|
248
|
+
return issues
|
|
249
|
+
|
|
250
|
+
|
|
251
|
+
def assert_behavior_conditions_match_telemetry(
|
|
252
|
+
behaviors: Mapping[str, Any],
|
|
253
|
+
telemetry_samples: Iterable[Mapping[str, Any]] | Mapping[str, Any],
|
|
254
|
+
*,
|
|
255
|
+
dynamic_condition_ids: Iterable[str] = (),
|
|
256
|
+
) -> None:
|
|
257
|
+
"""Fail a contract test when advertised conditions lack runtime telemetry."""
|
|
258
|
+
|
|
259
|
+
issues = find_behavior_telemetry_issues(
|
|
260
|
+
behaviors,
|
|
261
|
+
telemetry_samples,
|
|
262
|
+
dynamic_condition_ids=dynamic_condition_ids,
|
|
263
|
+
)
|
|
264
|
+
if issues:
|
|
265
|
+
details = "\n".join(f"- {issue.describe()}" for issue in issues)
|
|
266
|
+
raise AssertionError(
|
|
267
|
+
"Behavior conditions do not match emitted telemetry:\n" + details
|
|
268
|
+
)
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
+
from collections.abc import Iterable, Mapping
|
|
3
4
|
from dataclasses import dataclass
|
|
4
5
|
import json
|
|
5
6
|
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
|
|
@@ -169,7 +170,7 @@ class MockCoreServer:
|
|
|
169
170
|
|
|
170
171
|
def captured_telemetry_device_ids(self) -> list[Any]:
|
|
171
172
|
return [
|
|
172
|
-
request.json_body.get("device_id")
|
|
173
|
+
request.json_body.get("device_id", request.json_body.get("deviceId"))
|
|
173
174
|
for request in self.telemetry_requests
|
|
174
175
|
if isinstance(request.json_body, dict)
|
|
175
176
|
]
|
|
@@ -179,10 +180,15 @@ class MockCoreServer:
|
|
|
179
180
|
self._server.server_close()
|
|
180
181
|
self._thread.join(timeout=2)
|
|
181
182
|
|
|
182
|
-
def assert_telemetry_sent(
|
|
183
|
+
def assert_telemetry_sent(
|
|
184
|
+
self,
|
|
185
|
+
*,
|
|
186
|
+
device_id: str | None = None,
|
|
187
|
+
config_id: str | None = None,
|
|
188
|
+
) -> CapturedRequest:
|
|
183
189
|
from .assertions import assert_telemetry_sent
|
|
184
190
|
|
|
185
|
-
return assert_telemetry_sent(self, device_id=device_id)
|
|
191
|
+
return assert_telemetry_sent(self, device_id=device_id, config_id=config_id)
|
|
186
192
|
|
|
187
193
|
def assert_event_sent(
|
|
188
194
|
self,
|
|
@@ -199,3 +205,24 @@ class MockCoreServer:
|
|
|
199
205
|
config_id=config_id,
|
|
200
206
|
event_type=event_type,
|
|
201
207
|
)
|
|
208
|
+
|
|
209
|
+
def assert_behavior_conditions_match_telemetry(
|
|
210
|
+
self,
|
|
211
|
+
behaviors: Mapping[str, Any],
|
|
212
|
+
*,
|
|
213
|
+
dynamic_condition_ids: Iterable[str] = (),
|
|
214
|
+
) -> None:
|
|
215
|
+
"""Validate behaviors against telemetry captured by this mock Core."""
|
|
216
|
+
|
|
217
|
+
from .behavior_contract import assert_behavior_conditions_match_telemetry
|
|
218
|
+
|
|
219
|
+
samples = [
|
|
220
|
+
request.json_body
|
|
221
|
+
for request in self.telemetry_requests
|
|
222
|
+
if isinstance(request.json_body, dict)
|
|
223
|
+
]
|
|
224
|
+
assert_behavior_conditions_match_telemetry(
|
|
225
|
+
behaviors,
|
|
226
|
+
samples,
|
|
227
|
+
dynamic_condition_ids=dynamic_condition_ids,
|
|
228
|
+
)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: piphi-runtime-testkit-python
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.4
|
|
4
4
|
Summary: Pytest-first helpers for testing PiPhi runtime integrations
|
|
5
5
|
Keywords: piphi,pytest,testing,runtime,iot
|
|
6
6
|
Author-Email: KelvinSan <support@piphi.network>
|
|
@@ -69,6 +69,7 @@ It includes:
|
|
|
69
69
|
- a local mock Core server that captures outbound HTTP requests
|
|
70
70
|
- builder functions for runtime headers, `/config` payloads, and `/config/sync` snapshots
|
|
71
71
|
- readable assertion helpers for telemetry and event delivery
|
|
72
|
+
- a behavior/telemetry conformance gate that catches unusable automation conditions
|
|
72
73
|
- a pytest plugin so the fixtures are available automatically
|
|
73
74
|
|
|
74
75
|
It does not try to replace:
|
|
@@ -209,6 +210,27 @@ mock_core.assert_event_sent(config_id="sensor-1", event_type="device.configured"
|
|
|
209
210
|
|
|
210
211
|
That makes the test much easier to understand at a glance.
|
|
211
212
|
|
|
213
|
+
### Behavior and Telemetry Contract Gate
|
|
214
|
+
|
|
215
|
+
After a representative runtime test has emitted telemetry, validate that every
|
|
216
|
+
state-backed condition advertised by `behaviors.json` can read a real metric:
|
|
217
|
+
|
|
218
|
+
```python
|
|
219
|
+
import json
|
|
220
|
+
from pathlib import Path
|
|
221
|
+
|
|
222
|
+
|
|
223
|
+
def test_behavior_fields_match_runtime_telemetry(mock_core):
|
|
224
|
+
# Configure the runtime and wait for representative telemetry first.
|
|
225
|
+
behaviors = json.loads(Path("src/behaviors.json").read_text())
|
|
226
|
+
mock_core.assert_behavior_conditions_match_telemetry(behaviors)
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
The assertion checks condition fields, declared aliases, value types, and
|
|
230
|
+
template conditions. Parameter-driven dynamic conditions must be named with
|
|
231
|
+
`dynamic_condition_ids` and covered by a separate focused test; they are never
|
|
232
|
+
silently skipped.
|
|
233
|
+
|
|
212
234
|
## Install
|
|
213
235
|
|
|
214
236
|
### Install from PyPI
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
piphi_runtime_testkit_python-0.1.4.dist-info/METADATA,sha256=scck6ownxllmcSJjiiqUVvCBpJEazHwsxXwRVtzRlbU,26281
|
|
2
|
+
piphi_runtime_testkit_python-0.1.4.dist-info/WHEEL,sha256=IuIk3wbaEnFH1cx75iM2R1CmliRI3dFLXTbRakgvgFQ,90
|
|
3
|
+
piphi_runtime_testkit_python-0.1.4.dist-info/entry_points.txt,sha256=zTIXoQOJK3VVIMOfKpB5Y3vnSbyFgyU45GZCb7fLGHI,120
|
|
4
|
+
piphi_runtime_testkit_python-0.1.4.dist-info/licenses/LICENSE,sha256=pS6szQI4X7qAin1ODdoDp-XwZvsFy6ZZ28-JAwGHgzo,1070
|
|
5
|
+
piphi_runtime_testkit_python/__init__.py,sha256=oXliAmDgzDNPTvIqfmjqoALQ1ZEXr32NLavy7K4Oeao,933
|
|
6
|
+
piphi_runtime_testkit_python/assertions.py,sha256=uxekhD6uha5S00KAE42G_IlVbuG5AaZpErF5rKQwNLk,7105
|
|
7
|
+
piphi_runtime_testkit_python/behavior_contract.py,sha256=ENLodK3wpt_zxJn4AFBCgw-zhfT4du93SFQZyh194oE,9054
|
|
8
|
+
piphi_runtime_testkit_python/builders.py,sha256=UmxPB1fSDGqhQzcpmmzBH4w3ZSGxtWHXQ4bfZL18kd0,2961
|
|
9
|
+
piphi_runtime_testkit_python/mock_core.py,sha256=IG37ZwnrrXiVVIhApvwTRAo1uAy2FbICBpRdb6etnDc,7533
|
|
10
|
+
piphi_runtime_testkit_python/pytest_plugin.py,sha256=8LUxO-2o7PZOQeMLQ2THbtfwn2HJ91vXkwGiYipmNuw,965
|
|
11
|
+
piphi_runtime_testkit_python-0.1.4.dist-info/RECORD,,
|
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
piphi_runtime_testkit_python-0.1.2.dist-info/METADATA,sha256=vtARpxGqDCRUiKNBaNTk6t43wKMNFOU4_E4KzSV45UA,25426
|
|
2
|
-
piphi_runtime_testkit_python-0.1.2.dist-info/WHEEL,sha256=Z36eTX6lG3PITRleSd5hAZHCcz52yg3c0JQVxKBbLW0,90
|
|
3
|
-
piphi_runtime_testkit_python-0.1.2.dist-info/entry_points.txt,sha256=zTIXoQOJK3VVIMOfKpB5Y3vnSbyFgyU45GZCb7fLGHI,120
|
|
4
|
-
piphi_runtime_testkit_python-0.1.2.dist-info/licenses/LICENSE,sha256=pS6szQI4X7qAin1ODdoDp-XwZvsFy6ZZ28-JAwGHgzo,1070
|
|
5
|
-
piphi_runtime_testkit_python/__init__.py,sha256=Fy3rD1zp3NH_g4__52H7CWUeY0ErwTI8ChDRvlj361Y,668
|
|
6
|
-
piphi_runtime_testkit_python/assertions.py,sha256=g-lNyYwbpKYB3VduR9K8aFwKzMIFor0K2svBn_TfzxU,6882
|
|
7
|
-
piphi_runtime_testkit_python/builders.py,sha256=UmxPB1fSDGqhQzcpmmzBH4w3ZSGxtWHXQ4bfZL18kd0,2961
|
|
8
|
-
piphi_runtime_testkit_python/mock_core.py,sha256=vpAF0xDvoPHLoKbKuoxm3n1l92Yz28QxBlJNGe91oWk,6692
|
|
9
|
-
piphi_runtime_testkit_python/pytest_plugin.py,sha256=8LUxO-2o7PZOQeMLQ2THbtfwn2HJ91vXkwGiYipmNuw,965
|
|
10
|
-
piphi_runtime_testkit_python-0.1.2.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|