mbt-gen 0.0.1
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.
- checksums.yaml +7 -0
- data/lib/mbt-gen.rb +1774 -0
- data/lib/progress.rb +39 -0
- data/lib/regexp-to-smtlib.rb +120 -0
- data/lib/solver-lib.rb +374 -0
- metadata +65 -0
data/lib/progress.rb
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
#!/usr/bin/ruby
|
|
2
|
+
|
|
3
|
+
class Progress
|
|
4
|
+
|
|
5
|
+
def initialize()
|
|
6
|
+
@last_line = ""
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
def update_last_line(last_line)
|
|
10
|
+
@last_line = last_line
|
|
11
|
+
print "\033[M#{last_line}"
|
|
12
|
+
$stdout.flush()
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def print_line(line)
|
|
16
|
+
print "\033[M#{line}\n#{@last_line}"
|
|
17
|
+
$stdout.flush()
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
class ProgressBar < Progress
|
|
22
|
+
def initialize(max)
|
|
23
|
+
@max = max.to_f
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def render_bar(current, length)
|
|
27
|
+
bars = (current.to_f * length.to_f).floor
|
|
28
|
+
"=" * bars + ">" + " " * (length - bars)
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def report_progress(progress)
|
|
32
|
+
if progress then
|
|
33
|
+
current = (progress.to_f / @max)
|
|
34
|
+
update_last_line("[#{render_bar(current, 40)}] combinations covered: #{progress}/#{@max} (#{format("%.8f", current * 100.0)}%)")
|
|
35
|
+
else
|
|
36
|
+
update_last_line("[... ? ... ? ... ? ...] combinations covered: ?/∞ (?%)")
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
#!/usr/bin/ruby
|
|
2
|
+
|
|
3
|
+
class Regexp_to_SMTLIB
|
|
4
|
+
|
|
5
|
+
def self.translate_regexp_to_SMTLIB(regex_str)
|
|
6
|
+
if regex_str =~ /^\^(.*)\$$/ then
|
|
7
|
+
return translate_anchored_regexp_to_SMTLIB($1)
|
|
8
|
+
elsif regex_str =~ /^\^(.*)$/ then
|
|
9
|
+
return "(re.++ #{translate_anchored_regexp_to_SMTLIB($1)} (re.* re.allchar))"
|
|
10
|
+
elsif regex_str =~ /^(.*)\$$/ then
|
|
11
|
+
return "(re.++ (re.* re.allchar) #{translate_anchored_regexp_to_SMTLIB($1)})"
|
|
12
|
+
else
|
|
13
|
+
return "(re.++ (re.* re.allchar) #{translate_anchored_regexp_to_SMTLIB($1)} (re.* re.allchar))"
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def self.translate_anchored_regexp_to_SMTLIB(regex_str)
|
|
18
|
+
begin
|
|
19
|
+
rest = regex_str
|
|
20
|
+
res = []
|
|
21
|
+
found = true
|
|
22
|
+
endindex = nil
|
|
23
|
+
while found do
|
|
24
|
+
found = false
|
|
25
|
+
if rest =~ /^\[([^\]]*)\](\*|\+|\?|\{(\d+),(\d+)\})?/ then
|
|
26
|
+
inner = $1
|
|
27
|
+
multiplicator = $2
|
|
28
|
+
endindex = $~.end(0)
|
|
29
|
+
range = translate_regexp_range_to_SMTLIB(inner)
|
|
30
|
+
if multiplicator then
|
|
31
|
+
res << translate_multiplicator_for_re_to_SMTLIB(multiplicator, range)
|
|
32
|
+
found = true
|
|
33
|
+
else
|
|
34
|
+
res << range
|
|
35
|
+
found = true
|
|
36
|
+
end
|
|
37
|
+
elsif rest =~ /^([^\[]+)(\*|\+|\?|\{(\d+),(\d+)\})?/
|
|
38
|
+
string = $1
|
|
39
|
+
multiplicator = $2
|
|
40
|
+
endindex = $~.end(0)
|
|
41
|
+
|
|
42
|
+
inner = "(str.to.re #{string.inspect})"
|
|
43
|
+
if multiplicator then
|
|
44
|
+
res << translate_multiplicator_for_re_to_SMTLIB(multiplicator, inner)
|
|
45
|
+
found = true
|
|
46
|
+
else
|
|
47
|
+
res << inner
|
|
48
|
+
found = true
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
rest = rest.slice(endindex, rest.length) if rest
|
|
52
|
+
end
|
|
53
|
+
if res.size > 1 then
|
|
54
|
+
return "(re.++ #{res.join(" ")})"
|
|
55
|
+
else
|
|
56
|
+
if res.empty? then
|
|
57
|
+
return "re.nostr"
|
|
58
|
+
else
|
|
59
|
+
return res.first
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
rescue RuntimeError => rte
|
|
63
|
+
raise RuntimeError.new("cannot parse regex: #{regex_str.inspect}: #{rte.message}")
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
# TODO
|
|
67
|
+
# - . => re.allchar
|
|
68
|
+
# * abc => re.++
|
|
69
|
+
# - string => str.to.re
|
|
70
|
+
# * [A-Z] => re.range
|
|
71
|
+
# * [abc] => re.union
|
|
72
|
+
# * * => re.*
|
|
73
|
+
# * + => re.+
|
|
74
|
+
# * ? => re.opt
|
|
75
|
+
# * {a,b} => re.loop
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def self.translate_multiplicator_for_re_to_SMTLIB(multiplicator, expr)
|
|
79
|
+
if multiplicator == "*" then
|
|
80
|
+
return "(re.* #{expr})"
|
|
81
|
+
elsif multiplicator == "+" then
|
|
82
|
+
return "(re.+ #{expr})"
|
|
83
|
+
elsif multiplicator == "?" then
|
|
84
|
+
return "(re.opt #{expr})"
|
|
85
|
+
elsif multiplicator =~ /\{(\d+),(\d+)\}/ then
|
|
86
|
+
begin
|
|
87
|
+
lower = Integer($1)
|
|
88
|
+
upper = Integer($2)
|
|
89
|
+
rescue TypeError => e
|
|
90
|
+
raise RuntimeError.new("cannot parse regexp (#{$1} or #{$2} is no Integer)")
|
|
91
|
+
end
|
|
92
|
+
return "((_ re.loop #{lower} #{upper}) #{expr})"
|
|
93
|
+
else
|
|
94
|
+
raise RuntimeError.new("invalid multiplicator: #{multiplicator}")
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
# TODO: range complement with ^
|
|
99
|
+
def self.translate_regexp_range_to_SMTLIB(range)
|
|
100
|
+
rest = range
|
|
101
|
+
res = []
|
|
102
|
+
while rest =~ /^(.)\-(.)/ do
|
|
103
|
+
ch1 = $1
|
|
104
|
+
ch2 = $2
|
|
105
|
+
res << "(re.range \"#{ch1}\" \"#{ch2}\")"
|
|
106
|
+
rest = rest.slice($~.end(0), rest.length)
|
|
107
|
+
end
|
|
108
|
+
if res.size > 1 then
|
|
109
|
+
return "(re.union #{res.join(" ")})"
|
|
110
|
+
else
|
|
111
|
+
if res.empty? then
|
|
112
|
+
return "re.nostr"
|
|
113
|
+
else
|
|
114
|
+
return res.first
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
end
|
data/lib/solver-lib.rb
ADDED
|
@@ -0,0 +1,374 @@
|
|
|
1
|
+
require 'open3'
|
|
2
|
+
|
|
3
|
+
FTYPE_MANDATORY = 0
|
|
4
|
+
FTYPE_OPTIONAL = 1
|
|
5
|
+
|
|
6
|
+
SUBF_AND = 0
|
|
7
|
+
SUBF_OR = 1
|
|
8
|
+
SUBF_XOR = 2
|
|
9
|
+
|
|
10
|
+
LOG_DIR = 'logs'
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class SolverSession
|
|
15
|
+
|
|
16
|
+
def initialize(z3in, z3outAndErr, logFileName)
|
|
17
|
+
@z3in = z3in
|
|
18
|
+
@z3outAndErr = z3outAndErr
|
|
19
|
+
@protocol_filename = logFileName
|
|
20
|
+
@protocol = File.open(@protocol_filename, "w")
|
|
21
|
+
@assertCounter = -1
|
|
22
|
+
@associations = {}
|
|
23
|
+
@consts = {}
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def emptyReadBuffer(progress)
|
|
27
|
+
empty = false
|
|
28
|
+
while !empty do
|
|
29
|
+
begin
|
|
30
|
+
c = ""
|
|
31
|
+
@z3outAndErr.read_nonblock(1, c)
|
|
32
|
+
line = c + @z3outAndErr.gets
|
|
33
|
+
checkLineForSolverError(line, progress)
|
|
34
|
+
@protocol.puts("; Solver Response: #{line}")
|
|
35
|
+
rescue Errno::EWOULDBLOCK
|
|
36
|
+
# there is nothing to read -> just continue
|
|
37
|
+
empty = true
|
|
38
|
+
rescue Errno::EAGAIN
|
|
39
|
+
# there is nothing to read -> just continue
|
|
40
|
+
empty = true
|
|
41
|
+
rescue EOFError
|
|
42
|
+
# there is nothing to read -> just continue
|
|
43
|
+
empty = true
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def declare_const(progress, name, type, info)
|
|
49
|
+
info[:type] = type
|
|
50
|
+
@consts[name] = info
|
|
51
|
+
to_solver(progress, "(declare-const #{name} #{type})")
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def to_solver(progress, line)
|
|
55
|
+
emptyReadBuffer(progress);
|
|
56
|
+
@protocol.puts(line)
|
|
57
|
+
begin
|
|
58
|
+
@z3in.puts(line)
|
|
59
|
+
rescue Errno::EINVAL
|
|
60
|
+
progress.print_line "ERROR: could not write the line #{line.inspect} to the solver."
|
|
61
|
+
writeProtocolToFile("failure.smt2")
|
|
62
|
+
rescue Errno::EPIPE
|
|
63
|
+
writeProtocolToFile("failure.smt2")
|
|
64
|
+
throw RuntimeError.new("Broken Pipe")
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def checkLineForSolverError(line, progress)
|
|
69
|
+
if line =~ /\(error \"(.*)\"\)/ then
|
|
70
|
+
progress.print_line("WARN: Solver reported ERROR: #{$1}")
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def from_solver(progress)
|
|
76
|
+
result = @z3outAndErr.gets
|
|
77
|
+
checkLineForSolverError(result, progress)
|
|
78
|
+
@protocol.puts("; Solver Response: #{result}")
|
|
79
|
+
return result
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def replace_strings(line)
|
|
83
|
+
line.gsub(/"([^"\\]|\\.)*"/, "")
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def read_multiline_from_solver()
|
|
87
|
+
result = ""
|
|
88
|
+
for_counting = ""
|
|
89
|
+
@protocol.puts
|
|
90
|
+
@protocol.puts("; BEGIN Multiline Solver Response:")
|
|
91
|
+
line = @z3outAndErr.gets
|
|
92
|
+
result += line
|
|
93
|
+
for_counting += replace_strings(line)
|
|
94
|
+
@protocol.puts("; #{line}")
|
|
95
|
+
|
|
96
|
+
num_opening = for_counting.count("(")
|
|
97
|
+
num_closing = for_counting.count(")")
|
|
98
|
+
while num_opening > num_closing do
|
|
99
|
+
line = @z3outAndErr.gets
|
|
100
|
+
result += line
|
|
101
|
+
for_counting += replace_strings(line)
|
|
102
|
+
@protocol.puts("; #{line}")
|
|
103
|
+
|
|
104
|
+
num_opening = for_counting.count("(")
|
|
105
|
+
num_closing = for_counting.count(")")
|
|
106
|
+
end
|
|
107
|
+
@protocol.puts("; END Multiline Solver Response")
|
|
108
|
+
return result
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
def writeProtocolToFile(progress, filename)
|
|
112
|
+
dir = File.dirname(filename)
|
|
113
|
+
FileUtils.mkdir_p(dir) unless Dir.exist?(dir)
|
|
114
|
+
FileUtils.cp(@protocol_filename, filename) unless @protocol_filename == filename
|
|
115
|
+
progress.print_line "Solver protocol written to file #{filename}"
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
def generateNextAssertionIDAndAssociateWith(hash)
|
|
119
|
+
nextAssertionID = "a#{@assertCounter += 1}"
|
|
120
|
+
@associations[nextAssertionID] = hash
|
|
121
|
+
return nextAssertionID
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
def getAssociationForAssertionID(assertionID)
|
|
125
|
+
return @associations[assertionID]
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
def for_each_const(&block)
|
|
129
|
+
@consts.each do |key, value|
|
|
130
|
+
block.call(key, value)
|
|
131
|
+
end
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
def get_const_info(name)
|
|
135
|
+
@consts[name]
|
|
136
|
+
end
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
class Z3Solver
|
|
140
|
+
|
|
141
|
+
def initialize(progress, z3path)
|
|
142
|
+
z3version = `#{z3path} --version`.chomp()
|
|
143
|
+
if $?.exitstatus == 0 then
|
|
144
|
+
progress.print_line "INFO: Using #{z3version} found in z3path=#{z3path}"
|
|
145
|
+
@z3in, @z3outAndErr, @z3thr = Open3.popen2e(z3path, '-in', '-smt2')
|
|
146
|
+
Process.detach(@z3thr.pid)
|
|
147
|
+
else
|
|
148
|
+
progress.print_line "WARN: could not find z3 in z3path=#{z3path.inspect}"
|
|
149
|
+
`which z3`
|
|
150
|
+
if $?.exitstatus == 0 then
|
|
151
|
+
z3version = `z3 --version`.chomp()
|
|
152
|
+
progress.print_line "However, #{z3version} is availlable on the PATH. Using this one."
|
|
153
|
+
@z3in, @z3outAndErr, @z3thr = Open3.popen2e('z3', '-in', '-smt2')
|
|
154
|
+
Process.detach(@z3thr.pid)
|
|
155
|
+
else
|
|
156
|
+
raise RuntimeError.new("ERROR: unable to find Z3. Exiting! (Please provide its location in the config file using the key :z3path)")
|
|
157
|
+
end
|
|
158
|
+
end
|
|
159
|
+
@z3in.sync = true
|
|
160
|
+
|
|
161
|
+
@sessionIdCounter = 0
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
def close()
|
|
165
|
+
@z3in.close()
|
|
166
|
+
@z3outAndErr.close()
|
|
167
|
+
Thread.kill(@z3thr)
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
def query(&block)
|
|
171
|
+
solver = SolverSession.new(@z3in, @z3outAndErr)
|
|
172
|
+
|
|
173
|
+
block.call(solver)
|
|
174
|
+
|
|
175
|
+
solver.toSolver "(check-sat)"
|
|
176
|
+
result = solver.fromSolver()
|
|
177
|
+
if result.chomp == "sat"
|
|
178
|
+
# model is not contradictory -> continue
|
|
179
|
+
elsif result.chomp == "unsat"
|
|
180
|
+
# model is contradictory -> add a message
|
|
181
|
+
|
|
182
|
+
solver.toSolver("(get-unsat-core)")
|
|
183
|
+
unsat_core = solver.fromSolver()
|
|
184
|
+
explanation = build_explanation_based_on_unsat_core(solver, sid, unsat_core)
|
|
185
|
+
messages << {:type => :issue, :text => "Model is contradictory", :explanation => explanation}
|
|
186
|
+
solver.writeProtocolToFile("contradictions.smt2")
|
|
187
|
+
else
|
|
188
|
+
handleSolverError(solver, result)
|
|
189
|
+
end
|
|
190
|
+
|
|
191
|
+
messages = []
|
|
192
|
+
solver.toSolver "(reset)"
|
|
193
|
+
solver.toSolver "(set-logic ALL)"
|
|
194
|
+
solver.toSolver "(set-option :produce-unsat-cores true)"
|
|
195
|
+
return messages
|
|
196
|
+
end
|
|
197
|
+
|
|
198
|
+
# main entry point!
|
|
199
|
+
def query_model(progress, options, &block)
|
|
200
|
+
unless options[:solverLog]
|
|
201
|
+
raise RuntimeError.new("No solverLog specified, but the option :solverLog is required!")
|
|
202
|
+
end
|
|
203
|
+
progress.print_line("calling solver (solver-log is written to #{options[:solverLog]})")
|
|
204
|
+
solver = SolverSession.new(@z3in, @z3outAndErr, options[:solverLog])
|
|
205
|
+
|
|
206
|
+
block.call(solver)
|
|
207
|
+
|
|
208
|
+
solver.to_solver(progress, "(check-sat)")
|
|
209
|
+
result = solver.from_solver(progress)
|
|
210
|
+
if result.chomp == "sat"
|
|
211
|
+
# model is not contradictory -> extract model
|
|
212
|
+
const_list = []
|
|
213
|
+
const_info = {}
|
|
214
|
+
solver.for_each_const do |name, info|
|
|
215
|
+
const_list << name
|
|
216
|
+
const_info[name] = info
|
|
217
|
+
end
|
|
218
|
+
solver.to_solver(progress, "(get-value (#{const_list.join(" ")}))")
|
|
219
|
+
model_str = solver.read_multiline_from_solver()
|
|
220
|
+
model = {}
|
|
221
|
+
foreach_field_in_model_str(model_str, solver, const_info) do |varname, value, info|
|
|
222
|
+
unless info then
|
|
223
|
+
progress.print_line "WARN: no info availlable for varname=#{varname.inspect}, value=#{value.inspect}"
|
|
224
|
+
end
|
|
225
|
+
xpath = info[:xpath]
|
|
226
|
+
if model.has_key?(xpath) then
|
|
227
|
+
if varname.end_with?("-filled") then
|
|
228
|
+
unless model[xpath][:type] == :field then
|
|
229
|
+
raise RuntimeError.new("inconsistent model: xpath #{xpath} should be a field, not a #{model[xpath][:type]}.")
|
|
230
|
+
end
|
|
231
|
+
unless value == "true" or value == "false" then
|
|
232
|
+
raise RuntimeError.new("inconsistent model value filled-value of #{xpath} should be a Bool, but was #{value.inspect}.")
|
|
233
|
+
end
|
|
234
|
+
if value == "true" then
|
|
235
|
+
model[xpath][:filled] = true
|
|
236
|
+
else
|
|
237
|
+
model[xpath][:filled] = false
|
|
238
|
+
end
|
|
239
|
+
elsif varname.end_with?("-value") then
|
|
240
|
+
unless model[xpath][:type] == :field || model[xpath][:type] == :list then
|
|
241
|
+
raise RuntimeError.new("inconsistent model: xpath #{xpath} should be a field or a list, not a #{model[xpath][:type]}.")
|
|
242
|
+
end
|
|
243
|
+
if model[xpath][:type] == :field then
|
|
244
|
+
model[xpath][:value] = convert_solver_value(info[:type], info[:datatype], value, xpath)
|
|
245
|
+
elsif model[xpath][:type] == :list then
|
|
246
|
+
unless varname =~ /-index-(\d+)-value$/
|
|
247
|
+
raise RuntimeError.new("inconsistent model: xpath #{xpath} is a list value and hence its varname should end like -index-X-value, not #{varname.inspect}.")
|
|
248
|
+
end
|
|
249
|
+
index = Integer($1)
|
|
250
|
+
model[xpath][:xpath_element] = info[:xpath_element]
|
|
251
|
+
if model[xpath].has_key?(:value) then
|
|
252
|
+
val = convert_solver_value(info[:type], info[:datatype], value, xpath)
|
|
253
|
+
model[xpath][:value].insert(index, val)
|
|
254
|
+
else
|
|
255
|
+
val = convert_solver_value(info[:type], info[:datatype], value, xpath)
|
|
256
|
+
model[xpath][:value] = [val]
|
|
257
|
+
end
|
|
258
|
+
end
|
|
259
|
+
elsif varname.end_with?("-size") then
|
|
260
|
+
unless model[xpath][:type] == :list then
|
|
261
|
+
raise RuntimeError.new("inconsistent model: xpath #{xpath} should be a list, not a #{model[xpath][:type]}.")
|
|
262
|
+
end
|
|
263
|
+
model[xpath][:value] = value
|
|
264
|
+
elsif varname.start_with?("struct-") && varname.end_with?("-exists") then
|
|
265
|
+
unless model[xpath][:type] == :struct then
|
|
266
|
+
raise RuntimeError.new("inconsistent model: xpath #{xpath} should be a struct, not a #{model[xpath][:type]}.")
|
|
267
|
+
end
|
|
268
|
+
model[xpath][:exists] = value
|
|
269
|
+
end
|
|
270
|
+
else
|
|
271
|
+
if varname.end_with?("-filled") then
|
|
272
|
+
unless value == "true" or value == "false" then
|
|
273
|
+
raise RuntimeError.new("inconsistent model value filled-value of #{xpath} should be a Bool, but was #{value.inspect}.")
|
|
274
|
+
end
|
|
275
|
+
if value == "true" then
|
|
276
|
+
model[xpath] = { :type => :field, :filled => true }
|
|
277
|
+
else
|
|
278
|
+
model[xpath] = { :type => :field, :filled => false }
|
|
279
|
+
end
|
|
280
|
+
elsif varname.end_with?("-value") then
|
|
281
|
+
check_type(info[:type], value, xpath)
|
|
282
|
+
model[xpath] = { :type => :field, :value => value }
|
|
283
|
+
elsif varname.start_with?("struct-") && varname.end_with?("-exists") then
|
|
284
|
+
unless value == "true" or value == "false" then
|
|
285
|
+
raise RuntimeError.new("inconsistent model value exists-value of struct #{xpath} should be a Bool, but was #{value.inspect}.")
|
|
286
|
+
end
|
|
287
|
+
model[xpath] = { :type => :struct, :exists => value }
|
|
288
|
+
elsif varname.end_with?("-size") then
|
|
289
|
+
model[xpath] = { :type => :list, :size => value }
|
|
290
|
+
end
|
|
291
|
+
end
|
|
292
|
+
end
|
|
293
|
+
return model
|
|
294
|
+
elsif result.chomp == "unsat"
|
|
295
|
+
# model is contradictory -> add a message
|
|
296
|
+
|
|
297
|
+
solver.to_solver(progress, "(get-unsat-core)")
|
|
298
|
+
unsat_core = solver.from_solver(progress)
|
|
299
|
+
solver.writeProtocolToFile(progress, options[:solverLog])
|
|
300
|
+
progress.print_line "Solver reported a contradiction! please see #{options[:solverLog]} for further information."
|
|
301
|
+
return nil
|
|
302
|
+
else
|
|
303
|
+
handleSolverError(progress, solver, options, result)
|
|
304
|
+
end
|
|
305
|
+
|
|
306
|
+
messages = []
|
|
307
|
+
solver.to_solver "(reset)"
|
|
308
|
+
solver.to_solver "(set-logic ALL)"
|
|
309
|
+
solver.to_solver "(set-option :produce-unsat-cores true)"
|
|
310
|
+
|
|
311
|
+
return messages
|
|
312
|
+
end
|
|
313
|
+
|
|
314
|
+
def check_type(type, value, xpath)
|
|
315
|
+
if type == "String" then
|
|
316
|
+
unless value =~ /\".*\"/ then
|
|
317
|
+
raise RuntimeError.new("inconsistent model value: model value for #{xpath} should be a String, but was #{value}.")
|
|
318
|
+
end
|
|
319
|
+
elsif type == "Bool"
|
|
320
|
+
unless value == "true" or value == "false" then
|
|
321
|
+
raise RuntimeError.new("inconsistent model value for #{xpath} should be a Bool, but was #{value.inspect}.")
|
|
322
|
+
end
|
|
323
|
+
elsif type == "Int"
|
|
324
|
+
unless value =~ /\d+/ then
|
|
325
|
+
raise RuntimeError.new("inconsistent model value: model value for #{xpath} should be a Int, but was #{value}.")
|
|
326
|
+
end
|
|
327
|
+
else
|
|
328
|
+
raise RuntimeError.new("encountered unknown solver type: type of solver-var for #{xpath} is #{info[:type]}.")
|
|
329
|
+
end
|
|
330
|
+
end
|
|
331
|
+
|
|
332
|
+
def convert_solver_value(type, datatype, value, xpath)
|
|
333
|
+
check_type(type, value, xpath)
|
|
334
|
+
if datatype == :date then
|
|
335
|
+
return (Time.at(0).to_date + Integer(value)).to_s
|
|
336
|
+
elsif datatype == :timestamp then
|
|
337
|
+
return Time.at(Integer(value)).to_s
|
|
338
|
+
else
|
|
339
|
+
return value
|
|
340
|
+
end
|
|
341
|
+
end
|
|
342
|
+
|
|
343
|
+
def foreach_field_in_model_str(str, solver, const_info, &block)
|
|
344
|
+
unless str =~ /^\((.*)\)$/m
|
|
345
|
+
raise RuntimeError.new("could not parse model: #{str.inspect}")
|
|
346
|
+
end
|
|
347
|
+
str = $1
|
|
348
|
+
|
|
349
|
+
str.split("\n").each do |line|
|
|
350
|
+
unless line =~ /\(([^\ ]*)\ (.*)\)/
|
|
351
|
+
raise RuntimeError.new("could not parse model line: #{line.inspect}")
|
|
352
|
+
end
|
|
353
|
+
varname = $1
|
|
354
|
+
value = $2
|
|
355
|
+
if value =~ /\(-\ (\d+)\)/ then
|
|
356
|
+
simplified = "-#{$1}"
|
|
357
|
+
value = simplified
|
|
358
|
+
end
|
|
359
|
+
info = const_info[varname]
|
|
360
|
+
|
|
361
|
+
block.call(varname, value, info)
|
|
362
|
+
end
|
|
363
|
+
|
|
364
|
+
return nil
|
|
365
|
+
end
|
|
366
|
+
|
|
367
|
+
def handleSolverError(progress, solver, options, result)
|
|
368
|
+
solver.writeProtocolToFile(progress, options[:solverLog])
|
|
369
|
+
raise RuntimeError.new("Solver Error: Solver returned '#{result}', for more information see #{options[:solverLog]}")
|
|
370
|
+
end
|
|
371
|
+
|
|
372
|
+
end
|
|
373
|
+
|
|
374
|
+
|
metadata
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: mbt-gen
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.0.1
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- FM-enthusiast
|
|
8
|
+
autorequire:
|
|
9
|
+
bindir: bin
|
|
10
|
+
cert_chain: []
|
|
11
|
+
date: 2026-04-23 00:00:00.000000000 Z
|
|
12
|
+
dependencies:
|
|
13
|
+
- !ruby/object:Gem::Dependency
|
|
14
|
+
name: nokogiri
|
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
|
16
|
+
requirements:
|
|
17
|
+
- - "~>"
|
|
18
|
+
- !ruby/object:Gem::Version
|
|
19
|
+
version: '1.18'
|
|
20
|
+
type: :runtime
|
|
21
|
+
prerelease: false
|
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
23
|
+
requirements:
|
|
24
|
+
- - "~>"
|
|
25
|
+
- !ruby/object:Gem::Version
|
|
26
|
+
version: '1.18'
|
|
27
|
+
description: |
|
|
28
|
+
DSL for modelling XML structure along with logic-based validation rules / constraints.
|
|
29
|
+
Supports SMT-solver based generation of sample valid XML documents.
|
|
30
|
+
email: nf4eai9s@anonaddy.com
|
|
31
|
+
executables: []
|
|
32
|
+
extensions: []
|
|
33
|
+
extra_rdoc_files: []
|
|
34
|
+
files:
|
|
35
|
+
- lib/mbt-gen.rb
|
|
36
|
+
- lib/progress.rb
|
|
37
|
+
- lib/regexp-to-smtlib.rb
|
|
38
|
+
- lib/solver-lib.rb
|
|
39
|
+
homepage: https://github.com/FM-enthusiast/MBT-gen
|
|
40
|
+
licenses:
|
|
41
|
+
- GPL-3.0-only
|
|
42
|
+
metadata:
|
|
43
|
+
bug_tracker_uri: https://github.com/FM-enthusiast/MBT-gen/issues
|
|
44
|
+
source_code_uri: https://github.com/FM-enthusiast/MBT-gen
|
|
45
|
+
post_install_message:
|
|
46
|
+
rdoc_options: []
|
|
47
|
+
require_paths:
|
|
48
|
+
- lib
|
|
49
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
50
|
+
requirements:
|
|
51
|
+
- - ">="
|
|
52
|
+
- !ruby/object:Gem::Version
|
|
53
|
+
version: 3.0.0
|
|
54
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
55
|
+
requirements:
|
|
56
|
+
- - ">="
|
|
57
|
+
- !ruby/object:Gem::Version
|
|
58
|
+
version: '0'
|
|
59
|
+
requirements:
|
|
60
|
+
- an SMT-solver. Z3 is recommended, it can be obtained from https://github.com/Z3Prover/z3
|
|
61
|
+
rubygems_version: 3.5.22
|
|
62
|
+
signing_key:
|
|
63
|
+
specification_version: 4
|
|
64
|
+
summary: test data generator for Model-based testing
|
|
65
|
+
test_files: []
|