mbt-gen 0.0.2 → 0.0.3
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 +4 -4
- data/lib/mbt-gen.rb +332 -49
- data/lib/solver-lib.rb +85 -18
- metadata +3 -6
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 056ec1e0d6d661c5c4a092e4a7e879f610a7524dfa63fb7faf9779d8fc4bfc6e
|
|
4
|
+
data.tar.gz: 20f715037f76a5e23dd5407416018ff7c2cbf3c64a616c6fe32117bc0b411a20
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 7cfca600b07970cc752fa96da6e9dc1052af81024e94c87f581b49632cb414e13d6e71c05dfbb7dc51eab907e3f0ef504ac880994b59cd83347a038c8709fbd5
|
|
7
|
+
data.tar.gz: 2ba5fa686c5a8894431e714481953ea2c4989de0825e14c526e273ba10e798250003492195b5163a69ebfe53e1cba7d9b243d9f9c5ae82806484b3026a4e793a
|
data/lib/mbt-gen.rb
CHANGED
|
@@ -73,6 +73,11 @@ GENERATING MESSAGES FOR ALL COMBINATIONS:
|
|
|
73
73
|
be restarted later by adding -continue to the command line.
|
|
74
74
|
NOTE: do not expect the number of generated documents to equal the number of combinations as combinations may not have any valid message instances (for instance when they contradict the validation rules).
|
|
75
75
|
NOTE: the result can be found in the directory #{OUTPUT_DIR}
|
|
76
|
+
|
|
77
|
+
VALIDATING THE MODEL:
|
|
78
|
+
|
|
79
|
+
ruby #{$PROGRAM_NAME} -validate <file.xml>
|
|
80
|
+
- reads in the given XML file and validates it against the model - that is, it checks whether it conforms to the specified XML schema and to the validation rules.
|
|
76
81
|
EOF
|
|
77
82
|
|
|
78
83
|
class FatalError < RuntimeError
|
|
@@ -167,38 +172,100 @@ def enum_constraint_for_expr(expr, values)
|
|
|
167
172
|
"(or #{values.map{|x| "(= #{expr} #{translate_value_to_SMTLIB(x)})"}.join(" ")})"
|
|
168
173
|
end
|
|
169
174
|
|
|
170
|
-
def translate_field_def_to_SMTLIB_constraints(progress, solver, fielddef,
|
|
175
|
+
def translate_field_def_to_SMTLIB_constraints(progress, solver, fielddef, xpath)
|
|
176
|
+
varname = raw_field(xpath)
|
|
171
177
|
if fielddef[:optional] == nil || fielddef[:optional] == false then
|
|
172
178
|
# field is required
|
|
173
|
-
|
|
179
|
+
assertionID = "required-#{varname}"
|
|
180
|
+
info = {
|
|
181
|
+
:assertion_type => :constraint,
|
|
182
|
+
:xpath => xpath,
|
|
183
|
+
:constraint_type => :required
|
|
184
|
+
}
|
|
185
|
+
solver.associateAssertionIDWith(assertionID, info)
|
|
186
|
+
solver.to_solver(progress, "(assert (! #{varname}-filled :named #{assertionID}))")
|
|
174
187
|
end
|
|
175
188
|
datatype = fielddef[:datatype]
|
|
176
189
|
case datatype
|
|
177
190
|
when :int
|
|
178
191
|
if fielddef[:min] then
|
|
179
|
-
|
|
192
|
+
assertionID = "min-#{varname}"
|
|
193
|
+
info = {
|
|
194
|
+
:assertion_type => :constraint,
|
|
195
|
+
:xpath => xpath,
|
|
196
|
+
:datatype => :int,
|
|
197
|
+
:value => fielddef[:min],
|
|
198
|
+
:constraint_type => :min
|
|
199
|
+
}
|
|
200
|
+
solver.associateAssertionIDWith(assertionID, info)
|
|
201
|
+
solver.to_solver(progress, "(assert (! (=> #{varname}-filled #{min_constraint_for_expr("#{varname}-value", translate_value_to_SMTLIB(fielddef[:min]))}) :named #{assertionID}))")
|
|
180
202
|
end
|
|
181
203
|
if fielddef[:max] then
|
|
182
|
-
|
|
204
|
+
assertionID = "max-#{varname}"
|
|
205
|
+
info = {
|
|
206
|
+
:assertion_type => :constraint,
|
|
207
|
+
:xpath => xpath,
|
|
208
|
+
:datatype => :int,
|
|
209
|
+
:value => fielddef[:max],
|
|
210
|
+
:constraint_type => :max
|
|
211
|
+
}
|
|
212
|
+
solver.associateAssertionIDWith(assertionID, info)
|
|
213
|
+
solver.to_solver(progress, "(assert (! (=> #{varname}-filled #{max_constraint_for_expr("#{varname}-value", translate_value_to_SMTLIB(fielddef[:max]))}) :named #{assertionID}))")
|
|
183
214
|
end
|
|
184
215
|
when :string
|
|
185
216
|
if fielddef[:regex] then
|
|
186
|
-
|
|
217
|
+
assertionID = "regex-#{varname}"
|
|
218
|
+
info = {
|
|
219
|
+
:assertion_type => :constraint,
|
|
220
|
+
:xpath => xpath,
|
|
221
|
+
:datatype => :string,
|
|
222
|
+
:pattern => fielddef[:regex],
|
|
223
|
+
:constraint_type => :regex
|
|
224
|
+
}
|
|
225
|
+
solver.associateAssertionIDWith(assertionID, info)
|
|
226
|
+
solver.to_solver(progress, "(assert (! (=> #{varname}-filled #{regex_constraint_for_expr("#{varname}-value", fielddef[:regex])}) :named #{assertionID}))")
|
|
187
227
|
end
|
|
188
228
|
# TODO: minLength?
|
|
189
229
|
if fielddef[:maxLength] then
|
|
190
|
-
|
|
230
|
+
assertionID = "maxLength-#{varname}"
|
|
231
|
+
info = {
|
|
232
|
+
:assertion_type => :constraint,
|
|
233
|
+
:xpath => xpath,
|
|
234
|
+
:datatype => :string,
|
|
235
|
+
:value => fielddef[:maxLength],
|
|
236
|
+
:constraint_type => :maxLength
|
|
237
|
+
}
|
|
238
|
+
solver.associateAssertionIDWith(assertionID, info)
|
|
239
|
+
solver.to_solver(progress, "(assert (! (=> #{varname}-filled #{maxLength_contraint_for_expr("#{varname}-value", fielddef[:maxLength])}) :named #{assertionID}))")
|
|
191
240
|
end
|
|
192
241
|
if fielddef[:from_list] then
|
|
242
|
+
assertionID = "oneOf-#{varname}"
|
|
193
243
|
list_filename = "#{LISTS_DIR}/#{fielddef[:from_list]}"
|
|
244
|
+
info = {
|
|
245
|
+
:assertion_type => :constraint,
|
|
246
|
+
:xpath => xpath,
|
|
247
|
+
:datatype => :string,
|
|
248
|
+
:filename => list_filename,
|
|
249
|
+
:constraint_type => :from_list
|
|
250
|
+
}
|
|
194
251
|
unless File.exist?(list_filename)
|
|
195
252
|
raise RuntimeError.new("could not find list file #{list_filename}.")
|
|
196
253
|
end
|
|
197
254
|
values = File.read(list_filename).split("\n").map{|v| v.chomp()}
|
|
198
|
-
solver.
|
|
255
|
+
solver.associateAssertionIDWith(assertionID, info)
|
|
256
|
+
solver.to_solver(progress, "(assert (! (=> #{varname}-filled #{oneOf_contraint_for_expr("#{varname}-value", values)}) :named #{assertionID}))")
|
|
199
257
|
end
|
|
200
258
|
when :enum
|
|
201
|
-
|
|
259
|
+
assertionID = "enum-#{varname}"
|
|
260
|
+
info = {
|
|
261
|
+
:assertion_type => :constraint,
|
|
262
|
+
:xpath => xpath,
|
|
263
|
+
:datatype => :enum,
|
|
264
|
+
:values => fielddef[:values],
|
|
265
|
+
:constraint_type => :values
|
|
266
|
+
}
|
|
267
|
+
solver.associateAssertionIDWith(assertionID, info)
|
|
268
|
+
solver.to_solver(progress, "(assert (! (=> #{varname}-filled #{enum_constraint_for_expr("#{varname}-value", fielddef[:values])}) :named #{assertionID}))")
|
|
202
269
|
end
|
|
203
270
|
end
|
|
204
271
|
|
|
@@ -290,13 +357,15 @@ def translate_substructure_to_SMTLIB(progress, solver, struct, hash = {})
|
|
|
290
357
|
if (struct_varname)
|
|
291
358
|
solver.to_solver(progress, "(assert (! (=> (not #{struct_varname}) (not #{filled_var_for_field(xpath)})) :named empty-struct-has-no-fields-for-#{raw_field(xpath)}))")
|
|
292
359
|
end
|
|
293
|
-
translate_field_def_to_SMTLIB_constraints(progress, solver, definition,
|
|
360
|
+
translate_field_def_to_SMTLIB_constraints(progress, solver, definition, xpath)
|
|
294
361
|
elsif definition[:type] == :list then
|
|
295
362
|
display_prefix = ""
|
|
296
363
|
display_prefix = "#{prefix}/" if prefix
|
|
297
364
|
xpath = field.to_s
|
|
298
365
|
xpath = "#{prefix}/#{xpath}" if prefix
|
|
366
|
+
solver.declare_const(progress, filled_var_for_field(xpath), "Bool", {:xpath => xpath})
|
|
299
367
|
solver.declare_const(progress, size_var_for_list(xpath), "Int", {:xpath => xpath})
|
|
368
|
+
solver.to_solver(progress, "(assert (! (=> (not #{filled_var_for_field(xpath)}) (= #{size_var_for_list(xpath)} 0)) :named empty-list-field-has-zero-size-#{raw_field(xpath)}))")
|
|
300
369
|
definition[:model_maxLength].times do |i|
|
|
301
370
|
solver.declare_const(progress, value_var_for_list(xpath, i), translate_datatype_to_SMTLIB(definition[:datatype]), {:xpath => xpath, :xpath_element => definition[:xpath_element], :index => i, :datatype => definition[:datatype]})
|
|
302
371
|
solver.to_solver(progress, "(assert (! (=> (<= #{size_var_for_list(xpath)} #{i}) (= #{value_var_for_list(xpath, i)} #{default_value_for_fielddef(definition)})) :named empty-list-field-has-no-value-for-#{raw_field(xpath)}-index-#{i}))")
|
|
@@ -678,13 +747,13 @@ end
|
|
|
678
747
|
def add_model_struct_to_doc(progress, struct, doc, model, prefix = nil)
|
|
679
748
|
struct.each do |key, value|
|
|
680
749
|
next if key == :validation_rules || key == :predicates || key == :parent_link || key == :struct_xpath_prefix || key == :additional_smtlib
|
|
750
|
+
if prefix then
|
|
751
|
+
xpath = "#{prefix}/#{key}"
|
|
752
|
+
else
|
|
753
|
+
xpath = key.to_s
|
|
754
|
+
end
|
|
755
|
+
model_value = model[xpath]
|
|
681
756
|
if value[:type] == :field then
|
|
682
|
-
if prefix then
|
|
683
|
-
xpath = "#{prefix}/#{key}"
|
|
684
|
-
else
|
|
685
|
-
xpath = key.to_s
|
|
686
|
-
end
|
|
687
|
-
model_value = model[xpath]
|
|
688
757
|
unless model_value then
|
|
689
758
|
raise RuntimeError.new("xpath '#{xpath}' not contained in model.")
|
|
690
759
|
end
|
|
@@ -696,12 +765,6 @@ def add_model_struct_to_doc(progress, struct, doc, model, prefix = nil)
|
|
|
696
765
|
if node.is_a?(Nokogiri::XML::NodeSet) then
|
|
697
766
|
node = node.first
|
|
698
767
|
end
|
|
699
|
-
if prefix then
|
|
700
|
-
xpath = "#{prefix}/#{key}"
|
|
701
|
-
else
|
|
702
|
-
xpath = key.to_s
|
|
703
|
-
end
|
|
704
|
-
model_value = model[xpath]
|
|
705
768
|
xpath_element = value[:xpath_element]
|
|
706
769
|
if Integer(model_value[:size]) > 0 then
|
|
707
770
|
model_value[:value].slice(0, Integer(model_value[:size])).each do |mvalue|
|
|
@@ -709,19 +772,16 @@ def add_model_struct_to_doc(progress, struct, doc, model, prefix = nil)
|
|
|
709
772
|
end
|
|
710
773
|
end
|
|
711
774
|
elsif value[:type] == :structure then
|
|
712
|
-
node = doc.add_child("<#{key}></#{key}>")
|
|
713
|
-
if node.is_a?(Nokogiri::XML::NodeSet) then
|
|
714
|
-
node = node.first
|
|
715
|
-
end
|
|
716
775
|
unless value[:ref] then
|
|
717
776
|
raise RuntimeError.new("struct is broken: :structure #{key.inspect} does not have a :ref")
|
|
718
777
|
end
|
|
719
|
-
if
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
778
|
+
if model_value[:exists] == true || model_value[:exists] == "true" then
|
|
779
|
+
node = doc.add_child("<#{key}></#{key}>")
|
|
780
|
+
if node.is_a?(Nokogiri::XML::NodeSet) then
|
|
781
|
+
node = node.first
|
|
782
|
+
end
|
|
783
|
+
add_model_struct_to_doc(progress, value[:ref], node, model, xpath)
|
|
723
784
|
end
|
|
724
|
-
add_model_struct_to_doc(progress, value[:ref], node, model, new_prefix)
|
|
725
785
|
else
|
|
726
786
|
raise RuntimeError.new("struct is broken: encountered unknown :type #{value[:type].inspect}")
|
|
727
787
|
end
|
|
@@ -745,6 +805,12 @@ def collect_validation_rule_names(struct)
|
|
|
745
805
|
return result
|
|
746
806
|
end
|
|
747
807
|
|
|
808
|
+
def set_standard_options(progress, solver)
|
|
809
|
+
solver.to_solver(progress, "(set-option :produce-unsat-cores true) ; enable generation of unsat cores")
|
|
810
|
+
solver.to_solver(progress, "(set-option :smt.core.minimize true) ; ensure that unsat cores are minimal")
|
|
811
|
+
solver.to_solver(progress, "")
|
|
812
|
+
end
|
|
813
|
+
|
|
748
814
|
def generate_doc(progress, message, output_filename, config, options)
|
|
749
815
|
log = options[:log] || nil
|
|
750
816
|
log.puts "generating doc #{output_filename}" if log
|
|
@@ -754,8 +820,7 @@ def generate_doc(progress, message, output_filename, config, options)
|
|
|
754
820
|
z3 = Z3Solver.new(progress, z3path)
|
|
755
821
|
begin
|
|
756
822
|
model = z3.query_model(progress, options) do |solver|
|
|
757
|
-
|
|
758
|
-
solver.to_solver(progress, "")
|
|
823
|
+
set_standard_options(progress, solver)
|
|
759
824
|
log.puts "translating structure" if log
|
|
760
825
|
translate_structure_to_SMTLIB(progress, solver, message, :options => options, :config => config)
|
|
761
826
|
solver.to_solver(progress, "")
|
|
@@ -1043,6 +1108,20 @@ def list_validation_rules(message)
|
|
|
1043
1108
|
puts "Validation Rules:"
|
|
1044
1109
|
rules.each { |r| puts " #{r}" }
|
|
1045
1110
|
end
|
|
1111
|
+
|
|
1112
|
+
def value_of_type(value, type)
|
|
1113
|
+
if type == :string or type == :enum then
|
|
1114
|
+
"\"#{value}\""
|
|
1115
|
+
elsif type == :int then
|
|
1116
|
+
value.to_s
|
|
1117
|
+
elsif type == :date then
|
|
1118
|
+
(Date.parse(value) - Time.at(0).to_date).to_i.to_s
|
|
1119
|
+
elsif type == :timestamp then
|
|
1120
|
+
(Time.new(value) - Time.at(0)).to_i.to_s
|
|
1121
|
+
else
|
|
1122
|
+
raise RuntimeError.new("encountered unknown :datatype #{type.inspect}")
|
|
1123
|
+
end
|
|
1124
|
+
end
|
|
1046
1125
|
|
|
1047
1126
|
class CoreEnumerationAlgo
|
|
1048
1127
|
|
|
@@ -1081,14 +1160,6 @@ class CoreEnumerationAlgo
|
|
|
1081
1160
|
"fixed(#{fixed.join(", ")})"
|
|
1082
1161
|
end
|
|
1083
1162
|
|
|
1084
|
-
def value_of_type(value, type)
|
|
1085
|
-
if type == :string or type == :enum then
|
|
1086
|
-
"\"#{value}\""
|
|
1087
|
-
elsif type == :int then
|
|
1088
|
-
value.to_s
|
|
1089
|
-
end
|
|
1090
|
-
end
|
|
1091
|
-
|
|
1092
1163
|
# prevent the var from taking values that already have been enumerated exhaustively
|
|
1093
1164
|
def enumerate_blocking_clause(var_xpath, var_def, blocked)
|
|
1094
1165
|
clauses = []
|
|
@@ -1138,8 +1209,7 @@ class CoreEnumerationAlgo
|
|
|
1138
1209
|
z3 = Z3Solver.new(progress, z3path)
|
|
1139
1210
|
begin
|
|
1140
1211
|
model = z3.query_model(progress, options) do |solver|
|
|
1141
|
-
|
|
1142
|
-
solver.to_solver(progress, "")
|
|
1212
|
+
set_standard_options(progress, solver)
|
|
1143
1213
|
log.puts "translating structure" if log
|
|
1144
1214
|
translate_structure_to_SMTLIB(progress, solver, message, :options => options, :config => config)
|
|
1145
1215
|
solver.to_solver(progress, "")
|
|
@@ -1259,7 +1329,7 @@ class CoreEnumerationAlgo
|
|
|
1259
1329
|
res = switched.times.to_a.map do |index|
|
|
1260
1330
|
var_xpath = vars[index][0]
|
|
1261
1331
|
"#{var_xpath}=#{assignment[var_xpath].inspect}"
|
|
1262
|
-
end + ["#{vars[switched][0]}
|
|
1332
|
+
end + ["#{vars[switched][0]}\{#{blocked.map{|v| v.inspect}.join(",")}}"]
|
|
1263
1333
|
"{#{res.join(", ")}}"
|
|
1264
1334
|
end
|
|
1265
1335
|
|
|
@@ -1414,8 +1484,6 @@ class CoreEnumerationAlgo
|
|
|
1414
1484
|
end
|
|
1415
1485
|
end
|
|
1416
1486
|
|
|
1417
|
-
#progress.print_line "DEBUG: key_fields_list=#{key_fields_list.inspect}"
|
|
1418
|
-
|
|
1419
1487
|
cont_filename = "#{output_dir}/#{CONTINUATION_FILE}"
|
|
1420
1488
|
if options[:continue] then
|
|
1421
1489
|
unless File.exist?(cont_filename) then
|
|
@@ -1451,7 +1519,7 @@ class CoreEnumerationAlgo
|
|
|
1451
1519
|
|
|
1452
1520
|
@on_new_doc = Proc.new { |num_doc, doc_filename, combination|
|
|
1453
1521
|
# combination logging
|
|
1454
|
-
log.puts "#{doc_filename} <- {#{combination.map{|k,v| "#{k}
|
|
1522
|
+
log.puts "#{doc_filename} <- {#{combination.map{|k,v| "#{k}=#{v.inspect}"}.join(", ")}}"
|
|
1455
1523
|
|
|
1456
1524
|
doc_counter += 1
|
|
1457
1525
|
if options[:max_num_docs] && doc_counter >= options[:max_num_docs] then
|
|
@@ -1481,12 +1549,12 @@ class CoreEnumerationAlgo
|
|
|
1481
1549
|
File.open(log_filename, log_mode) do |log|
|
|
1482
1550
|
output_doc_filename = prepare_output_doc_filename(@num_tries, output_dir)
|
|
1483
1551
|
key_values = generate_doc_return_key_values(progress, message, key, output_doc_filename, config, options)
|
|
1484
|
-
log.puts "#{output_doc_filename} <- {#{key_values.map{|k,v| "#{k}
|
|
1552
|
+
log.puts "#{output_doc_filename} <- {#{key_values.map{|k,v| "#{k}=#{v.inspect}"}.join(", ")}}"
|
|
1485
1553
|
doc_counter += 1
|
|
1486
1554
|
@num_tries += 1
|
|
1487
1555
|
@on_new_doc = Proc.new { |num_doc, doc_filename, combination|
|
|
1488
1556
|
# combination logging
|
|
1489
|
-
log.puts "#{doc_filename} <- {#{combination.map{|k,v| "#{k}
|
|
1557
|
+
log.puts "#{doc_filename} <- {#{combination.map{|k,v| "#{k}=#{v.inspect}"}.join(", ")}}"
|
|
1490
1558
|
|
|
1491
1559
|
doc_counter += 1
|
|
1492
1560
|
if options[:max_num_docs] && doc_counter >= options[:max_num_docs] then
|
|
@@ -1585,6 +1653,205 @@ def validate_model(message, prefix=nil)
|
|
|
1585
1653
|
end
|
|
1586
1654
|
end
|
|
1587
1655
|
|
|
1656
|
+
def validate_doc_against_schema(struct, doc)
|
|
1657
|
+
root = doc.root
|
|
1658
|
+
sdef = struct[root.name.to_sym]
|
|
1659
|
+
unless sdef[:type] == :structure then
|
|
1660
|
+
puts "ERROR: schema validation failed: root element should be a structure"
|
|
1661
|
+
end
|
|
1662
|
+
# first, check that each doc element is defined for the struct
|
|
1663
|
+
validate_doc_struct_against_schema(sdef[:ref], root, root.name)
|
|
1664
|
+
# second, check that all required elements from the struct are actually present in the doc
|
|
1665
|
+
validate_schema_against_doc_struct(sdef[:ref], root, root.name)
|
|
1666
|
+
end
|
|
1667
|
+
|
|
1668
|
+
def validate_doc_struct_against_schema(struct, doc, prefix = nil)
|
|
1669
|
+
doc.children.to_a.filter{|x| x.is_a?(Nokogiri::XML::Element) }.each do |c|
|
|
1670
|
+
fdef = struct[c.name.to_sym]
|
|
1671
|
+
if prefix then
|
|
1672
|
+
xpath = "#{prefix}/#{c.name}"
|
|
1673
|
+
else
|
|
1674
|
+
xpath = c.name
|
|
1675
|
+
end
|
|
1676
|
+
if fdef
|
|
1677
|
+
if fdef[:type] == :structure then
|
|
1678
|
+
return unless validate_doc_struct_against_schema(fdef[:ref], c, xpath)
|
|
1679
|
+
end
|
|
1680
|
+
else
|
|
1681
|
+
if prefix then
|
|
1682
|
+
xpath = "#{prefix}/#{c.name}"
|
|
1683
|
+
else
|
|
1684
|
+
xpath = c.name
|
|
1685
|
+
end
|
|
1686
|
+
raise FatalError.new("ERROR: schema validation failed: #{c.name} of #{xpath} is present in document, but not defined in schema.")
|
|
1687
|
+
end
|
|
1688
|
+
end
|
|
1689
|
+
end
|
|
1690
|
+
|
|
1691
|
+
def validate_schema_against_doc_struct(struct, doc, prefix = nil)
|
|
1692
|
+
struct.each do |key, value|
|
|
1693
|
+
next if key == :additional_smtlib || key == :validation_rules || key == :predicates || key == :parent_link || key == :struct_xpath_prefix
|
|
1694
|
+
if prefix then
|
|
1695
|
+
xpath = "#{prefix}/#{key}"
|
|
1696
|
+
else
|
|
1697
|
+
xpath = key
|
|
1698
|
+
end
|
|
1699
|
+
if value[:type] == :structure then
|
|
1700
|
+
node = doc.at_xpath(key.to_s)
|
|
1701
|
+
if node then
|
|
1702
|
+
validate_schema_against_doc_struct(value[:ref], node, xpath)
|
|
1703
|
+
else
|
|
1704
|
+
if value[:optional] == false then
|
|
1705
|
+
raise FatalError.new("ERROR: schema validation failed: non-optional structure #{xpath} not found in doc.")
|
|
1706
|
+
end
|
|
1707
|
+
end
|
|
1708
|
+
elsif value[:type] == :field then
|
|
1709
|
+
if value[:optional] == false then
|
|
1710
|
+
unless doc.at_xpath(key.to_s) then
|
|
1711
|
+
raise FatalError.new("ERROR: schema validation failed: non-optional field #{xpath} not found in doc.")
|
|
1712
|
+
end
|
|
1713
|
+
end
|
|
1714
|
+
elsif value[:type] == :list then
|
|
1715
|
+
if value[:optional] == false then
|
|
1716
|
+
unless doc.at_xpath(key.to_s) then
|
|
1717
|
+
raise FatalError.new("ERROR: schema validation failed: non-optional list #{xpath} not found in doc.")
|
|
1718
|
+
end
|
|
1719
|
+
end
|
|
1720
|
+
list = doc.at_xpath(key.to_s)
|
|
1721
|
+
if list then
|
|
1722
|
+
list.children.to_a.each do |c|
|
|
1723
|
+
next unless c.is_a?(Nokogiri::XML::Element)
|
|
1724
|
+
unless c.name == value[:xpath_element] then
|
|
1725
|
+
raise FatalError.new("ERROR: schema validation failed: encountered element #{c.name} in list #{xpath}, which should only contain #{value[:xpath_element]} elements.")
|
|
1726
|
+
end
|
|
1727
|
+
end
|
|
1728
|
+
end
|
|
1729
|
+
else
|
|
1730
|
+
raise RuntimeError.new("Model error: encountered element #{xpath} of unknown type: #{value[:type]}.")
|
|
1731
|
+
end
|
|
1732
|
+
end
|
|
1733
|
+
end
|
|
1734
|
+
|
|
1735
|
+
def translate_doc_to_SMTLIB(progress, solver, struct, doc, hash = {})
|
|
1736
|
+
options = hash[:options]
|
|
1737
|
+
config = hash[:config]
|
|
1738
|
+
|
|
1739
|
+
solver.to_solver(progress, "; --- Document: ---")
|
|
1740
|
+
translate_struct_in_doc_to_SMTLIB(progress, solver, struct, doc, nil, hash)
|
|
1741
|
+
end
|
|
1742
|
+
|
|
1743
|
+
def translate_struct_in_doc_to_SMTLIB(progress, solver, struct, doc, prefix, hash = {})
|
|
1744
|
+
options = hash[:options]
|
|
1745
|
+
config = hash[:config]
|
|
1746
|
+
|
|
1747
|
+
struct.each do |key, value|
|
|
1748
|
+
next if key == :additional_smtlib || key == :validation_rules || key == :predicates || key == :parent_link || key == :struct_xpath_prefix
|
|
1749
|
+
if prefix then
|
|
1750
|
+
xpath = "#{prefix}/#{key.to_s}"
|
|
1751
|
+
else
|
|
1752
|
+
xpath = key.to_s
|
|
1753
|
+
end
|
|
1754
|
+
if value[:type] == :structure then
|
|
1755
|
+
node = doc.at_xpath(xpath)
|
|
1756
|
+
if node then
|
|
1757
|
+
info = {
|
|
1758
|
+
:assertion_type => :doc_structure_exists,
|
|
1759
|
+
:xpath => xpath,
|
|
1760
|
+
}
|
|
1761
|
+
assertionID = "doc-#{xpath.gsub('/', '-')}"
|
|
1762
|
+
solver.associateAssertionIDWith(assertionID, info)
|
|
1763
|
+
solver.to_solver(progress, "(assert (! (= #{exists_var_for_structure(xpath)} true) :named #{assertionID}))")
|
|
1764
|
+
|
|
1765
|
+
translate_struct_in_doc_to_SMTLIB(progress, solver, value[:ref], doc, xpath, hash)
|
|
1766
|
+
else
|
|
1767
|
+
info = {
|
|
1768
|
+
:assertion_type => :doc_structure_not_exists,
|
|
1769
|
+
:xpath => xpath,
|
|
1770
|
+
}
|
|
1771
|
+
assertionID = "doc-#{xpath.gsub('/', '-')}"
|
|
1772
|
+
solver.associateAssertionIDWith(assertionID, info)
|
|
1773
|
+
solver.to_solver(progress, "(assert (! (= #{exists_var_for_structure(xpath)} false) :named #{assertionID}))")
|
|
1774
|
+
end
|
|
1775
|
+
elsif value[:type] == :field then
|
|
1776
|
+
node = doc.at_xpath(xpath)
|
|
1777
|
+
if node then
|
|
1778
|
+
fvalue = node.text
|
|
1779
|
+
info = {
|
|
1780
|
+
:assertion_type => :doc_field_filled,
|
|
1781
|
+
:xpath => xpath,
|
|
1782
|
+
:value => fvalue
|
|
1783
|
+
}
|
|
1784
|
+
fvalue_str = value_of_type(fvalue, value[:datatype])
|
|
1785
|
+
assertionID = "doc-#{xpath.gsub('/', '-')}"
|
|
1786
|
+
solver.associateAssertionIDWith(assertionID, info)
|
|
1787
|
+
solver.to_solver(progress, "(assert (! (and (= #{filled_var_for_field(xpath)} true) (= #{value_var_for_field(xpath)} #{fvalue_str})) :named #{assertionID}))")
|
|
1788
|
+
else
|
|
1789
|
+
info = {
|
|
1790
|
+
:assertion_type => :doc_field_empty,
|
|
1791
|
+
:xpath => xpath,
|
|
1792
|
+
}
|
|
1793
|
+
assertionID = "doc-#{xpath.gsub('/', '-')}"
|
|
1794
|
+
solver.associateAssertionIDWith(assertionID, info)
|
|
1795
|
+
solver.to_solver(progress, "(assert (! (= #{filled_var_for_field(xpath)} false) :named #{assertionID}))")
|
|
1796
|
+
end
|
|
1797
|
+
elsif value[:type] == :list then
|
|
1798
|
+
node = doc.at_xpath(xpath)
|
|
1799
|
+
if node then
|
|
1800
|
+
fvalues = []
|
|
1801
|
+
node.children.to_a.each do |c|
|
|
1802
|
+
next unless c.is_a?(Nokogiri::XML::Element)
|
|
1803
|
+
fvalues << c.text
|
|
1804
|
+
end
|
|
1805
|
+
info = {
|
|
1806
|
+
:assertion_type => :doc_list_filled,
|
|
1807
|
+
:xpath => xpath,
|
|
1808
|
+
:values => fvalues
|
|
1809
|
+
}
|
|
1810
|
+
fvalue_str = value_of_type(fvalues, value[:datatype])
|
|
1811
|
+
assertionID = "doc-#{xpath.gsub('/', '-')}"
|
|
1812
|
+
solver.associateAssertionIDWith(assertionID, info)
|
|
1813
|
+
clauses = []
|
|
1814
|
+
fvalues.each_with_index do |fvalue, index|
|
|
1815
|
+
clauses << "(= #{value_var_for_list(xpath,index)} #{value_of_type(fvalue, value[:datatype])})"
|
|
1816
|
+
end
|
|
1817
|
+
solver.to_solver(progress, "(assert (! (and (= #{filled_var_for_field(xpath)} true) (= #{size_var_for_list(xpath)} #{fvalues.size}) #{clauses.join(' ')}) :named #{assertionID}))")
|
|
1818
|
+
else
|
|
1819
|
+
info = {
|
|
1820
|
+
:assertion_type => :doc_field_empty,
|
|
1821
|
+
:xpath => xpath,
|
|
1822
|
+
}
|
|
1823
|
+
assertionID = "doc-#{xpath.gsub('/', '-')}"
|
|
1824
|
+
solver.associateAssertionIDWith(assertionID, info)
|
|
1825
|
+
solver.to_solver(progress, "(assert (! (= #{filled_var_for_field(xpath)} false) :named #{assertionID}))")
|
|
1826
|
+
end
|
|
1827
|
+
else
|
|
1828
|
+
raise RuntimeError.new("Model Error: encountered unknown type #{value[:type].inspect}")
|
|
1829
|
+
end
|
|
1830
|
+
end
|
|
1831
|
+
end
|
|
1832
|
+
|
|
1833
|
+
def validate_doc(progress, struct, doc, options, config)
|
|
1834
|
+
options[:solverLog] = "validation.smt2"
|
|
1835
|
+
|
|
1836
|
+
z3path = config[:z3path] || DEFAULT_Z3_PATH
|
|
1837
|
+
z3 = Z3Solver.new(progress, z3path)
|
|
1838
|
+
begin
|
|
1839
|
+
result = z3.query(progress, options) do |solver|
|
|
1840
|
+
set_standard_options(progress, solver)
|
|
1841
|
+
translate_structure_to_SMTLIB(progress, solver, struct, :options => options, :config => config)
|
|
1842
|
+
translate_doc_to_SMTLIB(progress, solver, struct, doc, :options => options, :config => config)
|
|
1843
|
+
solver.to_solver(progress, "")
|
|
1844
|
+
end
|
|
1845
|
+
unless result == true then
|
|
1846
|
+
raise FatalError.new("VALIDATION FAILED! SMTLIB log was written to #{options[:solverLog]}.\n\n#{result}")
|
|
1847
|
+
end
|
|
1848
|
+
ensure
|
|
1849
|
+
z3.close()
|
|
1850
|
+
end
|
|
1851
|
+
|
|
1852
|
+
return true
|
|
1853
|
+
end
|
|
1854
|
+
|
|
1588
1855
|
def generate(message, argv, message_config_filename = nil)
|
|
1589
1856
|
if ARGV[0] == "--help" || ARGV[0] == "-help" || ARGV[0] == "help" then
|
|
1590
1857
|
puts USAGE
|
|
@@ -1617,6 +1884,12 @@ def generate(message, argv, message_config_filename = nil)
|
|
|
1617
1884
|
elsif opt == "-max-num-docs" then
|
|
1618
1885
|
options[:max_num_docs] = Integer(argv.shift)
|
|
1619
1886
|
puts "flag -max-num-docs used. Will stop after generating #{options[:max_num_docs]} documents."
|
|
1887
|
+
elsif opt == "-validate" then
|
|
1888
|
+
if options.has_key?(:validate_files) then
|
|
1889
|
+
options[:validate_files] << argv.shift
|
|
1890
|
+
else
|
|
1891
|
+
options[:validate_files] = [argv.shift]
|
|
1892
|
+
end
|
|
1620
1893
|
else
|
|
1621
1894
|
puts "FATAL: unknown option #{opt.inspect}."
|
|
1622
1895
|
puts USAGE
|
|
@@ -1652,7 +1925,17 @@ def generate(message, argv, message_config_filename = nil)
|
|
|
1652
1925
|
|
|
1653
1926
|
introduce_parent_links(message)
|
|
1654
1927
|
|
|
1655
|
-
if options[:
|
|
1928
|
+
if options[:validate_files] then
|
|
1929
|
+
puts "using #{options[:date_now]} as [Date.now]"
|
|
1930
|
+
puts "using #{options[:timestamp_now]} as [Timestamp.now]"
|
|
1931
|
+
options[:validate_files].each do |filename|
|
|
1932
|
+
puts "validating file #{filename}"
|
|
1933
|
+
doc = Nokogiri::XML(File.read(filename))
|
|
1934
|
+
validate_doc_against_schema(message, doc)
|
|
1935
|
+
progress = ProgressBar.new(1)
|
|
1936
|
+
validate_doc(progress, message, doc, options, config)
|
|
1937
|
+
end
|
|
1938
|
+
elsif options[:count_docs_for_key] then
|
|
1656
1939
|
puts "there are #{calc_combinations(message, options[:count_docs_for_key])} combinations."
|
|
1657
1940
|
elsif options[:docs_for_key] then
|
|
1658
1941
|
puts "using #{options[:date_now]} as [Date.now]"
|
data/lib/solver-lib.rb
CHANGED
|
@@ -113,6 +113,13 @@ class SolverSession
|
|
|
113
113
|
return nextAssertionID
|
|
114
114
|
end
|
|
115
115
|
|
|
116
|
+
def associateAssertionIDWith(assertionID, hash)
|
|
117
|
+
if @associations.has_key?(assertionID) then
|
|
118
|
+
raise RuntimeError.new("AssertionID #{assertionID.inspect} already has info associated with it!")
|
|
119
|
+
end
|
|
120
|
+
@associations[assertionID] = hash
|
|
121
|
+
end
|
|
122
|
+
|
|
116
123
|
def getAssociationForAssertionID(assertionID)
|
|
117
124
|
return @associations[assertionID]
|
|
118
125
|
end
|
|
@@ -159,32 +166,89 @@ class Z3Solver
|
|
|
159
166
|
Thread.kill(@z3thr)
|
|
160
167
|
end
|
|
161
168
|
|
|
162
|
-
def query(&block)
|
|
163
|
-
|
|
169
|
+
def query(progress, options, &block)
|
|
170
|
+
unless options[:solverLog]
|
|
171
|
+
raise RuntimeError.new("No solverLog specified, but the option :solverLog is required!")
|
|
172
|
+
end
|
|
173
|
+
progress.print_line("calling solver (solver-log is written to #{options[:solverLog]})")
|
|
174
|
+
solver = SolverSession.new(@z3in, @z3outAndErr, options[:solverLog])
|
|
164
175
|
|
|
165
176
|
block.call(solver)
|
|
166
177
|
|
|
167
|
-
solver.
|
|
168
|
-
result = solver.
|
|
178
|
+
solver.to_solver(progress, "(check-sat)")
|
|
179
|
+
result = solver.from_solver(progress)
|
|
180
|
+
explanation = true
|
|
169
181
|
if result.chomp == "sat"
|
|
170
182
|
# model is not contradictory -> continue
|
|
171
183
|
elsif result.chomp == "unsat"
|
|
172
184
|
# model is contradictory -> add a message
|
|
173
185
|
|
|
174
|
-
solver.
|
|
175
|
-
unsat_core = solver.
|
|
176
|
-
explanation =
|
|
177
|
-
messages << {:type => :issue, :text => "Model is contradictory", :explanation => explanation}
|
|
178
|
-
solver.writeProtocolToFile("contradictions.smt2")
|
|
186
|
+
solver.to_solver(progress, "(get-unsat-core)")
|
|
187
|
+
unsat_core = solver.from_solver(progress)
|
|
188
|
+
explanation = parse_unsat_core(solver, unsat_core)
|
|
179
189
|
else
|
|
180
|
-
handleSolverError(solver, result)
|
|
190
|
+
handleSolverError(progress, solver, options, result)
|
|
181
191
|
end
|
|
182
192
|
|
|
183
|
-
|
|
184
|
-
solver.
|
|
185
|
-
solver.
|
|
186
|
-
|
|
187
|
-
|
|
193
|
+
solver.to_solver(progress, "(reset)")
|
|
194
|
+
solver.to_solver(progress, "(set-logic ALL)")
|
|
195
|
+
solver.to_solver(progress, "(set-option :produce-unsat-cores true)")
|
|
196
|
+
return explanation
|
|
197
|
+
end
|
|
198
|
+
|
|
199
|
+
def display_assertion(assertion, solver)
|
|
200
|
+
if assertion =~ /validation_rule_(.*)_instance_(.*)/ then
|
|
201
|
+
return "Validation rule #{$1} of structure #{$2.gsub("_", "/")}"
|
|
202
|
+
else
|
|
203
|
+
info = solver.getAssociationForAssertionID(assertion)
|
|
204
|
+
if info then
|
|
205
|
+
if info[:assertion_type] == :doc_field_filled then
|
|
206
|
+
return "Field #{info[:xpath]} contains value #{info[:value].inspect} in document"
|
|
207
|
+
elsif info[:assertion_type] == :doc_field_empty then
|
|
208
|
+
return "Field #{info[:xpath]} is empty in document"
|
|
209
|
+
elsif info[:assertion_type] == :doc_structure_exists then
|
|
210
|
+
return "Structure #{info[:xpath]} exists in document"
|
|
211
|
+
elsif info[:assertion_type] == :doc_structure_not_exists then
|
|
212
|
+
return "Structure #{info[:xpath]} does not exist in document"
|
|
213
|
+
elsif info[:assertion_type] == :constraint then
|
|
214
|
+
if info[:constraint_type] == :min then
|
|
215
|
+
return "Field #{info[:xpath]} has a minimum value of #{info[:value]}."
|
|
216
|
+
elsif info[:constraint_type] == :max then
|
|
217
|
+
return "Field #{info[:xpath]} has a maximum value of #{info[:value]}."
|
|
218
|
+
elsif info[:constraint_type] == :required then
|
|
219
|
+
return "Field #{info[:xpath]} is required."
|
|
220
|
+
else
|
|
221
|
+
p info
|
|
222
|
+
raise RuntimeError.new("encountered unknown :constraint_type #{info[:constraint_type].inspect} for assertionID #{assertion.inspect}")
|
|
223
|
+
end
|
|
224
|
+
else
|
|
225
|
+
raise RuntimeError.new("encountered unknown :assertion_type #{info[:assertion_type].inspect} for assertionID #{assertion.inspect}")
|
|
226
|
+
end
|
|
227
|
+
else
|
|
228
|
+
return assertion
|
|
229
|
+
end
|
|
230
|
+
end
|
|
231
|
+
end
|
|
232
|
+
|
|
233
|
+
def parse_unsat_core(solver, unsat_core)
|
|
234
|
+
unsat_core = unsat_core.chomp
|
|
235
|
+
# TODO: implement
|
|
236
|
+
unless unsat_core =~ /\((.*)\)/ then
|
|
237
|
+
raise RuntimeError.new("could not parse unsat core: #{unsat_core.inspect}")
|
|
238
|
+
end
|
|
239
|
+
list = $1.split(" ")
|
|
240
|
+
|
|
241
|
+
result = "EXPLANATION:\n"
|
|
242
|
+
result += "The following values\n"
|
|
243
|
+
|
|
244
|
+
list.filter{|a| a.start_with?("doc-")}.each do |assertion|
|
|
245
|
+
result += "- #{display_assertion(assertion, solver)} (#{assertion})\n"
|
|
246
|
+
end
|
|
247
|
+
result += "\nviolate the following constraints\n"
|
|
248
|
+
list.filter{|a| !a.start_with?("doc-")}.each do |assertion|
|
|
249
|
+
result += "- #{display_assertion(assertion, solver)} (#{assertion})\n"
|
|
250
|
+
end
|
|
251
|
+
return result
|
|
188
252
|
end
|
|
189
253
|
|
|
190
254
|
# main entry point!
|
|
@@ -249,10 +313,13 @@ class Z3Solver
|
|
|
249
313
|
end
|
|
250
314
|
end
|
|
251
315
|
elsif varname.end_with?("-size") then
|
|
252
|
-
unless model[xpath][:type] == :list then
|
|
253
|
-
raise RuntimeError.new("inconsistent model: xpath #{xpath} should be a list, not a #{model[xpath][:type]}.")
|
|
316
|
+
unless model[xpath][:type] == :list || model[xpath][:type] == :field then
|
|
317
|
+
raise RuntimeError.new("inconsistent model: xpath #{xpath} should be a list or a field, not a #{model[xpath][:type]}.")
|
|
318
|
+
end
|
|
319
|
+
if model[xpath][:type] == :field then
|
|
320
|
+
model[xpath][:type] = :list
|
|
254
321
|
end
|
|
255
|
-
model[xpath][:
|
|
322
|
+
model[xpath][:size] = Integer(value)
|
|
256
323
|
elsif varname.start_with?("struct-") && varname.end_with?("-exists") then
|
|
257
324
|
unless model[xpath][:type] == :struct then
|
|
258
325
|
raise RuntimeError.new("inconsistent model: xpath #{xpath} should be a struct, not a #{model[xpath][:type]}.")
|
metadata
CHANGED
|
@@ -1,14 +1,13 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: mbt-gen
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.0.
|
|
4
|
+
version: 0.0.3
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- FM-enthusiast
|
|
8
|
-
autorequire:
|
|
9
8
|
bindir: bin
|
|
10
9
|
cert_chain: []
|
|
11
|
-
date:
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
12
11
|
dependencies:
|
|
13
12
|
- !ruby/object:Gem::Dependency
|
|
14
13
|
name: nokogiri
|
|
@@ -42,7 +41,6 @@ licenses:
|
|
|
42
41
|
metadata:
|
|
43
42
|
bug_tracker_uri: https://codeberg.org/FM-enthusiast/MBT-gen/issues
|
|
44
43
|
source_code_uri: https://codeberg.org/FM-enthusiast/MBT-gen
|
|
45
|
-
post_install_message:
|
|
46
44
|
rdoc_options: []
|
|
47
45
|
require_paths:
|
|
48
46
|
- lib
|
|
@@ -58,8 +56,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
58
56
|
version: '0'
|
|
59
57
|
requirements:
|
|
60
58
|
- an SMT-solver. Z3 is recommended, it can be obtained from https://github.com/Z3Prover/z3
|
|
61
|
-
rubygems_version: 3.
|
|
62
|
-
signing_key:
|
|
59
|
+
rubygems_version: 3.6.7
|
|
63
60
|
specification_version: 4
|
|
64
61
|
summary: test data generator for Model-based testing
|
|
65
62
|
test_files: []
|