Skip to content

Commit

Permalink
Add Apalache to CI (#141)
Browse files Browse the repository at this point in the history
Signed-off-by: Andrew Helwer <[email protected]>
  • Loading branch information
ahelwer authored Apr 20, 2024
1 parent d05ce72 commit 7c8ceab
Show file tree
Hide file tree
Showing 18 changed files with 149 additions and 71 deletions.
4 changes: 2 additions & 2 deletions .github/scripts/check_manifest_features.py
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,8 @@ def get_model_features(examples_root, path):
'Sequences',
'TLC',
'TLCExt',
'Toolbox'
'Toolbox',
'Apalache'
}

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

manifest_path = normpath(args.manifest_path)
Expand Down
21 changes: 15 additions & 6 deletions .github/scripts/check_proofs.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,26 +9,30 @@
from timeit import default_timer as timer
import tla_utils

logging.basicConfig(level=logging.INFO)

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

tlapm_path = normpath(args.tlapm_path)
manifest_path = normpath(args.manifest_path)
manifest = tla_utils.load_json(manifest_path)
examples_root = dirname(manifest_path)
skip_modules = [normpath(path) for path in args.skip]
skip_modules = args.skip
only_modules = args.only

logging.basicConfig(level = logging.DEBUG if args.verbose else logging.INFO)

proof_module_paths = [
module['path']
for spec in manifest['specifications']
for module in spec['modules']
if 'proof' in module['features']
and normpath(module['path']) not in skip_modules
and module['path'] not in skip_modules
and (only_modules == [] or model['path'] in only_models)
]

for path in skip_modules:
Expand All @@ -45,14 +49,19 @@
[
tlapm_path, module_path,
'-I', module_dir
], capture_output=True
],
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT,
text=True
)
end_time = timer()
logging.info(f'Checked proofs in {end_time - start_time:.1f}s')
if tlapm.returncode != 0:
logging.error(f'Proof checking failed in {module_path}:')
logging.error(tlapm.stderr.decode('utf-8'))
logging.error(tlapm.stdout)
success = False
else:
logging.debug(tlapm.stdout)

exit(0 if success else 1)

19 changes: 12 additions & 7 deletions .github/scripts/check_small_models.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,22 +13,25 @@

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

logging.basicConfig(level=logging.INFO)
logging.basicConfig(level = logging.DEBUG if args.verbose else logging.INFO)

tools_jar_path = normpath(args.tools_jar_path)
apalache_path = normpath(args.apalache_path)
tlapm_lib_path = normpath(args.tlapm_lib_path)
community_jar_path = normpath(args.community_modules_jar_path)
manifest_path = normpath(args.manifest_path)
examples_root = dirname(manifest_path)
skip_models = [normpath(path) for path in args.skip]
only_models = [normpath(path) for path in args.only]
skip_models = args.skip
only_models = args.only

def check_model(module_path, model, expected_runtime):
module_path = tla_utils.from_cwd(examples_root, module_path)
Expand All @@ -38,6 +41,7 @@ def check_model(module_path, model, expected_runtime):
start_time = timer()
tlc_result = tla_utils.check_model(
tools_jar_path,
apalache_path,
module_path,
model_path,
tlapm_lib_path,
Expand All @@ -46,10 +50,10 @@ def check_model(module_path, model, expected_runtime):
hard_timeout_in_seconds
)
end_time = timer()
output = ' '.join(tlc_result.args) + '\n' + tlc_result.stdout
match tlc_result:
case CompletedProcess():
logging.info(f'{model_path} in {end_time - start_time:.1f}s vs. {expected_runtime.seconds}s expected')
output = ' '.join(tlc_result.args) + '\n' + tlc_result.stdout
expected_result = model['result']
actual_result = tla_utils.resolve_tlc_exit_code(tlc_result.returncode)
if expected_result != actual_result:
Expand All @@ -67,10 +71,11 @@ def check_model(module_path, model, expected_runtime):
logging.error(f"(distinct/total/depth); expected: {tla_utils.get_state_count_info(model)}, actual: {state_count_info}")
logging.error(output)
return False
logging.debug(output)
return True
case TimeoutExpired():
logging.error(f'{model_path} hit hard timeout of {hard_timeout_in_seconds} seconds')
logging.error(tlc_result.output.decode('utf-8'))
logging.error(output)
return False
case _:
logging.error(f'Unhandled TLC result type {type(tlc_result)}: {tlc_result}')
Expand All @@ -85,8 +90,8 @@ def check_model(module_path, model, expected_runtime):
for module in spec['modules']
for model in module['models']
if model['size'] == 'small'
and normpath(model['path']) not in skip_models
and (only_models == [] or normpath(model['path']) in only_models)
and model['path'] not in skip_models
and (only_models == [] or model['path'] in only_models)
],
key = lambda m: m[2],
reverse=True
Expand Down
1 change: 0 additions & 1 deletion .github/scripts/generate_manifest.py
Original file line number Diff line number Diff line change
Expand Up @@ -167,7 +167,6 @@ def integrate_old_manifest_into_new(old_manifest, new_manifest):
parser = ArgumentParser(description='Generates a new manifest.json derived from files in the repo.')
parser.add_argument('--manifest_path', help='Path to the current tlaplus/examples manifest.json file', default='manifest.json')
parser.add_argument('--ci_ignore_path', help='Path to the CI ignore file', default='.ciignore')
parser.add_argument('--ts_path', help='[DEPRECATED, UNUSED] Path to tree-sitter-tlaplus directory', required=False)
args = parser.parse_args()

manifest_path = normpath(args.manifest_path)
Expand Down
3 changes: 3 additions & 0 deletions .github/scripts/linux-setup.sh
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,9 @@ main() {
# Get TLA⁺ tools
mkdir -p "$DEPS_DIR/tools"
wget -nv http://nightly.tlapl.us/dist/tla2tools.jar -P "$DEPS_DIR/tools"
# Get Apalache
wget -nv https://github.com/informalsystems/apalache/releases/latest/download/apalache.tgz -O /tmp/apalache.tgz
tar -xzf /tmp/apalache.tgz --directory "$DEPS_DIR"
# Get TLA⁺ community modules
mkdir -p "$DEPS_DIR/community"
wget -nv https://github.com/tlaplus/CommunityModules/releases/latest/download/CommunityModules-deps.jar \
Expand Down
20 changes: 14 additions & 6 deletions .github/scripts/parse_modules.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,13 @@
from concurrent.futures import ThreadPoolExecutor
import logging
from os import cpu_count
from os.path import dirname, normpath, pathsep
from os.path import join, dirname, normpath, pathsep
import subprocess
import tla_utils

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

tools_jar_path = normpath(args.tools_jar_path)
apalache_jar_path = normpath(join(args.apalache_path, 'lib', 'apalache.jar'))
tlaps_modules = normpath(args.tlapm_lib_path)
community_modules = normpath(args.community_modules_jar_path)
manifest_path = normpath(args.manifest_path)
examples_root = dirname(manifest_path)
skip_modules = [normpath(path) for path in args.skip]
only_modules = [normpath(path) for path in args.only]
skip_modules = args.skip
only_modules = args.only

def parse_module(path):
"""
Parse the given module using SANY.
"""
logging.info(path)
# Jar paths must go first
search_paths = pathsep.join([tools_jar_path, dirname(path), community_modules, tlaps_modules])
search_paths = pathsep.join([
tools_jar_path,
apalache_jar_path,
dirname(path),
community_modules,
tlaps_modules
])
sany = subprocess.run([
'java',
'-cp', search_paths,
Expand All @@ -63,8 +71,8 @@ def parse_module(path):
tla_utils.from_cwd(examples_root, module['path'])
for spec in manifest['specifications']
for module in spec['modules']
if normpath(module['path']) not in skip_modules
and (only_modules == [] or normpath(module['path']) in only_modules)
if module['path'] not in skip_modules
and (only_modules == [] or module['path'] in only_modules)
]

for path in skip_modules:
Expand Down
14 changes: 11 additions & 3 deletions .github/scripts/smoke_test_large_models.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,18 +13,23 @@

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

tools_jar_path = normpath(args.tools_jar_path)
apalache_path = normpath(args.apalache_path)
tlapm_lib_path = normpath(args.tlapm_lib_path)
community_jar_path = normpath(args.community_modules_jar_path)
manifest_path = normpath(args.manifest_path)
examples_root = dirname(manifest_path)
skip_models = [normpath(path) for path in args.skip]
skip_models = args.skip
only_models = args.only

def check_model(module_path, model):
module_path = tla_utils.from_cwd(examples_root, module_path)
Expand All @@ -33,6 +38,7 @@ def check_model(module_path, model):
smoke_test_timeout_in_seconds = 5
tlc_result = tla_utils.check_model(
tools_jar_path,
apalache_path,
module_path,
model_path,
tlapm_lib_path,
Expand All @@ -42,6 +48,7 @@ def check_model(module_path, model):
)
match tlc_result:
case TimeoutExpired():
logging.debug(tlc_result.stdout)
return True
case CompletedProcess():
logging.warning(f'Model {model_path} finished quickly, within {smoke_test_timeout_in_seconds} seconds; consider labeling it a small model')
Expand All @@ -56,7 +63,7 @@ def check_model(module_path, model):
logging.error(f'Unhandled TLC result type {type(tlc_result)}: {tlc_result}')
return False

logging.basicConfig(level=logging.INFO)
logging.basicConfig(level = logging.DEBUG if args.verbose else logging.INFO)

manifest = tla_utils.load_json(manifest_path)

Expand All @@ -66,7 +73,8 @@ def check_model(module_path, model):
for module in spec['modules']
for model in module['models']
if model['size'] != 'small'
and normpath(model['path']) not in skip_models
and model['path'] not in skip_models
and (only_models == [] or model['path'] in only_models)
]

for path in skip_models:
Expand Down
50 changes: 39 additions & 11 deletions .github/scripts/tla_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -87,37 +87,65 @@ def get_run_mode(mode):
else:
raise NotImplementedError(f'Undefined model-check mode {mode}')

def check_model(tools_jar_path, module_path, model_path, tlapm_lib_path, community_jar_path, mode, hard_timeout_in_seconds):
def check_model(
tools_jar_path,
apalache_path,
module_path,
model_path,
tlapm_lib_path,
community_jar_path,
mode,
hard_timeout_in_seconds
):
"""
Model-checks the given model against the given module.
"""
tools_jar_path = normpath(tools_jar_path)
apalache_path = normpath(join(apalache_path, 'bin', 'apalache-mc'))
apalache_jar_path = normpath(join(apalache_path, 'lib', 'apalache.jar'))
module_path = normpath(module_path)
model_path = normpath(model_path)
tlapm_lib_path = normpath(tlapm_lib_path)
community_jar_path = normpath(community_jar_path)
try:
tlc = subprocess.run(
[
if mode == 'symbolic':
apalache = subprocess.run([
apalache_path, 'check',
f'--config={model_path}',
module_path
],
timeout=hard_timeout_in_seconds,
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT,
text=True
)
return apalache
else:
tlc = subprocess.run([
'java',
'-Dtlc2.TLC.ide=Github',
'-Dutil.ExecutionStatisticsCollector.id=abcdef60f238424fa70d124d0c77ffff',
'-XX:+UseParallelGC',
# Jar paths must go first
'-cp', pathsep.join([tools_jar_path, community_jar_path, tlapm_lib_path]),
'-cp', pathsep.join([
tools_jar_path,
apalache_jar_path,
community_jar_path,
tlapm_lib_path
]),
'tlc2.TLC',
module_path,
'-config', model_path,
'-workers', 'auto',
'-lncheck', 'final',
'-cleanup'
] + get_run_mode(mode),
timeout=hard_timeout_in_seconds,
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT,
text=True
)
return tlc
] + get_run_mode(mode),
timeout=hard_timeout_in_seconds,
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT,
text=True
)
return tlc
except subprocess.TimeoutExpired as e:
return e

Expand Down
9 changes: 5 additions & 4 deletions .github/scripts/translate_pluscal.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,8 @@
tools_path = normpath(args.tools_jar_path)
manifest_path = normpath(args.manifest_path)
examples_root = dirname(manifest_path)
skip_modules = [normpath(path) for path in args.skip]
only_modules = [normpath(path) for path in args.only]
skip_modules = args.skip
only_modules = args.only

manifest = tla_utils.load_json(manifest_path)

Expand All @@ -35,8 +35,8 @@
for spec in manifest['specifications']
for module in spec['modules']
if 'pluscal' in module['features']
and normpath(module['path']) not in skip_modules
and (only_modules == [] or normpath(module['path']) in only_modules)
and module['path'] not in skip_modules
and (only_modules == [] or module['path'] in only_modules)
]

for path in skip_modules:
Expand All @@ -53,6 +53,7 @@ def translate_module(module_path):
match result:
case CompletedProcess():
if result.returncode == 0:
logging.debug(result.stdout)
return True
else:
logging.error(f'Module {module_path} conversion failed with return code {result.returncode}; output:\n{result.stdout}')
Expand Down
Loading

0 comments on commit 7c8ceab

Please sign in to comment.