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.
Files changed (185) hide show
  1. dstf/__init__.py +41 -0
  2. dstf/__main__.py +10 -0
  3. dstf/authoring/__init__.py +13 -0
  4. dstf/authoring/builtin/__init__.py +32 -0
  5. dstf/authoring/builtin/accept.py +205 -0
  6. dstf/authoring/builtin/edit_criteria.py +479 -0
  7. dstf/authoring/builtin/edit_family.py +228 -0
  8. dstf/authoring/builtin/edit_on_plot.py +1354 -0
  9. dstf/authoring/builtin/edit_tree.py +1399 -0
  10. dstf/authoring/builtin/enqueue.py +144 -0
  11. dstf/authoring/capabilities.py +463 -0
  12. dstf/authoring/errors.py +49 -0
  13. dstf/authoring/family_rows.py +526 -0
  14. dstf/authoring/forms.py +631 -0
  15. dstf/authoring/layout.py +104 -0
  16. dstf/authoring/live_rows.py +337 -0
  17. dstf/authoring/live_runs.py +385 -0
  18. dstf/authoring/needle.py +127 -0
  19. dstf/authoring/picker_rows.py +92 -0
  20. dstf/authoring/queue_rows.py +354 -0
  21. dstf/authoring/session.py +3016 -0
  22. dstf/authoring/shell/__init__.py +9 -0
  23. dstf/authoring/shell/actions.py +702 -0
  24. dstf/authoring/shell/app.py +53 -0
  25. dstf/authoring/shell/drawer_controls.py +190 -0
  26. dstf/authoring/shell/family_fold.py +468 -0
  27. dstf/authoring/shell/figure_hook.py +249 -0
  28. dstf/authoring/shell/grips.js +1000 -0
  29. dstf/authoring/shell/header.py +783 -0
  30. dstf/authoring/shell/live_view.py +93 -0
  31. dstf/authoring/shell/picker.py +267 -0
  32. dstf/authoring/shell/placing.py +122 -0
  33. dstf/authoring/shell/plot_card.py +1171 -0
  34. dstf/authoring/shell/queue_panel.py +433 -0
  35. dstf/authoring/shell/rail.py +593 -0
  36. dstf/authoring/shell/shell.py +528 -0
  37. dstf/authoring/shell/stats_card.py +501 -0
  38. dstf/authoring/shell/strip.py +204 -0
  39. dstf/authoring/shell/style.py +721 -0
  40. dstf/authoring/shell/suite.py +277 -0
  41. dstf/authoring/shell/suite_grid.js +414 -0
  42. dstf/authoring/shell/suite_grid.py +373 -0
  43. dstf/authoring/shell/tab_theme.py +110 -0
  44. dstf/authoring/shell/tabstate.py +146 -0
  45. dstf/authoring/shell/test_list.py +179 -0
  46. dstf/authoring/shell/test_page.py +1404 -0
  47. dstf/authoring/shell/tree_card.py +1171 -0
  48. dstf/authoring/shell/variables_drawer.py +589 -0
  49. dstf/authoring/shell/widgets.py +188 -0
  50. dstf/authoring/situation.py +287 -0
  51. dstf/authoring/spec_document.py +215 -0
  52. dstf/authoring/spec_preview.py +83 -0
  53. dstf/authoring/suite_rows.py +99 -0
  54. dstf/authoring/tree_move.py +516 -0
  55. dstf/authoring/tree_pointer.py +33 -0
  56. dstf/authoring/tree_text.py +61 -0
  57. dstf/authoring/typed_number.js +64 -0
  58. dstf/authoring/typed_number.py +76 -0
  59. dstf/authoring/unscorable.py +141 -0
  60. dstf/authoring/variable_rows.py +216 -0
  61. dstf/backends/__init__.py +283 -0
  62. dstf/backends/base.py +1117 -0
  63. dstf/backends/common/__init__.py +1 -0
  64. dstf/backends/common/json_driver.py +484 -0
  65. dstf/backends/common/live_mat.py +369 -0
  66. dstf/backends/common/mat_reader.py +632 -0
  67. dstf/backends/common/proc_kill.py +238 -0
  68. dstf/backends/common/proc_output.py +20 -0
  69. dstf/backends/common/result_files.py +109 -0
  70. dstf/backends/common/result_variables.py +329 -0
  71. dstf/backends/common/statistics.py +39 -0
  72. dstf/backends/cross_backend.py +193 -0
  73. dstf/backends/dymola/__init__.py +5 -0
  74. dstf/backends/dymola/backend.py +776 -0
  75. dstf/backends/dymola/interface_loader.py +241 -0
  76. dstf/backends/dymola/log_parser.py +396 -0
  77. dstf/backends/dymola/persistent_backend.py +949 -0
  78. dstf/backends/fmpy/__init__.py +11 -0
  79. dstf/backends/fmpy/backend.py +443 -0
  80. dstf/backends/julia/__init__.py +27 -0
  81. dstf/backends/julia/backend.py +156 -0
  82. dstf/backends/julia/persistent_backend.py +541 -0
  83. dstf/backends/julia/run_persistent.jl +167 -0
  84. dstf/backends/julia/run_test.jl +146 -0
  85. dstf/backends/openmodelica/__init__.py +21 -0
  86. dstf/backends/openmodelica/backend.py +368 -0
  87. dstf/backends/openmodelica/init_xml.py +76 -0
  88. dstf/backends/openmodelica/log_parser.py +119 -0
  89. dstf/backends/openmodelica/mos_generator.py +228 -0
  90. dstf/backends/openmodelica/persistent_backend.py +1045 -0
  91. dstf/backends/openmodelica/session_loader.py +63 -0
  92. dstf/backends/persistent.py +1811 -0
  93. dstf/backends/pool.py +602 -0
  94. dstf/backends/progress.py +395 -0
  95. dstf/backends/python/__init__.py +11 -0
  96. dstf/backends/python/backend.py +141 -0
  97. dstf/backends/python/run_test.py +141 -0
  98. dstf/backends/run_metadata.py +103 -0
  99. dstf/backends/types.py +310 -0
  100. dstf/cli/__init__.py +12 -0
  101. dstf/cli/author_cmd.py +61 -0
  102. dstf/cli/baseline_cmds.py +341 -0
  103. dstf/cli/common.py +231 -0
  104. dstf/cli/diag_cmds.py +350 -0
  105. dstf/cli/main.py +561 -0
  106. dstf/cli/review.py +398 -0
  107. dstf/cli/run_cmds.py +589 -0
  108. dstf/cli/spec_cmds.py +123 -0
  109. dstf/comparison/__init__.py +13 -0
  110. dstf/comparison/algorithms.py +2127 -0
  111. dstf/comparison/builtins.py +291 -0
  112. dstf/comparison/comparator.py +1189 -0
  113. dstf/comparison/geometry.py +164 -0
  114. dstf/comparison/handles.py +1194 -0
  115. dstf/comparison/metric_tree.py +332 -0
  116. dstf/comparison/mode_schema.py +176 -0
  117. dstf/comparison/modes.py +788 -0
  118. dstf/comparison/registry.py +230 -0
  119. dstf/comparison/seeds.py +281 -0
  120. dstf/comparison/sides.py +158 -0
  121. dstf/comparison/snap.py +62 -0
  122. dstf/comparison/strict_keys.py +246 -0
  123. dstf/comparison/summaries.py +354 -0
  124. dstf/comparison/tree_eval.py +868 -0
  125. dstf/comparison/tree_spec.py +678 -0
  126. dstf/comparison/types.py +194 -0
  127. dstf/comparison/validator.py +232 -0
  128. dstf/config.py +815 -0
  129. dstf/discovery/__init__.py +0 -0
  130. dstf/discovery/json_recognizer.py +702 -0
  131. dstf/discovery/library_state.py +248 -0
  132. dstf/discovery/mo_parser.py +794 -0
  133. dstf/discovery/patch_apply.py +308 -0
  134. dstf/discovery/patch_envelope.py +63 -0
  135. dstf/discovery/plots.py +318 -0
  136. dstf/discovery/recognizer.py +103 -0
  137. dstf/discovery/scan_cache.py +154 -0
  138. dstf/discovery/spec_parser.py +538 -0
  139. dstf/discovery/spec_shape.py +122 -0
  140. dstf/discovery/spec_update.py +493 -0
  141. dstf/discovery/test_registry.py +488 -0
  142. dstf/discovery/unit_tests.py +95 -0
  143. dstf/figures/__init__.py +14 -0
  144. dstf/figures/assemble.py +48 -0
  145. dstf/figures/decimate.py +150 -0
  146. dstf/figures/families.py +289 -0
  147. dstf/figures/palette.py +170 -0
  148. dstf/figures/plotly.py +1413 -0
  149. dstf/figures/view.py +233 -0
  150. dstf/pipeline.py +435 -0
  151. dstf/plugins.py +104 -0
  152. dstf/reporting/__init__.py +0 -0
  153. dstf/reporting/console_report.py +187 -0
  154. dstf/reporting/context_builder.py +979 -0
  155. dstf/reporting/dashboard_render.py +534 -0
  156. dstf/reporting/junit_report.py +192 -0
  157. dstf/reporting/overlay_loader.py +708 -0
  158. dstf/reporting/report_suite.py +1059 -0
  159. dstf/reporting/rows.py +458 -0
  160. dstf/reporting/schema_export.py +413 -0
  161. dstf/reporting/stats_rows.py +321 -0
  162. dstf/reporting/status.py +388 -0
  163. dstf/reporting/templates/axes.js +61 -0
  164. dstf/reporting/templates/dashboard.html +284 -0
  165. dstf/reporting/templates/dashboard.js +604 -0
  166. dstf/reporting/templates/filters.js +100 -0
  167. dstf/reporting/templates/interactive.html +420 -0
  168. dstf/reporting/templates/report.js +116 -0
  169. dstf/reporting/templates/vendor/plotly.min.js +8 -0
  170. dstf/reporting/theme.py +324 -0
  171. dstf/reporting/tree_rows.py +73 -0
  172. dstf/reporting/verdicts.py +124 -0
  173. dstf/storage/__init__.py +0 -0
  174. dstf/storage/accept.py +216 -0
  175. dstf/storage/baseline.py +43 -0
  176. dstf/storage/batch_manifest.py +204 -0
  177. dstf/storage/ref_index.py +295 -0
  178. dstf/storage/reference_store.py +661 -0
  179. dstf/storage/roles.py +576 -0
  180. dstf/storage/run_stats.py +85 -0
  181. dynamic_systems_testing_framework-1.0.1.dist-info/METADATA +266 -0
  182. dynamic_systems_testing_framework-1.0.1.dist-info/RECORD +185 -0
  183. dynamic_systems_testing_framework-1.0.1.dist-info/WHEEL +4 -0
  184. dynamic_systems_testing_framework-1.0.1.dist-info/entry_points.txt +2 -0
  185. dynamic_systems_testing_framework-1.0.1.dist-info/licenses/LICENSE +21 -0
@@ -0,0 +1,277 @@
1
+ """The Suite page (brief §3.2 "two pages", §3.8; DESIGN.md "Tables"): what a
2
+ batch acts on. The header, the progress line, one toolbar — the count pills
3
+ and the view toggles (drawn by the table's script), the selection's label,
4
+ the batch actions as icons — the Suite table (`suite_grid.py`) and, beside
5
+ it, the queue panel (`queue_panel.py`). The table filters, sorts and groups in the browser; Python sends its
6
+ rows and receives its selection, which lives in the tab (`tabstate.py`),
7
+ never in the Session (D129). One object per browser tab."""
8
+
9
+ from __future__ import annotations
10
+
11
+ import logging
12
+ from typing import TYPE_CHECKING, Any
13
+
14
+ from nicegui import ui
15
+
16
+ from ...reporting.rows import TestRow
17
+ from ..capabilities import Placement
18
+ from ..session import Selection
19
+ from ..suite_rows import suite_rows
20
+ from .actions import ActionBar
21
+ from .header import Header
22
+ from .queue_panel import ANCHOR, QueuePanel
23
+ from .suite_grid import GRID_CSS, STORAGE, SuiteGrid, scripts_html
24
+ from .tab_theme import first_paint
25
+ from .tabstate import TabState, caught_up, connected_tab_state, heard_spec
26
+
27
+ if TYPE_CHECKING:
28
+ from .shell import Shell
29
+
30
+ logger = logging.getLogger(__name__)
31
+
32
+
33
+ class SuitePage:
34
+ def __init__(self, shell: Shell) -> None:
35
+ self.shell = shell
36
+ self.client: Any = None
37
+ #: The tests the grid shows once this tick is flushed: the visible
38
+ #: tests, in the project's order.
39
+ self.ids: list[str] = []
40
+ #: The tests the grid holds now: what the last flush sent.
41
+ self._sent: list[str] = []
42
+ #: What this tick's events touched, repainted by `flush` in one
43
+ #: transaction.
44
+ self._update: set[str] = set()
45
+ #: The last flush raised building its rows: this one builds them one
46
+ #: by one and leaves out the ones that raise.
47
+ self._retrying = False
48
+ #: Visible tests whose row failed to build when they were to be
49
+ #: added: not in the grid, and added again once an event touches
50
+ #: them — or by the one retry below.
51
+ self._dropped: set[str] = set()
52
+ #: A retrying flush dropped rows: the next flush that builds tries
53
+ #: them once more, one by one. The failure may have been no one
54
+ #: row's (every build raising for two ticks dropped every row).
55
+ self._redo = False
56
+
57
+ async def build(self) -> None:
58
+ self.client = ui.context.client
59
+ # BEFORE the handshake, so they are in the page's first HTML: after
60
+ # it, `add_head_html` inserts through insertAdjacentHTML, which
61
+ # never runs a <script> — the grid drew no rows, "DstfSuite is not
62
+ # defined".
63
+ ui.add_head_html(scripts_html(f"{STORAGE}:{self._project_key()}"))
64
+ # The grid's height too: added after the handshake, it arrived after
65
+ # the grid was built whenever the scripts came from the browser's
66
+ # cache — every second load — and AG Grid, ready at NiceGUI's
67
+ # default 256 px, drew the first 15 rows of a 710 px table and left
68
+ # the rest blank until a scroll or a resize.
69
+ ui.add_css(GRID_CSS)
70
+ first_paint(self.shell)
71
+ state = await connected_tab_state(self.client)
72
+ if state is None:
73
+ return
74
+ self.state: TabState = state
75
+ caught_up(state, self.shell.session.spec_revision)
76
+ # The grid takes a switch by itself: its colours are the tokens, and
77
+ # NiceGUI turns its scheme with `body--dark`.
78
+ self.theme = self.shell.page_setup(state)
79
+ ui.page_title("DSTF")
80
+ self.header = Header(self.shell, self.theme)
81
+ with ui.column().classes("dstf-suite w-full q-pa-md gap-3"):
82
+ self.progress = ui.label().classes("dstf-progress").mark("progress")
83
+ with ui.column().classes("gap-1") as self.hint:
84
+ ui.label("No project is open. Start the tool on one:")
85
+ ui.code("dstf author --config <path/to/testing.json>")
86
+ with ui.row().classes(
87
+ "dstf-suite-toolbar items-center w-full gap-3 no-wrap"
88
+ ):
89
+ # Both drawn by `suite_grid.js`; Python never updates them.
90
+ ui.element("div").props("id=dstf-suite-tools").mark("tools")
91
+ ui.space()
92
+ ui.element("div").props("id=dstf-suite-selection").mark("selected")
93
+ self.actions = ActionBar(
94
+ self.shell,
95
+ Placement.SUITE,
96
+ lambda: Selection(tests=frozenset(self.state.checked)),
97
+ marker="suite-actions",
98
+ icons=True,
99
+ stale=self.header.stale,
100
+ )
101
+ self.ids = self.shell.visible_tests()
102
+ self._sent = list(self.ids)
103
+ with ui.element("div").classes("dstf-suite-body"):
104
+ with ui.element("div").classes("dstf-suite-main"):
105
+ self.grid = SuiteGrid(
106
+ self._rows(self.ids),
107
+ selected=self.state.checked,
108
+ on_selection=self._on_selection,
109
+ ).mark("grid")
110
+ self.queue = QueuePanel(self.shell, stale=self.header.stale)
111
+ # The header's indicator links here from every page; the panel is
112
+ # drawn after the browser looked for the anchor.
113
+ ui.run_javascript(
114
+ f"if (location.hash === '#{ANCHOR}') "
115
+ f"document.getElementById('{ANCHOR}')?.scrollIntoView();"
116
+ )
117
+ self.grid.seal()
118
+ self.shell.register(self)
119
+ self.refresh()
120
+
121
+ def _project_key(self) -> str:
122
+ """What names this project for the table's stored view: its config
123
+ file, resolved — known before the open and the same after it (the
124
+ work dir is derived by the open when `--work-dir` is not given).
125
+ `dstf author` refuses to start without a config, so there always is
126
+ one. A reopen on another backend keeps the view."""
127
+ return str(self.shell.config_path.resolve())
128
+
129
+ def _rows(self, ids: list[str]) -> list[TestRow]:
130
+ if self.shell.known:
131
+ # An open is under way: the Session's comparisons are the
132
+ # previous project's, or none. Its names wait for its verdicts.
133
+ return [TestRow(model_id=m, waiting=True) for m in ids]
134
+ return suite_rows(
135
+ self.shell.session, ids, failed=set(self.shell.load.failed_models)
136
+ )
137
+
138
+ # -- the selection ---------------------------------------------------------
139
+
140
+ def _on_selection(self, ids: list[str]) -> None:
141
+ """The grid's selection, from the browser: the tab's (D129)."""
142
+ self.state.checked = set(ids)
143
+ self.actions.refresh()
144
+
145
+ def refresh(self) -> None:
146
+ load = self.shell.load
147
+ text = load.text()
148
+ self.progress.set_text(text)
149
+ self.progress.set_visibility(bool(text))
150
+ self.hint.set_visibility(
151
+ self.shell.session.project is None and not load.in_progress
152
+ )
153
+
154
+ # -- the Session's events --------------------------------------------------
155
+
156
+ def handle(self, event: dict[str, Any]) -> None:
157
+ """Every Session event, from the shell's tick. No early return: the
158
+ panel, the actions and the rows are independent regions, and a
159
+ `spec` (a close, an `OpenProject`) is exactly the event after which
160
+ the panel must not show the previous project's queue. The rows an
161
+ event touches are collected; `flush` sends them."""
162
+ self.header.handle(event)
163
+ kind = event["event"]
164
+ if kind == "spec":
165
+ # The tab's folds: every page of it hears the rule (K1).
166
+ heard_spec(self.state, event)
167
+ self.queue.handle(event)
168
+ if kind in ("results", "queue", "notice", "spec", "opened") or (
169
+ kind == "loading" and event.get("phase") == "done"
170
+ ):
171
+ self.actions.refresh()
172
+ if kind in ("spec", "known"):
173
+ # A `spec` event names only the CHANGED models (an open's names
174
+ # all, before the CLI's filter; a close's none), and `known` the
175
+ # models an open named before its scan: what to add and drop is
176
+ # the visible tests against the grid's, at the flush.
177
+ self.ids = self.shell.visible_tests()
178
+ self._update |= self._touched(event) & set(self.ids)
179
+ self.refresh()
180
+
181
+ def _touched(self, event: dict[str, Any]) -> set[str]:
182
+ """The models whose row an event may have changed."""
183
+ kind = event["event"]
184
+ if kind == "known":
185
+ # Every row: an open's names wait, and its failure's `known`
186
+ # (naming none) turns every row back to the open project's
187
+ # verdict — repainting only the event's models left every row
188
+ # reading "…" after a failed reopen.
189
+ return set(self.ids)
190
+ if kind in ("spec", "results", "queue"):
191
+ return {str(m) for m in event.get("models", ())}
192
+ if kind == "loading" and event.get("phase") == "results":
193
+ return {str(event["model"])}
194
+ return set()
195
+
196
+ def flush(self) -> None:
197
+ """This tick's rows, as ONE transaction: the Accept path queues N + 2
198
+ events in a tick, and one transaction per event was 333 over 331
199
+ rows. The rows to add and drop are the visible tests against the
200
+ grid's — a model named and dropped within the tick is sent neither,
201
+ so the grid is never told to drop a row it never had, and one
202
+ dropped and named again stays (repainted if an event touched it).
203
+ An added row is sent once, with the tab's selection. What the grid
204
+ holds moves only once the transaction is sent: one that raises is
205
+ sent again on the next tick.
206
+
207
+ Retried once: when building the rows raised, the next tick builds
208
+ them one by one, sends the ones that build and moves on — one row
209
+ that always raises froze the grid (a traceback per tick, no update
210
+ sent). A row left out keeps its last painted state; one that was to
211
+ be added is not in the grid, and is added again once an event
212
+ touches it, or by the next flush that builds: that one tries every
213
+ dropped row once more, one by one, and leaves out without a log
214
+ line the ones that still raise — a failure of every row for two
215
+ ticks dropped them all, and none came back until touched."""
216
+ held, sent = set(self.ids), set(self._sent)
217
+ self._dropped &= held
218
+ add = [
219
+ m
220
+ for m in self.ids
221
+ if m not in sent and (m not in self._dropped or m in self._update)
222
+ ]
223
+ remove = [m for m in self._sent if m not in held]
224
+ update = [m for m in self.ids if m in sent and m in self._update]
225
+ retrying = self._retrying
226
+ redo = (
227
+ [m for m in self.ids if m in self._dropped and m not in add]
228
+ if self._redo and not retrying
229
+ else []
230
+ )
231
+ added = set(add)
232
+ if add or update or remove or redo:
233
+ if retrying:
234
+ add_rows, bad_add = self._rows_each(add)
235
+ update_rows, bad_update = self._rows_each(update)
236
+ if bad_add or bad_update:
237
+ logger.error(
238
+ "the Suite table left out %s: %s",
239
+ ", ".join(bad_add + bad_update),
240
+ "its row failed to build again",
241
+ )
242
+ added -= set(bad_add)
243
+ self._redo = self._redo or bool(bad_add)
244
+ else:
245
+ try:
246
+ add_rows, update_rows = self._rows(add), self._rows(update)
247
+ except Exception:
248
+ self._retrying = True
249
+ raise # the shell logs it; the next tick goes row by row
250
+ redo_rows, bad_redo = self._rows_each(redo)
251
+ add_rows += redo_rows
252
+ added |= set(redo) - set(bad_redo)
253
+ self.grid.transaction(
254
+ add=add_rows,
255
+ update=update_rows,
256
+ remove=remove,
257
+ select=[
258
+ m for m in add + redo if m in added and m in self.state.checked
259
+ ],
260
+ )
261
+ self._dropped = (self._dropped - added) | (set(add) - added)
262
+ if not retrying:
263
+ self._redo = False # tried, or nothing left to try
264
+ self._retrying = False
265
+ self._sent = [m for m in self.ids if m in sent or m in added]
266
+ self._update = set()
267
+
268
+ def _rows_each(self, ids: list[str]) -> tuple[list[TestRow], list[str]]:
269
+ """``ids``' rows built one at a time, and the ids whose build raised."""
270
+ rows: list[TestRow] = []
271
+ bad: list[str] = []
272
+ for m in ids:
273
+ try:
274
+ rows += self._rows([m])
275
+ except Exception:
276
+ bad.append(m)
277
+ return rows, bad