ruby-dbus 0.16.0 → 0.18.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (79) hide show
  1. checksums.yaml +4 -4
  2. data/NEWS.md +160 -0
  3. data/README.md +3 -5
  4. data/Rakefile +18 -8
  5. data/VERSION +1 -1
  6. data/doc/Reference.md +106 -7
  7. data/examples/doc/_extract_examples +7 -0
  8. data/examples/gdbus/gdbus +31 -24
  9. data/examples/no-introspect/nm-test.rb +2 -0
  10. data/examples/no-introspect/tracker-test.rb +3 -1
  11. data/examples/rhythmbox/playpause.rb +2 -1
  12. data/examples/service/call_service.rb +2 -1
  13. data/examples/service/complex-property.rb +21 -0
  14. data/examples/service/service_newapi.rb +1 -1
  15. data/examples/simple/call_introspect.rb +1 -0
  16. data/examples/simple/get_id.rb +2 -1
  17. data/examples/simple/properties.rb +2 -0
  18. data/examples/utils/listnames.rb +1 -0
  19. data/examples/utils/notify.rb +1 -0
  20. data/lib/dbus/api_options.rb +9 -0
  21. data/lib/dbus/auth.rb +20 -15
  22. data/lib/dbus/bus.rb +123 -75
  23. data/lib/dbus/bus_name.rb +12 -8
  24. data/lib/dbus/core_ext/class/attribute.rb +1 -1
  25. data/lib/dbus/data.rb +821 -0
  26. data/lib/dbus/emits_changed_signal.rb +83 -0
  27. data/lib/dbus/error.rb +4 -2
  28. data/lib/dbus/introspect.rb +132 -31
  29. data/lib/dbus/logger.rb +3 -1
  30. data/lib/dbus/marshall.rb +247 -296
  31. data/lib/dbus/matchrule.rb +16 -16
  32. data/lib/dbus/message.rb +44 -37
  33. data/lib/dbus/message_queue.rb +16 -10
  34. data/lib/dbus/object.rb +358 -24
  35. data/lib/dbus/object_path.rb +11 -6
  36. data/lib/dbus/proxy_object.rb +22 -1
  37. data/lib/dbus/proxy_object_factory.rb +13 -7
  38. data/lib/dbus/proxy_object_interface.rb +63 -30
  39. data/lib/dbus/raw_message.rb +91 -0
  40. data/lib/dbus/type.rb +318 -86
  41. data/lib/dbus/xml.rb +32 -17
  42. data/lib/dbus.rb +14 -7
  43. data/ruby-dbus.gemspec +7 -3
  44. data/spec/async_spec.rb +2 -0
  45. data/spec/binding_spec.rb +2 -0
  46. data/spec/bus_and_xml_backend_spec.rb +2 -0
  47. data/spec/bus_driver_spec.rb +2 -0
  48. data/spec/bus_name_spec.rb +3 -1
  49. data/spec/bus_spec.rb +2 -0
  50. data/spec/byte_array_spec.rb +2 -0
  51. data/spec/client_robustness_spec.rb +4 -2
  52. data/spec/data/marshall.yaml +1667 -0
  53. data/spec/data_spec.rb +673 -0
  54. data/spec/emits_changed_signal_spec.rb +58 -0
  55. data/spec/err_msg_spec.rb +2 -0
  56. data/spec/introspect_xml_parser_spec.rb +2 -0
  57. data/spec/introspection_spec.rb +2 -0
  58. data/spec/main_loop_spec.rb +3 -1
  59. data/spec/node_spec.rb +23 -0
  60. data/spec/object_path_spec.rb +3 -0
  61. data/spec/object_spec.rb +138 -0
  62. data/spec/packet_marshaller_spec.rb +41 -0
  63. data/spec/packet_unmarshaller_spec.rb +248 -0
  64. data/spec/property_spec.rb +192 -5
  65. data/spec/proxy_object_spec.rb +2 -0
  66. data/spec/server_robustness_spec.rb +2 -0
  67. data/spec/server_spec.rb +2 -0
  68. data/spec/service_newapi.rb +70 -70
  69. data/spec/session_bus_spec.rb +3 -1
  70. data/spec/session_bus_spec_manual.rb +2 -0
  71. data/spec/signal_spec.rb +5 -3
  72. data/spec/spec_helper.rb +37 -9
  73. data/spec/thread_safety_spec.rb +2 -0
  74. data/spec/tools/dbus-limited-session.conf +4 -0
  75. data/spec/type_spec.rb +214 -6
  76. data/spec/value_spec.rb +16 -1
  77. data/spec/variant_spec.rb +4 -2
  78. data/spec/zzz_quit_spec.rb +16 -0
  79. metadata +34 -8
@@ -0,0 +1,58 @@
1
+ #!/usr/bin/env rspec
2
+ # frozen_string_literal: true
3
+
4
+ require_relative "spec_helper"
5
+ require "dbus"
6
+
7
+ describe DBus::EmitsChangedSignal do
8
+ describe "#initialize" do
9
+ it "accepts a simple value" do
10
+ expect(described_class.new(:const).value).to eq :const
11
+ end
12
+
13
+ it "avoids nil by asking the interface" do
14
+ ifc = DBus::Interface.new("org.example.Foo")
15
+ ifc.emits_changed_signal = described_class.new(:invalidates)
16
+
17
+ expect(described_class.new(nil, interface: ifc).value).to eq :invalidates
18
+ end
19
+
20
+ it "fails for unknown value" do
21
+ expect { described_class.new(:huh) }.to raise_error(ArgumentError, /Seen :huh/)
22
+ end
23
+
24
+ it "fails for 2 nils" do
25
+ expect { described_class.new(nil, interface: nil) }.to raise_error(ArgumentError, /Both/)
26
+ end
27
+ end
28
+
29
+ describe "#==" do
30
+ it "is true for two different objects with the same value" do
31
+ const_a = described_class.new(:const)
32
+ const_b = described_class.new(:const)
33
+ expect(const_a == const_b).to be true
34
+ end
35
+ end
36
+
37
+ describe "#to_xml" do
38
+ it "uses a string value" do
39
+ expect(described_class.new(:const).to_xml)
40
+ .to eq " <annotation name=\"org.freedesktop.DBus.Property.EmitsChangedSignal\" value=\"const\"/>\n"
41
+ end
42
+ end
43
+
44
+ describe "#to_s" do
45
+ it "uses a string value" do
46
+ expect(described_class.new(:const).to_s).to eq "const"
47
+ end
48
+ end
49
+ end
50
+
51
+ describe DBus::Interface do
52
+ describe ".emits_changed_signal=" do
53
+ it "only allows an EmitsChangedSignal as argument" do
54
+ ifc = described_class.new("org.ruby.Interface")
55
+ expect { ifc.emits_changed_signal = :const }.to raise_error(TypeError)
56
+ end
57
+ end
58
+ end
data/spec/err_msg_spec.rb CHANGED
@@ -1,4 +1,6 @@
1
1
  #!/usr/bin/env rspec
2
+ # frozen_string_literal: true
3
+
2
4
  # should report it missing on org.ruby.SampleInterface
3
5
  # (on object...) instead of on DBus::Proxy::ObjectInterface
4
6
  require_relative "spec_helper"
@@ -1,4 +1,6 @@
1
1
  #!/usr/bin/env rspec
2
+ # frozen_string_literal: true
3
+
2
4
  require_relative "spec_helper"
3
5
  require "dbus"
4
6
 
@@ -1,4 +1,6 @@
1
1
  #!/usr/bin/env rspec
2
+ # frozen_string_literal: true
3
+
2
4
  require_relative "spec_helper"
3
5
  require "dbus"
4
6
 
@@ -1,4 +1,6 @@
1
1
  #!/usr/bin/env rspec
2
+ # frozen_string_literal: true
3
+
2
4
  # Test the main loop
3
5
  require_relative "spec_helper"
4
6
  require "dbus"
@@ -70,7 +72,7 @@ describe "MainLoopTest" do
70
72
  @obj.on_signal "LongTaskEnd"
71
73
  end
72
74
 
73
- it "tests loop quit" do
75
+ it "tests loop quit", slow: true do
74
76
  test_loop_quit 1
75
77
  end
76
78
 
data/spec/node_spec.rb ADDED
@@ -0,0 +1,23 @@
1
+ #!/usr/bin/env rspec
2
+ # frozen_string_literal: true
3
+
4
+ require_relative "spec_helper"
5
+ require "dbus"
6
+
7
+ describe DBus::Node do
8
+ describe "#inspect" do
9
+ # the behavior needs improvement
10
+ it "shows the node, poorly" do
11
+ parent = described_class.new("parent")
12
+ parent.object = DBus::Object.new("/parent")
13
+
14
+ 3.times do |i|
15
+ child_name = "child#{i}"
16
+ child = described_class.new(child_name)
17
+ parent[child_name] = child
18
+ end
19
+
20
+ expect(parent.inspect).to match(/<DBus::Node [0-9a-f]+ {child0 => {},child1 => {},child2 => {}}>/)
21
+ end
22
+ end
23
+ end
@@ -1,4 +1,6 @@
1
1
  #!/usr/bin/env rspec
2
+ # frozen_string_literal: true
3
+
2
4
  require_relative "spec_helper"
3
5
  require "dbus"
4
6
 
@@ -18,6 +20,7 @@ describe DBus::ObjectPath do
18
20
  expect(described_class.valid?("/EmptyLastComponent/")).to be_falsey
19
21
  expect(described_class.valid?("/Invalid Character")).to be_falsey
20
22
  expect(described_class.valid?("/Invalid-Character")).to be_falsey
23
+ expect(described_class.valid?("/InválídCháráctér")).to be_falsey
21
24
  end
22
25
  end
23
26
  end
@@ -0,0 +1,138 @@
1
+ #!/usr/bin/env rspec
2
+ # frozen_string_literal: true
3
+
4
+ require_relative "spec_helper"
5
+ require "dbus"
6
+
7
+ class ObjectTest < DBus::Object
8
+ T = DBus::Type unless const_defined? "T"
9
+
10
+ dbus_interface "org.ruby.ServerTest" do
11
+ dbus_attr_writer :write_me, T::Struct[String, String]
12
+
13
+ attr_accessor :read_only_for_dbus
14
+
15
+ dbus_reader :read_only_for_dbus, T::STRING, emits_changed_signal: :invalidates
16
+ end
17
+ end
18
+
19
+ describe DBus::Object do
20
+ describe ".dbus_attr_writer" do
21
+ describe "the declared assignment method" do
22
+ # Slightly advanced RSpec:
23
+ # https://rspec.info/documentation/3.9/rspec-expectations/RSpec/Matchers.html#satisfy-instance_method
24
+ let(:a_struct_in_a_variant) do
25
+ satisfying { |x| x.is_a?(DBus::Data::Variant) && x.member_type.to_s == "(ss)" }
26
+ # ^ This formatting keeps the matcher on a single line
27
+ # which enables RSpec to cite it if it fails, instead of saying "block".
28
+ end
29
+
30
+ it "emits PropertyChanged with correctly typed argument" do
31
+ obj = ObjectTest.new("/test")
32
+ expect(obj).to receive(:PropertiesChanged).with(
33
+ "org.ruby.ServerTest",
34
+ {
35
+ "WriteMe" => a_struct_in_a_variant
36
+ },
37
+ []
38
+ )
39
+ # bug: call PC with simply the assigned value,
40
+ # which will need type guessing
41
+ obj.write_me = ["two", "strings"]
42
+ end
43
+ end
44
+ end
45
+
46
+ describe ".dbus_accessor" do
47
+ it "can only be used within a dbus_interface" do
48
+ expect do
49
+ ObjectTest.instance_exec do
50
+ dbus_accessor :foo, DBus::Type::STRING
51
+ end
52
+ end.to raise_error(DBus::Object::UndefinedInterface)
53
+ end
54
+ end
55
+
56
+ describe ".dbus_reader" do
57
+ it "can only be used within a dbus_interface" do
58
+ expect do
59
+ ObjectTest.instance_exec do
60
+ dbus_reader :foo, DBus::Type::STRING
61
+ end
62
+ end.to raise_error(DBus::Object::UndefinedInterface)
63
+ end
64
+ end
65
+
66
+ describe ".dbus_reader, when paired with attr_accessor" do
67
+ describe "the declared assignment method" do
68
+ it "emits PropertyChanged" do
69
+ obj = ObjectTest.new("/test")
70
+ expect(obj).to receive(:PropertiesChanged).with(
71
+ "org.ruby.ServerTest",
72
+ {},
73
+ ["ReadOnlyForDbus"]
74
+ )
75
+ obj.read_only_for_dbus = "myvalue"
76
+ end
77
+ end
78
+ end
79
+
80
+ describe ".dbus_writer" do
81
+ it "can only be used within a dbus_interface" do
82
+ expect do
83
+ ObjectTest.instance_exec do
84
+ dbus_writer :foo, DBus::Type::STRING
85
+ end
86
+ end.to raise_error(DBus::Object::UndefinedInterface)
87
+ end
88
+ end
89
+
90
+ describe ".dbus_watcher" do
91
+ it "can only be used within a dbus_interface" do
92
+ expect do
93
+ ObjectTest.instance_exec do
94
+ dbus_watcher :foo
95
+ end
96
+ end.to raise_error(DBus::Object::UndefinedInterface)
97
+ end
98
+ end
99
+
100
+ describe ".dbus_method" do
101
+ it "can only be used within a dbus_interface" do
102
+ expect do
103
+ ObjectTest.instance_exec do
104
+ dbus_method :foo do
105
+ end
106
+ end
107
+ end.to raise_error(DBus::Object::UndefinedInterface)
108
+ end
109
+ end
110
+
111
+ describe ".emits_changed_signal" do
112
+ it "raises UndefinedInterface when so" do
113
+ expect { ObjectTest.emits_changed_signal = false }
114
+ .to raise_error DBus::Object::UndefinedInterface
115
+ end
116
+
117
+ it "assigns to the current interface" do
118
+ ObjectTest.instance_exec do
119
+ dbus_interface "org.ruby.Interface" do
120
+ self.emits_changed_signal = false
121
+ end
122
+ end
123
+ ecs = ObjectTest.intfs["org.ruby.Interface"].emits_changed_signal
124
+ expect(ecs).to eq false
125
+ end
126
+
127
+ it "only can be assigned once" do
128
+ expect do
129
+ Class.new(DBus::Object) do
130
+ dbus_interface "org.ruby.Interface" do
131
+ self.emits_changed_signal = false
132
+ self.emits_changed_signal = :invalidates
133
+ end
134
+ end
135
+ end.to raise_error(RuntimeError, /assigned more than once/)
136
+ end
137
+ end
138
+ end
@@ -0,0 +1,41 @@
1
+ #!/usr/bin/env rspec
2
+ # frozen_string_literal: true
3
+
4
+ require_relative "spec_helper"
5
+ require "dbus"
6
+ require "ostruct"
7
+ require "yaml"
8
+
9
+ data_dir = File.expand_path("data", __dir__)
10
+ marshall_yaml_s = File.read("#{data_dir}/marshall.yaml")
11
+ marshall_yaml = YAML.safe_load(marshall_yaml_s)
12
+
13
+ describe DBus::PacketMarshaller do
14
+ context "marshall.yaml" do
15
+ marshall_yaml.each do |test|
16
+ t = OpenStruct.new(test)
17
+ next if t.marshall == false
18
+ # skip test cases for invalid unmarshalling
19
+ next if t.val.nil?
20
+
21
+ # while the marshaller can use only native endianness, skip the other
22
+ endianness = t.end.to_sym
23
+
24
+ signature = t.sig
25
+ expected = buffer_from_yaml(t.buf)
26
+
27
+ it "writes a '#{signature}' with value #{t.val.inspect} (#{endianness})" do
28
+ subject = described_class.new(endianness: endianness)
29
+ subject.append(signature, t.val)
30
+ expect(subject.packet).to eq(expected)
31
+ end
32
+
33
+ it "writes a '#{signature}' with typed value #{t.val.inspect} (#{endianness})" do
34
+ subject = described_class.new(endianness: endianness)
35
+ typed_val = DBus::Data.make_typed(signature, t.val)
36
+ subject.append(signature, typed_val)
37
+ expect(subject.packet).to eq(expected)
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,248 @@
1
+ #!/usr/bin/env rspec
2
+ # frozen_string_literal: true
3
+
4
+ require_relative "spec_helper"
5
+ require "dbus"
6
+ require "ostruct"
7
+ require "yaml"
8
+
9
+ data_dir = File.expand_path("data", __dir__)
10
+ marshall_yaml_s = File.read("#{data_dir}/marshall.yaml")
11
+ marshall_yaml = YAML.safe_load(marshall_yaml_s)
12
+
13
+ # Helper to access PacketUnmarshaller internals.
14
+ # Add it to its public API?
15
+ # @param p_u [PacketUnmarshaller]
16
+ # @return [String] the binary string with unconsumed data
17
+ def remaining_buffer(p_u)
18
+ raw_msg = p_u.instance_variable_get(:@raw_msg)
19
+ raw_msg.remaining_bytes
20
+ end
21
+
22
+ RSpec.shared_examples "parses good data" do |cases|
23
+ describe "parses all the instances of good test data" do
24
+ cases.each_with_index do |(buffer, endianness, expected), i|
25
+ it "parses plain data ##{i}" do
26
+ buffer = String.new(buffer, encoding: Encoding::BINARY)
27
+ subject = described_class.new(buffer, endianness)
28
+
29
+ results = subject.unmarshall(signature, mode: :plain)
30
+ # unmarshall works on multiple signatures but we use one
31
+ expect(results).to be_an(Array)
32
+ expect(results.size).to eq(1)
33
+ result = results.first
34
+
35
+ expect(result).to eq(expected)
36
+
37
+ expect(remaining_buffer(subject)).to be_empty
38
+ end
39
+
40
+ it "parses exact data ##{i}" do
41
+ buffer = String.new(buffer, encoding: Encoding::BINARY)
42
+ subject = described_class.new(buffer, endianness)
43
+
44
+ results = subject.unmarshall(signature, mode: :exact)
45
+ # unmarshall works on multiple signatures but we use one
46
+ expect(results).to be_an(Array)
47
+ expect(results.size).to eq(1)
48
+ result = results.first
49
+
50
+ expect(result).to be_a(DBus::Data::Base)
51
+ expect(result.value).to eq(expected)
52
+
53
+ expect(remaining_buffer(subject)).to be_empty
54
+ end
55
+ end
56
+ end
57
+ end
58
+
59
+ # this is necessary because we do an early switch on the signature
60
+ RSpec.shared_examples "reports empty data" do
61
+ it "reports empty data" do
62
+ [:big, :little].each do |endianness|
63
+ subject = described_class.new("", endianness)
64
+ expect { subject.unmarshall(signature) }.to raise_error(DBus::IncompleteBufferException)
65
+ end
66
+ end
67
+ end
68
+
69
+ describe DBus::PacketUnmarshaller do
70
+ context "marshall.yaml" do
71
+ marshall_yaml.each do |test|
72
+ t = OpenStruct.new(test)
73
+ signature = t.sig
74
+ buffer = buffer_from_yaml(t.buf)
75
+ endianness = t.end.to_sym
76
+
77
+ # successful parse
78
+ if !t.val.nil?
79
+ expected = t.val
80
+
81
+ it "parses a '#{signature}' to get #{t.val.inspect} (plain)" do
82
+ subject = described_class.new(buffer, endianness)
83
+ results = subject.unmarshall(signature, mode: :plain)
84
+ # unmarshall works on multiple signatures but we use one
85
+ expect(results).to be_an(Array)
86
+ expect(results.size).to eq(1)
87
+ result = results.first
88
+
89
+ expect(result).to eq(expected)
90
+ expect(remaining_buffer(subject)).to be_empty
91
+ end
92
+
93
+ it "parses a '#{t.sig}' to get #{t.val.inspect} (exact)" do
94
+ subject = described_class.new(buffer, endianness)
95
+ results = subject.unmarshall(signature, mode: :exact)
96
+ # unmarshall works on multiple signatures but we use one
97
+ expect(results).to be_an(Array)
98
+ expect(results.size).to eq(1)
99
+ result = results.first
100
+
101
+ expect(result).to be_a(DBus::Data::Base)
102
+ expect(result.value).to eq(expected)
103
+
104
+ expect(remaining_buffer(subject)).to be_empty
105
+ end
106
+ elsif t.exc
107
+ next if t.unmarshall == false
108
+
109
+ exc_class = DBus.const_get(t.exc)
110
+ msg_re = Regexp.new(Regexp.escape(t.msg))
111
+
112
+ # TODO: InvalidPacketException is never rescued.
113
+ # The other end is sending invalid data. Can we do better than crashing?
114
+ # When we can test with peer connections, try it out.
115
+ it "parses a '#{signature}' to report a #{t.exc}" do
116
+ subject = described_class.new(buffer, endianness)
117
+ expect { subject.unmarshall(signature, mode: :plain) }.to raise_error(exc_class, msg_re)
118
+
119
+ subject = described_class.new(buffer, endianness)
120
+ expect { subject.unmarshall(signature, mode: :exact) }.to raise_error(exc_class, msg_re)
121
+ end
122
+ end
123
+ end
124
+ end
125
+
126
+ context "BYTEs" do
127
+ let(:signature) { "y" }
128
+ include_examples "reports empty data"
129
+ end
130
+
131
+ context "BOOLEANs" do
132
+ let(:signature) { "b" }
133
+ include_examples "reports empty data"
134
+ end
135
+
136
+ context "INT16s" do
137
+ let(:signature) { "n" }
138
+ include_examples "reports empty data"
139
+ end
140
+
141
+ context "UINT16s" do
142
+ let(:signature) { "q" }
143
+ include_examples "reports empty data"
144
+ end
145
+
146
+ context "INT32s" do
147
+ let(:signature) { "i" }
148
+ include_examples "reports empty data"
149
+ end
150
+
151
+ context "UINT32s" do
152
+ let(:signature) { "u" }
153
+ include_examples "reports empty data"
154
+ end
155
+
156
+ context "UNIX_FDs" do
157
+ let(:signature) { "h" }
158
+ include_examples "reports empty data"
159
+ end
160
+
161
+ context "INT64s" do
162
+ let(:signature) { "x" }
163
+ include_examples "reports empty data"
164
+ end
165
+
166
+ context "UINT64s" do
167
+ let(:signature) { "t" }
168
+ include_examples "reports empty data"
169
+ end
170
+
171
+ context "DOUBLEs" do
172
+ let(:signature) { "d" }
173
+ # See https://en.wikipedia.org/wiki/Double-precision_floating-point_format
174
+ # for binary representations
175
+ # TODO: figure out IEEE754 comparisons
176
+ good = [
177
+ # But == cant distinguish -0.0
178
+ ["\x00\x00\x00\x00\x00\x00\x00\x80", :little, -0.0],
179
+ # But NaN == NaN is false!
180
+ # ["\xff\xff\xff\xff\xff\xff\xff\xff", :little, Float::NAN],
181
+ ["\x80\x00\x00\x00\x00\x00\x00\x00", :big, -0.0]
182
+ # ["\xff\xff\xff\xff\xff\xff\xff\xff", :big, Float::NAN]
183
+ ]
184
+ include_examples "parses good data", good
185
+ include_examples "reports empty data"
186
+ end
187
+
188
+ context "STRINGs" do
189
+ let(:signature) { "s" }
190
+ include_examples "reports empty data"
191
+ end
192
+
193
+ context "OBJECT_PATHs" do
194
+ let(:signature) { "o" }
195
+ include_examples "reports empty data"
196
+ end
197
+
198
+ context "SIGNATUREs" do
199
+ let(:signature) { "g" }
200
+ include_examples "reports empty data"
201
+ end
202
+
203
+ context "ARRAYs" do
204
+ context "of BYTEs" do
205
+ let(:signature) { "ay" }
206
+ include_examples "reports empty data"
207
+ end
208
+
209
+ context "of UINT64s" do
210
+ let(:signature) { "at" }
211
+ include_examples "reports empty data"
212
+ end
213
+
214
+ context "of STRUCT of 2 UINT16s" do
215
+ let(:signature) { "a(qq)" }
216
+ include_examples "reports empty data"
217
+ end
218
+
219
+ context "of DICT_ENTRIES" do
220
+ let(:signature) { "a{yq}" }
221
+ include_examples "reports empty data"
222
+ end
223
+ end
224
+
225
+ context "STRUCTs" do
226
+ # TODO: this is invalid but does not raise
227
+ context "(generic 'r' struct)" do
228
+ let(:signature) { "r" }
229
+ end
230
+
231
+ context "of two shorts" do
232
+ let(:signature) { "(qq)" }
233
+ include_examples "reports empty data"
234
+ end
235
+ end
236
+
237
+ # makes sense here? or in array? remember invalid sigs are rejected elsewhere
238
+ context "DICT_ENTRYs" do
239
+ context "(generic 'e' dict_entry)" do
240
+ let(:signature) { "e" }
241
+ end
242
+ end
243
+
244
+ context "VARIANTs" do
245
+ let(:signature) { "v" }
246
+ include_examples "reports empty data"
247
+ end
248
+ end