dynamic-systems-testing-framework 1.0.1__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.
- dstf/__init__.py +41 -0
- dstf/__main__.py +10 -0
- dstf/authoring/__init__.py +13 -0
- dstf/authoring/builtin/__init__.py +32 -0
- dstf/authoring/builtin/accept.py +205 -0
- dstf/authoring/builtin/edit_criteria.py +479 -0
- dstf/authoring/builtin/edit_family.py +228 -0
- dstf/authoring/builtin/edit_on_plot.py +1354 -0
- dstf/authoring/builtin/edit_tree.py +1399 -0
- dstf/authoring/builtin/enqueue.py +144 -0
- dstf/authoring/capabilities.py +463 -0
- dstf/authoring/errors.py +49 -0
- dstf/authoring/family_rows.py +526 -0
- dstf/authoring/forms.py +631 -0
- dstf/authoring/layout.py +104 -0
- dstf/authoring/live_rows.py +337 -0
- dstf/authoring/live_runs.py +385 -0
- dstf/authoring/needle.py +127 -0
- dstf/authoring/picker_rows.py +92 -0
- dstf/authoring/queue_rows.py +354 -0
- dstf/authoring/session.py +3016 -0
- dstf/authoring/shell/__init__.py +9 -0
- dstf/authoring/shell/actions.py +702 -0
- dstf/authoring/shell/app.py +53 -0
- dstf/authoring/shell/drawer_controls.py +190 -0
- dstf/authoring/shell/family_fold.py +468 -0
- dstf/authoring/shell/figure_hook.py +249 -0
- dstf/authoring/shell/grips.js +1000 -0
- dstf/authoring/shell/header.py +783 -0
- dstf/authoring/shell/live_view.py +93 -0
- dstf/authoring/shell/picker.py +267 -0
- dstf/authoring/shell/placing.py +122 -0
- dstf/authoring/shell/plot_card.py +1171 -0
- dstf/authoring/shell/queue_panel.py +433 -0
- dstf/authoring/shell/rail.py +593 -0
- dstf/authoring/shell/shell.py +528 -0
- dstf/authoring/shell/stats_card.py +501 -0
- dstf/authoring/shell/strip.py +204 -0
- dstf/authoring/shell/style.py +721 -0
- dstf/authoring/shell/suite.py +277 -0
- dstf/authoring/shell/suite_grid.js +414 -0
- dstf/authoring/shell/suite_grid.py +373 -0
- dstf/authoring/shell/tab_theme.py +110 -0
- dstf/authoring/shell/tabstate.py +146 -0
- dstf/authoring/shell/test_list.py +179 -0
- dstf/authoring/shell/test_page.py +1404 -0
- dstf/authoring/shell/tree_card.py +1171 -0
- dstf/authoring/shell/variables_drawer.py +589 -0
- dstf/authoring/shell/widgets.py +188 -0
- dstf/authoring/situation.py +287 -0
- dstf/authoring/spec_document.py +215 -0
- dstf/authoring/spec_preview.py +83 -0
- dstf/authoring/suite_rows.py +99 -0
- dstf/authoring/tree_move.py +516 -0
- dstf/authoring/tree_pointer.py +33 -0
- dstf/authoring/tree_text.py +61 -0
- dstf/authoring/typed_number.js +64 -0
- dstf/authoring/typed_number.py +76 -0
- dstf/authoring/unscorable.py +141 -0
- dstf/authoring/variable_rows.py +216 -0
- dstf/backends/__init__.py +283 -0
- dstf/backends/base.py +1117 -0
- dstf/backends/common/__init__.py +1 -0
- dstf/backends/common/json_driver.py +484 -0
- dstf/backends/common/live_mat.py +369 -0
- dstf/backends/common/mat_reader.py +632 -0
- dstf/backends/common/proc_kill.py +238 -0
- dstf/backends/common/proc_output.py +20 -0
- dstf/backends/common/result_files.py +109 -0
- dstf/backends/common/result_variables.py +329 -0
- dstf/backends/common/statistics.py +39 -0
- dstf/backends/cross_backend.py +193 -0
- dstf/backends/dymola/__init__.py +5 -0
- dstf/backends/dymola/backend.py +776 -0
- dstf/backends/dymola/interface_loader.py +241 -0
- dstf/backends/dymola/log_parser.py +396 -0
- dstf/backends/dymola/persistent_backend.py +949 -0
- dstf/backends/fmpy/__init__.py +11 -0
- dstf/backends/fmpy/backend.py +443 -0
- dstf/backends/julia/__init__.py +27 -0
- dstf/backends/julia/backend.py +156 -0
- dstf/backends/julia/persistent_backend.py +541 -0
- dstf/backends/julia/run_persistent.jl +167 -0
- dstf/backends/julia/run_test.jl +146 -0
- dstf/backends/openmodelica/__init__.py +21 -0
- dstf/backends/openmodelica/backend.py +368 -0
- dstf/backends/openmodelica/init_xml.py +76 -0
- dstf/backends/openmodelica/log_parser.py +119 -0
- dstf/backends/openmodelica/mos_generator.py +228 -0
- dstf/backends/openmodelica/persistent_backend.py +1045 -0
- dstf/backends/openmodelica/session_loader.py +63 -0
- dstf/backends/persistent.py +1811 -0
- dstf/backends/pool.py +602 -0
- dstf/backends/progress.py +395 -0
- dstf/backends/python/__init__.py +11 -0
- dstf/backends/python/backend.py +141 -0
- dstf/backends/python/run_test.py +141 -0
- dstf/backends/run_metadata.py +103 -0
- dstf/backends/types.py +310 -0
- dstf/cli/__init__.py +12 -0
- dstf/cli/author_cmd.py +61 -0
- dstf/cli/baseline_cmds.py +341 -0
- dstf/cli/common.py +231 -0
- dstf/cli/diag_cmds.py +350 -0
- dstf/cli/main.py +561 -0
- dstf/cli/review.py +398 -0
- dstf/cli/run_cmds.py +589 -0
- dstf/cli/spec_cmds.py +123 -0
- dstf/comparison/__init__.py +13 -0
- dstf/comparison/algorithms.py +2127 -0
- dstf/comparison/builtins.py +291 -0
- dstf/comparison/comparator.py +1189 -0
- dstf/comparison/geometry.py +164 -0
- dstf/comparison/handles.py +1194 -0
- dstf/comparison/metric_tree.py +332 -0
- dstf/comparison/mode_schema.py +176 -0
- dstf/comparison/modes.py +788 -0
- dstf/comparison/registry.py +230 -0
- dstf/comparison/seeds.py +281 -0
- dstf/comparison/sides.py +158 -0
- dstf/comparison/snap.py +62 -0
- dstf/comparison/strict_keys.py +246 -0
- dstf/comparison/summaries.py +354 -0
- dstf/comparison/tree_eval.py +868 -0
- dstf/comparison/tree_spec.py +678 -0
- dstf/comparison/types.py +194 -0
- dstf/comparison/validator.py +232 -0
- dstf/config.py +815 -0
- dstf/discovery/__init__.py +0 -0
- dstf/discovery/json_recognizer.py +702 -0
- dstf/discovery/library_state.py +248 -0
- dstf/discovery/mo_parser.py +794 -0
- dstf/discovery/patch_apply.py +308 -0
- dstf/discovery/patch_envelope.py +63 -0
- dstf/discovery/plots.py +318 -0
- dstf/discovery/recognizer.py +103 -0
- dstf/discovery/scan_cache.py +154 -0
- dstf/discovery/spec_parser.py +538 -0
- dstf/discovery/spec_shape.py +122 -0
- dstf/discovery/spec_update.py +493 -0
- dstf/discovery/test_registry.py +488 -0
- dstf/discovery/unit_tests.py +95 -0
- dstf/figures/__init__.py +14 -0
- dstf/figures/assemble.py +48 -0
- dstf/figures/decimate.py +150 -0
- dstf/figures/families.py +289 -0
- dstf/figures/palette.py +170 -0
- dstf/figures/plotly.py +1413 -0
- dstf/figures/view.py +233 -0
- dstf/pipeline.py +435 -0
- dstf/plugins.py +104 -0
- dstf/reporting/__init__.py +0 -0
- dstf/reporting/console_report.py +187 -0
- dstf/reporting/context_builder.py +979 -0
- dstf/reporting/dashboard_render.py +534 -0
- dstf/reporting/junit_report.py +192 -0
- dstf/reporting/overlay_loader.py +708 -0
- dstf/reporting/report_suite.py +1059 -0
- dstf/reporting/rows.py +458 -0
- dstf/reporting/schema_export.py +413 -0
- dstf/reporting/stats_rows.py +321 -0
- dstf/reporting/status.py +388 -0
- dstf/reporting/templates/axes.js +61 -0
- dstf/reporting/templates/dashboard.html +284 -0
- dstf/reporting/templates/dashboard.js +604 -0
- dstf/reporting/templates/filters.js +100 -0
- dstf/reporting/templates/interactive.html +420 -0
- dstf/reporting/templates/report.js +116 -0
- dstf/reporting/templates/vendor/plotly.min.js +8 -0
- dstf/reporting/theme.py +324 -0
- dstf/reporting/tree_rows.py +73 -0
- dstf/reporting/verdicts.py +124 -0
- dstf/storage/__init__.py +0 -0
- dstf/storage/accept.py +216 -0
- dstf/storage/baseline.py +43 -0
- dstf/storage/batch_manifest.py +204 -0
- dstf/storage/ref_index.py +295 -0
- dstf/storage/reference_store.py +661 -0
- dstf/storage/roles.py +576 -0
- dstf/storage/run_stats.py +85 -0
- dynamic_systems_testing_framework-1.0.1.dist-info/METADATA +266 -0
- dynamic_systems_testing_framework-1.0.1.dist-info/RECORD +185 -0
- dynamic_systems_testing_framework-1.0.1.dist-info/WHEEL +4 -0
- dynamic_systems_testing_framework-1.0.1.dist-info/entry_points.txt +2 -0
- dynamic_systems_testing_framework-1.0.1.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
"""`dstf author` (§4.7 "Launch"; brief §3.8): the project checked for a
|
|
2
|
+
config or spec error, the shell mounted, the project opened on a thread
|
|
3
|
+
once the server runs, served on 127.0.0.1 until Stop server. The page
|
|
4
|
+
appears before a result is read."""
|
|
5
|
+
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
|
|
10
|
+
from nicegui import app, ui
|
|
11
|
+
|
|
12
|
+
from ..session import check_project
|
|
13
|
+
from .shell import Shell
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def run_app(
|
|
17
|
+
config_path: Path,
|
|
18
|
+
*,
|
|
19
|
+
filter: str | None = None,
|
|
20
|
+
package: str | None = None,
|
|
21
|
+
port: int | None = None,
|
|
22
|
+
work_dir: Path | None = None,
|
|
23
|
+
backend: str | None = None,
|
|
24
|
+
show: bool = True,
|
|
25
|
+
) -> None:
|
|
26
|
+
# Before anything is served: a config or spec error is one `Config
|
|
27
|
+
# error: …` line and exit 2, as from every other command (`cli.main`
|
|
28
|
+
# prints it). The open itself still runs on a thread, below — it scans
|
|
29
|
+
# the library and reads the results, which this does not.
|
|
30
|
+
check_project(config_path, backend=backend, work_dir=work_dir)
|
|
31
|
+
shell = Shell(
|
|
32
|
+
config_path,
|
|
33
|
+
work_dir=work_dir,
|
|
34
|
+
filter=filter,
|
|
35
|
+
package=package,
|
|
36
|
+
show_browser=show,
|
|
37
|
+
)
|
|
38
|
+
shell.mount()
|
|
39
|
+
|
|
40
|
+
def start() -> None:
|
|
41
|
+
shell.start()
|
|
42
|
+
shell.open_async(backend)
|
|
43
|
+
|
|
44
|
+
app.on_startup(start)
|
|
45
|
+
app.on_shutdown(shell.shutdown)
|
|
46
|
+
ui.run(
|
|
47
|
+
shell.index,
|
|
48
|
+
host="127.0.0.1",
|
|
49
|
+
port=port or 8080,
|
|
50
|
+
show=show,
|
|
51
|
+
reload=False,
|
|
52
|
+
title="DSTF",
|
|
53
|
+
)
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
"""What both drawers of the Test page draw (the owner's ruling Q7: one
|
|
2
|
+
control set on every list): the filter box with its `.*` and `Aa` toggles
|
|
3
|
+
beside it and the sentence under it, and an icon button opening a menu of
|
|
4
|
+
choices (`role=menuitemradio`, the current one checked). The drawers own
|
|
5
|
+
their state (`TabState`); these draw it and report a change."""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
from collections.abc import Callable, Sequence
|
|
10
|
+
from dataclasses import dataclass
|
|
11
|
+
from functools import partial
|
|
12
|
+
from typing import Any
|
|
13
|
+
|
|
14
|
+
from nicegui import ui
|
|
15
|
+
|
|
16
|
+
#: A menu's keyboard (Quasar's QMenu has none): ArrowDown / ArrowUp move
|
|
17
|
+
#: the focus to the next / previous choice, wrapping; Home / End to the
|
|
18
|
+
#: first / last. From outside the choices ArrowDown goes to the first and
|
|
19
|
+
#: ArrowUp to the last. Tab closes it; the focus returns to its button (the
|
|
20
|
+
#: menu pattern: its choices are out of the tab order, `tabindex="-1"`).
|
|
21
|
+
#: The choices are the menu's radio items, or its plain items that are not
|
|
22
|
+
#: disabled (the tree card's Move to…: a refused place is no choice).
|
|
23
|
+
#: ``__MENU_ID__`` is the menu's element id (`menu_keys`).
|
|
24
|
+
_MENU_KEYS = """(e) => {
|
|
25
|
+
if (e.key === 'Tab') { getElement(__MENU_ID__)?.hide(); return; }
|
|
26
|
+
const keys = ['ArrowDown', 'ArrowUp', 'Home', 'End'];
|
|
27
|
+
if (!keys.includes(e.key)) return;
|
|
28
|
+
const items = [...e.currentTarget.querySelectorAll(
|
|
29
|
+
'[role="menuitemradio"], [role="menuitem"]:not([aria-disabled="true"])')];
|
|
30
|
+
if (!items.length) return;
|
|
31
|
+
e.preventDefault();
|
|
32
|
+
const at = items.indexOf(document.activeElement);
|
|
33
|
+
const last = items.length - 1;
|
|
34
|
+
let to;
|
|
35
|
+
if (e.key === 'Home') to = 0;
|
|
36
|
+
else if (e.key === 'End') to = last;
|
|
37
|
+
else if (at < 0) to = e.key === 'ArrowDown' ? 0 : last;
|
|
38
|
+
else if (e.key === 'ArrowDown') to = at === last ? 0 : at + 1;
|
|
39
|
+
else to = at === 0 ? last : at - 1;
|
|
40
|
+
items[to].focus();
|
|
41
|
+
}"""
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
#: A view control's tooltip, above it: below, it covers what opens there.
|
|
45
|
+
TIP_ABOVE = 'anchor="top middle" self="bottom middle"'
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def menu_keys(menu: ui.menu) -> str:
|
|
49
|
+
"""`_MENU_KEYS` for ``menu``."""
|
|
50
|
+
return _MENU_KEYS.replace("__MENU_ID__", str(menu.id))
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def _pressed(button: ui.button, on: bool) -> None:
|
|
54
|
+
button.props(f'aria-pressed="{str(on).lower()}"')
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
@dataclass
|
|
58
|
+
class FilterBox:
|
|
59
|
+
needle: ui.input
|
|
60
|
+
regex: ui.button
|
|
61
|
+
case: ui.button
|
|
62
|
+
why: ui.label
|
|
63
|
+
|
|
64
|
+
def say(self, text: str | None) -> None:
|
|
65
|
+
"""The sentence under the box (a bad pattern, a timeout), or none."""
|
|
66
|
+
self.why.set_text(text or "")
|
|
67
|
+
self.why.set_visibility(bool(text))
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def filter_box(
|
|
71
|
+
needle: str,
|
|
72
|
+
regex: bool,
|
|
73
|
+
case: bool,
|
|
74
|
+
on_change: Callable[[str, bool, bool], None],
|
|
75
|
+
placeholder: str,
|
|
76
|
+
marker: str,
|
|
77
|
+
) -> FilterBox:
|
|
78
|
+
"""The input, with `.*` "Regular expression" and `Aa` "Match case" as
|
|
79
|
+
toggles (`aria-pressed`) beside it, then the sentence line, hidden.
|
|
80
|
+
Markers: ``<marker>needle``, ``<marker>regex``, ``<marker>case``,
|
|
81
|
+
``<marker>filter:why``. ``on_change(needle, regex, case)`` on any of
|
|
82
|
+
them."""
|
|
83
|
+
state: dict[str, Any] = {"needle": needle, "regex": regex, "case": case}
|
|
84
|
+
|
|
85
|
+
def changed() -> None:
|
|
86
|
+
on_change(state["needle"], state["regex"], state["case"])
|
|
87
|
+
|
|
88
|
+
def typed(e: Any) -> None:
|
|
89
|
+
state["needle"] = e.value or ""
|
|
90
|
+
changed()
|
|
91
|
+
|
|
92
|
+
def flip(key: str, button: ui.button) -> None:
|
|
93
|
+
state[key] = not state[key]
|
|
94
|
+
_pressed(button, state[key])
|
|
95
|
+
changed()
|
|
96
|
+
|
|
97
|
+
buttons: dict[str, ui.button] = {}
|
|
98
|
+
with ui.row().classes("items-center no-wrap gap-1 w-full"):
|
|
99
|
+
box = (
|
|
100
|
+
ui.input(placeholder=placeholder, value=needle, on_change=typed)
|
|
101
|
+
.props("dense outlined clearable debounce=250")
|
|
102
|
+
.classes("col")
|
|
103
|
+
.mark(f"{marker}needle")
|
|
104
|
+
)
|
|
105
|
+
for key, text, tip in (
|
|
106
|
+
("regex", ".*", "Regular expression"),
|
|
107
|
+
("case", "Aa", "Match case"),
|
|
108
|
+
):
|
|
109
|
+
button = (
|
|
110
|
+
ui.button(text)
|
|
111
|
+
.props("flat dense no-caps")
|
|
112
|
+
.classes("dstf-mono")
|
|
113
|
+
.mark(f"{marker}{key}")
|
|
114
|
+
)
|
|
115
|
+
button.on_click(partial(flip, key, button))
|
|
116
|
+
_pressed(button, state[key])
|
|
117
|
+
with button:
|
|
118
|
+
ui.tooltip(tip)
|
|
119
|
+
buttons[key] = button
|
|
120
|
+
why = ui.label().classes("dstf-why").mark(f"{marker}filter:why")
|
|
121
|
+
why.set_visibility(False)
|
|
122
|
+
return FilterBox(box, buttons["regex"], buttons["case"], why)
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
@dataclass
|
|
126
|
+
class MenuButton:
|
|
127
|
+
button: ui.button
|
|
128
|
+
items: dict[str, ui.menu_item]
|
|
129
|
+
value: str
|
|
130
|
+
|
|
131
|
+
def set(self, value: str) -> None:
|
|
132
|
+
"""Check ``value``'s item and no other."""
|
|
133
|
+
self.value = value
|
|
134
|
+
for key, item in self.items.items():
|
|
135
|
+
on = key == value
|
|
136
|
+
item.props(f'aria-checked="{str(on).lower()}"')
|
|
137
|
+
if on:
|
|
138
|
+
item.classes(add="dstf-menu-checked")
|
|
139
|
+
else:
|
|
140
|
+
item.classes(remove="dstf-menu-checked")
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
def menu_button(
|
|
144
|
+
icon: str,
|
|
145
|
+
tip: str,
|
|
146
|
+
options: Sequence[tuple[str, str]],
|
|
147
|
+
value: str,
|
|
148
|
+
on_change: Callable[[str], None],
|
|
149
|
+
marker: str,
|
|
150
|
+
) -> MenuButton:
|
|
151
|
+
"""An icon button, named and tooltipped ``tip``, opening a menu of
|
|
152
|
+
``options`` (``(value, label)``), each a `menuitemradio` with the
|
|
153
|
+
current one checked, and the arrow keys moving among them. Markers:
|
|
154
|
+
``<marker>`` on the button, ``<marker>:<value>`` on each item."""
|
|
155
|
+
button = ui.button(icon=icon).props("flat dense round").mark(marker)
|
|
156
|
+
# On the props dict: its name is the tip (the icon's ligature names
|
|
157
|
+
# nothing), and a props string would break on a quote.
|
|
158
|
+
button.props["aria-label"] = tip
|
|
159
|
+
button.props["aria-haspopup"] = "menu"
|
|
160
|
+
items: dict[str, ui.menu_item] = {}
|
|
161
|
+
|
|
162
|
+
def choose(key: str) -> None:
|
|
163
|
+
menu.set(key)
|
|
164
|
+
on_change(key)
|
|
165
|
+
|
|
166
|
+
with button:
|
|
167
|
+
# Above the button: below it (Quasar's default) the tooltip covered
|
|
168
|
+
# the open menu's first choice and its check.
|
|
169
|
+
ui.tooltip(tip).props(TIP_ABOVE)
|
|
170
|
+
with ui.menu().props('role="menu"') as element:
|
|
171
|
+
element.on("keydown", js_handler=menu_keys(element))
|
|
172
|
+
for key, label in options:
|
|
173
|
+
item = (
|
|
174
|
+
ui.menu_item(on_click=partial(choose, key))
|
|
175
|
+
.props('role="menuitemradio" tabindex="-1"')
|
|
176
|
+
.mark(f"{marker}:{key}")
|
|
177
|
+
)
|
|
178
|
+
# `ui.menu_item` has no `set_text`: the text is a label in
|
|
179
|
+
# an item section, the check a hidden icon beside it.
|
|
180
|
+
with item:
|
|
181
|
+
with ui.item_section().props("side"):
|
|
182
|
+
ui.icon("check").classes("dstf-menu-check").props(
|
|
183
|
+
'aria-hidden="true"'
|
|
184
|
+
)
|
|
185
|
+
with ui.item_section():
|
|
186
|
+
ui.item_label(label)
|
|
187
|
+
items[key] = item
|
|
188
|
+
menu = MenuButton(button, items, value)
|
|
189
|
+
menu.set(value)
|
|
190
|
+
return menu
|