umaudemc 0.13.1__py3-none-any.whl → 0.15.0__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.
- umaudemc/__init__.py +1 -1
- umaudemc/__main__.py +21 -0
- umaudemc/backend/_stormpy.py +2 -3
- umaudemc/backend/bmcalc.py +10 -10
- umaudemc/backend/ltsmin.py +1 -1
- umaudemc/command/check.py +1 -1
- umaudemc/command/graph.py +1 -2
- umaudemc/command/pcheck.py +1 -1
- umaudemc/command/scheck.py +23 -14
- umaudemc/command/sworker.py +185 -0
- umaudemc/command/test.py +3 -63
- umaudemc/common.py +102 -17
- umaudemc/data/select.htm +1 -1
- umaudemc/distributed.py +322 -0
- umaudemc/formatter.py +1 -1
- umaudemc/kleene.py +6 -1
- umaudemc/opsem.py +1 -1
- umaudemc/probabilistic.py +1 -1
- umaudemc/pyslang.py +142 -45
- umaudemc/quatex.py +75 -27
- umaudemc/resources.py +12 -26
- umaudemc/simulators.py +1 -1
- umaudemc/statistical.py +7 -7
- umaudemc/usermsgs.py +6 -0
- umaudemc/webui.py +1 -1
- {umaudemc-0.13.1.dist-info → umaudemc-0.15.0.dist-info}/METADATA +11 -11
- umaudemc-0.15.0.dist-info/RECORD +60 -0
- {umaudemc-0.13.1.dist-info → umaudemc-0.15.0.dist-info}/WHEEL +1 -1
- umaudemc-0.13.1.dist-info/RECORD +0 -58
- {umaudemc-0.13.1.dist-info → umaudemc-0.15.0.dist-info}/entry_points.txt +0 -0
- {umaudemc-0.13.1.dist-info → umaudemc-0.15.0.dist-info/licenses}/LICENSE +0 -0
- {umaudemc-0.13.1.dist-info → umaudemc-0.15.0.dist-info}/top_level.txt +0 -0
umaudemc/__init__.py
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
__version__ = '0.
|
|
1
|
+
__version__ = '0.15.0'
|
umaudemc/__main__.py
CHANGED
|
@@ -312,6 +312,10 @@ def build_parser():
|
|
|
312
312
|
type=int,
|
|
313
313
|
default=1
|
|
314
314
|
)
|
|
315
|
+
parser_scheck.add_argument(
|
|
316
|
+
'--distribute',
|
|
317
|
+
help='Distribute the computation over some machines'
|
|
318
|
+
)
|
|
315
319
|
parser_scheck.add_argument(
|
|
316
320
|
'--format', '-f',
|
|
317
321
|
help='Output format for the simulation results',
|
|
@@ -326,6 +330,18 @@ def build_parser():
|
|
|
326
330
|
|
|
327
331
|
parser_scheck.set_defaults(mode='scheck')
|
|
328
332
|
|
|
333
|
+
#
|
|
334
|
+
# Statistical model checking distributed worker
|
|
335
|
+
#
|
|
336
|
+
|
|
337
|
+
parser_sworker = subparsers.add_parser('sworker', help='Worker for distributed statistical model checking')
|
|
338
|
+
|
|
339
|
+
parser_sworker.add_argument('--address', '-a', help='listening address', default='127.0.0.1')
|
|
340
|
+
parser_sworker.add_argument('--port', '-p', help='listening port', type=int, default=1234)
|
|
341
|
+
parser_sworker.add_argument('--keep-file', help='keep received Maude file for debugging', action='store_true')
|
|
342
|
+
|
|
343
|
+
parser_sworker.set_defaults(mode='sworker')
|
|
344
|
+
|
|
329
345
|
#
|
|
330
346
|
# Test and benchmark test suites from the command line
|
|
331
347
|
#
|
|
@@ -501,6 +517,11 @@ def main():
|
|
|
501
517
|
from .command.scheck import scheck
|
|
502
518
|
return scheck(args)
|
|
503
519
|
|
|
520
|
+
# Werker for distributed statistical model-checking
|
|
521
|
+
elif args.mode == 'sworker':
|
|
522
|
+
from .command.sworker import sworker
|
|
523
|
+
return sworker(args)
|
|
524
|
+
|
|
504
525
|
# Batch test subcommand
|
|
505
526
|
elif args.mode == 'test':
|
|
506
527
|
from .command.test import test
|
umaudemc/backend/_stormpy.py
CHANGED
|
@@ -80,7 +80,7 @@ class StormBackend:
|
|
|
80
80
|
builder.add_next_value(row=st, column=st, value=1.0)
|
|
81
81
|
|
|
82
82
|
def _build_mdp(self, builder, graph):
|
|
83
|
-
"""Build an MDP from the
|
|
83
|
+
"""Build an MDP from the probabilistic rewrite graph"""
|
|
84
84
|
|
|
85
85
|
last_visited, row_index, num_states = -1, 0, len(graph)
|
|
86
86
|
|
|
@@ -127,7 +127,6 @@ class StormBackend:
|
|
|
127
127
|
self._build_mdp(builder, graph)
|
|
128
128
|
|
|
129
129
|
transition_matrix = builder.build()
|
|
130
|
-
#input(transition_matrix)
|
|
131
130
|
|
|
132
131
|
# State labeling with atomic propositions
|
|
133
132
|
state_labeling = stormpy.storage.StateLabeling(num_states)
|
|
@@ -233,7 +232,7 @@ class StormBackend:
|
|
|
233
232
|
def state_analysis(self, *args, **kwargs):
|
|
234
233
|
"""Steady and transient state analysis"""
|
|
235
234
|
|
|
236
|
-
# StormPy does not allow
|
|
235
|
+
# StormPy does not allow calculating state probabilities, as far as we know, so
|
|
237
236
|
# we delegate on the command-line interface to Storm
|
|
238
237
|
from ._storm import StormBackend as StormCmdLineBackend
|
|
239
238
|
|
umaudemc/backend/bmcalc.py
CHANGED
|
@@ -479,22 +479,22 @@ class BuiltinBackend:
|
|
|
479
479
|
return [modop, self.ctl2mucalc(rest[0], index=index)]
|
|
480
480
|
elif head == '_U_':
|
|
481
481
|
return ['mu_._', ['Var', f'Z{index}'], ['_\\/_',
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
482
|
+
self.ctl2mucalc(rest[1], index=index + 1),
|
|
483
|
+
['_/\\_', self.ctl2mucalc(rest[0], index=index + 1),
|
|
484
|
+
[modop, ['Var', f'Z{index}']]]]]
|
|
485
485
|
elif head == '_R_':
|
|
486
486
|
return ['nu_._', ['Var', f'Z{index}'], ['_/\\_',
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
487
|
+
self.ctl2mucalc(rest[1], index=index + 1),
|
|
488
|
+
['_\\/_', self.ctl2mucalc(rest[0], index=index + 1),
|
|
489
|
+
[modop, ['Var', f'Z{index}']]]]]
|
|
490
490
|
elif head == '`[`]_':
|
|
491
491
|
return ['nu_._', ['Var', f'Z{index}'], ['_/\\_',
|
|
492
|
-
|
|
493
|
-
|
|
492
|
+
self.ctl2mucalc(rest[0], index=index + 1),
|
|
493
|
+
[modop, ['Var', f'Z{index}']]]]
|
|
494
494
|
elif head == '<>_':
|
|
495
495
|
return ['mu_._', ['Var', f'Z{index}'], ['_\\/_',
|
|
496
|
-
|
|
497
|
-
|
|
496
|
+
self.ctl2mucalc(rest[0], index=index + 1),
|
|
497
|
+
[modop, ['Var', f'Z{index}']]]]
|
|
498
498
|
else:
|
|
499
499
|
return [head] + [self.ctl2mucalc(arg, index=index) for arg in rest]
|
|
500
500
|
|
umaudemc/backend/ltsmin.py
CHANGED
|
@@ -552,7 +552,7 @@ class LTSminRunner:
|
|
|
552
552
|
maude_lib_ltsmin = os.getenv('MAUDE_LIB_LTSMIN')
|
|
553
553
|
new_maude_lib = os.getenv('MAUDE_LIB', '') if maude_lib_ltsmin is None else maude_lib_ltsmin
|
|
554
554
|
new_maude_lib = ('' if new_maude_lib == '' else new_maude_lib + os.pathsep) \
|
|
555
|
-
|
|
555
|
+
+ os.path.dirname(filename)
|
|
556
556
|
|
|
557
557
|
if raw:
|
|
558
558
|
os.execve(self.ltsmin.pins2lts[variant], [self.ltsmin.pins2lts[variant]] + args,
|
umaudemc/command/check.py
CHANGED
|
@@ -8,7 +8,7 @@ import sys
|
|
|
8
8
|
import tempfile
|
|
9
9
|
|
|
10
10
|
from ..common import parse_initial_data, usermsgs
|
|
11
|
-
from ..backends import kleene_backends, get_backends, backend_for, format_statistics
|
|
11
|
+
from ..backends import kleene_backends, get_backends, backend_for, format_statistics, \
|
|
12
12
|
advance_counterexample, advance_kleene
|
|
13
13
|
from ..counterprint import SimplePrinter, JSONPrinter, HTMLPrinter, DOTPrinter, print_counterexample
|
|
14
14
|
from ..formulae import Parser, collect_aprops, add_path_premise, formula_list2str
|
umaudemc/command/graph.py
CHANGED
|
@@ -71,7 +71,7 @@ def deduce_format(args):
|
|
|
71
71
|
usermsgs.print_warning('PDF output is only supported with DOT. Changing to DOT.')
|
|
72
72
|
return 'dot'
|
|
73
73
|
|
|
74
|
-
#
|
|
74
|
+
# Probabilistic information is not considered in some cases
|
|
75
75
|
if args.passign and oformat not in ('dot', 'prism', 'jani', 'default'):
|
|
76
76
|
usermsgs.print_warning('Probabilistic graphs are only supported with PRISM, JANI and DOT output. '
|
|
77
77
|
'Ignoring probabilities.')
|
|
@@ -107,7 +107,6 @@ def graph(args):
|
|
|
107
107
|
else:
|
|
108
108
|
kleene_graph = True
|
|
109
109
|
|
|
110
|
-
|
|
111
110
|
# Select the appropriate rewriting graph
|
|
112
111
|
# (probabilistic, strategy-controlled, or standard)
|
|
113
112
|
if args.passign or oformat in ('prism', 'jani'):
|
umaudemc/command/pcheck.py
CHANGED
|
@@ -50,6 +50,7 @@ def _print_fraction(value):
|
|
|
50
50
|
|
|
51
51
|
return f'{num}/{den}' if num > 0 and den != 1 else num
|
|
52
52
|
|
|
53
|
+
|
|
53
54
|
def _select_backend(known_backends, backend_list):
|
|
54
55
|
"""Get the first available backend according to the user preferences"""
|
|
55
56
|
|
|
@@ -78,7 +79,6 @@ def _select_backend(known_backends, backend_list):
|
|
|
78
79
|
elif first_known is None:
|
|
79
80
|
first_known = name
|
|
80
81
|
|
|
81
|
-
|
|
82
82
|
if first_available is None:
|
|
83
83
|
if first_known is None:
|
|
84
84
|
usermsgs.print_error('The backend list does not contain any valid option.')
|
umaudemc/command/scheck.py
CHANGED
|
@@ -19,10 +19,10 @@ def show_results(program, nsims, qdata):
|
|
|
19
19
|
qdata_it = iter(qdata)
|
|
20
20
|
q = next(qdata_it, None)
|
|
21
21
|
|
|
22
|
-
for k, (line, column, params) in enumerate(program.query_locations):
|
|
22
|
+
for k, (fname, line, column, params) in enumerate(program.query_locations):
|
|
23
23
|
# Print the query name and location only if there are many
|
|
24
24
|
if program.nqueries > 1:
|
|
25
|
-
print(f'Query {k + 1} (
|
|
25
|
+
print(f'Query {k + 1} ({fname}:{line}:{column})')
|
|
26
26
|
|
|
27
27
|
# For parametric queries, we show the result for every value
|
|
28
28
|
var = params[0] if params else None
|
|
@@ -136,7 +136,7 @@ def scheck(args):
|
|
|
136
136
|
return 1
|
|
137
137
|
|
|
138
138
|
with open(args.query) as quatex_file:
|
|
139
|
-
program = parse_quatex(quatex_file, filename=args.query)
|
|
139
|
+
program, seen_files = parse_quatex(quatex_file, filename=args.query, legacy=args.assign == 'pmaude')
|
|
140
140
|
|
|
141
141
|
if not program:
|
|
142
142
|
return 1
|
|
@@ -145,14 +145,7 @@ def scheck(args):
|
|
|
145
145
|
usermsgs.print_warning('No queries in the input file.')
|
|
146
146
|
return 0
|
|
147
147
|
|
|
148
|
-
# Get the simulator for the given assignment method
|
|
149
|
-
simulator = get_simulator(args.assign, data)
|
|
150
|
-
|
|
151
|
-
if not simulator:
|
|
152
|
-
return 1
|
|
153
|
-
|
|
154
148
|
# Check the simulation parameters
|
|
155
|
-
|
|
156
149
|
if not (0 <= args.alpha <= 1):
|
|
157
150
|
usermsgs.print_error(f'Wrong value {args.alpha} for the alpha parameter (must be between 0 and 1).')
|
|
158
151
|
return 1
|
|
@@ -170,10 +163,26 @@ def scheck(args):
|
|
|
170
163
|
if min_sim is None and max_sim is None:
|
|
171
164
|
return 1
|
|
172
165
|
|
|
173
|
-
#
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
166
|
+
# Distributed computations follow a different path
|
|
167
|
+
if args.distribute:
|
|
168
|
+
from ..distributed import distributed_check
|
|
169
|
+
|
|
170
|
+
num_sims, qdata = distributed_check(args, data, min_sim, max_sim, program, seen_files)
|
|
171
|
+
|
|
172
|
+
if num_sims is None:
|
|
173
|
+
return 1
|
|
174
|
+
|
|
175
|
+
else:
|
|
176
|
+
# Get the simulator for the given assignment method
|
|
177
|
+
simulator = get_simulator(args.assign, data)
|
|
178
|
+
|
|
179
|
+
if not simulator:
|
|
180
|
+
return 1
|
|
181
|
+
|
|
182
|
+
# Call the statistical model checker
|
|
183
|
+
num_sims, qdata = check(program, simulator,
|
|
184
|
+
args.seed, args.alpha, args.delta, args.block,
|
|
185
|
+
min_sim, max_sim, args.jobs, args.verbose)
|
|
177
186
|
|
|
178
187
|
# Print the results on the terminal
|
|
179
188
|
(show_json if args.format == 'json' else show_results)(program, num_sims, qdata)
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
#
|
|
2
|
+
# Command for a worker of the statistical model checker
|
|
3
|
+
#
|
|
4
|
+
|
|
5
|
+
import json
|
|
6
|
+
import multiprocessing as mp
|
|
7
|
+
import os
|
|
8
|
+
import socket
|
|
9
|
+
import sys
|
|
10
|
+
import tarfile
|
|
11
|
+
import tempfile
|
|
12
|
+
from array import array
|
|
13
|
+
|
|
14
|
+
from ..common import maude, parse_initial_data, usermsgs
|
|
15
|
+
from ..quatex import parse_quatex
|
|
16
|
+
from ..simulators import get_simulator
|
|
17
|
+
from ..statistical import run, QueryData, make_parameter_dicts
|
|
18
|
+
|
|
19
|
+
# Python-version-dependent option for safer TAR extraction
|
|
20
|
+
EXTRACT_OPTIONS = {'filter': 'data'} if sys.version_info >= (3, 12) else {}
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def read_json(sock):
|
|
24
|
+
"""Read a JSON value from a socket"""
|
|
25
|
+
|
|
26
|
+
# Read a 32 bit integer for the value length in bytes
|
|
27
|
+
if not (data := sock.recv(4)):
|
|
28
|
+
return None
|
|
29
|
+
|
|
30
|
+
length = int.from_bytes(data, 'big')
|
|
31
|
+
|
|
32
|
+
# Read the JSON to a byte string
|
|
33
|
+
data = b''
|
|
34
|
+
|
|
35
|
+
while len(data) < length:
|
|
36
|
+
data += sock.recv(length)
|
|
37
|
+
|
|
38
|
+
return json.loads(data.decode())
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
class Dummy:
|
|
42
|
+
"""Dummy class to be used as a namespace"""
|
|
43
|
+
|
|
44
|
+
def __init__(self, values):
|
|
45
|
+
self.__dict__ = values
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
class Worker:
|
|
49
|
+
"""Worker for simulations"""
|
|
50
|
+
|
|
51
|
+
def __init__(self):
|
|
52
|
+
self.program = None
|
|
53
|
+
self.simulator = None
|
|
54
|
+
self.block = 100
|
|
55
|
+
|
|
56
|
+
def setup(self, tmp_dir, args):
|
|
57
|
+
"""Setup the execution environment"""
|
|
58
|
+
|
|
59
|
+
# Copy parameters
|
|
60
|
+
self.block = args.block
|
|
61
|
+
|
|
62
|
+
maude.setRandomSeed(args.seed)
|
|
63
|
+
|
|
64
|
+
# Do the same as the scheck command without checks
|
|
65
|
+
args.file = os.path.join(tmp_dir, args.file)
|
|
66
|
+
|
|
67
|
+
if not (data := parse_initial_data(args)):
|
|
68
|
+
return False
|
|
69
|
+
|
|
70
|
+
with open(os.path.join(tmp_dir, args.query)) as quatex_file:
|
|
71
|
+
self.program, _ = parse_quatex(quatex_file, filename=args.query,
|
|
72
|
+
legacy=args.assign == 'pmaude')
|
|
73
|
+
|
|
74
|
+
if not self.program:
|
|
75
|
+
return False
|
|
76
|
+
|
|
77
|
+
# Get the simulator for the given assignment method
|
|
78
|
+
self.simulator = get_simulator(args.assign, data)
|
|
79
|
+
|
|
80
|
+
if not self.simulator:
|
|
81
|
+
return False
|
|
82
|
+
|
|
83
|
+
return True
|
|
84
|
+
|
|
85
|
+
def run(self, conn):
|
|
86
|
+
"""Run the simulation until it is finished"""
|
|
87
|
+
|
|
88
|
+
program = self.program
|
|
89
|
+
simulator = self.simulator
|
|
90
|
+
block = self.block
|
|
91
|
+
|
|
92
|
+
# Query data
|
|
93
|
+
qdata = [QueryData(k, idict)
|
|
94
|
+
for k, qinfo in enumerate(program.query_locations)
|
|
95
|
+
for idict in make_parameter_dicts(qinfo[3])]
|
|
96
|
+
|
|
97
|
+
sums = array('d', [0.0] * len(qdata))
|
|
98
|
+
sum_sq = array('d', [0.0] * len(qdata))
|
|
99
|
+
|
|
100
|
+
while True:
|
|
101
|
+
|
|
102
|
+
for _ in range(block):
|
|
103
|
+
# Run the simulation and compute all queries at once
|
|
104
|
+
values = run(program, qdata, simulator)
|
|
105
|
+
|
|
106
|
+
for k in range(len(qdata)):
|
|
107
|
+
sums[k] += values[k]
|
|
108
|
+
sum_sq[k] += values[k] * values[k]
|
|
109
|
+
|
|
110
|
+
conn.send(b'b' + sums.tobytes() + sum_sq.tobytes())
|
|
111
|
+
|
|
112
|
+
# Check whether to continue
|
|
113
|
+
answer = conn.recv(1)
|
|
114
|
+
|
|
115
|
+
if answer == b's':
|
|
116
|
+
print('Done')
|
|
117
|
+
return
|
|
118
|
+
|
|
119
|
+
elif answer != b'c':
|
|
120
|
+
usermsgs.print_error(f'Unknown command {answer.decode()}. Stopping.')
|
|
121
|
+
return
|
|
122
|
+
|
|
123
|
+
for k in range(len(qdata)):
|
|
124
|
+
sums[k] = 0
|
|
125
|
+
sum_sq[k] = 0
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
def handle_request(message, conn, addr, keep_file):
|
|
129
|
+
"""Handle a request in a separate process"""
|
|
130
|
+
|
|
131
|
+
command = Dummy(message)
|
|
132
|
+
|
|
133
|
+
with tempfile.TemporaryDirectory(delete=not keep_file) as tmp_dir:
|
|
134
|
+
# Print the temporary working directory for debugging purposes
|
|
135
|
+
if keep_file:
|
|
136
|
+
print('Temporary directory:', tmp_dir)
|
|
137
|
+
|
|
138
|
+
# Recover the required files
|
|
139
|
+
with conn.makefile('rb', buffering=0) as fobj:
|
|
140
|
+
with tarfile.open(mode='r|*', fileobj=fobj) as tarf:
|
|
141
|
+
tarf.extractall(tmp_dir, **EXTRACT_OPTIONS)
|
|
142
|
+
|
|
143
|
+
# Setup a worker object
|
|
144
|
+
worker = Worker()
|
|
145
|
+
|
|
146
|
+
if not worker.setup(tmp_dir, command):
|
|
147
|
+
usermsgs.print_error(f'{addr}: bad request from scheck')
|
|
148
|
+
conn.send(b'e')
|
|
149
|
+
return
|
|
150
|
+
|
|
151
|
+
# Send confirmation
|
|
152
|
+
conn.send(b'o')
|
|
153
|
+
|
|
154
|
+
# Wait for start signal
|
|
155
|
+
conn.recv(1)
|
|
156
|
+
|
|
157
|
+
worker.run(conn)
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
def sworker(args):
|
|
161
|
+
"""Worker for distributed statistical model checking"""
|
|
162
|
+
|
|
163
|
+
# Parse the listing address
|
|
164
|
+
try:
|
|
165
|
+
with socket.create_server((args.address, args.port), backlog=1) as sock:
|
|
166
|
+
while True:
|
|
167
|
+
print(f'👂 Listening on {args.address}:{args.port}...')
|
|
168
|
+
conn, addr = sock.accept()
|
|
169
|
+
|
|
170
|
+
usermsgs.print_info(f'Accepted connection from {":".join(map(str, addr))}.')
|
|
171
|
+
|
|
172
|
+
while True:
|
|
173
|
+
# Read the initiation message
|
|
174
|
+
if (message := read_json(conn)) is None:
|
|
175
|
+
break
|
|
176
|
+
|
|
177
|
+
# A separate process to cleanup Maude state
|
|
178
|
+
process = mp.Process(target=handle_request, args=(message, conn, addr, args.keep_file))
|
|
179
|
+
process.start()
|
|
180
|
+
process.join()
|
|
181
|
+
|
|
182
|
+
except KeyboardInterrupt:
|
|
183
|
+
print('Server closed by the user.')
|
|
184
|
+
|
|
185
|
+
return 0
|
umaudemc/command/test.py
CHANGED
|
@@ -14,7 +14,7 @@ import sys # To find the Python executable path
|
|
|
14
14
|
import threading # To support timeouts
|
|
15
15
|
import time # To measure time
|
|
16
16
|
|
|
17
|
-
from ..common import maude, usermsgs
|
|
17
|
+
from ..common import maude, usermsgs, load_specification
|
|
18
18
|
from ..formulae import Parser, collect_aprops
|
|
19
19
|
from ..backends import supported_logics, get_backends, backend_for
|
|
20
20
|
from ..terminal import terminal as tmn
|
|
@@ -62,67 +62,7 @@ class TestCase:
|
|
|
62
62
|
def load_cases(filename):
|
|
63
63
|
"""Load test cases from YAML or JSON specifications"""
|
|
64
64
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
# The YAML package is only loaded when needed
|
|
68
|
-
# (pyaml is an optional dependency)
|
|
69
|
-
if extension in ('.yaml', '.yml'):
|
|
70
|
-
try:
|
|
71
|
-
import yaml
|
|
72
|
-
from yaml.loader import SafeLoader
|
|
73
|
-
|
|
74
|
-
except ImportError:
|
|
75
|
-
usermsgs.print_error(
|
|
76
|
-
'Cannot load cases from YAML file, since the yaml package is not installed.\n'
|
|
77
|
-
'Please convert the YAML to JSON or install it with pip install pyaml.')
|
|
78
|
-
return None
|
|
79
|
-
|
|
80
|
-
# The YAML loader is replaced so that entities have its line number
|
|
81
|
-
# associated to print more useful messages. This is not possible with
|
|
82
|
-
# the standard JSON library.
|
|
83
|
-
|
|
84
|
-
class SafeLineLoader(SafeLoader):
|
|
85
|
-
def construct_mapping(self, node, deep=False):
|
|
86
|
-
mapping = super(SafeLineLoader, self).construct_mapping(node, deep=deep)
|
|
87
|
-
# Add 1 so line numbering starts at 1
|
|
88
|
-
mapping['__line__'] = node.start_mark.line + 1
|
|
89
|
-
return mapping
|
|
90
|
-
|
|
91
|
-
try:
|
|
92
|
-
with open(filename) as caspec:
|
|
93
|
-
return yaml.load(caspec, Loader=SafeLineLoader)
|
|
94
|
-
|
|
95
|
-
except yaml.error.YAMLError as ype:
|
|
96
|
-
usermsgs.print_error(f'Error while parsing test file: {ype}.')
|
|
97
|
-
|
|
98
|
-
# TOML format
|
|
99
|
-
if extension == '.toml':
|
|
100
|
-
try:
|
|
101
|
-
import tomllib
|
|
102
|
-
|
|
103
|
-
except ImportError:
|
|
104
|
-
usermsgs.print_error(
|
|
105
|
-
'Cannot load cases from TOML file, '
|
|
106
|
-
'which is only available since Python 3.10.')
|
|
107
|
-
return None
|
|
108
|
-
|
|
109
|
-
try:
|
|
110
|
-
with open(filename) as caspec:
|
|
111
|
-
return tomllib.load(caspec)
|
|
112
|
-
|
|
113
|
-
except tomllib.TOMLDecodeError as tde:
|
|
114
|
-
usermsgs.print_error(f'Error while parsing test file: {tde}.')
|
|
115
|
-
|
|
116
|
-
# JSON format
|
|
117
|
-
else:
|
|
118
|
-
try:
|
|
119
|
-
with open(filename) as caspec:
|
|
120
|
-
return json.load(caspec)
|
|
121
|
-
|
|
122
|
-
except json.JSONDecodeError as jde:
|
|
123
|
-
usermsgs.print_error(f'Error while parsing test file: {jde}.')
|
|
124
|
-
|
|
125
|
-
return None
|
|
65
|
+
return load_specification(filename, 'cases')
|
|
126
66
|
|
|
127
67
|
|
|
128
68
|
def read_suite(filename, from_file=None, skip_case=0):
|
|
@@ -280,7 +220,7 @@ def _depends_on_vars(node, variables):
|
|
|
280
220
|
|
|
281
221
|
|
|
282
222
|
class ParameterSet:
|
|
283
|
-
"""
|
|
223
|
+
"""Assignment to parameters to be replaced on test attributes"""
|
|
284
224
|
|
|
285
225
|
def __init__(self, dic, is_subs=None):
|
|
286
226
|
# Dictionary with the parameter assignments
|
umaudemc/common.py
CHANGED
|
@@ -18,7 +18,8 @@ if not hasattr(maude.Term, 'getVarName'):
|
|
|
18
18
|
usermsgs.print_warning('Version 1.0 of the maude package adds some useful features for this program.\n'
|
|
19
19
|
'Please update.')
|
|
20
20
|
|
|
21
|
-
maude.Term.getVarName = lambda self: str(self).split(':')[0] if self.symbol() ==
|
|
21
|
+
maude.Term.getVarName = lambda self: str(self).split(':')[0] if self.symbol() == \
|
|
22
|
+
self.symbol().getModule().parseTerm(f'$$$:{self.getSort()}').symbol() else None
|
|
22
23
|
maude.Term.isVariable = lambda self: self.getVarName() is None
|
|
23
24
|
|
|
24
25
|
|
|
@@ -43,27 +44,43 @@ def find_maude_file_abs(filename):
|
|
|
43
44
|
return None
|
|
44
45
|
|
|
45
46
|
|
|
46
|
-
|
|
47
|
-
"""
|
|
48
|
-
if os.path.isabs(filename):
|
|
49
|
-
return find_maude_file_abs(filename)
|
|
47
|
+
class MaudeFileFinder:
|
|
48
|
+
"""Locate Maude files as Maude does"""
|
|
50
49
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
paths = [os.getcwd(), os.path.dirname(maude.__file__)]
|
|
50
|
+
MAUDE_STD = {'file', 'linear', 'machine-int', 'metaInterpreter', 'model-checker',
|
|
51
|
+
'prelude', 'prng', 'process', 'smt', 'socket', 'term-order', 'time'}
|
|
54
52
|
|
|
55
|
-
maudelib = os.getenv('MAUDE_LIB')
|
|
56
53
|
|
|
57
|
-
|
|
58
|
-
|
|
54
|
+
def __init__(self):
|
|
55
|
+
# Maude also considers the current working directory
|
|
56
|
+
# and the directory of the Maude binary
|
|
57
|
+
self.paths = [os.path.dirname(maude.__file__),
|
|
58
|
+
*os.getenv('MAUDE_LIB', '').split(os.pathsep)]
|
|
59
59
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
fullname = find_maude_file_abs(abspath)
|
|
63
|
-
if fullname is not None:
|
|
64
|
-
return fullname
|
|
60
|
+
def _is_std(self, name):
|
|
61
|
+
"""Is this a file from the Maude distribution"""
|
|
65
62
|
|
|
66
|
-
|
|
63
|
+
return name in self.MAUDE_STD or f'{name}.maude' in self.MAUDE_STD
|
|
64
|
+
|
|
65
|
+
def find(self, name, cwd):
|
|
66
|
+
# Absolute path, no ambiguity
|
|
67
|
+
if os.path.isabs(name):
|
|
68
|
+
return find_maude_file_abs(name), False
|
|
69
|
+
|
|
70
|
+
for path in (cwd, *self.paths):
|
|
71
|
+
abspath = os.path.join(path, name)
|
|
72
|
+
fullname = find_maude_file_abs(abspath)
|
|
73
|
+
|
|
74
|
+
if fullname is not None:
|
|
75
|
+
return fullname, path != cwd and self._is_std(name)
|
|
76
|
+
|
|
77
|
+
return None, None
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def find_maude_file(filename):
|
|
81
|
+
"""Find a Maude file taking MAUDE_LIB into account"""
|
|
82
|
+
|
|
83
|
+
return MaudeFileFinder().find(filename, os.getcwd())[0]
|
|
67
84
|
|
|
68
85
|
|
|
69
86
|
def parse_initial_data(args):
|
|
@@ -190,3 +207,71 @@ def default_model_settings(logic, purge_fails, merge_states, strategy, tableau=F
|
|
|
190
207
|
merge_states = 'state' if logic.startswith('CTL') else ('edge' if logic == 'Mucalc' else 'no')
|
|
191
208
|
|
|
192
209
|
return purge_fails, merge_states
|
|
210
|
+
|
|
211
|
+
|
|
212
|
+
def load_specification(filename, topic):
|
|
213
|
+
"""Load specifications from YAML or JSON files"""
|
|
214
|
+
|
|
215
|
+
extension = os.path.splitext(filename)[1]
|
|
216
|
+
|
|
217
|
+
# The YAML package is only loaded when needed
|
|
218
|
+
# (pyaml is an optional dependency)
|
|
219
|
+
if extension in ('.yaml', '.yml'):
|
|
220
|
+
try:
|
|
221
|
+
import yaml
|
|
222
|
+
from yaml.loader import SafeLoader
|
|
223
|
+
|
|
224
|
+
except ImportError:
|
|
225
|
+
usermsgs.print_error(
|
|
226
|
+
f'Cannot load {topic} from YAML file, since the yaml package is not installed.\n'
|
|
227
|
+
'Please convert the YAML to JSON or install it with pip install pyaml.')
|
|
228
|
+
return None
|
|
229
|
+
|
|
230
|
+
# The YAML loader is replaced so that entities have its line number
|
|
231
|
+
# associated to print more useful messages. This is not possible with
|
|
232
|
+
# the standard JSON library.
|
|
233
|
+
|
|
234
|
+
class SafeLineLoader(SafeLoader):
|
|
235
|
+
def construct_mapping(self, node, deep=False):
|
|
236
|
+
mapping = super(SafeLineLoader, self).construct_mapping(node, deep=deep)
|
|
237
|
+
# Add 1 so line numbering starts at 1
|
|
238
|
+
mapping['__line__'] = node.start_mark.line + 1
|
|
239
|
+
return mapping
|
|
240
|
+
|
|
241
|
+
try:
|
|
242
|
+
with open(filename) as caspec:
|
|
243
|
+
return yaml.load(caspec, Loader=SafeLineLoader)
|
|
244
|
+
|
|
245
|
+
except yaml.error.YAMLError as ype:
|
|
246
|
+
usermsgs.print_error(f'Error while parsing {topic} file: {ype}.')
|
|
247
|
+
|
|
248
|
+
# TOML format
|
|
249
|
+
if extension == '.toml':
|
|
250
|
+
try:
|
|
251
|
+
import tomllib
|
|
252
|
+
|
|
253
|
+
except ImportError:
|
|
254
|
+
usermsgs.print_error(
|
|
255
|
+
f'Cannot load {topic} from TOML file, '
|
|
256
|
+
'which is only available since Python 3.10.')
|
|
257
|
+
return None
|
|
258
|
+
|
|
259
|
+
try:
|
|
260
|
+
with open(filename, 'rb') as caspec:
|
|
261
|
+
return tomllib.load(caspec)
|
|
262
|
+
|
|
263
|
+
except tomllib.TOMLDecodeError as tde:
|
|
264
|
+
usermsgs.print_error(f'Error while parsing {topic} file: {tde}.')
|
|
265
|
+
|
|
266
|
+
# JSON format
|
|
267
|
+
else:
|
|
268
|
+
import json
|
|
269
|
+
|
|
270
|
+
try:
|
|
271
|
+
with open(filename) as caspec:
|
|
272
|
+
return json.load(caspec)
|
|
273
|
+
|
|
274
|
+
except json.JSONDecodeError as jde:
|
|
275
|
+
usermsgs.print_error(f'Error while parsing {topic} file: {jde}.')
|
|
276
|
+
|
|
277
|
+
return None
|
umaudemc/data/select.htm
CHANGED
|
@@ -85,7 +85,7 @@
|
|
|
85
85
|
<option value="metadata">Weighted by metadata</option>
|
|
86
86
|
<option value="strategy">Probabilistic strategy</option>
|
|
87
87
|
</select>
|
|
88
|
-
<input type="checkbox" id="mdp" style="margin-left: 1.5ex; display: none;"
|
|
88
|
+
<input type="checkbox" id="mdp" style="margin-left: 1.5ex; display: none;"/>
|
|
89
89
|
<label for="mdp" style="display: none;">MDP</label>
|
|
90
90
|
<label for="parg" style="margin-left: 1.5ex; display: none;">Weight term:</label>
|
|
91
91
|
<input type="text" id="parg" style="flex-grow: 1; display: none;" onchange="buttonToggle()"
|