-
Notifications
You must be signed in to change notification settings - Fork 161
Expand file tree
/
Copy pathtest_refute_node.py
More file actions
340 lines (276 loc) · 11.4 KB
/
test_refute_node.py
File metadata and controls
340 lines (276 loc) · 11.4 KB
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
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
from __future__ import annotations
from pathlib import Path
from typing import TYPE_CHECKING
import pytest
from pyk.cterm import CTerm
from pyk.kast import Atts, KAtt
from pyk.kast.inner import KApply, KRewrite, KSequence, KToken, KVariable
from pyk.kast.manip import free_vars
from pyk.kast.outer import KClaim
from pyk.kcfg import KCFG
from pyk.kcfg.minimize import KCFGMinimizer
from pyk.kcfg.semantics import DefaultSemantics
from pyk.prelude.kint import gtInt, intToken, leInt
from pyk.prelude.ml import is_top, mlEqualsTrue
from pyk.proof import APRProof, APRProver, ImpliesProver, ProofStatus, RefutationProof
from pyk.testing import KCFGExploreTest, KProveTest
from pyk.utils import single
from ..utils import K_FILES
if TYPE_CHECKING:
from collections.abc import Iterable
from typing import Union
from pytest import TempPathFactory
from pyk.kast.inner import KInner
from pyk.kast.outer import KDefinition
from pyk.kcfg import KCFGExplore
from pyk.kcfg.semantics import KCFGSemantics
from pyk.ktool.kprove import KProve
STATE = Union[tuple[str, str], tuple[str, str, str]]
class RefuteSemantics(DefaultSemantics):
def is_terminal(self, c: CTerm) -> bool:
k_cell = c.cell('K_CELL')
if type(k_cell) is KSequence:
if len(k_cell) == 0:
return True
if len(k_cell) == 1 and type(k_cell[0]) is KVariable:
return True
if (
len(k_cell) == 2
and type(k_cell[1]) is KVariable
and type(k_cell[0]) is KApply
and (
k_cell[0].label.name == 'e(_)_REFUTE-NODE-SYNTAX_A_Int'
or k_cell[0].label.name == 'f(_)_REFUTE-NODE-SYNTAX_A_Int'
)
):
return True
if type(k_cell) is KVariable:
return True
return False
REFUTE_NODE_TEST_DATA: Iterable[tuple[str, Iterable[KInner], ProofStatus]] = (
('refute-node-fail', (leInt(KVariable('N'), intToken(0)),), ProofStatus.FAILED),
)
class TestAPRProof(KCFGExploreTest, KProveTest):
KOMPILE_MAIN_FILE = K_FILES / 'refute-node.k'
def semantics(self, definition: KDefinition) -> KCFGSemantics:
return RefuteSemantics()
@pytest.fixture(scope='function')
def proof_dir(self, tmp_path_factory: TempPathFactory) -> Path:
return tmp_path_factory.mktemp('proofs')
def build_prover(
self,
kprove: KProve,
proof_dir: Path,
kcfg_explore: KCFGExplore,
spec_file: Path,
spec_module: str,
claim_id: str,
) -> tuple[APRProver, APRProof]:
claim = single(
kprove.get_claims(Path(spec_file), spec_module_name=spec_module, claim_labels=[f'{spec_module}.{claim_id}'])
)
kcfg_pre, init_node, target_node = KCFG.from_claim(kprove.definition, claim, proof_dir)
proof = APRProof(
f'{spec_module}.{claim_id}',
kcfg_pre,
[],
init=init_node,
target=target_node,
logs={},
proof_dir=proof_dir,
)
return (APRProver(kcfg_explore), proof)
def test_apr_proof_unrefute_node(
self,
kprove: KProve,
proof_dir: Path,
kcfg_explore: KCFGExplore,
) -> None:
# Given
spec_file = K_FILES / 'refute-node-spec.k'
spec_module = 'REFUTE-NODE-SPEC'
claim_id = 'split-int-succeed'
prover, proof = self.build_prover(kprove, proof_dir, kcfg_explore, spec_file, spec_module, claim_id)
# When
prover.advance_proof(proof, max_iterations=1)
frontier_nodes = proof.pending
assert proof.status == ProofStatus.PENDING
assert len(frontier_nodes) == 2
frontier_node = frontier_nodes[0]
proof.refute_node(frontier_node)
proof.unrefute_node(frontier_node)
prover.advance_proof(proof)
# Then
assert proof.status == ProofStatus.PASSED
@pytest.mark.parametrize(
'test_id,expected_subproof_constraints,expected_status',
REFUTE_NODE_TEST_DATA,
ids=[test_id for test_id, *_ in REFUTE_NODE_TEST_DATA],
)
def test_apr_proof_refute_node(
self,
kprove: KProve,
kcfg_explore: KCFGExplore,
proof_dir: Path,
test_id: str,
expected_subproof_constraints: Iterable[KInner],
expected_status: ProofStatus,
) -> None:
# Given
spec_file = K_FILES / 'refute-node-spec.k'
spec_module = 'REFUTE-NODE-SPEC'
claim_id = 'split-int-fail'
prover, proof = self.build_prover(kprove, proof_dir, kcfg_explore, spec_file, spec_module, claim_id)
# When
prover.advance_proof(proof)
assert proof.status == ProofStatus.FAILED
failing_node = single(proof.failing)
proof.refute_node(failing_node)
assert len(proof.subproof_ids) == 1
subproof = single(proof.subproofs)
assert type(subproof) is RefutationProof
refutation_prover = ImpliesProver(subproof, kcfg_explore)
refutation_prover.advance_proof(subproof)
assert subproof.status == ProofStatus.FAILED
expected_subproof_constraints = tuple(
[kprove.definition.sort_vars(constraint) for constraint in expected_subproof_constraints]
)
actual_subproof_constraints = tuple(
[
kprove.definition.sort_vars(constraint)
for constraint in (list(subproof.pre_constraints) + [subproof.last_constraint])
if not is_top(constraint)
]
)
assert expected_subproof_constraints == actual_subproof_constraints
# Then
assert proof.status == expected_status
def test_apr_proof_read_node_refutations(
self,
kprove: KProve,
proof_dir: Path,
kcfg_explore: KCFGExplore,
) -> None:
# Given
spec_file = K_FILES / 'refute-node-spec.k'
spec_module = 'REFUTE-NODE-SPEC'
claim_id = 'split-int-succeed'
prover, proof = self.build_prover(kprove, proof_dir, kcfg_explore, spec_file, spec_module, claim_id)
# When
prover.advance_proof(proof, max_iterations=1)
frontier_nodes = proof.pending
assert proof.status == ProofStatus.PENDING
assert len(frontier_nodes) == 2
frontier_node = frontier_nodes[0]
proof.refute_node(frontier_node)
proof_from_file = APRProof.read_proof_data(proof_dir, proof.id)
refutation_id = proof.get_refutation_id(frontier_node.id)
# Then
assert len(proof_from_file.node_refutations) == 1
assert frontier_node.id in proof_from_file.node_refutations
assert proof_from_file.node_refutations[frontier_node.id].id == refutation_id
def test_apr_proof_refute_node_no_successors(
self,
kprove: KProve,
proof_dir: Path,
kcfg_explore: KCFGExplore,
) -> None:
# Given
spec_file = K_FILES / 'refute-node-spec.k'
spec_module = 'REFUTE-NODE-SPEC'
claim_id = 'split-int-fail'
prover, proof = self.build_prover(kprove, proof_dir, kcfg_explore, spec_file, spec_module, claim_id)
# When
prover.advance_proof(proof)
failing_node = single(proof.failing)
predecessors = proof.kcfg.predecessors(failing_node.id)
assert len(predecessors) == 1
predecessor_node = predecessors[0].source
result_predecessor = proof.refute_node(predecessor_node)
result_successor = proof.refute_node(failing_node)
# Then
assert result_predecessor is None # fails because the node has successors
assert result_successor is not None # succeeds because the node has no successors
def test_apr_proof_refute_node_no_useless_constraints(
self,
kprove: KProve,
proof_dir: Path,
kcfg_explore: KCFGExplore,
) -> None:
# Given
spec_file = K_FILES / 'refute-node-spec.k'
spec_module = 'REFUTE-NODE-SPEC'
claim_id = 'triple-split-int-fail'
prover, proof = self.build_prover(kprove, proof_dir, kcfg_explore, spec_file, spec_module, claim_id)
# When
prover.advance_proof(proof)
failing_node = single(proof.failing)
all_free_vars = {v for c in failing_node.cterm.constraints for v in free_vars(c)}
assert all_free_vars == {'L', 'M', 'N'}
refutation = proof.refute_node(failing_node)
assert refutation is not None
# Then
# last_constraint = (N <=Int 0)
# pre_constraints = [(L +Int N <=Int 0)]
# (M <=Int 0) has been removed from pre_constraints because it is independent from last_constraint
assert len(refutation.pre_constraints) == 1
refutation_free_vars = set(free_vars(refutation.pre_constraints[0]))
assert refutation_free_vars == {'L', 'N'}
def test_apr_proof_refute_node_to_claim(
self,
kprove: KProve,
proof_dir: Path,
kcfg_explore: KCFGExplore,
) -> None:
# Given
spec_file = K_FILES / 'refute-node-spec.k'
spec_module = 'REFUTE-NODE-SPEC'
claim_id = 'triple-split-int-fail'
prover, proof = self.build_prover(kprove, proof_dir, kcfg_explore, spec_file, spec_module, claim_id)
# When
prover.advance_proof(proof)
failing_node = single(proof.failing)
refutation = proof.refute_node(failing_node)
assert refutation is not None
claim, _ = refutation.to_claim('refute-node-claim')
# Then
# claim ['refute-node-claim']: <k> ( N:Int <=Int 0 => false ) </k>
# requires _L +Int N:Int <=Int 0 [label(refute-node-claim)]
expected = KClaim(
body=KRewrite(KApply('_<=Int_', KVariable('N', 'Int'), KToken('0', 'Int')), KToken('false', 'Bool')),
requires=KApply(
'_<=Int_', KApply('_+Int_', KVariable('_L', 'Int'), KVariable('N', 'Int')), KToken('0', 'Int')
),
att=KAtt(entries=[Atts.LABEL('refute-node-claim')]),
)
assert claim == expected
def test_apr_proof_refute_node_multiple_constraints(
self,
kprove: KProve,
proof_dir: Path,
kcfg_explore: KCFGExplore,
) -> None:
# Given
spec_file = K_FILES / 'refute-node-spec.k'
spec_module = 'REFUTE-NODE-SPEC'
claim_id = 'multi-constraint-split'
prover, proof = self.build_prover(kprove, proof_dir, kcfg_explore, spec_file, spec_module, claim_id)
cfg = proof.kcfg
config = cfg.node(1).cterm.config
l_gt_0 = mlEqualsTrue(gtInt(KVariable('L'), intToken(0)))
l_le_0 = mlEqualsTrue(leInt(KVariable('L'), intToken(0)))
m_gt_0 = mlEqualsTrue(gtInt(KVariable('M'), intToken(0)))
m_le_0 = mlEqualsTrue(leInt(KVariable('M'), intToken(0)))
cfg.create_node(CTerm(config, [l_gt_0, m_gt_0]))
cfg.create_node(CTerm(config, [l_gt_0, m_le_0]))
cfg.create_node(CTerm(config, [l_le_0, m_gt_0]))
cfg.create_node(CTerm(config, [l_le_0, m_le_0]))
proof.kcfg.create_split_by_nodes(1, [3, 4, 5, 6])
# When
prover.advance_proof(proof, max_iterations=4)
# After the minimization, nodes 7-10 created by the advancement of the proof
# will have multiple constraints in their immediately preceding splits
KCFGMinimizer(proof.kcfg).minimize()
# Then
for i in [7, 8, 9, 10]:
assert proof.refute_node(cfg.node(i)) is not None