mbt-gen 0.0.2 → 0.0.4
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 +451 -150
- data/lib/progress.rb +47 -5
- data/lib/solver-lib.rb +245 -98
- metadata +2 -2
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,23 +805,24 @@ 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)
|
|
815
|
+
raise RuntimeError.new("option :solverFactory is required") unless options[:solverFactory]
|
|
749
816
|
log = options[:log] || nil
|
|
750
817
|
log.puts "generating doc #{output_filename}" if log
|
|
751
818
|
options[:solverLog] = options[:solverLog] || "#{output_filename}.smt2"
|
|
752
819
|
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
log.puts "translating structure" if log
|
|
760
|
-
translate_structure_to_SMTLIB(progress, solver, message, :options => options, :config => config)
|
|
761
|
-
solver.to_solver(progress, "")
|
|
762
|
-
end
|
|
763
|
-
ensure
|
|
764
|
-
z3.close()
|
|
820
|
+
solverFactory = options[:solverFactory]
|
|
821
|
+
model = solverFactory.query_model(options) do |solver|
|
|
822
|
+
set_standard_options(progress, solver)
|
|
823
|
+
log.puts "translating structure" if log
|
|
824
|
+
translate_structure_to_SMTLIB(progress, solver, message, :options => options, :config => config)
|
|
825
|
+
solver.to_solver(progress, "")
|
|
765
826
|
end
|
|
766
827
|
|
|
767
828
|
if model then
|
|
@@ -860,6 +921,9 @@ end
|
|
|
860
921
|
|
|
861
922
|
def calc_combinations(message, key)
|
|
862
923
|
key_fields = collect_key_fields(key, message)
|
|
924
|
+
if key_fields.empty?
|
|
925
|
+
raise FatalError.new("unknown key: #{key}")
|
|
926
|
+
end
|
|
863
927
|
return calc_combinations_for_key_fields(key_fields)
|
|
864
928
|
end
|
|
865
929
|
|
|
@@ -1043,6 +1107,20 @@ def list_validation_rules(message)
|
|
|
1043
1107
|
puts "Validation Rules:"
|
|
1044
1108
|
rules.each { |r| puts " #{r}" }
|
|
1045
1109
|
end
|
|
1110
|
+
|
|
1111
|
+
def value_of_type(value, type)
|
|
1112
|
+
if type == :string or type == :enum then
|
|
1113
|
+
"\"#{value}\""
|
|
1114
|
+
elsif type == :int then
|
|
1115
|
+
value.to_s
|
|
1116
|
+
elsif type == :date then
|
|
1117
|
+
(Date.parse(value) - Time.at(0).to_date).to_i.to_s
|
|
1118
|
+
elsif type == :timestamp then
|
|
1119
|
+
(Time.new(value) - Time.at(0)).to_i.to_s
|
|
1120
|
+
else
|
|
1121
|
+
raise RuntimeError.new("encountered unknown :datatype #{type.inspect}")
|
|
1122
|
+
end
|
|
1123
|
+
end
|
|
1046
1124
|
|
|
1047
1125
|
class CoreEnumerationAlgo
|
|
1048
1126
|
|
|
@@ -1081,14 +1159,6 @@ class CoreEnumerationAlgo
|
|
|
1081
1159
|
"fixed(#{fixed.join(", ")})"
|
|
1082
1160
|
end
|
|
1083
1161
|
|
|
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
1162
|
# prevent the var from taking values that already have been enumerated exhaustively
|
|
1093
1163
|
def enumerate_blocking_clause(var_xpath, var_def, blocked)
|
|
1094
1164
|
clauses = []
|
|
@@ -1128,24 +1198,19 @@ class CoreEnumerationAlgo
|
|
|
1128
1198
|
end
|
|
1129
1199
|
|
|
1130
1200
|
def generate_doc_return_key_values(progress, message, key, output_filename, config, options)
|
|
1201
|
+
raise RuntimeError.new("option :solverFactory is required.") unless options[:solverFactory]
|
|
1131
1202
|
log = options[:log] || nil
|
|
1132
1203
|
log.puts "generating doc #{output_filename}" if log
|
|
1133
1204
|
options[:solverLog] = log_filename_for_output_filename(output_filename)
|
|
1134
1205
|
|
|
1135
1206
|
key_fields = collect_key_fields(key, message)
|
|
1136
1207
|
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
log.puts "translating structure" if log
|
|
1144
|
-
translate_structure_to_SMTLIB(progress, solver, message, :options => options, :config => config)
|
|
1145
|
-
solver.to_solver(progress, "")
|
|
1146
|
-
end
|
|
1147
|
-
ensure
|
|
1148
|
-
z3.close()
|
|
1208
|
+
solverFactory = options[:solverFactory]
|
|
1209
|
+
model = solverFactory.query_model(options) do |solver|
|
|
1210
|
+
set_standard_options(progress, solver)
|
|
1211
|
+
log.puts "translating structure" if log
|
|
1212
|
+
translate_structure_to_SMTLIB(progress, solver, message, :options => options, :config => config)
|
|
1213
|
+
solver.to_solver(progress, "")
|
|
1149
1214
|
end
|
|
1150
1215
|
|
|
1151
1216
|
if model then
|
|
@@ -1163,9 +1228,12 @@ class CoreEnumerationAlgo
|
|
|
1163
1228
|
end
|
|
1164
1229
|
end
|
|
1165
1230
|
|
|
1166
|
-
def store_continuation_point(doc_number, state, assignment, new_assignment, filename)
|
|
1231
|
+
def store_continuation_point(doc_number, state, assignment, new_assignment, filename, options, time)
|
|
1167
1232
|
File.open(filename, "w") do |f|
|
|
1168
|
-
|
|
1233
|
+
state_str = "state(#{state.map{|s| if s then "[#{s.map{|v| v.inspect}.join(",")}]" else "nil" end}.join("#")})"
|
|
1234
|
+
assignment_str = "assignment(#{assignment.to_a.map{|k,v| "#{k}:#{v.inspect}"}.join(",")})"
|
|
1235
|
+
new_assignment_str = "new_assignment(#{new_assignment.to_a.map{|k,v| "#{k}:#{v.inspect}"}.join(",")})"
|
|
1236
|
+
f.puts("#{doc_number};#{@num_docs};#{state_str};#{assignment_str};#{new_assignment_str};#{time}")
|
|
1169
1237
|
end
|
|
1170
1238
|
end
|
|
1171
1239
|
|
|
@@ -1246,7 +1314,8 @@ class CoreEnumerationAlgo
|
|
|
1246
1314
|
state = CoreEnumerationAlgo.parse_state(split[2])
|
|
1247
1315
|
assignment = CoreEnumerationAlgo.parse_assignment(split[3])
|
|
1248
1316
|
new_assignment = CoreEnumerationAlgo.parse_assignment(split[4])
|
|
1249
|
-
|
|
1317
|
+
processessing_time = Integer(split[5])
|
|
1318
|
+
return [doc_number, num_docs, state, assignment, new_assignment, processessing_time]
|
|
1250
1319
|
end
|
|
1251
1320
|
|
|
1252
1321
|
# TODO: logging
|
|
@@ -1259,16 +1328,17 @@ class CoreEnumerationAlgo
|
|
|
1259
1328
|
res = switched.times.to_a.map do |index|
|
|
1260
1329
|
var_xpath = vars[index][0]
|
|
1261
1330
|
"#{var_xpath}=#{assignment[var_xpath].inspect}"
|
|
1262
|
-
end + ["#{vars[switched][0]}
|
|
1331
|
+
end + ["#{vars[switched][0]}\{#{blocked.map{|v| v.inspect}.join(",")}}"]
|
|
1263
1332
|
"{#{res.join(", ")}}"
|
|
1264
1333
|
end
|
|
1334
|
+
|
|
1265
1335
|
|
|
1266
1336
|
# core algo
|
|
1267
|
-
def enumerate(progress, switched, vars, assignment, message, key, output_dir, config, options, state = [nil] * vars.size())
|
|
1268
|
-
enumerate(progress, switched+1, vars, assignment, message, key, output_dir, config, options, state) if switched < vars.size()-1
|
|
1337
|
+
def enumerate(progress, switched, vars, assignment, message, key, output_dir, config, options, state = [nil] * vars.size(), start_time, prior_processing_time)
|
|
1338
|
+
enumerate(progress, switched+1, vars, assignment, message, key, output_dir, config, options, state, start_time, prior_processing_time) if switched < vars.size()-1
|
|
1269
1339
|
is_finite_state = keys_are_finite_state(vars)
|
|
1270
1340
|
if state[switched] == nil then
|
|
1271
|
-
blocked = [assignment[vars[switched][0]]]
|
|
1341
|
+
blocked = [assignment[vars[switched][0]]]
|
|
1272
1342
|
state[switched] = blocked
|
|
1273
1343
|
else
|
|
1274
1344
|
blocked = state[switched] + [assignment[vars[switched][0]]]
|
|
@@ -1284,24 +1354,33 @@ class CoreEnumerationAlgo
|
|
|
1284
1354
|
output_doc_filename = prepare_output_doc_filename(@num_tries, output_dir)
|
|
1285
1355
|
new_assignment = generate_sparse_doc(progress, message, key, config, options, clauses, output_doc_filename)
|
|
1286
1356
|
generated_doc_number = @num_tries
|
|
1287
|
-
|
|
1357
|
+
time = prior_processing_time + (Time.now().to_i - options[:timestamp_now])
|
|
1358
|
+
store_continuation_point(generated_doc_number, state, assignment, new_assignment, "#{output_dir}/#{CONTINUATION_FILE}", options, time)
|
|
1359
|
+
progress.print_line "STORING continuation point"
|
|
1360
|
+
progress.print_line "|time=#{time}"
|
|
1361
|
+
progress.print_line "|doc_number=#{generated_doc_number}"
|
|
1362
|
+
progress.print_line "|num_docs=#{@num_docs}"
|
|
1363
|
+
progress.print_line "|state=#{state.inspect}"
|
|
1364
|
+
progress.print_line "|assignment=#{assignment.inspect}"
|
|
1365
|
+
progress.print_line "|new_assignment=#{new_assignment.inspect}"
|
|
1366
|
+
|
|
1288
1367
|
if is_finite_state then
|
|
1289
|
-
progress.report_progress(determine_progress(state, vars))
|
|
1368
|
+
progress.report_progress(determine_progress(state, vars), start_time, prior_processing_time, @num_docs)
|
|
1290
1369
|
else
|
|
1291
|
-
progress.report_progress(nil)
|
|
1370
|
+
progress.report_progress(nil, start_time, prior_processing_time, @num_docs)
|
|
1292
1371
|
end
|
|
1293
1372
|
@num_tries += 1
|
|
1294
1373
|
if new_assignment then # sat
|
|
1295
1374
|
@num_docs += 1
|
|
1296
1375
|
@on_new_doc.call(generated_doc_number, output_doc_filename, new_assignment) if @on_new_doc
|
|
1297
1376
|
if switched < vars.size()-1 then
|
|
1298
|
-
enumerate(progress, switched+1, vars, new_assignment, message, key, output_dir, config, options, state)
|
|
1377
|
+
enumerate(progress, switched+1, vars, new_assignment, message, key, output_dir, config, options, state, start_time, prior_processing_time)
|
|
1299
1378
|
end
|
|
1300
1379
|
blocked << new_assignment[vars[switched][0]]
|
|
1301
1380
|
state[switched] = blocked
|
|
1302
1381
|
found = true
|
|
1303
1382
|
else # unsat
|
|
1304
|
-
@on_unsat.call(generated_doc_number, log_filename_for_output_filename(output_doc_filename), display_combination_space(assignment, vars, switched, blocked)) if @on_unsat
|
|
1383
|
+
@on_unsat.call(generated_doc_number, log_filename_for_output_filename(output_doc_filename), display_combination_space(assignment, vars, switched, blocked), switched) if @on_unsat
|
|
1305
1384
|
found = false
|
|
1306
1385
|
end
|
|
1307
1386
|
end
|
|
@@ -1367,18 +1446,17 @@ class CoreEnumerationAlgo
|
|
|
1367
1446
|
return true
|
|
1368
1447
|
end
|
|
1369
1448
|
|
|
1370
|
-
def generate_docs_for_key(message, key, output_dir, config, options)
|
|
1449
|
+
def generate_docs_for_key(progress, message, key, output_dir, config, options)
|
|
1371
1450
|
|
|
1451
|
+
prior_processing_time = 0
|
|
1452
|
+
start_time = options[:timestamp_now]
|
|
1372
1453
|
number_of_combinations = calc_combinations(message, key)
|
|
1373
1454
|
if number_of_combinations.is_a?(String) then
|
|
1374
|
-
progress
|
|
1455
|
+
progress.set_max(100000000000000)
|
|
1375
1456
|
else
|
|
1376
|
-
progress
|
|
1377
|
-
end
|
|
1378
|
-
begin
|
|
1379
|
-
ensure
|
|
1457
|
+
progress.set_max(number_of_combinations)
|
|
1380
1458
|
end
|
|
1381
|
-
progress.report_progress(0.0)
|
|
1459
|
+
progress.report_progress(0.0, start_time, prior_processing_time, 0)
|
|
1382
1460
|
progress.print_line "generating sparse documents efficiently..."
|
|
1383
1461
|
progress.print_line "there is a theoretical maximum of #{number_of_combinations} combinations."
|
|
1384
1462
|
progress.print_line "Note, however, that we are generating only those documents that are valid, which are usually much less."
|
|
@@ -1414,28 +1492,27 @@ class CoreEnumerationAlgo
|
|
|
1414
1492
|
end
|
|
1415
1493
|
end
|
|
1416
1494
|
|
|
1417
|
-
#progress.print_line "DEBUG: key_fields_list=#{key_fields_list.inspect}"
|
|
1418
|
-
|
|
1419
1495
|
cont_filename = "#{output_dir}/#{CONTINUATION_FILE}"
|
|
1420
1496
|
if options[:continue] then
|
|
1421
1497
|
unless File.exist?(cont_filename) then
|
|
1422
1498
|
raise FatalError.new("FATAL: file #{cont_filename} not found. There seems to be no continuation point to continue from...")
|
|
1423
1499
|
end
|
|
1424
|
-
doc_number, num_docs, state, assignment, new_assignment = CoreEnumerationAlgo.parse_continuation_point(File.read(cont_filename).chomp)
|
|
1500
|
+
doc_number, num_docs, state, assignment, new_assignment, prior_processing_time = CoreEnumerationAlgo.parse_continuation_point(File.read(cont_filename).chomp)
|
|
1425
1501
|
if is_final_state(state, new_assignment, key_fields_list) then
|
|
1426
|
-
progress.report_progress(number_of_combinations)
|
|
1502
|
+
progress.report_progress(number_of_combinations, start_time, prior_processing_time, num_docs)
|
|
1427
1503
|
raise FatalError.new("FATAL: The enumeration is finished. There is nothing left to do. Resuming the enumeration hence does not make sense.")
|
|
1428
1504
|
end
|
|
1429
1505
|
progress.print_line "RESUMING enumeration from the following continuation point:"
|
|
1506
|
+
progress.print_line "|time=#{prior_processing_time}"
|
|
1430
1507
|
progress.print_line "|doc_number=#{doc_number}"
|
|
1431
1508
|
progress.print_line "|num_docs=#{num_docs}"
|
|
1432
1509
|
progress.print_line "|state=#{state.inspect}"
|
|
1433
1510
|
progress.print_line "|assignment=#{assignment.inspect}"
|
|
1434
1511
|
progress.print_line "|new_assignment=#{new_assignment.inspect}"
|
|
1435
1512
|
if keys_are_finite_state(key_fields_list) then
|
|
1436
|
-
progress.report_progress(determine_progress(state, key_fields_list))
|
|
1513
|
+
progress.report_progress(determine_progress(state, key_fields_list), start_time, prior_processing_time, num_docs)
|
|
1437
1514
|
else
|
|
1438
|
-
progress.report_progress(nil)
|
|
1515
|
+
progress.report_progress(nil, start_time, prior_processing_time, num_docs)
|
|
1439
1516
|
end
|
|
1440
1517
|
|
|
1441
1518
|
if new_assignment && !new_assignment.empty? then
|
|
@@ -1446,15 +1523,13 @@ class CoreEnumerationAlgo
|
|
|
1446
1523
|
@num_tries = doc_number+1 # we do not want to overwrite the last document written before the STOP
|
|
1447
1524
|
number_of_combinations += 1 # account for this in the safety mechanism
|
|
1448
1525
|
@num_docs = num_docs
|
|
1449
|
-
doc_counter = 0
|
|
1450
1526
|
File.open(log_filename, log_mode) do |log|
|
|
1451
1527
|
|
|
1452
1528
|
@on_new_doc = Proc.new { |num_doc, doc_filename, combination|
|
|
1453
1529
|
# combination logging
|
|
1454
|
-
log.puts "#{doc_filename} <- {#{combination.map{|k,v| "#{k}
|
|
1530
|
+
log.puts "#{doc_filename} <- {#{combination.map{|k,v| "#{k}=#{v.inspect}"}.join(", ")}}"
|
|
1455
1531
|
|
|
1456
|
-
|
|
1457
|
-
if options[:max_num_docs] && doc_counter >= options[:max_num_docs] then
|
|
1532
|
+
if options[:max_num_docs] && @num_docs >= options[:max_num_docs] then
|
|
1458
1533
|
raise NormalProgramTermination.new("-max-num-docs exceeded. Stopping!")
|
|
1459
1534
|
end
|
|
1460
1535
|
|
|
@@ -1463,33 +1538,27 @@ class CoreEnumerationAlgo
|
|
|
1463
1538
|
raise RuntimeError.new("This should never happen! Number of combinations exceeded. #{@num_docs} documents generated, but there are only #{number_of_combinations} possible combinations of the key vars.")
|
|
1464
1539
|
end
|
|
1465
1540
|
}
|
|
1466
|
-
@on_unsat = Proc.new { |num_doc, log_filename, combination_str|
|
|
1541
|
+
@on_unsat = Proc.new { |num_doc, log_filename, combination_str, switched|
|
|
1467
1542
|
log.puts "CONTRADICTION (see: #{log_filename}) <- #{combination_str}"
|
|
1468
1543
|
progress.print_line "doc#{num_doc} NOT generated"
|
|
1469
|
-
progress.print_line "No more valid combinations of the free vars could be found. -> Switching"
|
|
1544
|
+
progress.print_line "No more valid combinations of the free vars could be found. -> Switching (var #{switched})"
|
|
1470
1545
|
}
|
|
1471
1546
|
|
|
1472
1547
|
# core algo - resume from state
|
|
1473
|
-
enumerate(progress, 0, key_fields_list, assignment, message, key, output_dir, config, options, state)
|
|
1548
|
+
enumerate(progress, 0, key_fields_list, assignment, message, key, output_dir, config, options, state, start_time, prior_processing_time)
|
|
1474
1549
|
end
|
|
1475
1550
|
else
|
|
1476
1551
|
# init safety mechanism
|
|
1477
1552
|
@num_tries = 0
|
|
1478
1553
|
@num_docs = 0
|
|
1479
|
-
doc_counter = 0
|
|
1480
1554
|
# first try - a freely generated document
|
|
1481
1555
|
File.open(log_filename, log_mode) do |log|
|
|
1482
1556
|
output_doc_filename = prepare_output_doc_filename(@num_tries, output_dir)
|
|
1483
|
-
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}: #{v.inspect}"}.join(", ")}}"
|
|
1485
|
-
doc_counter += 1
|
|
1486
|
-
@num_tries += 1
|
|
1487
1557
|
@on_new_doc = Proc.new { |num_doc, doc_filename, combination|
|
|
1488
1558
|
# combination logging
|
|
1489
|
-
log.puts "#{doc_filename} <- {#{combination.map{|k,v| "#{k}
|
|
1559
|
+
log.puts "#{doc_filename} <- {#{combination.map{|k,v| "#{k}=#{v.inspect}"}.join(", ")}}"
|
|
1490
1560
|
|
|
1491
|
-
|
|
1492
|
-
if options[:max_num_docs] && doc_counter >= options[:max_num_docs] then
|
|
1561
|
+
if options[:max_num_docs] && @num_docs >= options[:max_num_docs] then
|
|
1493
1562
|
raise NormalProgramTermination.new("-max-num-docs exceeded. Stopping!")
|
|
1494
1563
|
end
|
|
1495
1564
|
|
|
@@ -1500,19 +1569,28 @@ class CoreEnumerationAlgo
|
|
|
1500
1569
|
end
|
|
1501
1570
|
end
|
|
1502
1571
|
}
|
|
1503
|
-
@on_unsat = Proc.new { |num_doc, log_filename, combination_str|
|
|
1572
|
+
@on_unsat = Proc.new { |num_doc, log_filename, combination_str, switched|
|
|
1504
1573
|
log.puts "CONTRADICTION (see: #{log_filename}) <- #{combination_str}"
|
|
1505
1574
|
progress.print_line "doc#{num_doc} NOT generated"
|
|
1506
|
-
progress.print_line "No more valid combinations of the free vars could be found. -> Switching"
|
|
1575
|
+
progress.print_line "No more valid combinations of the free vars could be found. -> Switching (var #{switched})"
|
|
1507
1576
|
}
|
|
1577
|
+
key_values = generate_doc_return_key_values(progress, message, key, output_doc_filename, config, options)
|
|
1578
|
+
@num_tries += 1
|
|
1579
|
+
if key_values then
|
|
1580
|
+
@num_docs += 1
|
|
1581
|
+
@on_new_doc.call(@num_tries, output_doc_filename, key_values)
|
|
1582
|
+
else
|
|
1583
|
+
# NOTE: this only occurs when the model is inconsistent and has no valid documents
|
|
1584
|
+
progress.print_line("ERROR: Model inconsistent - no valid documents could be found.")
|
|
1585
|
+
@on_unsat.call(@num_tries, log_filename_for_output_filename(output_doc_filename), "{}", 0)
|
|
1586
|
+
end
|
|
1508
1587
|
|
|
1509
1588
|
# core algo - start enumeration
|
|
1510
|
-
enumerate(progress, 0, key_fields_list, key_values, message, key, output_dir, config, options)
|
|
1589
|
+
enumerate(progress, 0, key_fields_list, key_values, message, key, output_dir, config, options, start_time, prior_processing_time)
|
|
1511
1590
|
end
|
|
1512
|
-
|
|
1513
|
-
progress.print_line "No more valid documents could be found. -> Exiting"
|
|
1514
|
-
progress.report_progress(number_of_combinations)
|
|
1515
1591
|
end
|
|
1592
|
+
progress.print_line "No more valid documents could be found. -> Exiting"
|
|
1593
|
+
progress.report_progress(number_of_combinations, start_time, prior_processing_time, @num_docs)
|
|
1516
1594
|
end
|
|
1517
1595
|
end
|
|
1518
1596
|
|
|
@@ -1585,6 +1663,211 @@ def validate_model(message, prefix=nil)
|
|
|
1585
1663
|
end
|
|
1586
1664
|
end
|
|
1587
1665
|
|
|
1666
|
+
def validate_doc_against_schema(struct, doc)
|
|
1667
|
+
root = doc.root
|
|
1668
|
+
sdef = struct[root.name.to_sym]
|
|
1669
|
+
unless sdef[:type] == :structure then
|
|
1670
|
+
puts "ERROR: schema validation failed: root element should be a structure"
|
|
1671
|
+
end
|
|
1672
|
+
# first, check that each doc element is defined for the struct
|
|
1673
|
+
validate_doc_struct_against_schema(sdef[:ref], root, root.name)
|
|
1674
|
+
# second, check that all required elements from the struct are actually present in the doc
|
|
1675
|
+
validate_schema_against_doc_struct(sdef[:ref], root, root.name)
|
|
1676
|
+
end
|
|
1677
|
+
|
|
1678
|
+
def validate_doc_struct_against_schema(struct, doc, prefix = nil)
|
|
1679
|
+
doc.children.to_a.filter{|x| x.is_a?(Nokogiri::XML::Element) }.each do |c|
|
|
1680
|
+
fdef = struct[c.name.to_sym]
|
|
1681
|
+
if prefix then
|
|
1682
|
+
xpath = "#{prefix}/#{c.name}"
|
|
1683
|
+
else
|
|
1684
|
+
xpath = c.name
|
|
1685
|
+
end
|
|
1686
|
+
if fdef
|
|
1687
|
+
if fdef[:type] == :structure then
|
|
1688
|
+
return unless validate_doc_struct_against_schema(fdef[:ref], c, xpath)
|
|
1689
|
+
end
|
|
1690
|
+
else
|
|
1691
|
+
if prefix then
|
|
1692
|
+
xpath = "#{prefix}/#{c.name}"
|
|
1693
|
+
else
|
|
1694
|
+
xpath = c.name
|
|
1695
|
+
end
|
|
1696
|
+
raise FatalError.new("ERROR: schema validation failed: #{c.name} of #{xpath} is present in document, but not defined in schema.")
|
|
1697
|
+
end
|
|
1698
|
+
end
|
|
1699
|
+
end
|
|
1700
|
+
|
|
1701
|
+
def validate_schema_against_doc_struct(struct, doc, prefix = nil)
|
|
1702
|
+
struct.each do |key, value|
|
|
1703
|
+
next if key == :additional_smtlib || key == :validation_rules || key == :predicates || key == :parent_link || key == :struct_xpath_prefix
|
|
1704
|
+
if prefix then
|
|
1705
|
+
xpath = "#{prefix}/#{key}"
|
|
1706
|
+
else
|
|
1707
|
+
xpath = key
|
|
1708
|
+
end
|
|
1709
|
+
if value[:type] == :structure then
|
|
1710
|
+
node = doc.at_xpath(key.to_s)
|
|
1711
|
+
if node then
|
|
1712
|
+
validate_schema_against_doc_struct(value[:ref], node, xpath)
|
|
1713
|
+
else
|
|
1714
|
+
if value[:optional] == false then
|
|
1715
|
+
raise FatalError.new("ERROR: schema validation failed: non-optional structure #{xpath} not found in doc.")
|
|
1716
|
+
end
|
|
1717
|
+
end
|
|
1718
|
+
elsif value[:type] == :field then
|
|
1719
|
+
if value[:optional] == false then
|
|
1720
|
+
unless doc.at_xpath(key.to_s) then
|
|
1721
|
+
raise FatalError.new("ERROR: schema validation failed: non-optional field #{xpath} not found in doc.")
|
|
1722
|
+
end
|
|
1723
|
+
end
|
|
1724
|
+
elsif value[:type] == :list then
|
|
1725
|
+
if value[:optional] == false then
|
|
1726
|
+
unless doc.at_xpath(key.to_s) then
|
|
1727
|
+
raise FatalError.new("ERROR: schema validation failed: non-optional list #{xpath} not found in doc.")
|
|
1728
|
+
end
|
|
1729
|
+
end
|
|
1730
|
+
list = doc.at_xpath(key.to_s)
|
|
1731
|
+
if list then
|
|
1732
|
+
list.children.to_a.each do |c|
|
|
1733
|
+
next unless c.is_a?(Nokogiri::XML::Element)
|
|
1734
|
+
unless c.name == value[:xpath_element] then
|
|
1735
|
+
raise FatalError.new("ERROR: schema validation failed: encountered element #{c.name} in list #{xpath}, which should only contain #{value[:xpath_element]} elements.")
|
|
1736
|
+
end
|
|
1737
|
+
end
|
|
1738
|
+
end
|
|
1739
|
+
else
|
|
1740
|
+
raise RuntimeError.new("Model error: encountered element #{xpath} of unknown type: #{value[:type]}.")
|
|
1741
|
+
end
|
|
1742
|
+
end
|
|
1743
|
+
end
|
|
1744
|
+
|
|
1745
|
+
def translate_doc_to_SMTLIB(progress, solver, struct, doc, hash = {})
|
|
1746
|
+
options = hash[:options]
|
|
1747
|
+
config = hash[:config]
|
|
1748
|
+
|
|
1749
|
+
solver.to_solver(progress, "; --- Document: ---")
|
|
1750
|
+
translate_struct_in_doc_to_SMTLIB(progress, solver, struct, doc, nil, hash)
|
|
1751
|
+
end
|
|
1752
|
+
|
|
1753
|
+
def translate_struct_in_doc_to_SMTLIB(progress, solver, struct, doc, prefix, hash = {})
|
|
1754
|
+
options = hash[:options]
|
|
1755
|
+
config = hash[:config]
|
|
1756
|
+
|
|
1757
|
+
struct.each do |key, value|
|
|
1758
|
+
next if key == :additional_smtlib || key == :validation_rules || key == :predicates || key == :parent_link || key == :struct_xpath_prefix
|
|
1759
|
+
if prefix then
|
|
1760
|
+
xpath = "#{prefix}/#{key.to_s}"
|
|
1761
|
+
else
|
|
1762
|
+
xpath = key.to_s
|
|
1763
|
+
end
|
|
1764
|
+
if value[:type] == :structure then
|
|
1765
|
+
node = doc.at_xpath(xpath)
|
|
1766
|
+
if node then
|
|
1767
|
+
info = {
|
|
1768
|
+
:assertion_type => :doc_structure_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)} true) :named #{assertionID}))")
|
|
1774
|
+
|
|
1775
|
+
translate_struct_in_doc_to_SMTLIB(progress, solver, value[:ref], doc, xpath, hash)
|
|
1776
|
+
else
|
|
1777
|
+
info = {
|
|
1778
|
+
:assertion_type => :doc_structure_not_exists,
|
|
1779
|
+
:xpath => xpath,
|
|
1780
|
+
}
|
|
1781
|
+
assertionID = "doc-#{xpath.gsub('/', '-')}"
|
|
1782
|
+
solver.associateAssertionIDWith(assertionID, info)
|
|
1783
|
+
solver.to_solver(progress, "(assert (! (= #{exists_var_for_structure(xpath)} false) :named #{assertionID}))")
|
|
1784
|
+
end
|
|
1785
|
+
elsif value[:type] == :field then
|
|
1786
|
+
node = doc.at_xpath(xpath)
|
|
1787
|
+
if node then
|
|
1788
|
+
fvalue = node.text
|
|
1789
|
+
info = {
|
|
1790
|
+
:assertion_type => :doc_field_filled,
|
|
1791
|
+
:xpath => xpath,
|
|
1792
|
+
:value => fvalue
|
|
1793
|
+
}
|
|
1794
|
+
fvalue_str = value_of_type(fvalue, value[:datatype])
|
|
1795
|
+
assertionID = "doc-#{xpath.gsub('/', '-')}"
|
|
1796
|
+
solver.associateAssertionIDWith(assertionID, info)
|
|
1797
|
+
solver.to_solver(progress, "(assert (! (and (= #{filled_var_for_field(xpath)} true) (= #{value_var_for_field(xpath)} #{fvalue_str})) :named #{assertionID}))")
|
|
1798
|
+
else
|
|
1799
|
+
info = {
|
|
1800
|
+
:assertion_type => :doc_field_empty,
|
|
1801
|
+
:xpath => xpath,
|
|
1802
|
+
}
|
|
1803
|
+
assertionID = "doc-#{xpath.gsub('/', '-')}"
|
|
1804
|
+
solver.associateAssertionIDWith(assertionID, info)
|
|
1805
|
+
solver.to_solver(progress, "(assert (! (= #{filled_var_for_field(xpath)} false) :named #{assertionID}))")
|
|
1806
|
+
end
|
|
1807
|
+
elsif value[:type] == :list then
|
|
1808
|
+
node = doc.at_xpath(xpath)
|
|
1809
|
+
if node then
|
|
1810
|
+
fvalues = []
|
|
1811
|
+
node.children.to_a.each do |c|
|
|
1812
|
+
next unless c.is_a?(Nokogiri::XML::Element)
|
|
1813
|
+
fvalues << c.text
|
|
1814
|
+
end
|
|
1815
|
+
info = {
|
|
1816
|
+
:assertion_type => :doc_list_filled,
|
|
1817
|
+
:xpath => xpath,
|
|
1818
|
+
:values => fvalues
|
|
1819
|
+
}
|
|
1820
|
+
fvalue_str = value_of_type(fvalues, value[:datatype])
|
|
1821
|
+
assertionID = "doc-#{xpath.gsub('/', '-')}"
|
|
1822
|
+
solver.associateAssertionIDWith(assertionID, info)
|
|
1823
|
+
clauses = []
|
|
1824
|
+
fvalues.each_with_index do |fvalue, index|
|
|
1825
|
+
clauses << "(= #{value_var_for_list(xpath,index)} #{value_of_type(fvalue, value[:datatype])})"
|
|
1826
|
+
end
|
|
1827
|
+
solver.to_solver(progress, "(assert (! (and (= #{filled_var_for_field(xpath)} true) (= #{size_var_for_list(xpath)} #{fvalues.size}) #{clauses.join(' ')}) :named #{assertionID}))")
|
|
1828
|
+
else
|
|
1829
|
+
info = {
|
|
1830
|
+
:assertion_type => :doc_field_empty,
|
|
1831
|
+
:xpath => xpath,
|
|
1832
|
+
}
|
|
1833
|
+
assertionID = "doc-#{xpath.gsub('/', '-')}"
|
|
1834
|
+
solver.associateAssertionIDWith(assertionID, info)
|
|
1835
|
+
solver.to_solver(progress, "(assert (! (= #{filled_var_for_field(xpath)} false) :named #{assertionID}))")
|
|
1836
|
+
end
|
|
1837
|
+
else
|
|
1838
|
+
raise RuntimeError.new("Model Error: encountered unknown type #{value[:type].inspect}")
|
|
1839
|
+
end
|
|
1840
|
+
end
|
|
1841
|
+
end
|
|
1842
|
+
|
|
1843
|
+
def validate_doc(progress, struct, doc, options, config)
|
|
1844
|
+
raise RuntimeError.new("option :solverFactory is required") unless options[:solverFactory]
|
|
1845
|
+
options[:solverLog] = "validation.smt2"
|
|
1846
|
+
|
|
1847
|
+
solverFactory = options[:solverFactory]
|
|
1848
|
+
result = solverFactory.query(options) do |solver|
|
|
1849
|
+
set_standard_options(progress, solver)
|
|
1850
|
+
translate_structure_to_SMTLIB(progress, solver, struct, :options => options, :config => config)
|
|
1851
|
+
translate_doc_to_SMTLIB(progress, solver, struct, doc, :options => options, :config => config)
|
|
1852
|
+
solver.to_solver(progress, "")
|
|
1853
|
+
end
|
|
1854
|
+
unless result == true then
|
|
1855
|
+
raise FatalError.new("VALIDATION FAILED! SMTLIB log was written to #{options[:solverLog]}.\n\n#{result}")
|
|
1856
|
+
end
|
|
1857
|
+
|
|
1858
|
+
return true
|
|
1859
|
+
end
|
|
1860
|
+
|
|
1861
|
+
def generate_unused_logfile_name()
|
|
1862
|
+
number = 0
|
|
1863
|
+
result = "logs/#{number}.log"
|
|
1864
|
+
while File.exist?(result) do
|
|
1865
|
+
number += 1
|
|
1866
|
+
result = "logs/#{number}.log"
|
|
1867
|
+
end
|
|
1868
|
+
result
|
|
1869
|
+
end
|
|
1870
|
+
|
|
1588
1871
|
def generate(message, argv, message_config_filename = nil)
|
|
1589
1872
|
if ARGV[0] == "--help" || ARGV[0] == "-help" || ARGV[0] == "help" then
|
|
1590
1873
|
puts USAGE
|
|
@@ -1617,6 +1900,12 @@ def generate(message, argv, message_config_filename = nil)
|
|
|
1617
1900
|
elsif opt == "-max-num-docs" then
|
|
1618
1901
|
options[:max_num_docs] = Integer(argv.shift)
|
|
1619
1902
|
puts "flag -max-num-docs used. Will stop after generating #{options[:max_num_docs]} documents."
|
|
1903
|
+
elsif opt == "-validate" then
|
|
1904
|
+
if options.has_key?(:validate_files) then
|
|
1905
|
+
options[:validate_files] << argv.shift
|
|
1906
|
+
else
|
|
1907
|
+
options[:validate_files] = [argv.shift]
|
|
1908
|
+
end
|
|
1620
1909
|
else
|
|
1621
1910
|
puts "FATAL: unknown option #{opt.inspect}."
|
|
1622
1911
|
puts USAGE
|
|
@@ -1625,51 +1914,63 @@ def generate(message, argv, message_config_filename = nil)
|
|
|
1625
1914
|
opt = argv.shift
|
|
1626
1915
|
end
|
|
1627
1916
|
|
|
1628
|
-
|
|
1629
|
-
|
|
1917
|
+
ProgressBar.logging(1, generate_unused_logfile_name()) do |progress|
|
|
1918
|
+
unless options[:negated_validation_rules].empty? then
|
|
1919
|
+
progress.print_line "negating rules: #{options[:negated_validation_rules].join(", ")}"
|
|
1920
|
+
|
|
1921
|
+
rules = collect_validation_rule_names(message)
|
|
1922
|
+
unknown_negated_rules = options[:negated_validation_rules].filter { |rule_name| !rules.include?(rule_name) }
|
|
1923
|
+
unless unknown_negated_rules.empty? then
|
|
1924
|
+
throw RuntimeError.new("unknown negated rules #{unknown_negated_rules.join(", ")}")
|
|
1925
|
+
end
|
|
1630
1926
|
|
|
1631
|
-
rules = collect_validation_rule_names(message)
|
|
1632
|
-
unknown_negated_rules = options[:negated_validation_rules].filter { |rule_name| !rules.include?(rule_name) }
|
|
1633
|
-
unless unknown_negated_rules.empty? then
|
|
1634
|
-
throw RuntimeError.new("unknown negated rules #{unknown_negated_rules.join(", ")}")
|
|
1635
1927
|
end
|
|
1636
1928
|
|
|
1637
|
-
|
|
1929
|
+
if File.exist?(CONFIG_FILE) then
|
|
1930
|
+
config = YAML.load_file(CONFIG_FILE)
|
|
1931
|
+
else
|
|
1932
|
+
config = DEFAULT_CONFIG
|
|
1933
|
+
File.open(CONFIG_FILE, "w") do |f|
|
|
1934
|
+
f.puts(config.to_yaml)
|
|
1935
|
+
end
|
|
1936
|
+
progress.print_line "No config file found. Default config written to #{CONFIG_FILE}"
|
|
1937
|
+
end
|
|
1938
|
+
if message_config_filename then
|
|
1939
|
+
message_config = YAML.load_file(message_config_filename)
|
|
1940
|
+
config = config.merge(message_config)
|
|
1941
|
+
end
|
|
1638
1942
|
|
|
1639
|
-
|
|
1640
|
-
|
|
1641
|
-
|
|
1642
|
-
|
|
1643
|
-
|
|
1644
|
-
|
|
1645
|
-
|
|
1646
|
-
|
|
1647
|
-
|
|
1648
|
-
|
|
1649
|
-
|
|
1650
|
-
|
|
1651
|
-
|
|
1652
|
-
|
|
1653
|
-
|
|
1654
|
-
|
|
1655
|
-
|
|
1656
|
-
|
|
1657
|
-
|
|
1658
|
-
|
|
1659
|
-
|
|
1660
|
-
|
|
1661
|
-
|
|
1662
|
-
|
|
1663
|
-
|
|
1664
|
-
|
|
1665
|
-
|
|
1666
|
-
|
|
1667
|
-
|
|
1668
|
-
|
|
1669
|
-
|
|
1670
|
-
|
|
1671
|
-
validate_model(message)
|
|
1672
|
-
progress = ProgressBar.new(1)
|
|
1673
|
-
generate_doc(progress, message, OUTPUT_FILE, config, options)
|
|
1943
|
+
introduce_parent_links(message)
|
|
1944
|
+
z3path = config[:z3path] || DEFAULT_Z3_PATH
|
|
1945
|
+
options[:solverFactory] = SolverFactory.new(progress, z3path)
|
|
1946
|
+
|
|
1947
|
+
if options[:validate_files] then
|
|
1948
|
+
progress.print_line "using #{options[:date_now]} as [Date.now]"
|
|
1949
|
+
progress.print_line "using #{options[:timestamp_now]} as [Timestamp.now]"
|
|
1950
|
+
options[:validate_files].each do |filename|
|
|
1951
|
+
progress.print_line "validating file #{filename}"
|
|
1952
|
+
doc = Nokogiri::XML(File.read(filename))
|
|
1953
|
+
validate_doc_against_schema(message, doc)
|
|
1954
|
+
validate_doc(progress, message, doc, options, config)
|
|
1955
|
+
end
|
|
1956
|
+
elsif options[:count_docs_for_key] then
|
|
1957
|
+
progress.print_line "there are #{calc_combinations(message, options[:count_docs_for_key])} combinations."
|
|
1958
|
+
elsif options[:docs_for_key] then
|
|
1959
|
+
progress.print_line "using #{options[:date_now]} as [Date.now]"
|
|
1960
|
+
progress.print_line "using #{options[:timestamp_now]} as [Timestamp.now]"
|
|
1961
|
+
validate_model(message)
|
|
1962
|
+
CoreEnumerationAlgo.new().generate_docs_for_key(progress, message, options[:docs_for_key], OUTPUT_DIR, config, options)
|
|
1963
|
+
elsif options[:list_keys] then
|
|
1964
|
+
list_keys(message)
|
|
1965
|
+
elsif options[:list] then
|
|
1966
|
+
list_fields_and_structures(message, options[:list])
|
|
1967
|
+
elsif options[:list_validation_rules] then
|
|
1968
|
+
list_validation_rules(message)
|
|
1969
|
+
else
|
|
1970
|
+
progress.print_line "using #{options[:date_now]} as [Date.now]"
|
|
1971
|
+
progress.print_line "using #{options[:timestamp_now]} as [Timestamp.now]"
|
|
1972
|
+
validate_model(message)
|
|
1973
|
+
generate_doc(progress, message, OUTPUT_FILE, config, options)
|
|
1974
|
+
end
|
|
1674
1975
|
end
|
|
1675
1976
|
end
|