mbt-gen 0.0.9 → 0.0.11

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.
Files changed (3) hide show
  1. checksums.yaml +4 -4
  2. data/lib/mbt-gen.rb +348 -0
  3. metadata +2 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: dd9a7ea0a78ff033dc866b98d56e7cae0f058e96ea63a9b150872fcf393197a5
4
- data.tar.gz: ec0bda3f93bad648f79d52190bc6cfadaab7e6d06988f81869d017a49d1ccc56
3
+ metadata.gz: b4c7dc3614c0f3345b728127808e7bd517487298968c8edab06f6436b84d60c1
4
+ data.tar.gz: 780f4e406e9b84d112074106bb905e48a8eab8fac3e02936ab36f85087381c07
5
5
  SHA512:
6
- metadata.gz: 8a9aa5db05da1d32904fb227f2791023745d6727a34eaac76711424911522a402705f656ecc81f953a9ac3b1fd722ef9b870d210296f9d023c8b8f9a42f878ab
7
- data.tar.gz: 9d81c6d1867f1cafaff988665310ee65a0ac0e41d23d46f5bbb326f8752d0a9ec58fe392fc3c24fd3d9279dcf3367489b9e9b1ffbcdad6768e9e37d7e3b67942
6
+ metadata.gz: 05d34d034b0aa1438c15f47969d8c9bd3d61545d2c9ade72efc0edfcee081e562c9fb3cc6f421e846319bbc43c0fe426b10bf81b8030b5ff560f97942405f5b7
7
+ data.tar.gz: 9f7b485dc1034d0529c3b8033633a862adb30c938899cfa8ceaaab95e130a10385c7d77c9e5859d31d8f5aeedf271e742e242e8699785ba8936b67df8f8f6b17
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,244 @@ 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
+
1121
+ generate_equality_test(message, "#{output_dir}/test-equality.rb")
1122
+ end
1123
+
1124
+ def generate_equality_test(message, output_filename)
1125
+
1126
+ base_filename = BASE_FILENAME
1127
+ positive_tests = []
1128
+ positive_tests << "assert(equality('#{base_filename}', '#{base_filename}'), 'Equality implementation is not reflexive on file #{base_filename}')"
1129
+ each_structure_and_field_in(message) do |desc, xpath|
1130
+ if desc[:type] == :field then
1131
+ positive_tests << "assert(equality('doc-mutated-#{xpath_to_filename(xpath)}.xml', 'doc-mutated-#{xpath_to_filename(xpath)}.xml'), 'Equality implementation is not reflexive on file doc-mutated-#{xpath_to_filename(xpath)}.xml')"
1132
+ if desc[:optional] == true then
1133
+ positive_tests << "assert(equality('doc-removed-#{xpath_to_filename(xpath)}.xml', 'doc-removed-#{xpath_to_filename(xpath)}.xml'), 'Equality implementation is not reflexive on file doc-removed-#{xpath_to_filename(xpath)}.xml')"
1134
+ end
1135
+ elsif desc[:type] == :list then
1136
+ if desc[:element] then
1137
+ positive_tests << "assert(equality('doc-mutated-#{xpath_to_filename(xpath)}.xml', 'doc-mutated-#{xpath_to_filename(xpath)}.xml'), 'Equality implementation is not reflexive on file doc-mutated-#{xpath_to_filename(xpath)}.xml')"
1138
+ else
1139
+ positive_tests << "assert(equality('doc-removed-#{xpath_to_filename(xpath)}.xml', 'doc-removed-#{xpath_to_filename(xpath)}.xml'), 'Equality implementation is not reflexive on file doc-removed-#{xpath_to_filename(xpath)}.xml')"
1140
+ end
1141
+ elsif desc[:type] == :objlist then
1142
+ if desc[:element] then
1143
+ # list elements
1144
+ # no additional test data neccessary as the element objects will contain fields that are modified already
1145
+ else
1146
+ # the list itself
1147
+ positive_tests << "assert(equality('doc-removed-#{xpath_to_filename(xpath)}.xml', 'doc-removed-#{xpath_to_filename(xpath)}.xml'), 'Equality implementation is not reflexive on file doc-removed-#{xpath_to_filename(xpath)}.xml')"
1148
+ end
1149
+ elsif desc[:type] == :structure then
1150
+ if desc[:optional] == true then
1151
+ positive_tests << "assert(equality('doc-removed-#{xpath_to_filename(xpath)}.xml', 'doc-removed-#{xpath_to_filename(xpath)}.xml'), 'Equality implementation is not reflexive on file doc-removed-#{xpath_to_filename(xpath)}.xml')"
1152
+ end
1153
+ else
1154
+ raise RuntimeError.new("encountered unknown :type #{desc[:type].inspect}")
1155
+ end
1156
+ end
1157
+
1158
+ negative_tests = []
1159
+ each_structure_and_field_in(message) do |desc, xpath|
1160
+ if desc[:type] == :field then
1161
+ negative_tests << "assert(!equality('#{base_filename}', 'doc-mutated-#{xpath_to_filename(xpath)}.xml'), 'Equality implementation seems not to take the field #{xpath} into account. It regards the files #{base_filename} and doc-mutated-#{xpath_to_filename(xpath)}.xml as equal.')"
1162
+ if desc[:optional] == true then
1163
+ negative_tests << "assert(!equality('#{base_filename}', 'doc-removed-#{xpath_to_filename(xpath)}.xml'), 'Equality implementation seems not to take the existence of field #{xpath} into account. It regards the files #{base_filename} and doc-removed-#{xpath_to_filename(xpath)}.xml as equal.')"
1164
+ end
1165
+ elsif desc[:type] == :list then
1166
+ if desc[:element] then
1167
+ negative_tests << "assert(!equality('#{base_filename}', 'doc-mutated-#{xpath_to_filename(xpath)}.xml'), 'Equality implementation seems not to take the list #{xpath} into account. It regards the files #{base_filename} and doc-mutated-#{xpath_to_filename(xpath)}.xml as equal.')"
1168
+ else
1169
+ negative_tests << "assert(!equality('#{base_filename}', 'doc-removed-#{xpath_to_filename(xpath)}.xml'), 'Equality implementation seems not to take the existence of the list #{xpath} into account. It regards the files #{base_filename} and doc-removed-#{xpath_to_filename(xpath)}.xml as equal.')"
1170
+ end
1171
+ elsif desc[:type] == :objlist then
1172
+ if desc[:element] then
1173
+ # list elements
1174
+ # no additional test data neccessary as the element objects will contain fields that are modified already
1175
+ else
1176
+ # the list itself
1177
+ negative_tests << "assert(!equality('#{base_filename}', 'doc-removed-#{xpath_to_filename(xpath)}.xml'), 'Equality implementation seems not to take the existence of objlist #{xpath} into account. It regards the files #{base_filename} and doc-removed-#{xpath_to_filename(xpath)}.xml as equal.')"
1178
+ end
1179
+ elsif desc[:type] == :structure then
1180
+ if desc[:optional] == true then
1181
+ negative_tests << "assert(!equality('#{base_filename}', 'doc-removed-#{xpath_to_filename(xpath)}.xml'), 'Equality implementation seems not to take the existence of structure #{xpath} into account. It regards the files #{base_filename} and doc-removed-#{xpath_to_filename(xpath)}.xml as equal.')"
1182
+ end
1183
+ else
1184
+ raise RuntimeError.new("encountered unknown :type #{desc[:type].inspect}")
1185
+ end
1186
+ end
1187
+
1188
+
1189
+ content = <<EOF
1190
+ #!/bin/ruby
1191
+
1192
+ require 'test/unit'
1193
+
1194
+ class EqualityTest < Test::Unit::TestCase
1195
+
1196
+ def equality(filename1, filename2)
1197
+ # TODO: call your equality implementation here.
1198
+ # This is just an example showing how to call an externally-provided implementation.
1199
+ system("diff", filename1, filename2)
1200
+ end
1201
+
1202
+ def test_equality_positive
1203
+ #{positive_tests.join("\n ")}
1204
+ end
1205
+
1206
+ def test_equality_negative
1207
+ #{negative_tests.join("\n ")}
1208
+ end
1209
+ end
1210
+ EOF
1211
+
1212
+ File.open(output_filename, "w") do |f|
1213
+ f.puts(content)
1214
+ end
1215
+
1216
+ end
1217
+
1218
+ def generate_max_doc(progress, message, output_filename, config, options)
1219
+ result_doc = build_max_doc(progress, message)
1220
+ dir = File.dirname(output_filename)
1221
+ FileUtils.mkdir_p(dir) unless Dir.exist?(dir)
1222
+ File.open(output_filename, "w") do |f|
1223
+ f.puts result_doc.to_xml()
1224
+ end
1225
+ progress.print_line "DONE. Generated XML was written to #{output_filename}."
1226
+ return true
1227
+ end
1228
+
891
1229
  def generate_doc(progress, message, output_filename, config, options)
892
1230
  raise RuntimeError.new("option :solverFactory is required") unless options[:solverFactory]
893
1231
  log = options[:log] || nil
@@ -2132,6 +2470,10 @@ def generate(message, argv, message_config_filename = nil)
2132
2470
  else
2133
2471
  options[:validate_files] = [argv.shift]
2134
2472
  end
2473
+ elsif opt == '-generate-max' then
2474
+ options[:generate_max] = true
2475
+ elsif opt == '-generate-equality-test-data' then
2476
+ options[:generate_equality_test_data] = true
2135
2477
  else
2136
2478
  puts "FATAL: unknown option #{opt.inspect}."
2137
2479
  puts USAGE
@@ -2192,6 +2534,12 @@ def generate(message, argv, message_config_filename = nil)
2192
2534
  list_fields_and_structures(message, options[:list])
2193
2535
  elsif options[:list_validation_rules] then
2194
2536
  list_validation_rules(message)
2537
+ elsif options[:generate_max] then
2538
+ validate_model(message)
2539
+ generate_max_doc(progress, message, OUTPUT_FILE, config, options)
2540
+ elsif options[:generate_equality_test_data] then
2541
+ validate_model(message)
2542
+ generate_equality_test_data(progress, message, OUTPUT_DIR, config, options)
2195
2543
  else
2196
2544
  progress.print_line "using #{options[:date_now]} as [Date.now]"
2197
2545
  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.9
4
+ version: 0.0.11
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-08-10 00:00:00.000000000 Z
11
+ date: 2026-09-03 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: nokogiri