modspec 0.2.1 → 0.2.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 74bb2806f9ebad2c91453ba473103f5e581c76b78223d9eeba79f4580e8858db
4
- data.tar.gz: cb2903e0b33726419f118c65f64ad5463a2bef0b385643f877de977bc447f8d9
3
+ metadata.gz: 280d4bf45944e0e451d2e1c3deee8075ca6cb78ea0b5fd1e6c1c24b3d31bb9c5
4
+ data.tar.gz: 95cf4c68c04904ca8bb028d2f1e9a1922078c903e5db93a6009aeade96fe21b5
5
5
  SHA512:
6
- metadata.gz: d31ab6413761a379a95aa692e94471538daa63cb507ecfcf25ab6e4b3bc34cdfc22d8234ca4239e0159921bdd203ce4e8bec8bec3e4d99323222c34d49d59ac0
7
- data.tar.gz: 615225d2adf7b489e0d627147b3a3dbdd43009ed0c96df174f690b9ebeec6c7c752eb5a684505f6822af40d7ee9710ef2a165e9aa601ba6144d82b52a2192a38
6
+ metadata.gz: e6949a03cbf3aa75fd5b553729a20b81344c87bd1924a7422389a060e63b6bf9b1c681b661d118a1e83dff59711aa3483445685ce2604df2bd7265ed62a9603c
7
+ data.tar.gz: b14cd04d41b9750fc7cdcb05bce52453c658ced2f3cbaef62129bba800b42577de105561904ff546fa4ce1b202b65abd1abc8f190b80068e60b2b85b0717274e
data/.rubocop.yml CHANGED
@@ -20,3 +20,6 @@ AllCops:
20
20
 
21
21
  RSpec/ExampleLength:
22
22
  Max: 30
23
+
24
+ RSpec/MultipleMemoizedHelpers:
25
+ Max: 14
data/.rubocop_todo.yml CHANGED
@@ -1,6 +1,6 @@
1
1
  # This configuration was generated by
2
2
  # `rubocop --auto-gen-config`
3
- # on 2026-05-06 00:19:18 UTC using RuboCop version 1.86.1.
3
+ # on 2026-05-06 04:01:19 UTC using RuboCop version 1.86.1.
4
4
  # The point is for the user to remove these configuration records
5
5
  # one by one as the offenses are removed from the code base.
6
6
  # Note that changes in the inspected code, or installation of new
@@ -11,56 +11,13 @@ Gemspec/RequiredRubyVersion:
11
11
  Exclude:
12
12
  - 'modspec.gemspec'
13
13
 
14
- # Offense count: 7
15
- # This cop supports safe autocorrection (--autocorrect).
16
- # Configuration parameters: EnforcedStyle, IndentationWidth.
17
- # SupportedStyles: with_first_argument, with_fixed_indentation
18
- Layout/ArgumentAlignment:
19
- Exclude:
20
- - 'lib/modspec/suite.rb'
21
- - 'spec/modspec/suite_spec.rb'
22
-
23
- # Offense count: 1
24
- # This cop supports safe autocorrection (--autocorrect).
25
- # Configuration parameters: EnforcedStyleAlignWith.
26
- # SupportedStylesAlignWith: either, start_of_block, start_of_line
27
- Layout/BlockAlignment:
28
- Exclude:
29
- - 'lib/modspec/suite.rb'
30
-
31
- # Offense count: 1
32
- # This cop supports safe autocorrection (--autocorrect).
33
- Layout/BlockEndNewline:
34
- Exclude:
35
- - 'lib/modspec/suite.rb'
36
-
37
- # Offense count: 6
38
- # This cop supports safe autocorrection (--autocorrect).
39
- # Configuration parameters: AllowMultipleStyles, EnforcedHashRocketStyle, EnforcedColonStyle, EnforcedLastArgumentHashStyle.
40
- # SupportedHashRocketStyles: key, separator, table
41
- # SupportedColonStyles: key, separator, table
42
- # SupportedLastArgumentHashStyles: always_inspect, always_ignore, ignore_implicit, ignore_explicit
43
- Layout/HashAlignment:
44
- Exclude:
45
- - 'spec/modspec/suite_spec.rb'
46
-
47
- # Offense count: 2
48
- # This cop supports safe autocorrection (--autocorrect).
49
- # Configuration parameters: Width, EnforcedStyleAlignWith, AllowedPatterns.
50
- # SupportedStylesAlignWith: start_of_line, relative_to_receiver
51
- Layout/IndentationWidth:
52
- Exclude:
53
- - 'lib/modspec/suite.rb'
54
-
55
- # Offense count: 36
14
+ # Offense count: 31
56
15
  # This cop supports safe autocorrection (--autocorrect).
57
16
  # Configuration parameters: Max, AllowHeredoc, AllowURI, AllowQualifiedName, URISchemes, AllowRBSInlineAnnotation, AllowCopDirectives, AllowedPatterns, SplitStrings.
58
17
  # URISchemes: http, https
59
18
  Layout/LineLength:
60
19
  Exclude:
61
- - 'lib/modspec/conformance_class.rb'
62
20
  - 'lib/modspec/conformance_test.rb'
63
- - 'lib/modspec/normative_statements_class.rb'
64
21
  - 'lib/modspec/suite.rb'
65
22
  - 'spec/modspec/conformance_class_spec.rb'
66
23
  - 'spec/modspec/conformance_test_spec.rb'
@@ -68,21 +25,14 @@ Layout/LineLength:
68
25
  - 'spec/modspec/normative_statements_class_spec.rb'
69
26
  - 'spec/modspec/suite_spec.rb'
70
27
 
71
- # Offense count: 4
72
- # This cop supports safe autocorrection (--autocorrect).
73
- # Configuration parameters: AllowInHeredoc.
74
- Layout/TrailingWhitespace:
75
- Exclude:
76
- - 'lib/modspec/suite.rb'
77
- - 'spec/modspec/suite_spec.rb'
78
-
79
- # Offense count: 5
28
+ # Offense count: 6
80
29
  # Configuration parameters: AllowedMethods, AllowedPatterns, CountRepeatedAttributes, Max.
81
30
  Metrics/AbcSize:
82
31
  Exclude:
32
+ - 'lib/modspec/child_container.rb'
83
33
  - 'lib/modspec/suite.rb'
84
34
 
85
- # Offense count: 5
35
+ # Offense count: 4
86
36
  # Configuration parameters: AllowedMethods, AllowedPatterns, Max.
87
37
  Metrics/CyclomaticComplexity:
88
38
  Exclude:
@@ -91,7 +41,7 @@ Metrics/CyclomaticComplexity:
91
41
  # Offense count: 9
92
42
  # Configuration parameters: CountComments, CountAsOne, AllowedMethods, AllowedPatterns.
93
43
  Metrics/MethodLength:
94
- Max: 19
44
+ Max: 20
95
45
 
96
46
  # Offense count: 2
97
47
  # Configuration parameters: AllowedMethods, AllowedPatterns, Max.
@@ -99,7 +49,7 @@ Metrics/PerceivedComplexity:
99
49
  Exclude:
100
50
  - 'lib/modspec/suite.rb'
101
51
 
102
- # Offense count: 2
52
+ # Offense count: 1
103
53
  # Configuration parameters: MinSize.
104
54
  Performance/CollectionLiteralInLoop:
105
55
  Exclude:
@@ -118,33 +68,11 @@ RSpec/IndexedLet:
118
68
  - 'spec/modspec/normative_statements_class_spec.rb'
119
69
  - 'spec/modspec/suite_spec.rb'
120
70
 
121
- # Offense count: 8
71
+ # Offense count: 13
122
72
  RSpec/MultipleExpectations:
123
73
  Max: 9
124
74
 
125
- # Offense count: 7
126
- # Configuration parameters: AllowSubject.
127
- RSpec/MultipleMemoizedHelpers:
128
- Max: 12
129
-
130
75
  # Offense count: 2
131
76
  RSpec/RepeatedExample:
132
77
  Exclude:
133
78
  - 'spec/modspec_spec.rb'
134
-
135
- # Offense count: 1
136
- # This cop supports safe autocorrection (--autocorrect).
137
- # Configuration parameters: EnforcedStyle, ProceduralMethods, FunctionalMethods, AllowedMethods, AllowedPatterns, AllowBracesOnProceduralOneLiners, BracesRequiredMethods.
138
- # SupportedStyles: line_count_based, semantic, braces_for_chaining, always_braces
139
- # ProceduralMethods: benchmark, bm, bmbm, create, each_with_object, measure, new, realtime, tap, with_object
140
- # FunctionalMethods: let, let!, subject, watch
141
- # AllowedMethods: lambda, proc, it
142
- Style/BlockDelimiters:
143
- Exclude:
144
- - 'lib/modspec/suite.rb'
145
-
146
- # Offense count: 2
147
- # This cop supports safe autocorrection (--autocorrect).
148
- Style/MultilineIfModifier:
149
- Exclude:
150
- - 'lib/modspec/suite.rb'
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Modspec
4
+ module ChildContainer
5
+ def self.included(base)
6
+ base.extend(ClassMethods)
7
+ end
8
+
9
+ module ClassMethods
10
+ def validates_children(collection_name, empty_label:, child_label:)
11
+ define_method(:validate_children_presence) do
12
+ children = send(collection_name)
13
+ if children.nil? || children.empty?
14
+ ["#{empty_label} #{identifier} has no child #{child_label}"]
15
+ else
16
+ []
17
+ end
18
+ end
19
+
20
+ define_method(:validate_children_identifier_prefix) do
21
+ children = send(collection_name)
22
+ return [] unless children
23
+
24
+ expected_prefix = "#{identifier}/"
25
+ children.filter_map do |child|
26
+ unless child.identifier.to_s.start_with?(expected_prefix)
27
+ msg = "#{child_label} #{child.identifier} "
28
+ msg += "does not share the expected prefix #{expected_prefix}"
29
+ msg
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end
@@ -4,6 +4,8 @@ require "lutaml/model"
4
4
 
5
5
  module Modspec
6
6
  class ConformanceClass < Lutaml::Model::Serializable
7
+ include ChildContainer
8
+
7
9
  attribute :identifier, Identifier
8
10
  attribute :name, :string
9
11
  attribute :description, :string
@@ -15,6 +17,9 @@ module Modspec
15
17
  attribute :belongs_to, Identifier, collection: true
16
18
  attribute :reference, :string
17
19
 
20
+ validates_children :tests, empty_label: "Conformance class",
21
+ child_label: "conformance tests"
22
+
18
23
  xml do
19
24
  element "conformance-class"
20
25
  map_attribute "identifier", to: :identifier
@@ -31,29 +36,10 @@ module Modspec
31
36
 
32
37
  def validate
33
38
  errors = super
34
- errors.concat(validate_identifier_prefix)
35
- errors.concat(validate_class_children_mapping)
39
+ errors.concat(validate_children_identifier_prefix)
40
+ errors.concat(validate_children_presence)
36
41
  errors.concat(tests.flat_map(&:validate))
37
42
  errors
38
43
  end
39
-
40
- private
41
-
42
- def validate_class_children_mapping
43
- if tests.nil? || tests.empty?
44
- ["Conformance class #{identifier} has no child conformance tests"]
45
- else
46
- []
47
- end
48
- end
49
-
50
- def validate_identifier_prefix
51
- errors = []
52
- expected_prefix = "#{identifier}/"
53
- tests&.each do |test|
54
- errors << "Conformance test #{test.identifier} does not share the expected prefix #{expected_prefix}" unless test.identifier.to_s.start_with?(expected_prefix)
55
- end
56
- errors
57
- end
58
44
  end
59
45
  end
@@ -45,8 +45,8 @@ module Modspec
45
45
  map_element "parts", to: :parts
46
46
  end
47
47
 
48
- def validate(_suite = nil, register: Lutaml::Model::Config.default_register)
49
- super(register: register)
48
+ def validate(register: Lutaml::Model::Config.default_register)
49
+ super
50
50
  end
51
51
  end
52
52
  end
@@ -4,6 +4,8 @@ require "lutaml/model"
4
4
 
5
5
  module Modspec
6
6
  class NormativeStatementsClass < Lutaml::Model::Serializable
7
+ include ChildContainer
8
+
7
9
  attribute :identifier, Identifier
8
10
  attribute :name, :string
9
11
  attribute :description, :string
@@ -16,6 +18,9 @@ module Modspec
16
18
  attribute :reference, :string
17
19
  attribute :source, :string
18
20
 
21
+ validates_children :normative_statements, empty_label: "Requirement class",
22
+ child_label: "requirements"
23
+
19
24
  xml do
20
25
  element "normative-statements-class"
21
26
  map_attribute "identifier", to: :identifier
@@ -31,31 +36,12 @@ module Modspec
31
36
  map_element "source", to: :source
32
37
  end
33
38
 
34
- def validate(suite = nil)
35
- errors = super()
36
- errors.concat(validate_identifier_prefix)
37
- errors.concat(validate_class_children_mapping)
38
- errors.concat(normative_statements.flat_map { |n| n.validate(suite) })
39
+ def validate
40
+ errors = super
41
+ errors.concat(validate_children_identifier_prefix)
42
+ errors.concat(validate_children_presence)
43
+ errors.concat(normative_statements.flat_map(&:validate))
39
44
  errors
40
45
  end
41
-
42
- private
43
-
44
- def validate_identifier_prefix
45
- return [] if normative_statements.nil? || normative_statements.empty?
46
-
47
- expected_prefix = "#{identifier}/"
48
- normative_statements.each_with_object([]) do |statement, errors|
49
- errors << "Normative statement #{statement.identifier} does not share the expected prefix #{expected_prefix}" unless statement.identifier.to_s.start_with?(expected_prefix)
50
- end
51
- end
52
-
53
- def validate_class_children_mapping
54
- if normative_statements.nil? || normative_statements.empty?
55
- ["Requirement class #{identifier} has no child requirements"]
56
- else
57
- []
58
- end
59
- end
60
46
  end
61
47
  end
data/lib/modspec/suite.rb CHANGED
@@ -21,15 +21,13 @@ module Modspec
21
21
 
22
22
  def validate
23
23
  setup_relationships
24
- self.all_identifiers = nil
24
+ reset_statement_index
25
25
  errors = super
26
26
  errors.concat(validate_cycles)
27
27
  errors.concat(validate_label_uniqueness)
28
28
  errors.concat(validate_dependencies)
29
29
  unless normative_statements_classes.nil?
30
- errors.concat(normative_statements_classes.flat_map do |n|
31
- n.validate(self)
32
- end)
30
+ errors.concat(normative_statements_classes.flat_map(&:validate))
33
31
  end
34
32
  errors.concat(conformance_classes.flat_map(&:validate)) unless conformance_classes.nil?
35
33
  errors
@@ -42,7 +40,7 @@ module Modspec
42
40
  end
43
41
 
44
42
  combined_suite = dup
45
- combined_suite.all_identifiers = nil
43
+ combined_suite.reset_statement_index
46
44
  if other_suite.normative_statements_classes
47
45
  combined_suite.normative_statements_classes ||= []
48
46
  combined_suite.normative_statements_classes += other_suite.normative_statements_classes
@@ -63,19 +61,17 @@ module Modspec
63
61
  combined_suite
64
62
  end
65
63
 
66
- def all_identifiers
67
- return @all_identifiers if @all_identifiers
68
-
69
- nsc = normative_statements_classes || []
70
- cc = conformance_classes || []
64
+ def statement_index
65
+ @statement_index ||= build_statement_index
66
+ end
71
67
 
72
- @all_identifiers = (nsc.flat_map(&:normative_statements) +
73
- cc.flat_map(&:tests) +
74
- nsc +
75
- cc).map(&:identifier)
68
+ def reset_statement_index
69
+ @statement_index = nil
76
70
  end
77
71
 
78
- attr_writer :all_identifiers
72
+ def all_identifiers
73
+ statement_index.keys
74
+ end
79
75
 
80
76
  def resolve_conflicts(other_suite)
81
77
  resolve_conflicts_for(normative_statements_classes,
@@ -131,13 +127,16 @@ module Modspec
131
127
  end
132
128
 
133
129
  def merge_attributes(existing_item, other_item)
134
- existing_item.class.attribute_names.each do |attr|
130
+ existing_item.class.attributes.each_key do |attr|
135
131
  next if %i[identifier name].include?(attr)
136
132
 
137
- if existing_item.send(attr).is_a?(Array)
138
- existing_item.send(attr).concat(other_item.send(attr)).uniq!
139
- elsif existing_item.send(attr).nil?
140
- existing_item.send("#{attr}=", other_item.send(attr))
133
+ existing_val = existing_item.send(attr)
134
+ other_val = other_item.send(attr)
135
+
136
+ if existing_val.is_a?(Array)
137
+ existing_item.send(attr).concat(other_val).uniq!
138
+ elsif existing_val.nil? && !other_val.nil?
139
+ existing_item.send("#{attr}=", other_val)
141
140
  end
142
141
  end
143
142
  end
@@ -148,21 +147,20 @@ module Modspec
148
147
  cycles.map { |cycle| "Cycle detected: #{cycle.join(' -> ')}" }
149
148
  end
150
149
 
151
- # Combine all statements into a single array
152
- # This includes both normative statements and conformance tests
153
150
  def all_statements
154
- nsc = if normative_statements_classes
155
- normative_statements_classes.flat_map(&:normative_statements)
156
- else
157
- []
158
- end
159
- cc = if conformance_classes
160
- conformance_classes.flat_map(&:tests)
161
- else
162
- []
163
- end
164
-
165
- nsc + cc
151
+ statement_index.values
152
+ end
153
+
154
+ def each_statement(&block)
155
+ normative_statements_classes&.each do |nsc|
156
+ yield nsc
157
+ nsc.normative_statements.each(&block)
158
+ end
159
+
160
+ conformance_classes&.each do |cc|
161
+ yield cc
162
+ cc.tests.each(&block)
163
+ end
166
164
  end
167
165
 
168
166
  def build_dependency_graph
@@ -219,20 +217,21 @@ module Modspec
219
217
  end
220
218
 
221
219
  def validate_label_uniqueness
222
- labels = {}
220
+ seen = {}
223
221
  errors = []
224
- all_statements.each do |statement|
225
- if labels[statement.identifier]
222
+ each_statement do |statement|
223
+ id = statement.identifier.to_s
224
+ if seen[id]
226
225
  errors << "Duplicate identifier found: #{statement.identifier}"
227
226
  else
228
- labels[statement.identifier] = true
227
+ seen[id] = true
229
228
  end
230
229
  end
231
230
  errors
232
231
  end
233
232
 
234
233
  def validate_dependencies
235
- all_ids = collect_all_identifiers
234
+ all_ids = statement_index
236
235
 
237
236
  errors = []
238
237
  normative_statements_classes&.each do |nsc|
@@ -256,24 +255,10 @@ module Modspec
256
255
  errors
257
256
  end
258
257
 
259
- def collect_all_identifiers
260
- identifiers = {}
261
-
262
- normative_statements_classes&.each do |nsc|
263
- identifiers[nsc.identifier.to_s] = nsc
264
- nsc.normative_statements.each do |ns|
265
- identifiers[ns.identifier.to_s] = ns
266
- end
267
- end
268
-
269
- conformance_classes&.each do |cc|
270
- identifiers[cc.identifier.to_s] = cc
271
- cc.tests.each do |ct|
272
- identifiers[ct.identifier.to_s] = ct
273
- end
274
- end
275
-
276
- identifiers
258
+ def build_statement_index
259
+ index = {}
260
+ each_statement { |s| index[s.identifier.to_s] = s }
261
+ index
277
262
  end
278
263
 
279
264
  def validate_refs(obj, all_ids, property)
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Modspec
4
- VERSION = "0.2.1"
4
+ VERSION = "0.2.3"
5
5
  end
data/lib/modspec.rb CHANGED
@@ -5,6 +5,7 @@ require "lutaml/model"
5
5
  require "set"
6
6
 
7
7
  module Modspec
8
+ autoload :ChildContainer, "modspec/child_container"
8
9
  autoload :Identifier, "modspec/identifier"
9
10
  autoload :NormativeStatement, "modspec/normative_statement"
10
11
  autoload :NormativeStatementPart, "modspec/normative_statement"
@@ -63,6 +63,36 @@ RSpec.describe Modspec::Suite do
63
63
  expect(errors).to include(a_string_matching(/Duplicate identifier/))
64
64
  end
65
65
 
66
+ it "detects cross-type identifier collisions" do
67
+ suite = described_class.new(
68
+ identifier: "/suite",
69
+ name: "Test",
70
+ normative_statements_classes: [
71
+ Modspec::NormativeStatementsClass.new(
72
+ identifier: "/req/test",
73
+ normative_statements: [
74
+ Modspec::NormativeStatement.new(identifier: "/req/test/a",
75
+ name: "A", statement: "a"),
76
+ ],
77
+ ),
78
+ ],
79
+ conformance_classes: [
80
+ Modspec::ConformanceClass.new(
81
+ identifier: "/conf/test",
82
+ tests: [
83
+ Modspec::ConformanceTest.new(
84
+ identifier: "/req/test/a",
85
+ name: "CT collision",
86
+ ),
87
+ ],
88
+ ),
89
+ ],
90
+ )
91
+
92
+ errors = suite.validate
93
+ expect(errors).to include(a_string_matching(/Duplicate identifier.*\/req\/test\/a/))
94
+ end
95
+
66
96
  it "detects dependency cycles" do
67
97
  ns_a = Modspec::NormativeStatement.new(
68
98
  identifier: "/req/test/a", name: "A", statement: "a",
@@ -267,4 +297,202 @@ RSpec.describe Modspec::Suite do
267
297
  expect(errors).to be_empty
268
298
  end
269
299
  end
300
+
301
+ describe "#setup_relationships" do
302
+ it "links conformance tests to their target normative statements" do
303
+ suite = described_class.new(
304
+ identifier: "/suite",
305
+ name: "Test",
306
+ normative_statements_classes: [
307
+ Modspec::NormativeStatementsClass.new(
308
+ identifier: "/req/test",
309
+ normative_statements: [
310
+ Modspec::NormativeStatement.new(identifier: "/req/test/a",
311
+ name: "A", statement: "a"),
312
+ ],
313
+ ),
314
+ ],
315
+ conformance_classes: [
316
+ Modspec::ConformanceClass.new(
317
+ identifier: "/conf/test",
318
+ tests: [
319
+ Modspec::ConformanceTest.new(
320
+ identifier: "/conf/test/a",
321
+ name: "CT-A",
322
+ targets: ["/req/test/a"],
323
+ ),
324
+ ],
325
+ ),
326
+ ],
327
+ )
328
+
329
+ suite.setup_relationships
330
+ ct = suite.conformance_classes.first.tests.first
331
+ expect(ct.corresponding_requirements.map(&:identifier)).to eq(["/req/test/a"])
332
+ expect(ct.parent_class).to eq(suite.conformance_classes.first)
333
+ end
334
+
335
+ it "handles missing target gracefully (no matching requirement)" do
336
+ suite = described_class.new(
337
+ identifier: "/suite",
338
+ name: "Test",
339
+ normative_statements_classes: [
340
+ Modspec::NormativeStatementsClass.new(
341
+ identifier: "/req/test",
342
+ normative_statements: [
343
+ Modspec::NormativeStatement.new(identifier: "/req/test/a",
344
+ name: "A", statement: "a"),
345
+ ],
346
+ ),
347
+ ],
348
+ conformance_classes: [
349
+ Modspec::ConformanceClass.new(
350
+ identifier: "/conf/test",
351
+ tests: [
352
+ Modspec::ConformanceTest.new(
353
+ identifier: "/conf/test/a",
354
+ name: "CT-A",
355
+ targets: ["/req/test/missing"],
356
+ ),
357
+ ],
358
+ ),
359
+ ],
360
+ )
361
+
362
+ suite.setup_relationships
363
+ ct = suite.conformance_classes.first.tests.first
364
+ expect(ct.corresponding_requirements).to be_empty
365
+ end
366
+
367
+ it "returns early when conformance_classes is nil" do
368
+ suite = described_class.new(
369
+ identifier: "/suite",
370
+ name: "Test",
371
+ normative_statements_classes: [
372
+ Modspec::NormativeStatementsClass.new(
373
+ identifier: "/req/test",
374
+ normative_statements: [],
375
+ ),
376
+ ],
377
+ conformance_classes: nil,
378
+ )
379
+
380
+ expect { suite.setup_relationships }.not_to raise_error
381
+ end
382
+ end
383
+
384
+ describe "#resolve_conflicts" do
385
+ let(:nsc_with_data) do
386
+ Modspec::NormativeStatementsClass.new(
387
+ identifier: "/req/test",
388
+ name: "Original",
389
+ description: "First description",
390
+ normative_statements: [
391
+ Modspec::NormativeStatement.new(identifier: "/req/test/a",
392
+ name: "A", statement: "a"),
393
+ ],
394
+ )
395
+ end
396
+
397
+ let(:nsc_partial) do
398
+ Modspec::NormativeStatementsClass.new(
399
+ identifier: "/req/test",
400
+ name: "Original",
401
+ description: "Second description",
402
+ subject: "Added subject",
403
+ normative_statements: [],
404
+ )
405
+ end
406
+
407
+ let(:nsc_other) do
408
+ Modspec::NormativeStatementsClass.new(
409
+ identifier: "/req/test2",
410
+ name: "Two",
411
+ normative_statements: [
412
+ Modspec::NormativeStatement.new(identifier: "/req/test2/b",
413
+ name: "B", statement: "b"),
414
+ ],
415
+ )
416
+ end
417
+
418
+ it "merges attributes of items with matching identifiers" do
419
+ suite1 = described_class.new(
420
+ identifier: "/suite", name: "Test1",
421
+ normative_statements_classes: [nsc_with_data],
422
+ conformance_classes: []
423
+ )
424
+ suite2 = described_class.new(
425
+ identifier: "/suite", name: "Test2",
426
+ normative_statements_classes: [nsc_partial],
427
+ conformance_classes: []
428
+ )
429
+
430
+ suite1.resolve_conflicts(suite2)
431
+ nsc = suite1.normative_statements_classes.first
432
+ expect(nsc.description).to eq("First description")
433
+ expect(nsc.subject).to eq("Added subject")
434
+ end
435
+
436
+ it "appends items with new identifiers" do
437
+ suite1 = described_class.new(
438
+ identifier: "/suite", name: "Test1",
439
+ normative_statements_classes: [nsc_with_data],
440
+ conformance_classes: []
441
+ )
442
+ suite2 = described_class.new(
443
+ identifier: "/suite", name: "Test2",
444
+ normative_statements_classes: [nsc_other],
445
+ conformance_classes: []
446
+ )
447
+
448
+ suite1.resolve_conflicts(suite2)
449
+ expect(suite1.normative_statements_classes.map(&:identifier)).to eq(
450
+ ["/req/test", "/req/test2"],
451
+ )
452
+ end
453
+ end
454
+
455
+ describe "YAML round-trip" do
456
+ it "serializes and deserializes a normative statements class" do
457
+ original = described_class.from_yaml(rc_yaml)
458
+ yaml_out = original.to_yaml
459
+ round_tripped = described_class.from_yaml(yaml_out)
460
+
461
+ expect(round_tripped.identifier).to eq(original.identifier)
462
+ expect(round_tripped.name).to eq(original.name)
463
+ expect(round_tripped.normative_statements_classes.count).to eq(
464
+ original.normative_statements_classes.count,
465
+ )
466
+ end
467
+
468
+ it "serializes and deserializes a conformance class" do
469
+ original = described_class.from_yaml(cc_yaml)
470
+ yaml_out = original.to_yaml
471
+ round_tripped = described_class.from_yaml(yaml_out)
472
+
473
+ expect(round_tripped.identifier).to eq(original.identifier)
474
+ expect(round_tripped.conformance_classes.count).to eq(
475
+ original.conformance_classes.count,
476
+ )
477
+ end
478
+ end
479
+
480
+ describe "JSON round-trip" do
481
+ let(:json_rc) { File.read("spec/fixtures/basic-ypr-json-rc.yaml") }
482
+ let(:json_cc) { File.read("spec/fixtures/basic-ypr-json-cc.yaml") }
483
+
484
+ it "parses JSON-format YAML fixtures" do
485
+ suite = described_class.from_yaml(json_rc)
486
+ expect(suite.normative_statements_classes).not_to be_empty
487
+ end
488
+
489
+ it "round-trips through JSON serialization" do
490
+ original = described_class.from_yaml(rc_yaml)
491
+ json_out = original.to_json
492
+ round_tripped = described_class.from_json(json_out)
493
+
494
+ expect(round_tripped.identifier).to eq(original.identifier)
495
+ expect(round_tripped.name).to eq(original.name)
496
+ end
497
+ end
270
498
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: modspec
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.1
4
+ version: 0.2.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ribose
@@ -71,6 +71,7 @@ files:
71
71
  - README.adoc
72
72
  - Rakefile
73
73
  - lib/modspec.rb
74
+ - lib/modspec/child_container.rb
74
75
  - lib/modspec/conformance_class.rb
75
76
  - lib/modspec/conformance_test.rb
76
77
  - lib/modspec/identifier.rb