ruby-dbus 0.16.0 → 0.25.0

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 (108) hide show
  1. checksums.yaml +4 -4
  2. data/NEWS.md +288 -3
  3. data/README.md +3 -5
  4. data/Rakefile +27 -12
  5. data/VERSION +1 -1
  6. data/doc/Reference.md +109 -10
  7. data/examples/doc/_extract_examples +7 -0
  8. data/examples/no-bus/pulseaudio.rb +50 -0
  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 +3 -3
  15. data/examples/simple/call_introspect.rb +1 -0
  16. data/examples/simple/get_id.rb +6 -3
  17. data/examples/simple/properties.rb +2 -0
  18. data/examples/utils/listnames.rb +10 -1
  19. data/examples/utils/notify.rb +1 -0
  20. data/lib/dbus/api_options.rb +9 -0
  21. data/lib/dbus/auth.rb +307 -217
  22. data/lib/dbus/bus.rb +155 -588
  23. data/lib/dbus/bus_name.rb +12 -8
  24. data/lib/dbus/connection.rb +363 -0
  25. data/lib/dbus/core_ext/class/attribute.rb +1 -1
  26. data/lib/dbus/data.rb +821 -0
  27. data/lib/dbus/emits_changed_signal.rb +83 -0
  28. data/lib/dbus/error.rb +4 -2
  29. data/lib/dbus/introspect.rb +133 -31
  30. data/lib/dbus/logger.rb +6 -3
  31. data/lib/dbus/main.rb +66 -0
  32. data/lib/dbus/marshall.rb +247 -296
  33. data/lib/dbus/matchrule.rb +16 -16
  34. data/lib/dbus/message.rb +66 -57
  35. data/lib/dbus/message_queue.rb +40 -25
  36. data/lib/dbus/node_tree.rb +105 -0
  37. data/lib/dbus/object.rb +442 -33
  38. data/lib/dbus/object_manager.rb +61 -0
  39. data/lib/dbus/object_path.rb +11 -6
  40. data/lib/dbus/object_server.rb +149 -0
  41. data/lib/dbus/org.freedesktop.DBus.xml +97 -0
  42. data/lib/dbus/platform.rb +26 -0
  43. data/lib/dbus/proxy_object.rb +30 -11
  44. data/lib/dbus/proxy_object_factory.rb +13 -7
  45. data/lib/dbus/proxy_object_interface.rb +63 -30
  46. data/lib/dbus/proxy_service.rb +107 -0
  47. data/lib/dbus/raw_message.rb +91 -0
  48. data/lib/dbus/type.rb +319 -86
  49. data/lib/dbus/xml.rb +40 -19
  50. data/lib/dbus.rb +28 -17
  51. data/ruby-dbus.gemspec +13 -6
  52. data/spec/async_spec.rb +2 -0
  53. data/spec/auth_spec.rb +225 -0
  54. data/spec/binding_spec.rb +2 -0
  55. data/spec/bus_and_xml_backend_spec.rb +5 -1
  56. data/spec/bus_connection_spec.rb +93 -0
  57. data/spec/bus_name_spec.rb +3 -1
  58. data/spec/bus_spec.rb +2 -0
  59. data/spec/byte_array_spec.rb +2 -0
  60. data/spec/client_robustness_spec.rb +4 -2
  61. data/spec/connection_spec.rb +37 -0
  62. data/spec/coverage_helper.rb +39 -0
  63. data/spec/data/marshall.yaml +1667 -0
  64. data/spec/data_spec.rb +673 -0
  65. data/spec/dbus_spec.rb +22 -0
  66. data/spec/emits_changed_signal_spec.rb +58 -0
  67. data/spec/err_msg_spec.rb +2 -0
  68. data/spec/introspect_xml_parser_spec.rb +2 -0
  69. data/spec/introspection_spec.rb +2 -0
  70. data/spec/main_loop_spec.rb +17 -1
  71. data/spec/message_spec.rb +21 -0
  72. data/spec/mock-service/cockpit-dbustests.rb +29 -0
  73. data/spec/mock-service/com.redhat.Cockpit.DBusTests.xml +180 -0
  74. data/spec/mock-service/org.ruby.service.service +4 -0
  75. data/spec/mock-service/org.rubygems.ruby_dbus.DBusTests.service +4 -0
  76. data/spec/{service_newapi.rb → mock-service/spaghetti-monster.rb} +122 -75
  77. data/spec/node_spec.rb +65 -0
  78. data/spec/object_manager_spec.rb +33 -0
  79. data/spec/object_path_spec.rb +3 -0
  80. data/spec/object_server_spec.rb +138 -0
  81. data/spec/object_spec.rb +194 -0
  82. data/spec/packet_marshaller_spec.rb +41 -0
  83. data/spec/packet_unmarshaller_spec.rb +248 -0
  84. data/spec/platform_spec.rb +14 -0
  85. data/spec/property_spec.rb +214 -12
  86. data/spec/proxy_object_interface_spec.rb +35 -0
  87. data/spec/proxy_object_spec.rb +2 -0
  88. data/spec/{bus_driver_spec.rb → proxy_service_spec.rb} +15 -8
  89. data/spec/raw_message_spec.rb +32 -0
  90. data/spec/server_robustness_spec.rb +20 -6
  91. data/spec/session_bus_spec.rb +36 -1
  92. data/spec/session_bus_spec_manual.rb +2 -0
  93. data/spec/signal_spec.rb +16 -3
  94. data/spec/spec_helper.rb +25 -33
  95. data/spec/thread_safety_spec.rb +55 -12
  96. data/spec/tools/dbus-launch-simple +9 -6
  97. data/spec/tools/dbus-limited-session.conf +32 -0
  98. data/spec/tools/test_env +26 -6
  99. data/spec/type_spec.rb +214 -6
  100. data/spec/value_spec.rb +16 -1
  101. data/spec/variant_spec.rb +4 -2
  102. data/spec/zzz_quit_spec.rb +16 -0
  103. metadata +92 -20
  104. data/examples/gdbus/gdbus +0 -257
  105. data/examples/gdbus/gdbus.glade +0 -98
  106. data/examples/gdbus/launch.sh +0 -4
  107. data/spec/server_spec.rb +0 -53
  108. data/spec/tools/test_server +0 -39
@@ -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
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env rspec
2
+ # frozen_string_literal: true
3
+
4
+ require_relative "spec_helper"
5
+ require "dbus"
6
+
7
+ describe DBus::Platform do
8
+ describe ".macos?" do
9
+ # code coverage chasing, as other tests mock it out
10
+ it "doesn't crash" do
11
+ expect { described_class.macos? }.to_not raise_error
12
+ end
13
+ end
14
+ end
@@ -1,11 +1,21 @@
1
1
  #!/usr/bin/env rspec
2
+ # frozen_string_literal: true
3
+
2
4
  require_relative "spec_helper"
3
5
  require "dbus"
4
6
 
7
+ # FIXME: factor out DBus::TestFixtures::Value in spec_helper
8
+ require "ostruct"
9
+ require "yaml"
10
+
11
+ data_dir = File.expand_path("data", __dir__)
12
+ marshall_yaml_s = File.read("#{data_dir}/marshall.yaml")
13
+ marshall_yaml = YAML.safe_load(marshall_yaml_s)
14
+
5
15
  describe "PropertyTest" do
6
16
  before(:each) do
7
- session_bus = DBus::ASessionBus.new
8
- @svc = session_bus.service("org.ruby.service")
17
+ @session_bus = DBus::ASessionBus.new
18
+ @svc = @session_bus.service("org.ruby.service")
9
19
  @obj = @svc.object("/org/ruby/MyInstance")
10
20
  @iface = @obj["org.ruby.SampleInterface"]
11
21
  end
@@ -21,17 +31,40 @@ describe "PropertyTest" do
21
31
  expect(iface["ReadMe"]).to eq("READ ME")
22
32
  end
23
33
 
34
+ context "when reading a property fails" do
35
+ it "gets an error, mentioning the qualified property name" do
36
+ expect { @iface["Explosive"] }
37
+ .to raise_error(DBus::Error, /getting.*SampleInterface.Explosive.*Something failed/)
38
+ end
39
+ end
40
+
24
41
  it "tests property nonreading" do
25
42
  expect { @iface["WriteMe"] }.to raise_error(DBus::Error, /not readable/)
26
43
  end
27
44
 
28
- it "tests property writing" do
29
- @iface["ReadOrWriteMe"] = "VALUE"
30
- expect(@iface["ReadOrWriteMe"]).to eq("VALUE")
45
+ context "writing properties" do
46
+ it "tests property writing" do
47
+ @iface["ReadOrWriteMe"] = "VALUE"
48
+ expect(@iface["ReadOrWriteMe"]).to eq("VALUE")
49
+ end
50
+
51
+ context "when writing a read-only property" do
52
+ it "gets an error, mentioning the qualified property name" do
53
+ expect { @iface["ReadMe"] = "WROTE" }
54
+ .to raise_error(DBus::Error, /SampleInterface.ReadMe.*not writable/)
55
+ end
56
+ end
57
+
58
+ context "when writing a property fails" do
59
+ it "gets an error, mentioning the qualified property name" do
60
+ expect { @iface["WriteMe"] = "Bruno is a city in Czechia" }
61
+ .to raise_error(DBus::Error, /setting.*SampleInterface.WriteMe/)
62
+ end
63
+ end
31
64
  end
32
65
 
33
66
  # https://github.com/mvidner/ruby-dbus/pull/19
34
- it "tests service select timeout" do
67
+ it "tests service select timeout", slow: true do
35
68
  @iface["ReadOrWriteMe"] = "VALUE"
36
69
  expect(@iface["ReadOrWriteMe"]).to eq("VALUE")
37
70
  # wait for the service to become idle
@@ -40,13 +73,9 @@ describe "PropertyTest" do
40
73
  expect(@iface["ReadOrWriteMe"]).to eq("VALUE")
41
74
  end
42
75
 
43
- it "tests property nonwriting" do
44
- expect { @iface["ReadMe"] = "WROTE" }.to raise_error(DBus::Error, /not writable/)
45
- end
46
-
47
76
  it "tests get all" do
48
77
  all = @iface.all_properties
49
- expect(all.keys.sort).to eq(["ReadMe", "ReadOrWriteMe"])
78
+ expect(all.keys.sort).to eq(["MyArray", "MyByte", "MyDict", "MyStruct", "MyVariant", "ReadMe", "ReadOrWriteMe"])
50
79
  end
51
80
 
52
81
  it "tests get all on a V1 object" do
@@ -54,7 +83,7 @@ describe "PropertyTest" do
54
83
  iface = obj["org.ruby.SampleInterface"]
55
84
 
56
85
  all = iface.all_properties
57
- expect(all.keys.sort).to eq(["ReadMe", "ReadOrWriteMe"])
86
+ expect(all.keys.sort).to eq(["MyArray", "MyByte", "MyDict", "MyStruct", "MyVariant", "ReadMe", "ReadOrWriteMe"])
58
87
  end
59
88
 
60
89
  it "tests unknown property reading" do
@@ -64,4 +93,177 @@ describe "PropertyTest" do
64
93
  it "tests unknown property writing" do
65
94
  expect { @iface["Spoon"] = "FPRK" }.to raise_error(DBus::Error, /not found/)
66
95
  end
96
+
97
+ it "errors for a property on an unknown interface" do
98
+ # our idiomatic way would error out on interface lookup already,
99
+ # so do it the low level way
100
+ prop_if = @obj[DBus::PROPERTY_INTERFACE]
101
+ expect { prop_if.Get("org.ruby.NoSuchInterface", "SomeProperty") }.to raise_error(DBus::Error) do |e|
102
+ expect(e.name).to match(/UnknownProperty/)
103
+ expect(e.message).to match(/no such interface/)
104
+ end
105
+ end
106
+
107
+ it "errors for GetAll on an unknown interface" do
108
+ # no idiomatic way?
109
+ # so do it the low level way
110
+ prop_if = @obj[DBus::PROPERTY_INTERFACE]
111
+ expect { prop_if.GetAll("org.ruby.NoSuchInterface") }.to raise_error(DBus::Error) do |e|
112
+ expect(e.name).to match(/UnknownProperty/)
113
+ expect(e.message).to match(/no such interface/)
114
+ end
115
+ end
116
+
117
+ it "receives a PropertiesChanged signal", slow: true do
118
+ received = {}
119
+
120
+ # TODO: for client side, provide a helper on_properties_changed,
121
+ # or automate it even more in ProxyObject, ProxyObjectInterface
122
+ prop_if = @obj[DBus::PROPERTY_INTERFACE]
123
+ prop_if.on_signal("PropertiesChanged") do |_interface_name, changed_props, _invalidated_props|
124
+ received.merge!(changed_props)
125
+ end
126
+
127
+ @iface["ReadOrWriteMe"] = "VALUE"
128
+ @iface.SetTwoProperties("REAMDE", 255)
129
+
130
+ # loop to process the signal. complicated :-( see signal_spec.rb
131
+ loop = DBus::Main.new
132
+ loop << @session_bus
133
+ quitter = Thread.new do
134
+ sleep 1
135
+ loop.quit
136
+ end
137
+ loop.run
138
+ # quitter has told loop.run to quit
139
+ quitter.join
140
+
141
+ expect(received["ReadOrWriteMe"]).to eq("VALUE")
142
+ expect(received["ReadMe"]).to eq("REAMDE")
143
+ expect(received["MyByte"]).to eq(255)
144
+ end
145
+
146
+ context "a struct-typed property" do
147
+ it "gets read as a struct, not an array (#97)" do
148
+ struct = @iface["MyStruct"]
149
+ expect(struct).to be_frozen
150
+ end
151
+
152
+ it "Get returns the correctly typed value (check with dbus-send)" do
153
+ # As big as the DBus::Data branch is,
154
+ # it still does not handle the :exact mode on the client/proxy side.
155
+ # So we resort to parsing dbus-send output.
156
+ cmd = "dbus-send --print-reply " \
157
+ "--dest=org.ruby.service " \
158
+ "/org/ruby/MyInstance " \
159
+ "org.freedesktop.DBus.Properties.Get " \
160
+ "string:org.ruby.SampleInterface " \
161
+ "string:MyStruct"
162
+ reply = `#{cmd}`
163
+ expect(reply).to match(/variant\s+struct {\s+string "three"\s+string "strings"\s+string "in a struct"\s+}/)
164
+ end
165
+
166
+ it "GetAll returns the correctly typed value (check with dbus-send)" do
167
+ cmd = "dbus-send --print-reply " \
168
+ "--dest=org.ruby.service " \
169
+ "/org/ruby/MyInstance " \
170
+ "org.freedesktop.DBus.Properties.GetAll " \
171
+ "string:org.ruby.SampleInterface "
172
+ reply = `#{cmd}`
173
+ expect(reply).to match(/variant\s+struct {\s+string "three"\s+string "strings"\s+string "in a struct"\s+}/)
174
+ end
175
+ end
176
+
177
+ context "an array-typed property" do
178
+ it "gets read as an array" do
179
+ val = @iface["MyArray"]
180
+ expect(val).to eq([42, 43])
181
+ end
182
+ end
183
+
184
+ context "a dict-typed property" do
185
+ it "gets read as a hash" do
186
+ val = @iface["MyDict"]
187
+ expect(val).to eq({
188
+ "one" => 1,
189
+ "two" => "dva",
190
+ "three" => [3, 3, 3]
191
+ })
192
+ end
193
+
194
+ it "Get returns the correctly typed value (check with dbus-send)" do
195
+ cmd = "dbus-send --print-reply " \
196
+ "--dest=org.ruby.service " \
197
+ "/org/ruby/MyInstance " \
198
+ "org.freedesktop.DBus.Properties.Get " \
199
+ "string:org.ruby.SampleInterface " \
200
+ "string:MyDict"
201
+ reply = `#{cmd}`
202
+ # a bug about variant nesting lead to a "variant variant int32 1" value
203
+ match_rx = /variant \s+ array \s \[ \s+
204
+ dict \s entry\( \s+
205
+ string \s "one" \s+
206
+ variant \s+ int32 \s 1 \s+
207
+ \)/x
208
+ expect(reply).to match(match_rx)
209
+ end
210
+ end
211
+
212
+ context "a variant-typed property" do
213
+ it "gets read at all" do
214
+ obj = @svc.object("/org/ruby/MyDerivedInstance")
215
+ iface = obj["org.ruby.SampleInterface"]
216
+ val = iface["MyVariant"]
217
+ expect(val).to eq([42, 43])
218
+ end
219
+ end
220
+
221
+ context "a byte-typed property" do
222
+ # Slightly advanced RSpec:
223
+ # https://rspec.info/documentation/3.9/rspec-expectations/RSpec/Matchers.html#satisfy-instance_method
224
+ let(:a_byte_in_a_variant) do
225
+ satisfying { |x| x.is_a?(DBus::Data::Variant) && x.member_type.to_s == DBus::Type::BYTE }
226
+ # ^ This formatting keeps the matcher on a single line
227
+ # which enables RSpec to cite it if it fails, instead of saying "block".
228
+ end
229
+
230
+ let(:prop_iface) { @obj[DBus::PROPERTY_INTERFACE] }
231
+
232
+ it "gets set with a correct type (#108)" do
233
+ expect(prop_iface).to receive(:Set).with(
234
+ "org.ruby.SampleInterface",
235
+ "MyByte",
236
+ a_byte_in_a_variant
237
+ )
238
+ @iface["MyByte"] = 1
239
+ end
240
+
241
+ it "gets set with a correct type (#108), when using the DBus.variant workaround" do
242
+ expect(prop_iface).to receive(:Set).with(
243
+ "org.ruby.SampleInterface",
244
+ "MyByte",
245
+ a_byte_in_a_variant
246
+ )
247
+ @iface["MyByte"] = DBus.variant("y", 1)
248
+ end
249
+ end
250
+
251
+ context "marshall.yaml round-trip via a VARIANT property" do
252
+ marshall_yaml.each do |test|
253
+ t = OpenStruct.new(test)
254
+ next if t.val.nil?
255
+
256
+ # Round trips do not work yet because the properties
257
+ # must present a plain Ruby value so the exact D-Bus type is lost.
258
+ # Round trips will work once users can declare accepting DBus::Data
259
+ # in properties and method arguments.
260
+ it "Sets #{t.sig.inspect}:#{t.val.inspect} and Gets something back" do
261
+ before = DBus::Data.make_typed(t.sig, t.val)
262
+ expect { @iface["MyVariant"] = before }.to_not raise_error
263
+ expect { _after = @iface["MyVariant"] }.to_not raise_error
264
+ # round-trip:
265
+ # expect(after).to eq(before.value)
266
+ end
267
+ end
268
+ end
67
269
  end
@@ -0,0 +1,35 @@
1
+ #!/usr/bin/env rspec
2
+ # frozen_string_literal: true
3
+
4
+ require_relative "spec_helper"
5
+ require "dbus"
6
+
7
+ describe DBus::ProxyObjectInterface do
8
+ # TODO: tag tests that need a service, eg "needs-service"
9
+ # TODO: maybe remove this and rely on a packaged tool
10
+ around(:each) do |example|
11
+ with_private_bus do
12
+ with_service_by_activation(&example)
13
+ end
14
+ end
15
+
16
+ let(:bus) { DBus::ASessionBus.new }
17
+
18
+ context "when calling org.ruby.service" do
19
+ let(:svc) { bus["org.ruby.service"] }
20
+
21
+ # This is white box testing, knowing the implementation
22
+ # A better way would be structuring it according to the D-Bus Spec
23
+ # Or testing the service side doing the right thing? (What if our bugs cancel out)
24
+ describe "#define_method_from_descriptor" do
25
+ it "can call a method with multiple OUT arguments" do
26
+ obj = svc["/org/ruby/MyInstance"]
27
+ ifc = obj["org.ruby.SampleInterface"]
28
+
29
+ even, odd = ifc.EvenOdd([3, 1, 4, 1, 5, 9, 2, 6])
30
+ expect(even).to eq [4, 2, 6]
31
+ expect(odd).to eq [3, 1, 1, 5, 9]
32
+ end
33
+ end
34
+ end
35
+ 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
 
@@ -1,15 +1,11 @@
1
1
  #!/usr/bin/env rspec
2
+ # frozen_string_literal: true
3
+
2
4
  require_relative "spec_helper"
3
5
  require "dbus"
4
6
 
5
- describe DBus::Service do
6
- context "when a private bus is set up" do
7
- around(:each) do |example|
8
- with_private_bus do
9
- with_service_by_activation(&example)
10
- end
11
- end
12
-
7
+ describe DBus::ProxyService do
8
+ context "when a private bus is set up", tag_service: true do
13
9
  let(:bus) { DBus::ASessionBus.new }
14
10
 
15
11
  describe "#exists?" do
@@ -24,5 +20,16 @@ describe DBus::Service do
24
20
  expect(svc.exists?).to be false
25
21
  end
26
22
  end
23
+
24
+ # This method is used by dbus-gui-gtk.
25
+ # Deprecate it? In favor of introspecting the tree gradually
26
+ # or move it to the application code?
27
+ describe "#introspect" do
28
+ it "creates the whole node tree" do
29
+ svc = bus.service("org.ruby.service")
30
+ expect { svc.introspect }.to_not raise_error
31
+ expect(svc.root.dig("org", "ruby", "MyInstance")).to be_a DBus::Node
32
+ end
33
+ end
27
34
  end
28
35
  end
@@ -0,0 +1,32 @@
1
+ #!/usr/bin/env rspec
2
+ # frozen_string_literal: true
3
+
4
+ require_relative "spec_helper"
5
+ require "dbus"
6
+
7
+ # Pedantic full coverage test.
8
+ # The happy paths are covered via calling classes
9
+ describe DBus::RawMessage do
10
+ describe ".endianness" do
11
+ it "returns :little for 'l'" do
12
+ expect(described_class.endianness("l")).to eq :little
13
+ end
14
+
15
+ it "returns :big for 'B'" do
16
+ expect(described_class.endianness("B")).to eq :big
17
+ end
18
+
19
+ it "raises for other strings" do
20
+ expect { described_class.endianness("m") }
21
+ .to raise_error(DBus::InvalidPacketException, /Incorrect endianness/)
22
+ end
23
+ end
24
+
25
+ describe "#align" do
26
+ it "raises for values other than 1 2 4 8" do
27
+ subject = described_class.new("l")
28
+ expect { subject.align(3) }.to raise_error(ArgumentError)
29
+ expect { subject.align(16) }.to raise_error(ArgumentError)
30
+ end
31
+ end
32
+ end