forked from septract/starling-tool
-
Notifications
You must be signed in to change notification settings - Fork 0
/
starling_common.py
149 lines (112 loc) · 4.64 KB
/
starling_common.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
"""Common definitions for regress.py and bench.py."""
import os
import re
import subprocess
import tempfile
import pathlib
# Top level examples directory.
from dataclasses import dataclass
from typing import List, Tuple, Optional, Iterator
EXAMPLES_DIR = 'Examples'
Z3_ARGS = ['-ssmt-sat']
GH_ARGS = ['-sgrass', '-Btry-approx']
# Used to match GRASShopper failures
GH_FAIL_RE = re.compile(r'^A postcondition of (?P<term>[^ ]+) might not hold at this return point$')
def inflate_example_name(bucket: str, name: str) -> pathlib.Path:
"""Creates an example filepath from its bucket and name.
Args:
bucket: The category (Pass, Fail, etc.) in which the example
lives.
name: The name, less the file extension, of the example.
Returns:
The inflated example filepath.
"""
return pathlib.Path(EXAMPLES_DIR, bucket, (name + ".cvf"))
def read_infile(path: os.PathLike) -> Iterator[List[str]]:
"""Reads a colon-separated, hash-commented file.
These are used both by regress.py and bench.py.
Args:
path: The path to the file to read.
Yields:
Each record in the file, as a list of fields.
"""
with open(path, 'r') as f:
for line in f:
sline, _, _ = line.partition('#')
if sline.strip() != '':
yield [r.strip() for r in sline.split(':')]
@dataclass
class Output:
"""Contains cooked stdout and stderr lines."""
stdout: List[str]
stderr: List[str]
def run_and_cook(args: List[str]) -> Output:
"""Runs a process and returns its output as UTF-8 lines.
Args:
args: A list containing the program name, and zero or more arguments.
Returns:
The cooked, UTF-8 stdout and stderr lines.
"""
proc = subprocess.Popen(args,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE)
stdout, stderr = proc.communicate()
return Output(stdout=stdout.decode('utf-8').split('\n'),
stderr=stderr.decode('utf-8').split('\n'))
@dataclass
class Starling:
"""Represents a way of invoking Starling."""
# A list, where the zeroth element is the program to run, and all
# subsequent elements are arguments.
args: List[str]
def run_z3(self, path: os.PathLike, extra_args: Optional[List[str]] = None) -> Output:
"""Runs Starling in Z3 mode on path.
Args:
self: This object.
path: The name of the file to check.
extra_args: Any additional arguments to pass to Starling.
Returns:
The tuple of cooked, UTF-8 stdout and stderr lines.
"""
all_z3_args = Z3_ARGS + ([] if extra_args is None else extra_args)
return run_and_cook(self.args + all_z3_args + [os.fspath(path)])
def run_grasshopper_translate(self, path: os.PathLike) -> pathlib.Path:
"""Runs Starling on path, translating it into GRASShopper.
Args:
self: This object.
path: The name of the file to check.
Returns:
The name of the temporary file generated containing the
GRASShopper input.
"""
# GRASShopper can't read from stdin, so we have to make a temporary file.
args : List[str] = self.args + GH_ARGS + [os.fspath(path)]
# Having the current directory as dir is a nasty hack.
with tempfile.NamedTemporaryFile(dir='', suffix='.spl', delete=False) as tempf:
path = pathlib.Path(tempf.name)
starling = subprocess.Popen(args, stdout=tempf)
starling.wait()
return path
def run_grasshopper(self, grasshopper_args: List[str], path: os.PathLike) -> Output:
"""Runs Starling/GRASShopper mode on path.
Args:
self: This object.
grasshopper_args: A list containing the program name, and zero or
more arguments, needed to run GRASShopper.
path: The name of the file to check.
Returns:
The tuple of cooked, UTF-8 stdout and stderr lines coming from
GRASShopper (not Starling).
"""
grass_path: pathlib.Path = self.run_grasshopper_translate(path)
output = run_and_cook(grasshopper_args + [grass_path.name])
grass_path.unlink()
return output
def get_starling() -> Starling:
"""Tries to find the correct invocation for Starling on this platform.
Returns:
A Starling object that can be run to invoke Starling.
"""
# This method used to do some platform-specific stuff, but now we just
# assume the .NET Core runtime exists.
return Starling(args=['dotnet', 'run', '--no-build', '--project', 'starling'])