Skip to content

Commit 7c8ceab

Browse files
authored
Add Apalache to CI (#141)
Signed-off-by: Andrew Helwer <[email protected]>
1 parent d05ce72 commit 7c8ceab

18 files changed

+149
-71
lines changed

.github/scripts/check_manifest_features.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,8 @@ def get_model_features(examples_root, path):
112112
'Sequences',
113113
'TLC',
114114
'TLCExt',
115-
'Toolbox'
115+
'Toolbox',
116+
'Apalache'
116117
}
117118

118119
# All the standard modules available when using TLAPS
@@ -230,7 +231,6 @@ def check_features(parser, queries, manifest, examples_root):
230231
if __name__ == '__main__':
231232
parser = ArgumentParser(description='Checks metadata in tlaplus/examples manifest.json against module and model files in repository.')
232233
parser.add_argument('--manifest_path', help='Path to the tlaplus/examples manifest.json file', required=True)
233-
parser.add_argument('--ts_path', help='[DEPRECATED, UNUSED] Path to tree-sitter-tlaplus directory', required=False)
234234
args = parser.parse_args()
235235

236236
manifest_path = normpath(args.manifest_path)

.github/scripts/check_proofs.py

+15-6
Original file line numberDiff line numberDiff line change
@@ -9,26 +9,30 @@
99
from timeit import default_timer as timer
1010
import tla_utils
1111

12-
logging.basicConfig(level=logging.INFO)
13-
1412
parser = ArgumentParser(description='Validate all proofs in all modules with TLAPM.')
1513
parser.add_argument('--tlapm_path', help='Path to TLAPM install dir; should have bin and lib subdirs', required=True)
1614
parser.add_argument('--manifest_path', help='Path to the tlaplus/examples manifest.json file', required=True)
1715
parser.add_argument('--skip', nargs='+', help='Space-separated list of .tla modules to skip checking', required=False, default=[])
16+
parser.add_argument('--only', nargs='+', help='If provided, only check proofs in this space-separated list', required=False, default=[])
17+
parser.add_argument('--verbose', help='Set logging output level to debug', action='store_true')
1818
args = parser.parse_args()
1919

2020
tlapm_path = normpath(args.tlapm_path)
2121
manifest_path = normpath(args.manifest_path)
2222
manifest = tla_utils.load_json(manifest_path)
2323
examples_root = dirname(manifest_path)
24-
skip_modules = [normpath(path) for path in args.skip]
24+
skip_modules = args.skip
25+
only_modules = args.only
26+
27+
logging.basicConfig(level = logging.DEBUG if args.verbose else logging.INFO)
2528

2629
proof_module_paths = [
2730
module['path']
2831
for spec in manifest['specifications']
2932
for module in spec['modules']
3033
if 'proof' in module['features']
31-
and normpath(module['path']) not in skip_modules
34+
and module['path'] not in skip_modules
35+
and (only_modules == [] or model['path'] in only_models)
3236
]
3337

3438
for path in skip_modules:
@@ -45,14 +49,19 @@
4549
[
4650
tlapm_path, module_path,
4751
'-I', module_dir
48-
], capture_output=True
52+
],
53+
stdout=subprocess.PIPE,
54+
stderr=subprocess.STDOUT,
55+
text=True
4956
)
5057
end_time = timer()
5158
logging.info(f'Checked proofs in {end_time - start_time:.1f}s')
5259
if tlapm.returncode != 0:
5360
logging.error(f'Proof checking failed in {module_path}:')
54-
logging.error(tlapm.stderr.decode('utf-8'))
61+
logging.error(tlapm.stdout)
5562
success = False
63+
else:
64+
logging.debug(tlapm.stdout)
5665

5766
exit(0 if success else 1)
5867

.github/scripts/check_small_models.py

+12-7
Original file line numberDiff line numberDiff line change
@@ -13,22 +13,25 @@
1313

1414
parser = ArgumentParser(description='Checks all small TLA+ models in the tlaplus/examples repo using TLC.')
1515
parser.add_argument('--tools_jar_path', help='Path to the tla2tools.jar file', required=True)
16+
parser.add_argument('--apalache_path', help='Path to the Apalache directory', required=True)
1617
parser.add_argument('--tlapm_lib_path', help='Path to the TLA+ proof manager module directory; .tla files should be in this directory', required=True)
1718
parser.add_argument('--community_modules_jar_path', help='Path to the CommunityModules-deps.jar file', required=True)
1819
parser.add_argument('--manifest_path', help='Path to the tlaplus/examples manifest.json file', required=True)
1920
parser.add_argument('--skip', nargs='+', help='Space-separated list of models to skip checking', required=False, default=[])
2021
parser.add_argument('--only', nargs='+', help='If provided, only check models in this space-separated list', required=False, default=[])
22+
parser.add_argument('--verbose', help='Set logging output level to debug', action='store_true')
2123
args = parser.parse_args()
2224

23-
logging.basicConfig(level=logging.INFO)
25+
logging.basicConfig(level = logging.DEBUG if args.verbose else logging.INFO)
2426

2527
tools_jar_path = normpath(args.tools_jar_path)
28+
apalache_path = normpath(args.apalache_path)
2629
tlapm_lib_path = normpath(args.tlapm_lib_path)
2730
community_jar_path = normpath(args.community_modules_jar_path)
2831
manifest_path = normpath(args.manifest_path)
2932
examples_root = dirname(manifest_path)
30-
skip_models = [normpath(path) for path in args.skip]
31-
only_models = [normpath(path) for path in args.only]
33+
skip_models = args.skip
34+
only_models = args.only
3235

3336
def check_model(module_path, model, expected_runtime):
3437
module_path = tla_utils.from_cwd(examples_root, module_path)
@@ -38,6 +41,7 @@ def check_model(module_path, model, expected_runtime):
3841
start_time = timer()
3942
tlc_result = tla_utils.check_model(
4043
tools_jar_path,
44+
apalache_path,
4145
module_path,
4246
model_path,
4347
tlapm_lib_path,
@@ -46,10 +50,10 @@ def check_model(module_path, model, expected_runtime):
4650
hard_timeout_in_seconds
4751
)
4852
end_time = timer()
53+
output = ' '.join(tlc_result.args) + '\n' + tlc_result.stdout
4954
match tlc_result:
5055
case CompletedProcess():
5156
logging.info(f'{model_path} in {end_time - start_time:.1f}s vs. {expected_runtime.seconds}s expected')
52-
output = ' '.join(tlc_result.args) + '\n' + tlc_result.stdout
5357
expected_result = model['result']
5458
actual_result = tla_utils.resolve_tlc_exit_code(tlc_result.returncode)
5559
if expected_result != actual_result:
@@ -67,10 +71,11 @@ def check_model(module_path, model, expected_runtime):
6771
logging.error(f"(distinct/total/depth); expected: {tla_utils.get_state_count_info(model)}, actual: {state_count_info}")
6872
logging.error(output)
6973
return False
74+
logging.debug(output)
7075
return True
7176
case TimeoutExpired():
7277
logging.error(f'{model_path} hit hard timeout of {hard_timeout_in_seconds} seconds')
73-
logging.error(tlc_result.output.decode('utf-8'))
78+
logging.error(output)
7479
return False
7580
case _:
7681
logging.error(f'Unhandled TLC result type {type(tlc_result)}: {tlc_result}')
@@ -85,8 +90,8 @@ def check_model(module_path, model, expected_runtime):
8590
for module in spec['modules']
8691
for model in module['models']
8792
if model['size'] == 'small'
88-
and normpath(model['path']) not in skip_models
89-
and (only_models == [] or normpath(model['path']) in only_models)
93+
and model['path'] not in skip_models
94+
and (only_models == [] or model['path'] in only_models)
9095
],
9196
key = lambda m: m[2],
9297
reverse=True

.github/scripts/generate_manifest.py

-1
Original file line numberDiff line numberDiff line change
@@ -167,7 +167,6 @@ def integrate_old_manifest_into_new(old_manifest, new_manifest):
167167
parser = ArgumentParser(description='Generates a new manifest.json derived from files in the repo.')
168168
parser.add_argument('--manifest_path', help='Path to the current tlaplus/examples manifest.json file', default='manifest.json')
169169
parser.add_argument('--ci_ignore_path', help='Path to the CI ignore file', default='.ciignore')
170-
parser.add_argument('--ts_path', help='[DEPRECATED, UNUSED] Path to tree-sitter-tlaplus directory', required=False)
171170
args = parser.parse_args()
172171

173172
manifest_path = normpath(args.manifest_path)

.github/scripts/linux-setup.sh

+3
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,9 @@ main() {
4040
# Get TLA⁺ tools
4141
mkdir -p "$DEPS_DIR/tools"
4242
wget -nv http://nightly.tlapl.us/dist/tla2tools.jar -P "$DEPS_DIR/tools"
43+
# Get Apalache
44+
wget -nv https://github.com/informalsystems/apalache/releases/latest/download/apalache.tgz -O /tmp/apalache.tgz
45+
tar -xzf /tmp/apalache.tgz --directory "$DEPS_DIR"
4346
# Get TLA⁺ community modules
4447
mkdir -p "$DEPS_DIR/community"
4548
wget -nv https://github.com/tlaplus/CommunityModules/releases/latest/download/CommunityModules-deps.jar \

.github/scripts/parse_modules.py

+14-6
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,13 @@
66
from concurrent.futures import ThreadPoolExecutor
77
import logging
88
from os import cpu_count
9-
from os.path import dirname, normpath, pathsep
9+
from os.path import join, dirname, normpath, pathsep
1010
import subprocess
1111
import tla_utils
1212

1313
parser = ArgumentParser(description='Parses all TLA+ modules in the tlaplus/examples repo using SANY.')
1414
parser.add_argument('--tools_jar_path', help='Path to the tla2tools.jar file', required=True)
15+
parser.add_argument('--apalache_path', help='Path to the Apalache directory', required=True)
1516
parser.add_argument('--tlapm_lib_path', help='Path to the TLA+ proof manager module directory; .tla files should be in this directory', required=True)
1617
parser.add_argument('--community_modules_jar_path', help='Path to the CommunityModules-deps.jar file', required=True)
1718
parser.add_argument('--manifest_path', help='Path to the tlaplus/examples manifest.json file', required=True)
@@ -23,20 +24,27 @@
2324
logging.basicConfig(level = logging.DEBUG if args.verbose else logging.INFO)
2425

2526
tools_jar_path = normpath(args.tools_jar_path)
27+
apalache_jar_path = normpath(join(args.apalache_path, 'lib', 'apalache.jar'))
2628
tlaps_modules = normpath(args.tlapm_lib_path)
2729
community_modules = normpath(args.community_modules_jar_path)
2830
manifest_path = normpath(args.manifest_path)
2931
examples_root = dirname(manifest_path)
30-
skip_modules = [normpath(path) for path in args.skip]
31-
only_modules = [normpath(path) for path in args.only]
32+
skip_modules = args.skip
33+
only_modules = args.only
3234

3335
def parse_module(path):
3436
"""
3537
Parse the given module using SANY.
3638
"""
3739
logging.info(path)
3840
# Jar paths must go first
39-
search_paths = pathsep.join([tools_jar_path, dirname(path), community_modules, tlaps_modules])
41+
search_paths = pathsep.join([
42+
tools_jar_path,
43+
apalache_jar_path,
44+
dirname(path),
45+
community_modules,
46+
tlaps_modules
47+
])
4048
sany = subprocess.run([
4149
'java',
4250
'-cp', search_paths,
@@ -63,8 +71,8 @@ def parse_module(path):
6371
tla_utils.from_cwd(examples_root, module['path'])
6472
for spec in manifest['specifications']
6573
for module in spec['modules']
66-
if normpath(module['path']) not in skip_modules
67-
and (only_modules == [] or normpath(module['path']) in only_modules)
74+
if module['path'] not in skip_modules
75+
and (only_modules == [] or module['path'] in only_modules)
6876
]
6977

7078
for path in skip_modules:

.github/scripts/smoke_test_large_models.py

+11-3
Original file line numberDiff line numberDiff line change
@@ -13,18 +13,23 @@
1313

1414
parser = ArgumentParser(description='Smoke-tests all larger TLA+ models in the tlaplus/examples repo using TLC.')
1515
parser.add_argument('--tools_jar_path', help='Path to the tla2tools.jar file', required=True)
16+
parser.add_argument('--apalache_path', help='Path to the Apalache directory', required=True)
1617
parser.add_argument('--tlapm_lib_path', help='Path to the TLA+ proof manager module directory; .tla files should be in this directory', required=True)
1718
parser.add_argument('--community_modules_jar_path', help='Path to the CommunityModules-deps.jar file', required=True)
1819
parser.add_argument('--manifest_path', help='Path to the tlaplus/examples manifest.json file', required=True)
1920
parser.add_argument('--skip', nargs='+', help='Space-separated list of models to skip checking', required=False, default=[])
21+
parser.add_argument('--only', nargs='+', help='If provided, only check models in this space-separated list', required=False, default=[])
22+
parser.add_argument('--verbose', help='Set logging output level to debug', action='store_true')
2023
args = parser.parse_args()
2124

2225
tools_jar_path = normpath(args.tools_jar_path)
26+
apalache_path = normpath(args.apalache_path)
2327
tlapm_lib_path = normpath(args.tlapm_lib_path)
2428
community_jar_path = normpath(args.community_modules_jar_path)
2529
manifest_path = normpath(args.manifest_path)
2630
examples_root = dirname(manifest_path)
27-
skip_models = [normpath(path) for path in args.skip]
31+
skip_models = args.skip
32+
only_models = args.only
2833

2934
def check_model(module_path, model):
3035
module_path = tla_utils.from_cwd(examples_root, module_path)
@@ -33,6 +38,7 @@ def check_model(module_path, model):
3338
smoke_test_timeout_in_seconds = 5
3439
tlc_result = tla_utils.check_model(
3540
tools_jar_path,
41+
apalache_path,
3642
module_path,
3743
model_path,
3844
tlapm_lib_path,
@@ -42,6 +48,7 @@ def check_model(module_path, model):
4248
)
4349
match tlc_result:
4450
case TimeoutExpired():
51+
logging.debug(tlc_result.stdout)
4552
return True
4653
case CompletedProcess():
4754
logging.warning(f'Model {model_path} finished quickly, within {smoke_test_timeout_in_seconds} seconds; consider labeling it a small model')
@@ -56,7 +63,7 @@ def check_model(module_path, model):
5663
logging.error(f'Unhandled TLC result type {type(tlc_result)}: {tlc_result}')
5764
return False
5865

59-
logging.basicConfig(level=logging.INFO)
66+
logging.basicConfig(level = logging.DEBUG if args.verbose else logging.INFO)
6067

6168
manifest = tla_utils.load_json(manifest_path)
6269

@@ -66,7 +73,8 @@ def check_model(module_path, model):
6673
for module in spec['modules']
6774
for model in module['models']
6875
if model['size'] != 'small'
69-
and normpath(model['path']) not in skip_models
76+
and model['path'] not in skip_models
77+
and (only_models == [] or model['path'] in only_models)
7078
]
7179

7280
for path in skip_models:

.github/scripts/tla_utils.py

+39-11
Original file line numberDiff line numberDiff line change
@@ -87,37 +87,65 @@ def get_run_mode(mode):
8787
else:
8888
raise NotImplementedError(f'Undefined model-check mode {mode}')
8989

90-
def check_model(tools_jar_path, module_path, model_path, tlapm_lib_path, community_jar_path, mode, hard_timeout_in_seconds):
90+
def check_model(
91+
tools_jar_path,
92+
apalache_path,
93+
module_path,
94+
model_path,
95+
tlapm_lib_path,
96+
community_jar_path,
97+
mode,
98+
hard_timeout_in_seconds
99+
):
91100
"""
92101
Model-checks the given model against the given module.
93102
"""
94103
tools_jar_path = normpath(tools_jar_path)
104+
apalache_path = normpath(join(apalache_path, 'bin', 'apalache-mc'))
105+
apalache_jar_path = normpath(join(apalache_path, 'lib', 'apalache.jar'))
95106
module_path = normpath(module_path)
96107
model_path = normpath(model_path)
97108
tlapm_lib_path = normpath(tlapm_lib_path)
98109
community_jar_path = normpath(community_jar_path)
99110
try:
100-
tlc = subprocess.run(
101-
[
111+
if mode == 'symbolic':
112+
apalache = subprocess.run([
113+
apalache_path, 'check',
114+
f'--config={model_path}',
115+
module_path
116+
],
117+
timeout=hard_timeout_in_seconds,
118+
stdout=subprocess.PIPE,
119+
stderr=subprocess.STDOUT,
120+
text=True
121+
)
122+
return apalache
123+
else:
124+
tlc = subprocess.run([
102125
'java',
103126
'-Dtlc2.TLC.ide=Github',
104127
'-Dutil.ExecutionStatisticsCollector.id=abcdef60f238424fa70d124d0c77ffff',
105128
'-XX:+UseParallelGC',
106129
# Jar paths must go first
107-
'-cp', pathsep.join([tools_jar_path, community_jar_path, tlapm_lib_path]),
130+
'-cp', pathsep.join([
131+
tools_jar_path,
132+
apalache_jar_path,
133+
community_jar_path,
134+
tlapm_lib_path
135+
]),
108136
'tlc2.TLC',
109137
module_path,
110138
'-config', model_path,
111139
'-workers', 'auto',
112140
'-lncheck', 'final',
113141
'-cleanup'
114-
] + get_run_mode(mode),
115-
timeout=hard_timeout_in_seconds,
116-
stdout=subprocess.PIPE,
117-
stderr=subprocess.STDOUT,
118-
text=True
119-
)
120-
return tlc
142+
] + get_run_mode(mode),
143+
timeout=hard_timeout_in_seconds,
144+
stdout=subprocess.PIPE,
145+
stderr=subprocess.STDOUT,
146+
text=True
147+
)
148+
return tlc
121149
except subprocess.TimeoutExpired as e:
122150
return e
123151

.github/scripts/translate_pluscal.py

+5-4
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,8 @@
2424
tools_path = normpath(args.tools_jar_path)
2525
manifest_path = normpath(args.manifest_path)
2626
examples_root = dirname(manifest_path)
27-
skip_modules = [normpath(path) for path in args.skip]
28-
only_modules = [normpath(path) for path in args.only]
27+
skip_modules = args.skip
28+
only_modules = args.only
2929

3030
manifest = tla_utils.load_json(manifest_path)
3131

@@ -35,8 +35,8 @@
3535
for spec in manifest['specifications']
3636
for module in spec['modules']
3737
if 'pluscal' in module['features']
38-
and normpath(module['path']) not in skip_modules
39-
and (only_modules == [] or normpath(module['path']) in only_modules)
38+
and module['path'] not in skip_modules
39+
and (only_modules == [] or module['path'] in only_modules)
4040
]
4141

4242
for path in skip_modules:
@@ -53,6 +53,7 @@ def translate_module(module_path):
5353
match result:
5454
case CompletedProcess():
5555
if result.returncode == 0:
56+
logging.debug(result.stdout)
5657
return True
5758
else:
5859
logging.error(f'Module {module_path} conversion failed with return code {result.returncode}; output:\n{result.stdout}')

0 commit comments

Comments
 (0)