mbt-gen 0.0.9 → 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.
Files changed (3) hide show
  1. checksums.yaml +4 -4
  2. data/lib/mbt-gen.rb +252 -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: 77cbf93998b731d074f62e0b364c20f87f324c8409e0e223193e9d8c0a0ddc52
4
+ data.tar.gz: 98985c6b4ace9d25b36fc73d9110f50368bc8badff62918b52a09557c0c52bd3
5
5
  SHA512:
6
- metadata.gz: 8a9aa5db05da1d32904fb227f2791023745d6727a34eaac76711424911522a402705f656ecc81f953a9ac3b1fd722ef9b870d210296f9d023c8b8f9a42f878ab
7
- data.tar.gz: 9d81c6d1867f1cafaff988665310ee65a0ac0e41d23d46f5bbb326f8752d0a9ec58fe392fc3c24fd3d9279dcf3367489b9e9b1ffbcdad6768e9e37d7e3b67942
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
@@ -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.9
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-08-10 00:00:00.000000000 Z
11
+ date: 2026-09-01 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: nokogiri