mbt-gen 0.0.8 → 0.0.10
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 +257 -5
- metadata +2 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 77cbf93998b731d074f62e0b364c20f87f324c8409e0e223193e9d8c0a0ddc52
|
|
4
|
+
data.tar.gz: 98985c6b4ace9d25b36fc73d9110f50368bc8badff62918b52a09557c0c52bd3
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: aad6c3b1ea3c63b7510cfc214a020fc8cf91e8f2cb97f2da44c39543d350524a62999440c2a97ea9ecfbdc3c036365667d8853150f6eeda485bf07d464b09fa1
|
|
7
|
+
data.tar.gz: e8d4adb8a2ea41d6a5200ca4002245afe66ab5779539567d18c206323d1c7c45226ac9273569609e2ed12ccfb3bc434c31ab8c4717bc81d639a4f1c5020cdb0f
|
data/lib/mbt-gen.rb
CHANGED
|
@@ -18,6 +18,7 @@ DEFAULT_Z3_PATH = "z3"
|
|
|
18
18
|
|
|
19
19
|
CONFIG_FILE = "config.yml"
|
|
20
20
|
OUTPUT_FILE = "result.xml"
|
|
21
|
+
BASE_FILENAME = "doc-base.xml"
|
|
21
22
|
CONTINUATION_FILE = "continuation.log"
|
|
22
23
|
COMBINATION_LOG = "combinations.log"
|
|
23
24
|
VARS_LOG = "vars.log"
|
|
@@ -51,6 +52,19 @@ GENERATING A SINGLE MESSAGE:
|
|
|
51
52
|
-negate <validation_rule> - negates the named validation rule. This causes the resulting message to still satisfy all other validation rules, but to violate this one.
|
|
52
53
|
NOTE: the result can be found in #{OUTPUT_FILE}
|
|
53
54
|
|
|
55
|
+
GENERATING MESSAGES FOR SPECIAL REQUIREMENTS:
|
|
56
|
+
|
|
57
|
+
ruby #{$PROGRAM_NAME} -generate-max
|
|
58
|
+
- generates a message of maximum size. The message will be valid XML and will adhere to the schema, but it will
|
|
59
|
+
not neccessarily satisfy all validation rules. It will contain all structures, all optional fields and all lists of maximum size.
|
|
60
|
+
|
|
61
|
+
ruby #{$PROGRAM_NAME} -generate-equality-test-data
|
|
62
|
+
- generates a base message of maximum size (see -generate-max option) as well as the following test messages for each field and structure
|
|
63
|
+
- one test message in which the field contains a different value (only for fields)
|
|
64
|
+
- one test message in which the field/structure is empty in case it is optional
|
|
65
|
+
NOTE: all the other fields and structures will be the same as in the base message. This kind of test data is useful for
|
|
66
|
+
testing implementations of equality and comparison functionality on the specified messages.
|
|
67
|
+
|
|
54
68
|
GENERATING MESSAGES FOR ALL COMBINATIONS:
|
|
55
69
|
|
|
56
70
|
ruby #{$PROGRAM_NAME} -list-keys
|
|
@@ -772,6 +786,7 @@ def parse_field_value(fielddef, value)
|
|
|
772
786
|
when :enum
|
|
773
787
|
return solver_value_to_string(value)
|
|
774
788
|
when :date
|
|
789
|
+
puts "DEBUG: parsing date #{value.inspect}"
|
|
775
790
|
return Date.parse(value)
|
|
776
791
|
when :timestamp
|
|
777
792
|
return Time.new(value)
|
|
@@ -799,6 +814,91 @@ def format_solver_value(fielddef, solver_value)
|
|
|
799
814
|
end
|
|
800
815
|
end
|
|
801
816
|
|
|
817
|
+
def build_max_doc(progress, struct)
|
|
818
|
+
doc = Nokogiri::XML(XML_TEMPLATE)
|
|
819
|
+
add_max_struct_to_doc(progress, struct, doc)
|
|
820
|
+
return doc
|
|
821
|
+
end
|
|
822
|
+
|
|
823
|
+
def pick_solver_value_for_field(field_desc)
|
|
824
|
+
if field_desc[:datatype] == :int then
|
|
825
|
+
if field_desc[:min] then
|
|
826
|
+
field_desc[:min].to_s
|
|
827
|
+
elsif field_desc[:max] then
|
|
828
|
+
field_desc[:max].to_s
|
|
829
|
+
else
|
|
830
|
+
"0"
|
|
831
|
+
end
|
|
832
|
+
elsif field_desc[:datatype] == :string then
|
|
833
|
+
# TODO: when a string field has a :regexp, use the solver to find a value satisfying it
|
|
834
|
+
if field_desc[:maxLength] then
|
|
835
|
+
"a" * field_desc[:maxLength]
|
|
836
|
+
else
|
|
837
|
+
"default"
|
|
838
|
+
end
|
|
839
|
+
elsif field_desc[:datatype] == :enum then
|
|
840
|
+
field_desc[:values].first.to_s
|
|
841
|
+
elsif field_desc[:datatype] == :date then
|
|
842
|
+
Time.at(0).to_date.to_s
|
|
843
|
+
elsif field_desc[:datatype] == :timestamp then
|
|
844
|
+
Time.at(0).to_s
|
|
845
|
+
else
|
|
846
|
+
raise RuntimeError.new("encountered unknown :datatype #{field_desc[:datatype].inspect}")
|
|
847
|
+
end
|
|
848
|
+
end
|
|
849
|
+
|
|
850
|
+
def add_max_struct_to_doc(progress, struct, doc, prefix = nil)
|
|
851
|
+
struct.each do |key, value|
|
|
852
|
+
next if key == :validation_rules || key == :predicates || key == :parent_link || key == :struct_xpath_prefix || key == :additional_smtlib
|
|
853
|
+
if prefix then
|
|
854
|
+
xpath = "#{prefix}/#{key}"
|
|
855
|
+
else
|
|
856
|
+
xpath = key.to_s
|
|
857
|
+
end
|
|
858
|
+
if value[:type] == :field then
|
|
859
|
+
val = pick_solver_value_for_field(value)
|
|
860
|
+
fvalue_str = format_solver_value(value, val)
|
|
861
|
+
doc.add_child("<#{key}>#{fvalue_str}</#{key}>")
|
|
862
|
+
elsif value[:type] == :list then
|
|
863
|
+
node = doc.add_child("<#{key}></#{key}>")
|
|
864
|
+
if node.is_a?(Nokogiri::XML::NodeSet) then
|
|
865
|
+
node = node.first
|
|
866
|
+
end
|
|
867
|
+
xpath_element = value[:xpath_element]
|
|
868
|
+
if Integer(value[:model_maxLength]) > 0 then
|
|
869
|
+
Integer(value[:model_maxLength]).times do |i|
|
|
870
|
+
node.add_child("<#{xpath_element}>#{format_solver_value(value, pick_solver_value_for_field(value))}</#{xpath_element}>")
|
|
871
|
+
end
|
|
872
|
+
end
|
|
873
|
+
elsif value[:type] == :objlist then
|
|
874
|
+
node = doc.add_child("<#{key}></#{key}>")
|
|
875
|
+
if node.is_a?(Nokogiri::XML::NodeSet) then
|
|
876
|
+
node = node.first
|
|
877
|
+
end
|
|
878
|
+
xpath_element = value[:xpath_element]
|
|
879
|
+
value[:model_maxLength].times do |index|
|
|
880
|
+
cnode = node.add_child("<#{xpath_element}></#{xpath_element}>")
|
|
881
|
+
if cnode.is_a?(Nokogiri::XML::NodeSet) then
|
|
882
|
+
cnode = cnode.first
|
|
883
|
+
end
|
|
884
|
+
add_max_struct_to_doc(progress, value[:ref], cnode, "#{xpath}/#{index}")
|
|
885
|
+
end
|
|
886
|
+
elsif value[:type] == :structure then
|
|
887
|
+
unless value[:ref] then
|
|
888
|
+
raise RuntimeError.new("struct is broken: :structure #{key.inspect} does not have a :ref")
|
|
889
|
+
end
|
|
890
|
+
node = doc.add_child("<#{key}></#{key}>")
|
|
891
|
+
if node.is_a?(Nokogiri::XML::NodeSet) then
|
|
892
|
+
node = node.first
|
|
893
|
+
end
|
|
894
|
+
add_max_struct_to_doc(progress, value[:ref], node, xpath)
|
|
895
|
+
else
|
|
896
|
+
raise RuntimeError.new("struct is broken: encountered unknown :type #{value[:type].inspect}")
|
|
897
|
+
end
|
|
898
|
+
end
|
|
899
|
+
end
|
|
900
|
+
|
|
901
|
+
|
|
802
902
|
def build_document_from_model(progress, struct, model)
|
|
803
903
|
doc = Nokogiri::XML(XML_TEMPLATE)
|
|
804
904
|
add_model_struct_to_doc(progress, struct, doc, model)
|
|
@@ -888,6 +988,148 @@ def set_standard_options(progress, solver)
|
|
|
888
988
|
solver.to_solver(progress, "")
|
|
889
989
|
end
|
|
890
990
|
|
|
991
|
+
def each_structure_and_field_in(struct, prefix = nil, &block)
|
|
992
|
+
struct.each do |key, value|
|
|
993
|
+
next if key == :validation_rules || key == :predicates || key == :parent_link || key == :struct_xpath_prefix || key == :additional_smtlib
|
|
994
|
+
|
|
995
|
+
if prefix then
|
|
996
|
+
xpath = "#{prefix}/#{key}"
|
|
997
|
+
else
|
|
998
|
+
xpath = key
|
|
999
|
+
end
|
|
1000
|
+
|
|
1001
|
+
if value[:type] == :field then
|
|
1002
|
+
block.call(value, xpath)
|
|
1003
|
+
elsif value[:type] == :list then
|
|
1004
|
+
block.call(value, xpath)
|
|
1005
|
+
value[:model_maxLength].times do |i|
|
|
1006
|
+
cvalue = value.clone()
|
|
1007
|
+
cvalue[:element] = true
|
|
1008
|
+
block.call(cvalue, xpath + "/#{value[:xpath_element]}[#{i+1}]")
|
|
1009
|
+
end
|
|
1010
|
+
elsif value[:type] == :objlist then
|
|
1011
|
+
block.call(value, xpath)
|
|
1012
|
+
value[:model_maxLength].times do |i|
|
|
1013
|
+
cxpath = xpath + "/#{value[:xpath_element]}[#{i+1}]"
|
|
1014
|
+
cvalue = value.clone()
|
|
1015
|
+
cvalue[:element] = true
|
|
1016
|
+
block.call(cvalue, cxpath)
|
|
1017
|
+
each_structure_and_field_in(value[:ref], cxpath, &block)
|
|
1018
|
+
end
|
|
1019
|
+
elsif value[:type] == :structure then
|
|
1020
|
+
block.call(value, xpath)
|
|
1021
|
+
each_structure_and_field_in(value[:ref], xpath, &block)
|
|
1022
|
+
else
|
|
1023
|
+
raise RuntimeError.new("encountered unknown :type #{value[:type].inspect}")
|
|
1024
|
+
end
|
|
1025
|
+
end
|
|
1026
|
+
end
|
|
1027
|
+
|
|
1028
|
+
def xpath_to_filename(xpath)
|
|
1029
|
+
xpath.gsub("/", "-").gsub(/\[\d*\]/) {|s| if s =~ /\[(\d*)\]/ then "-#{$1}-" else s end }
|
|
1030
|
+
end
|
|
1031
|
+
|
|
1032
|
+
def generate_equality_test_data(progress, message, output_dir, config, options)
|
|
1033
|
+
unless Dir.exist?(output_dir)
|
|
1034
|
+
FileUtils.mkdir_p(output_dir)
|
|
1035
|
+
end
|
|
1036
|
+
|
|
1037
|
+
base_filename = "#{output_dir}/#{BASE_FILENAME}"
|
|
1038
|
+
generate_max_doc(progress, message, base_filename, config, options)
|
|
1039
|
+
|
|
1040
|
+
base_doc = Nokogiri::XML(File.read(base_filename))
|
|
1041
|
+
|
|
1042
|
+
each_structure_and_field_in(message) do |desc, xpath|
|
|
1043
|
+
if desc[:type] == :field then
|
|
1044
|
+
mutated_doc = base_doc.clone()
|
|
1045
|
+
node = mutated_doc.at_xpath(xpath)
|
|
1046
|
+
unless node then
|
|
1047
|
+
raise RuntimeError.new("xpath #{xpath.inspect} could not be found in base_doc")
|
|
1048
|
+
end
|
|
1049
|
+
node.inner_html = node.inner_html + "x"
|
|
1050
|
+
File.open("#{output_dir}/doc-mutated-#{xpath_to_filename(xpath)}.xml", "w") do |f|
|
|
1051
|
+
f.puts(mutated_doc.to_xml)
|
|
1052
|
+
end
|
|
1053
|
+
if desc[:optional] == true then
|
|
1054
|
+
mutated_doc = base_doc.clone()
|
|
1055
|
+
node = mutated_doc.at_xpath(xpath)
|
|
1056
|
+
unless node then
|
|
1057
|
+
raise RuntimeError.new("xpath #{xpath.inspect} could not be found in base_doc")
|
|
1058
|
+
end
|
|
1059
|
+
node.remove()
|
|
1060
|
+
File.open("#{output_dir}/doc-removed-#{xpath_to_filename(xpath)}.xml", "w") do |f|
|
|
1061
|
+
f.puts(mutated_doc.to_xml)
|
|
1062
|
+
end
|
|
1063
|
+
end
|
|
1064
|
+
elsif desc[:type] == :list then
|
|
1065
|
+
if desc[:element] then
|
|
1066
|
+
# list elements
|
|
1067
|
+
mutated_doc = base_doc.clone()
|
|
1068
|
+
node = mutated_doc.at_xpath(xpath)
|
|
1069
|
+
unless node then
|
|
1070
|
+
raise RuntimeError.new("xpath #{xpath.inspect} could not be found in base_doc")
|
|
1071
|
+
end
|
|
1072
|
+
node.inner_html = node.inner_html + "x"
|
|
1073
|
+
File.open("#{output_dir}/doc-mutated-#{xpath_to_filename(xpath)}.xml", "w") do |f|
|
|
1074
|
+
f.puts(mutated_doc.to_xml)
|
|
1075
|
+
end
|
|
1076
|
+
else
|
|
1077
|
+
# the list itself
|
|
1078
|
+
mutated_doc = base_doc.clone()
|
|
1079
|
+
node = mutated_doc.at_xpath(xpath)
|
|
1080
|
+
unless node then
|
|
1081
|
+
raise RuntimeError.new("xpath #{xpath.inspect} could not be found in base_doc")
|
|
1082
|
+
end
|
|
1083
|
+
node.remove()
|
|
1084
|
+
File.open("#{output_dir}/doc-removed-#{xpath_to_filename(xpath)}.xml", "w") do |f|
|
|
1085
|
+
f.puts(mutated_doc.to_xml)
|
|
1086
|
+
end
|
|
1087
|
+
end
|
|
1088
|
+
elsif desc[:type] == :objlist then
|
|
1089
|
+
if desc[:element] then
|
|
1090
|
+
# list elements
|
|
1091
|
+
# no additional test data neccessary as the element objects will contain fields that are modified already
|
|
1092
|
+
else
|
|
1093
|
+
# the list itself
|
|
1094
|
+
mutated_doc = base_doc.clone()
|
|
1095
|
+
node = mutated_doc.at_xpath(xpath)
|
|
1096
|
+
unless node then
|
|
1097
|
+
raise RuntimeError.new("xpath #{xpath.inspect} could not be found in base_doc")
|
|
1098
|
+
end
|
|
1099
|
+
node.remove()
|
|
1100
|
+
File.open("#{output_dir}/doc-removed-#{xpath_to_filename(xpath)}.xml", "w") do |f|
|
|
1101
|
+
f.puts(mutated_doc.to_xml)
|
|
1102
|
+
end
|
|
1103
|
+
end
|
|
1104
|
+
elsif desc[:type] == :structure then
|
|
1105
|
+
if desc[:optional] == true then
|
|
1106
|
+
mutated_doc = base_doc.clone()
|
|
1107
|
+
node = mutated_doc.at_xpath(xpath)
|
|
1108
|
+
unless node then
|
|
1109
|
+
raise RuntimeError.new("xpath #{xpath.inspect} could not be found in base_doc")
|
|
1110
|
+
end
|
|
1111
|
+
node.remove()
|
|
1112
|
+
File.open("#{output_dir}/doc-removed-#{xpath_to_filename(xpath)}.xml", "w") do |f|
|
|
1113
|
+
f.puts(mutated_doc.to_xml)
|
|
1114
|
+
end
|
|
1115
|
+
end
|
|
1116
|
+
else
|
|
1117
|
+
raise RuntimeError.new("encountered unknown :type #{desc[:type].inspect}")
|
|
1118
|
+
end
|
|
1119
|
+
end
|
|
1120
|
+
end
|
|
1121
|
+
|
|
1122
|
+
def generate_max_doc(progress, message, output_filename, config, options)
|
|
1123
|
+
result_doc = build_max_doc(progress, message)
|
|
1124
|
+
dir = File.dirname(output_filename)
|
|
1125
|
+
FileUtils.mkdir_p(dir) unless Dir.exist?(dir)
|
|
1126
|
+
File.open(output_filename, "w") do |f|
|
|
1127
|
+
f.puts result_doc.to_xml()
|
|
1128
|
+
end
|
|
1129
|
+
progress.print_line "DONE. Generated XML was written to #{output_filename}."
|
|
1130
|
+
return true
|
|
1131
|
+
end
|
|
1132
|
+
|
|
891
1133
|
def generate_doc(progress, message, output_filename, config, options)
|
|
892
1134
|
raise RuntimeError.new("option :solverFactory is required") unless options[:solverFactory]
|
|
893
1135
|
log = options[:log] || nil
|
|
@@ -1978,7 +2220,7 @@ def translate_struct_in_doc_to_SMTLIB(progress, solver, struct, doc, prefix, var
|
|
|
1978
2220
|
:assertion_type => :doc_field_empty,
|
|
1979
2221
|
:xpath => xpath,
|
|
1980
2222
|
}
|
|
1981
|
-
assertionID = "doc-#{
|
|
2223
|
+
assertionID = "doc-#{var_xpath.gsub('/', '-')}"
|
|
1982
2224
|
solver.associateAssertionIDWith(assertionID, info)
|
|
1983
2225
|
solver.to_solver(progress, "(assert (! (= #{filled_var_for_field(var_xpath)} false) :named #{assertionID}))")
|
|
1984
2226
|
end
|
|
@@ -1995,7 +2237,7 @@ def translate_struct_in_doc_to_SMTLIB(progress, solver, struct, doc, prefix, var
|
|
|
1995
2237
|
:xpath => xpath,
|
|
1996
2238
|
:values => fvalues
|
|
1997
2239
|
}
|
|
1998
|
-
assertionID = "doc-#{
|
|
2240
|
+
assertionID = "doc-#{var_xpath.gsub('/', '-')}"
|
|
1999
2241
|
solver.associateAssertionIDWith(assertionID, info)
|
|
2000
2242
|
clauses = []
|
|
2001
2243
|
fvalues.each_with_index do |fvalue, index|
|
|
@@ -2007,7 +2249,7 @@ def translate_struct_in_doc_to_SMTLIB(progress, solver, struct, doc, prefix, var
|
|
|
2007
2249
|
:assertion_type => :doc_field_empty,
|
|
2008
2250
|
:xpath => xpath,
|
|
2009
2251
|
}
|
|
2010
|
-
assertionID = "doc-#{
|
|
2252
|
+
assertionID = "doc-#{var_xpath.gsub('/', '-')}"
|
|
2011
2253
|
solver.associateAssertionIDWith(assertionID, info)
|
|
2012
2254
|
solver.to_solver(progress, "(assert (! (= #{filled_var_for_field(var_xpath)} false) :named #{assertionID}))")
|
|
2013
2255
|
end
|
|
@@ -2047,7 +2289,7 @@ def translate_struct_in_doc_to_SMTLIB(progress, solver, struct, doc, prefix, var
|
|
|
2047
2289
|
:size => num_elements,
|
|
2048
2290
|
:xpath => xpath
|
|
2049
2291
|
}
|
|
2050
|
-
assertionID = "doc-#{
|
|
2292
|
+
assertionID = "doc-#{var_xpath.gsub('/', '-')}"
|
|
2051
2293
|
solver.associateAssertionIDWith(assertionID, info)
|
|
2052
2294
|
clauses = []
|
|
2053
2295
|
solver.to_solver(progress, "(assert (! (and (= #{filled_var_for_field(var_xpath)} true) (= #{size_var_for_list(var_xpath)} #{num_elements})) :named #{assertionID}))")
|
|
@@ -2056,7 +2298,7 @@ def translate_struct_in_doc_to_SMTLIB(progress, solver, struct, doc, prefix, var
|
|
|
2056
2298
|
:assertion_type => :doc_field_empty,
|
|
2057
2299
|
:xpath => xpath,
|
|
2058
2300
|
}
|
|
2059
|
-
assertionID = "doc-#{
|
|
2301
|
+
assertionID = "doc-#{var_xpath.gsub('/', '-')}"
|
|
2060
2302
|
solver.associateAssertionIDWith(assertionID, info)
|
|
2061
2303
|
solver.to_solver(progress, "(assert (! (= #{filled_var_for_field(var_xpath)} false) :named #{assertionID}))")
|
|
2062
2304
|
end
|
|
@@ -2132,6 +2374,10 @@ def generate(message, argv, message_config_filename = nil)
|
|
|
2132
2374
|
else
|
|
2133
2375
|
options[:validate_files] = [argv.shift]
|
|
2134
2376
|
end
|
|
2377
|
+
elsif opt == '-generate-max' then
|
|
2378
|
+
options[:generate_max] = true
|
|
2379
|
+
elsif opt == '-generate-equality-test-data' then
|
|
2380
|
+
options[:generate_equality_test_data] = true
|
|
2135
2381
|
else
|
|
2136
2382
|
puts "FATAL: unknown option #{opt.inspect}."
|
|
2137
2383
|
puts USAGE
|
|
@@ -2192,6 +2438,12 @@ def generate(message, argv, message_config_filename = nil)
|
|
|
2192
2438
|
list_fields_and_structures(message, options[:list])
|
|
2193
2439
|
elsif options[:list_validation_rules] then
|
|
2194
2440
|
list_validation_rules(message)
|
|
2441
|
+
elsif options[:generate_max] then
|
|
2442
|
+
validate_model(message)
|
|
2443
|
+
generate_max_doc(progress, message, OUTPUT_FILE, config, options)
|
|
2444
|
+
elsif options[:generate_equality_test_data] then
|
|
2445
|
+
validate_model(message)
|
|
2446
|
+
generate_equality_test_data(progress, message, OUTPUT_DIR, config, options)
|
|
2195
2447
|
else
|
|
2196
2448
|
progress.print_line "using #{options[:date_now]} as [Date.now]"
|
|
2197
2449
|
progress.print_line "using #{options[:timestamp_now]} as [Timestamp.now]"
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
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.10
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- FM-enthusiast
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-
|
|
11
|
+
date: 2026-09-01 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: nokogiri
|