mbt-gen 0.0.1 → 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 +335 -151
- data/lib/solver-lib.rb +87 -28
- metadata +6 -9
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
|
@@ -18,9 +18,9 @@ DEFAULT_Z3_PATH = "z3"
|
|
|
18
18
|
|
|
19
19
|
CONFIG_FILE = "config.yml"
|
|
20
20
|
OUTPUT_FILE = "result.xml"
|
|
21
|
-
CONTINUATION_FILE =
|
|
22
|
-
COMBINATION_LOG =
|
|
23
|
-
VARS_LOG =
|
|
21
|
+
CONTINUATION_FILE = "continuation.log"
|
|
22
|
+
COMBINATION_LOG = "combinations.log"
|
|
23
|
+
VARS_LOG = "vars.log"
|
|
24
24
|
|
|
25
25
|
OUTPUT_DIR = "result"
|
|
26
26
|
LISTS_DIR = "lists"
|
|
@@ -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, "")
|
|
@@ -830,33 +895,6 @@ def all_values_for_field(field)
|
|
|
830
895
|
return values
|
|
831
896
|
end
|
|
832
897
|
|
|
833
|
-
def each_combination(fields, &block)
|
|
834
|
-
if fields.size < 1 then
|
|
835
|
-
return
|
|
836
|
-
end
|
|
837
|
-
if fields.size == 1 then
|
|
838
|
-
field = fields.first
|
|
839
|
-
values = field[1][:values].clone()
|
|
840
|
-
if field[1][:optional] == true then
|
|
841
|
-
values << nil
|
|
842
|
-
end
|
|
843
|
-
values.each do |value|
|
|
844
|
-
block.call({ field[0] => value })
|
|
845
|
-
end
|
|
846
|
-
else
|
|
847
|
-
first = fields.shift
|
|
848
|
-
values = first[1][:values].clone()
|
|
849
|
-
if first[1][:optional] == true then
|
|
850
|
-
values << nil
|
|
851
|
-
end
|
|
852
|
-
each_combination(fields) do |mapping|
|
|
853
|
-
values.each do |value|
|
|
854
|
-
block.call(mapping.merge({ first[0] => value }))
|
|
855
|
-
end
|
|
856
|
-
end
|
|
857
|
-
end
|
|
858
|
-
end
|
|
859
|
-
|
|
860
898
|
def calc_combinations_for_key_fields(key_fields)
|
|
861
899
|
count = 1
|
|
862
900
|
key_fields.each do |field|
|
|
@@ -923,46 +961,6 @@ def read_docs_from_log_file(log_filename)
|
|
|
923
961
|
return result
|
|
924
962
|
end
|
|
925
963
|
|
|
926
|
-
def blocking_clause(docs, vars = docs.map{|e| e.keys}.flatten)
|
|
927
|
-
unless docs.empty?
|
|
928
|
-
codes = docs.map do |key_values|
|
|
929
|
-
value_codes = key_values.filter{|xpath,value| vars.include?(xpath) }.map do |xpath, value|
|
|
930
|
-
if value == nil then
|
|
931
|
-
"(= #{filled_var_for_field(xpath)} false)"
|
|
932
|
-
else
|
|
933
|
-
"(and (= #{filled_var_for_field(xpath)} true) (= #{value_var_for_field(xpath)} \"#{value}\"))"
|
|
934
|
-
end
|
|
935
|
-
end
|
|
936
|
-
if value_codes.empty? then
|
|
937
|
-
"true"
|
|
938
|
-
else
|
|
939
|
-
"(not (and #{value_codes.join(" ")}))"
|
|
940
|
-
end
|
|
941
|
-
end
|
|
942
|
-
return "" if codes.empty?
|
|
943
|
-
return "(assert (! (and #{codes.join(" ")}) :named blocking-clause))"
|
|
944
|
-
end
|
|
945
|
-
return nil
|
|
946
|
-
end
|
|
947
|
-
|
|
948
|
-
#def fixing_clause(key_values, fixed = key_value.keys)
|
|
949
|
-
# codes = []
|
|
950
|
-
# key_values.each do |xpath, value|
|
|
951
|
-
# if fixed.include?(xpath) then
|
|
952
|
-
# if value then
|
|
953
|
-
# codes << "(and (= #{filled_var_for_field(xpath)} true) (= #{value_var_for_field(xpath)} \"#{value}\"))"
|
|
954
|
-
# else
|
|
955
|
-
# codes << "(= #{filled_var_for_field(xpath)} false)"
|
|
956
|
-
# end
|
|
957
|
-
# end
|
|
958
|
-
# end
|
|
959
|
-
# if codes.empty? then
|
|
960
|
-
# return ""
|
|
961
|
-
# else
|
|
962
|
-
# return "(assert (! (and #{codes.join(" ")}) :named fixing-clause))"
|
|
963
|
-
# end
|
|
964
|
-
#end
|
|
965
|
-
|
|
966
964
|
def prepare_output_doc_filename(number, output_dir)
|
|
967
965
|
chunk_number = number / 100000
|
|
968
966
|
dirname = "#{output_dir}/#{chunk_number}"
|
|
@@ -972,30 +970,6 @@ def prepare_output_doc_filename(number, output_dir)
|
|
|
972
970
|
return "#{dirname}/doc#{number}.xml"
|
|
973
971
|
end
|
|
974
972
|
|
|
975
|
-
def switch_blocking_clause(fixed, current_value, var_xpath, blocked_values)
|
|
976
|
-
clauses = []
|
|
977
|
-
blocked_values.each do |value|
|
|
978
|
-
if value != nil then
|
|
979
|
-
clauses << "(not (and (= #{filled_var_for_field(var_xpath)} true) (= #{value_var_for_field(var_xpath)} \"#{value}\")))"
|
|
980
|
-
else
|
|
981
|
-
clauses << "(not (= #{filled_var_for_field(var_xpath)} false))"
|
|
982
|
-
end
|
|
983
|
-
end
|
|
984
|
-
if fixed then
|
|
985
|
-
if current_value != nil then
|
|
986
|
-
result = "; switch fixing clause\n(assert (! (and (= #{filled_var_for_field(var_xpath)} true) (= #{value_var_for_field(var_xpath)} \"#{current_value}\")) :named switch_fixing_clause))"
|
|
987
|
-
else
|
|
988
|
-
result = "; switch fixing clause\n(assert (! (= #{filled_var_for_field(var_xpath)} false) :named switch_fixing_clause))"
|
|
989
|
-
end
|
|
990
|
-
else
|
|
991
|
-
result = ""
|
|
992
|
-
end
|
|
993
|
-
unless clauses.empty? then
|
|
994
|
-
result = result + "\n; switch blocking clause\n(assert (! (and #{clauses.join(" ")}) :named switch_blocking_clause))"
|
|
995
|
-
end
|
|
996
|
-
return result
|
|
997
|
-
end
|
|
998
|
-
|
|
999
973
|
def values_for_key_field(field)
|
|
1000
974
|
if field[:type] == :field then
|
|
1001
975
|
if field[:datatype] == :enum then
|
|
@@ -1024,14 +998,6 @@ def values_for_key_field(field)
|
|
|
1024
998
|
end
|
|
1025
999
|
end
|
|
1026
1000
|
|
|
1027
|
-
def fixing_clause(xpath, switched_info)
|
|
1028
|
-
if switched_info[:current_value] == nil then
|
|
1029
|
-
return "(assert (! (= #{filled_var_for_field(xpath)} false) :named fixing-clause-#{raw_field(xpath)}))"
|
|
1030
|
-
else
|
|
1031
|
-
return "(assert (! (and (= #{filled_var_for_field(xpath)} true) (= #{value_var_for_field(xpath)} \"#{switched_info[:current_value]}\")) :named fixing-clause-#{raw_field(xpath)}))"
|
|
1032
|
-
end
|
|
1033
|
-
end
|
|
1034
|
-
|
|
1035
1001
|
def collect_key_fields(fkey, struct, prefix = nil)
|
|
1036
1002
|
result = {}
|
|
1037
1003
|
struct.each do |key, value|
|
|
@@ -1142,6 +1108,20 @@ def list_validation_rules(message)
|
|
|
1142
1108
|
puts "Validation Rules:"
|
|
1143
1109
|
rules.each { |r| puts " #{r}" }
|
|
1144
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
|
|
1145
1125
|
|
|
1146
1126
|
class CoreEnumerationAlgo
|
|
1147
1127
|
|
|
@@ -1180,14 +1160,6 @@ class CoreEnumerationAlgo
|
|
|
1180
1160
|
"fixed(#{fixed.join(", ")})"
|
|
1181
1161
|
end
|
|
1182
1162
|
|
|
1183
|
-
def value_of_type(value, type)
|
|
1184
|
-
if type == :string or type == :enum then
|
|
1185
|
-
"\"#{value}\""
|
|
1186
|
-
elsif type == :int then
|
|
1187
|
-
value.to_s
|
|
1188
|
-
end
|
|
1189
|
-
end
|
|
1190
|
-
|
|
1191
1163
|
# prevent the var from taking values that already have been enumerated exhaustively
|
|
1192
1164
|
def enumerate_blocking_clause(var_xpath, var_def, blocked)
|
|
1193
1165
|
clauses = []
|
|
@@ -1237,8 +1209,7 @@ class CoreEnumerationAlgo
|
|
|
1237
1209
|
z3 = Z3Solver.new(progress, z3path)
|
|
1238
1210
|
begin
|
|
1239
1211
|
model = z3.query_model(progress, options) do |solver|
|
|
1240
|
-
|
|
1241
|
-
solver.to_solver(progress, "")
|
|
1212
|
+
set_standard_options(progress, solver)
|
|
1242
1213
|
log.puts "translating structure" if log
|
|
1243
1214
|
translate_structure_to_SMTLIB(progress, solver, message, :options => options, :config => config)
|
|
1244
1215
|
solver.to_solver(progress, "")
|
|
@@ -1358,7 +1329,7 @@ class CoreEnumerationAlgo
|
|
|
1358
1329
|
res = switched.times.to_a.map do |index|
|
|
1359
1330
|
var_xpath = vars[index][0]
|
|
1360
1331
|
"#{var_xpath}=#{assignment[var_xpath].inspect}"
|
|
1361
|
-
end + ["#{vars[switched][0]}
|
|
1332
|
+
end + ["#{vars[switched][0]}\{#{blocked.map{|v| v.inspect}.join(",")}}"]
|
|
1362
1333
|
"{#{res.join(", ")}}"
|
|
1363
1334
|
end
|
|
1364
1335
|
|
|
@@ -1513,8 +1484,6 @@ class CoreEnumerationAlgo
|
|
|
1513
1484
|
end
|
|
1514
1485
|
end
|
|
1515
1486
|
|
|
1516
|
-
#progress.print_line "DEBUG: key_fields_list=#{key_fields_list.inspect}"
|
|
1517
|
-
|
|
1518
1487
|
cont_filename = "#{output_dir}/#{CONTINUATION_FILE}"
|
|
1519
1488
|
if options[:continue] then
|
|
1520
1489
|
unless File.exist?(cont_filename) then
|
|
@@ -1550,7 +1519,7 @@ class CoreEnumerationAlgo
|
|
|
1550
1519
|
|
|
1551
1520
|
@on_new_doc = Proc.new { |num_doc, doc_filename, combination|
|
|
1552
1521
|
# combination logging
|
|
1553
|
-
log.puts "#{doc_filename} <- {#{combination.map{|k,v| "#{k}
|
|
1522
|
+
log.puts "#{doc_filename} <- {#{combination.map{|k,v| "#{k}=#{v.inspect}"}.join(", ")}}"
|
|
1554
1523
|
|
|
1555
1524
|
doc_counter += 1
|
|
1556
1525
|
if options[:max_num_docs] && doc_counter >= options[:max_num_docs] then
|
|
@@ -1580,12 +1549,12 @@ class CoreEnumerationAlgo
|
|
|
1580
1549
|
File.open(log_filename, log_mode) do |log|
|
|
1581
1550
|
output_doc_filename = prepare_output_doc_filename(@num_tries, output_dir)
|
|
1582
1551
|
key_values = generate_doc_return_key_values(progress, message, key, output_doc_filename, config, options)
|
|
1583
|
-
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(", ")}}"
|
|
1584
1553
|
doc_counter += 1
|
|
1585
1554
|
@num_tries += 1
|
|
1586
1555
|
@on_new_doc = Proc.new { |num_doc, doc_filename, combination|
|
|
1587
1556
|
# combination logging
|
|
1588
|
-
log.puts "#{doc_filename} <- {#{combination.map{|k,v| "#{k}
|
|
1557
|
+
log.puts "#{doc_filename} <- {#{combination.map{|k,v| "#{k}=#{v.inspect}"}.join(", ")}}"
|
|
1589
1558
|
|
|
1590
1559
|
doc_counter += 1
|
|
1591
1560
|
if options[:max_num_docs] && doc_counter >= options[:max_num_docs] then
|
|
@@ -1684,6 +1653,205 @@ def validate_model(message, prefix=nil)
|
|
|
1684
1653
|
end
|
|
1685
1654
|
end
|
|
1686
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
|
+
|
|
1687
1855
|
def generate(message, argv, message_config_filename = nil)
|
|
1688
1856
|
if ARGV[0] == "--help" || ARGV[0] == "-help" || ARGV[0] == "help" then
|
|
1689
1857
|
puts USAGE
|
|
@@ -1716,6 +1884,12 @@ def generate(message, argv, message_config_filename = nil)
|
|
|
1716
1884
|
elsif opt == "-max-num-docs" then
|
|
1717
1885
|
options[:max_num_docs] = Integer(argv.shift)
|
|
1718
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
|
|
1719
1893
|
else
|
|
1720
1894
|
puts "FATAL: unknown option #{opt.inspect}."
|
|
1721
1895
|
puts USAGE
|
|
@@ -1751,7 +1925,17 @@ def generate(message, argv, message_config_filename = nil)
|
|
|
1751
1925
|
|
|
1752
1926
|
introduce_parent_links(message)
|
|
1753
1927
|
|
|
1754
|
-
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
|
|
1755
1939
|
puts "there are #{calc_combinations(message, options[:count_docs_for_key])} combinations."
|
|
1756
1940
|
elsif options[:docs_for_key] then
|
|
1757
1941
|
puts "using #{options[:date_now]} as [Date.now]"
|
data/lib/solver-lib.rb
CHANGED
|
@@ -1,14 +1,6 @@
|
|
|
1
|
-
|
|
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'
|
|
1
|
+
#!/usr/bin/ruby
|
|
11
2
|
|
|
3
|
+
require 'open3'
|
|
12
4
|
|
|
13
5
|
|
|
14
6
|
class SolverSession
|
|
@@ -121,6 +113,13 @@ class SolverSession
|
|
|
121
113
|
return nextAssertionID
|
|
122
114
|
end
|
|
123
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
|
+
|
|
124
123
|
def getAssociationForAssertionID(assertionID)
|
|
125
124
|
return @associations[assertionID]
|
|
126
125
|
end
|
|
@@ -167,32 +166,89 @@ class Z3Solver
|
|
|
167
166
|
Thread.kill(@z3thr)
|
|
168
167
|
end
|
|
169
168
|
|
|
170
|
-
def query(&block)
|
|
171
|
-
|
|
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])
|
|
172
175
|
|
|
173
176
|
block.call(solver)
|
|
174
177
|
|
|
175
|
-
solver.
|
|
176
|
-
result = solver.
|
|
178
|
+
solver.to_solver(progress, "(check-sat)")
|
|
179
|
+
result = solver.from_solver(progress)
|
|
180
|
+
explanation = true
|
|
177
181
|
if result.chomp == "sat"
|
|
178
182
|
# model is not contradictory -> continue
|
|
179
183
|
elsif result.chomp == "unsat"
|
|
180
184
|
# model is contradictory -> add a message
|
|
181
185
|
|
|
182
|
-
solver.
|
|
183
|
-
unsat_core = solver.
|
|
184
|
-
explanation =
|
|
185
|
-
messages << {:type => :issue, :text => "Model is contradictory", :explanation => explanation}
|
|
186
|
-
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)
|
|
187
189
|
else
|
|
188
|
-
handleSolverError(solver, result)
|
|
190
|
+
handleSolverError(progress, solver, options, result)
|
|
189
191
|
end
|
|
190
192
|
|
|
191
|
-
|
|
192
|
-
solver.
|
|
193
|
-
solver.
|
|
194
|
-
|
|
195
|
-
|
|
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
|
|
196
252
|
end
|
|
197
253
|
|
|
198
254
|
# main entry point!
|
|
@@ -257,10 +313,13 @@ class Z3Solver
|
|
|
257
313
|
end
|
|
258
314
|
end
|
|
259
315
|
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]}.")
|
|
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
|
|
262
321
|
end
|
|
263
|
-
model[xpath][:
|
|
322
|
+
model[xpath][:size] = Integer(value)
|
|
264
323
|
elsif varname.start_with?("struct-") && varname.end_with?("-exists") then
|
|
265
324
|
unless model[xpath][:type] == :struct then
|
|
266
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
|
|
@@ -36,13 +35,12 @@ files:
|
|
|
36
35
|
- lib/progress.rb
|
|
37
36
|
- lib/regexp-to-smtlib.rb
|
|
38
37
|
- lib/solver-lib.rb
|
|
39
|
-
homepage: https://
|
|
38
|
+
homepage: https://codeberg.org/FM-enthusiast/MBT-gen
|
|
40
39
|
licenses:
|
|
41
40
|
- GPL-3.0-only
|
|
42
41
|
metadata:
|
|
43
|
-
bug_tracker_uri: https://
|
|
44
|
-
source_code_uri: https://
|
|
45
|
-
post_install_message:
|
|
42
|
+
bug_tracker_uri: https://codeberg.org/FM-enthusiast/MBT-gen/issues
|
|
43
|
+
source_code_uri: https://codeberg.org/FM-enthusiast/MBT-gen
|
|
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: []
|