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
dstf/__init__.py
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
"""Dynamic Systems Testing Framework — regression & unit testing."""
|
|
2
|
+
|
|
3
|
+
from typing import TYPE_CHECKING
|
|
4
|
+
|
|
5
|
+
# The DISTRIBUTION name, which is not the import name: you `import dstf`,
|
|
6
|
+
# but you install `dynamic-systems-testing-framework`. They differ because
|
|
7
|
+
# the short name was already taken on PyPI by an unrelated project. Every
|
|
8
|
+
# importlib.metadata lookup must use this constant — a stale literal does not
|
|
9
|
+
# raise, it silently yields the unknown-version fallback below, so
|
|
10
|
+
# tests/test_distribution_name.py pins this to pyproject.toml's [project] name.
|
|
11
|
+
_DIST_NAME = "dynamic-systems-testing-framework"
|
|
12
|
+
|
|
13
|
+
# Version source-of-truth lives in pyproject.toml; importlib.metadata
|
|
14
|
+
# reads the installed package's metadata so `dstf --version` and any
|
|
15
|
+
# downstream tooling stay in sync without manual duplication.
|
|
16
|
+
try:
|
|
17
|
+
from importlib.metadata import version as _pkg_version
|
|
18
|
+
|
|
19
|
+
__version__ = _pkg_version(_DIST_NAME)
|
|
20
|
+
except Exception: # noqa: BLE001 — fall back during editable / unbuilt installs
|
|
21
|
+
__version__ = "0.0.0+unknown"
|
|
22
|
+
|
|
23
|
+
if TYPE_CHECKING:
|
|
24
|
+
from .plugins import load_plugins
|
|
25
|
+
|
|
26
|
+
__all__ = ["__version__", "load_plugins"]
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def __getattr__(name: str):
|
|
30
|
+
"""Lazily expose :func:`dstf.plugins.load_plugins` at package level.
|
|
31
|
+
|
|
32
|
+
`import dstf` must stay near-free — the CLI imports it before argparse
|
|
33
|
+
has even run. Module-level `from .plugins import load_plugins` would
|
|
34
|
+
drag in the plug-in machinery on every import, so it resolves on first
|
|
35
|
+
attribute access instead (PEP 562).
|
|
36
|
+
"""
|
|
37
|
+
if name == "load_plugins":
|
|
38
|
+
from .plugins import load_plugins
|
|
39
|
+
|
|
40
|
+
return load_plugins
|
|
41
|
+
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
|
dstf/__main__.py
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
"""The authoring tier's core — the Session, its spec document and the
|
|
2
|
+
capability contract (two-tier design, §4.5–4.6). Pure Python: no UI
|
|
3
|
+
framework is imported here; sub-project 4's shell sits on top.
|
|
4
|
+
|
|
5
|
+
Importing the package registers the capabilities it ships (the shell then
|
|
6
|
+
lists whatever is registered); the capability modules reach NiceGUI only
|
|
7
|
+
inside their ``panel``, so this import works without the ``[author]``
|
|
8
|
+
extra.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from .builtin import register_builtins
|
|
12
|
+
|
|
13
|
+
register_builtins()
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
"""The capabilities that ship with the tool. They register through the same
|
|
2
|
+
call a plug-in makes; the shell learns of them the same way."""
|
|
3
|
+
|
|
4
|
+
from __future__ import annotations
|
|
5
|
+
|
|
6
|
+
from ..capabilities import register_capability, registered_capabilities
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def register_builtins() -> None:
|
|
10
|
+
"""Idempotent, like ``comparison.builtins.register_builtins``."""
|
|
11
|
+
names = {c.name for c in registered_capabilities()}
|
|
12
|
+
from .accept import Accept
|
|
13
|
+
from .edit_criteria import EditCriteria
|
|
14
|
+
from .edit_family import EditFamily
|
|
15
|
+
from .edit_on_plot import EditOnPlot
|
|
16
|
+
from .edit_tree import EditTree
|
|
17
|
+
from .enqueue import EnqueueTests
|
|
18
|
+
|
|
19
|
+
# Before Edit criteria: a folded row's editor reads which elements,
|
|
20
|
+
# then what the criterion is (a host stacks its panels in this order).
|
|
21
|
+
if EditFamily.name not in names:
|
|
22
|
+
register_capability(EditFamily())
|
|
23
|
+
if EditCriteria.name not in names:
|
|
24
|
+
register_capability(EditCriteria())
|
|
25
|
+
if EditTree.name not in names:
|
|
26
|
+
register_capability(EditTree())
|
|
27
|
+
if Accept.name not in names:
|
|
28
|
+
register_capability(Accept())
|
|
29
|
+
if EnqueueTests.name not in names:
|
|
30
|
+
register_capability(EnqueueTests())
|
|
31
|
+
if EditOnPlot.name not in names:
|
|
32
|
+
register_capability(EditOnPlot())
|
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
"""Accept (§4.6 table, §4.7): SUITE and TEST placements. The selected tests'
|
|
2
|
+
current results become baselines through
|
|
3
|
+
``storage.accept.accept_run_results`` — the one function the CLI's two
|
|
4
|
+
``--accept`` paths call — so the tool cannot accept a result the CLI would
|
|
5
|
+
refuse.
|
|
6
|
+
|
|
7
|
+
The confirm list says, per selected model, what accepting will do with it,
|
|
8
|
+
including the aborted-run refusal and the comparison's own (a base tolerance
|
|
9
|
+
that is no number, in the comparison's sentence), so it never promises what the shared
|
|
10
|
+
function declines — with one exception it cannot see from here: a trajectory
|
|
11
|
+
carrying NaN/Inf is refused by ``store_reference`` as it writes, and shows up
|
|
12
|
+
in the write's own line instead. The button's own availability is read off
|
|
13
|
+
those same rows, so an enabled Accept always has at least one baseline to
|
|
14
|
+
write. The write reports one line (§4.6 amendment 1): what it stored, every
|
|
15
|
+
refusal with its reason, and the shared function's warning for a baseline
|
|
16
|
+
file that is large (D153).
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
from __future__ import annotations
|
|
20
|
+
|
|
21
|
+
from typing import TYPE_CHECKING, Any
|
|
22
|
+
|
|
23
|
+
from ...backends.pool import ItemState
|
|
24
|
+
from ...reporting.status import classify, refusal
|
|
25
|
+
from ...storage.accept import accept_run_results
|
|
26
|
+
from ...storage.batch_manifest import unfinished_run_models
|
|
27
|
+
from ..capabilities import Emphasis, Panel, Placement, StoreWrite, plural
|
|
28
|
+
from ..situation import Situation
|
|
29
|
+
|
|
30
|
+
if TYPE_CHECKING:
|
|
31
|
+
from pathlib import Path
|
|
32
|
+
|
|
33
|
+
from ...storage.reference_store import ReferenceStore
|
|
34
|
+
from ..session import SessionView
|
|
35
|
+
|
|
36
|
+
# NO module-level `nicegui` import: `import dstf.authoring` runs
|
|
37
|
+
# register_builtins(), and the Session must import without the extra.
|
|
38
|
+
|
|
39
|
+
#: The confirm row's verdict on a model — every word but the last is a
|
|
40
|
+
#: reason the shared accept function stores no baseline for it; the last is
|
|
41
|
+
#: what it stores. ``WILL_STORE`` is the one truth the button's count, its
|
|
42
|
+
#: availability and this forecast share.
|
|
43
|
+
REFUSED = "re-run before accepting"
|
|
44
|
+
#: A run that did not finish left a partial result (D151): it is shown on
|
|
45
|
+
#: the test's page, and is no baseline.
|
|
46
|
+
PARTIAL = "a partial result is never a baseline: queue it again"
|
|
47
|
+
NO_RESULT = "no simulation result"
|
|
48
|
+
SIM_FAILED = "simulation failed"
|
|
49
|
+
SIMULATE_ONLY = "simulate-only: no baseline"
|
|
50
|
+
NO_VARIABLES = "no tracked variables in result"
|
|
51
|
+
WILL_STORE = "will store"
|
|
52
|
+
|
|
53
|
+
#: The states of a test the simulation queue holds.
|
|
54
|
+
_HELD = (ItemState.WAITING, ItemState.RUNNING)
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def _rows(s: SessionView) -> list[tuple[str, str, str]]:
|
|
58
|
+
"""``(model_id, status, what accept will do)`` for every selected model,
|
|
59
|
+
in project order."""
|
|
60
|
+
project = s.project
|
|
61
|
+
if project is None:
|
|
62
|
+
return []
|
|
63
|
+
selection = s.selection.tests
|
|
64
|
+
work_dir = project.config.work_dir
|
|
65
|
+
assert work_dir is not None # resolved in Config.__post_init__
|
|
66
|
+
unfinished = unfinished_run_models(work_dir)
|
|
67
|
+
rows: list[tuple[str, str, str]] = []
|
|
68
|
+
for test in project.tests:
|
|
69
|
+
model_id = test.model_id
|
|
70
|
+
if model_id not in selection:
|
|
71
|
+
continue
|
|
72
|
+
comp = s.comparisons.get(model_id)
|
|
73
|
+
status = classify(comp) if comp is not None else ""
|
|
74
|
+
result = project.results.get(model_id)
|
|
75
|
+
if s.partial(model_id) is not None:
|
|
76
|
+
action = PARTIAL # what its page draws is not a result
|
|
77
|
+
elif model_id in unfinished:
|
|
78
|
+
action = REFUSED # the guard runs before the result is looked at
|
|
79
|
+
elif result is None:
|
|
80
|
+
action = NO_RESULT
|
|
81
|
+
elif not result.success:
|
|
82
|
+
action = SIM_FAILED
|
|
83
|
+
elif comp is not None and (refused := refusal(comp)) is not None:
|
|
84
|
+
# A test the comparison refused: the row says the comparison's
|
|
85
|
+
# own sentence, which `accept_run_results` skips it with.
|
|
86
|
+
action = refused
|
|
87
|
+
elif test.is_simulate_only:
|
|
88
|
+
# `accept_results` neither stores nor skips these, by design.
|
|
89
|
+
action = SIMULATE_ONLY
|
|
90
|
+
elif not result.variables:
|
|
91
|
+
# `store_reference` returns False for a variable-less result and
|
|
92
|
+
# `accept_results` skips it with exactly this reason.
|
|
93
|
+
action = NO_VARIABLES
|
|
94
|
+
else:
|
|
95
|
+
action = WILL_STORE
|
|
96
|
+
rows.append((model_id, status, action))
|
|
97
|
+
return rows
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def _report(
|
|
101
|
+
stored: int,
|
|
102
|
+
skipped: list[tuple[str, str]],
|
|
103
|
+
warnings: list[str],
|
|
104
|
+
notes: list[str],
|
|
105
|
+
*,
|
|
106
|
+
reference_dir: Path,
|
|
107
|
+
) -> str:
|
|
108
|
+
"""The one line the queue panel shows. A partial accept has no other way
|
|
109
|
+
to say which tests it refused and why — or that a baseline file it
|
|
110
|
+
wrote is large, or which tests it passed over as simulate-only, in the
|
|
111
|
+
sentences the CLI prints."""
|
|
112
|
+
parts = [f"Accept: stored {stored} baseline(s) to {reference_dir}"]
|
|
113
|
+
parts += [f"; skipped {model_id}: {reason}" for model_id, reason in skipped]
|
|
114
|
+
parts += [f"; {line}" for line in notes]
|
|
115
|
+
parts += [f"; {line}" for line in warnings]
|
|
116
|
+
return "".join(parts)
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
class Accept:
|
|
120
|
+
"""The selection's current results as baselines, comparison settings
|
|
121
|
+
travelling with them as the CLI's accept does."""
|
|
122
|
+
|
|
123
|
+
name = "accept"
|
|
124
|
+
label = "Accept"
|
|
125
|
+
placements = frozenset({Placement.SUITE, Placement.TEST})
|
|
126
|
+
#: drawn as this icon where a bar draws icons (the Suite toolbar)
|
|
127
|
+
icon = "done_all"
|
|
128
|
+
#: the Test page's strip: the next action for a result that differs
|
|
129
|
+
#: from its baseline or has none; nothing new to accept otherwise
|
|
130
|
+
emphasis = {
|
|
131
|
+
Situation.FAIL: Emphasis.NEXT,
|
|
132
|
+
Situation.NO_REF: Emphasis.NEXT,
|
|
133
|
+
Situation.PASS: Emphasis.MENU,
|
|
134
|
+
Situation.EDITS: Emphasis.MENU,
|
|
135
|
+
# a criterion's variable to record first: accepted now, the
|
|
136
|
+
# baseline would not hold it
|
|
137
|
+
Situation.EDITS_NEW: Emphasis.MENU,
|
|
138
|
+
Situation.NEW_VARIABLES: Emphasis.MENU,
|
|
139
|
+
Situation.SIM_FAIL: Emphasis.MENU,
|
|
140
|
+
Situation.NO_RESULT: Emphasis.MENU,
|
|
141
|
+
Situation.QUEUED: Emphasis.MENU,
|
|
142
|
+
Situation.RUNNING: Emphasis.MENU,
|
|
143
|
+
Situation.CANCELLED: Emphasis.MENU,
|
|
144
|
+
Situation.START_FAILED: Emphasis.MENU,
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
def available(self, s: SessionView) -> bool | str:
|
|
148
|
+
project = s.project
|
|
149
|
+
if project is None:
|
|
150
|
+
return "no project open"
|
|
151
|
+
if not s.selection.tests:
|
|
152
|
+
return "Accept needs a selection"
|
|
153
|
+
selection = s.selection.tests
|
|
154
|
+
for test in project.tests:
|
|
155
|
+
# The Session's refusal (`apply`), said before the click: the
|
|
156
|
+
# result in memory is about to be replaced by its run's.
|
|
157
|
+
state = s.queue_state(test.model_id)
|
|
158
|
+
if test.model_id in selection and state in _HELD:
|
|
159
|
+
doing = "running" if state is ItemState.RUNNING else "queued"
|
|
160
|
+
return f"{test.model_id} is {doing}: accept it when its run has ended"
|
|
161
|
+
# The button's answer comes from the rows the panel draws, so an
|
|
162
|
+
# enabled button never reads `Accept 0`: a pending, simulate-only or
|
|
163
|
+
# variable-less result is a row the write will not store, and the
|
|
164
|
+
# button carries that row's reason.
|
|
165
|
+
rows = _rows(s)
|
|
166
|
+
if any(action == WILL_STORE for _, _, action in rows):
|
|
167
|
+
return True
|
|
168
|
+
if not rows:
|
|
169
|
+
return "no successful result in the selection"
|
|
170
|
+
return rows[0][2]
|
|
171
|
+
|
|
172
|
+
def caption(self, s: SessionView) -> str:
|
|
173
|
+
count = sum(1 for _, _, action in _rows(s) if action == WILL_STORE)
|
|
174
|
+
return f"Accept {plural(count, 'result')} as baselines"
|
|
175
|
+
|
|
176
|
+
def panel(self, s: SessionView, ui_panel: Panel) -> None:
|
|
177
|
+
from nicegui import ui
|
|
178
|
+
|
|
179
|
+
rows = _rows(s)
|
|
180
|
+
for model_id, status, action in rows:
|
|
181
|
+
ui.label(f"{model_id} · {status} · {action}").classes("text-xs")
|
|
182
|
+
ui.button(self.caption(s), on_click=lambda: ui_panel.submit()).props(
|
|
183
|
+
"dense no-caps"
|
|
184
|
+
).mark("accept-submit")
|
|
185
|
+
|
|
186
|
+
def act(self, s: SessionView, **inputs: Any) -> StoreWrite:
|
|
187
|
+
project = s.project
|
|
188
|
+
if project is None:
|
|
189
|
+
raise ValueError("no project open")
|
|
190
|
+
selection = set(s.selection.tests)
|
|
191
|
+
# Everything the write needs is read HERE: `run` is handed a store
|
|
192
|
+
# by the Session and holds no view.
|
|
193
|
+
tests = [t for t in project.tests if t.model_id in selection]
|
|
194
|
+
results = dict(project.results)
|
|
195
|
+
work_dir = project.config.work_dir
|
|
196
|
+
assert work_dir is not None # resolved in Config.__post_init__
|
|
197
|
+
reference_dir = project.config.reference_dir
|
|
198
|
+
|
|
199
|
+
def run(store: ReferenceStore) -> str:
|
|
200
|
+
return _report(
|
|
201
|
+
*accept_run_results(store, tests, results, work_dir),
|
|
202
|
+
reference_dir=reference_dir,
|
|
203
|
+
)
|
|
204
|
+
|
|
205
|
+
return StoreWrite(models=frozenset(selection), run=run)
|