ruby-dbus 0.18.1 → 0.20.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.
data/lib/dbus/bus.rb CHANGED
@@ -76,6 +76,7 @@ module DBus
76
76
  def export(obj)
77
77
  obj.service = self
78
78
  get_node(obj.path, create: true).object = obj
79
+ object_manager_for(obj)&.object_added(obj)
79
80
  end
80
81
 
81
82
  # Undo exporting an object _obj_.
@@ -93,6 +94,7 @@ module DBus
93
94
  parent_node = get_node(parent_path, create: false)
94
95
  return false unless parent_node
95
96
 
97
+ object_manager_for(obj)&.object_removed(obj)
96
98
  obj.service = nil
97
99
  parent_node.delete(node_name).object
98
100
  end
@@ -112,18 +114,51 @@ module DBus
112
114
  end
113
115
  n = n[elem]
114
116
  end
115
- if n.nil?
116
- DBus.logger.debug "Warning, unknown object #{path}"
117
- end
118
117
  n
119
118
  end
120
119
 
120
+ # Find the (closest) parent of *object*
121
+ # implementing the ObjectManager interface, or nil
122
+ # @return [DBus::Object,nil]
123
+ def object_manager_for(object)
124
+ path = object.path
125
+ node_chain = get_node_chain(path)
126
+ om_node = node_chain.reverse_each.find do |node|
127
+ node.object&.is_a? DBus::ObjectManager
128
+ end
129
+ om_node&.object
130
+ end
131
+
132
+ # All objects (not paths) under this path (except itself).
133
+ # @param path [ObjectPath]
134
+ # @return [Array<DBus::Object>]
135
+ # @raise ArgumentError if the *path* does not exist
136
+ def descendants_for(path)
137
+ node = get_node(path, create: false)
138
+ raise ArgumentError, "Object path #{path} doesn't exist" if node.nil?
139
+
140
+ node.descendant_objects
141
+ end
142
+
121
143
  #########
122
144
 
123
145
  private
124
146
 
125
147
  #########
126
148
 
149
+ # @raise ArgumentError if the *path* does not exist
150
+ def get_node_chain(path)
151
+ n = @root
152
+ result = [n]
153
+ path.sub(%r{^/}, "").split("/").each do |elem|
154
+ n = n[elem]
155
+ raise ArgumentError, "Object path #{path} doesn't exist" if n.nil?
156
+
157
+ result.push(n)
158
+ end
159
+ result
160
+ end
161
+
127
162
  # Perform a recursive retrospection on the given current _node_
128
163
  # on the given _path_.
129
164
  def rec_introspect(node, path)
@@ -198,6 +233,15 @@ module DBus
198
233
  .join(",")
199
234
  "#{s}{#{contents_sub_inspect}}"
200
235
  end
236
+
237
+ # All objects (not paths) under this path (except itself).
238
+ # @return [Array<DBus::Object>]
239
+ def descendant_objects
240
+ children_objects = values.map(&:object).compact
241
+ descendants = values.map(&:descendant_objects)
242
+ flat_descendants = descendants.reduce([], &:+)
243
+ children_objects + flat_descendants
244
+ end
201
245
  end
202
246
 
203
247
  # FIXME: rename Connection to Bus?
@@ -233,7 +277,6 @@ module DBus
233
277
  @method_call_msgs = {}
234
278
  @signal_matchrules = {}
235
279
  @proxy = nil
236
- @object_root = Node.new("/")
237
280
  end
238
281
 
239
282
  # Dispatch all messages that are available in the queue,
@@ -561,23 +604,22 @@ module DBus
561
604
  DBus.logger.debug "Got method call on /org/freedesktop/DBus"
562
605
  end
563
606
  node = @service.get_node(msg.path, create: false)
564
- if !node
565
- reply = Message.error(msg, "org.freedesktop.DBus.Error.UnknownObject",
566
- "Object #{msg.path} doesn't exist")
567
- @message_queue.push(reply)
568
- # handle introspectable as an exception:
569
- elsif msg.interface == "org.freedesktop.DBus.Introspectable" &&
570
- msg.member == "Introspect"
607
+ # introspect a known path even if there is no object on it
608
+ if node &&
609
+ msg.interface == "org.freedesktop.DBus.Introspectable" &&
610
+ msg.member == "Introspect"
571
611
  reply = Message.new(Message::METHOD_RETURN).reply_to(msg)
572
612
  reply.sender = @unique_name
573
613
  xml = node.to_xml(msg.path)
574
614
  reply.add_param(Type::STRING, xml)
575
615
  @message_queue.push(reply)
616
+ # dispatch for an object
617
+ elsif node&.object
618
+ node.object.dispatch(msg)
576
619
  else
577
- obj = node.object
578
- return if obj.nil? # FIXME, pushes no reply
579
-
580
- obj&.dispatch(msg)
620
+ reply = Message.error(msg, "org.freedesktop.DBus.Error.UnknownObject",
621
+ "Object #{msg.path} doesn't exist")
622
+ @message_queue.push(reply)
581
623
  end
582
624
  when DBus::Message::SIGNAL
583
625
  # the signal can match multiple different rules
@@ -270,7 +270,7 @@ module DBus
270
270
  class Property
271
271
  # @return [Symbol] The name of the property, for example FooBar.
272
272
  attr_reader :name
273
- # @return [SingleCompleteType]
273
+ # @return [Type]
274
274
  attr_reader :type
275
275
  # @return [Symbol] :read :write or :readwrite
276
276
  attr_reader :access
@@ -282,6 +282,7 @@ module DBus
282
282
 
283
283
  def initialize(name, type, access, ruby_name:)
284
284
  @name = name.to_sym
285
+ type = DBus.type(type) unless type.is_a?(Type)
285
286
  @type = type
286
287
  @access = access
287
288
  @ruby_name = ruby_name
data/lib/dbus/marshall.rb CHANGED
@@ -327,7 +327,7 @@ module DBus
327
327
  # Damn ruby rocks here
328
328
  val = val.to_a
329
329
  end
330
- # If string is recieved and ay is expected, explode the string
330
+ # If string is received and ay is expected, explode the string
331
331
  if val.is_a?(String) && child_type.sigtype == Type::BYTE
332
332
  val = val.bytes
333
333
  end
@@ -19,9 +19,11 @@ module DBus
19
19
  attr_reader :socket
20
20
 
21
21
  def initialize(address)
22
+ DBus.logger.debug "MessageQueue: #{address}"
22
23
  @address = address
23
24
  @buffer = ""
24
25
  @is_tcp = false
26
+ @mutex = Mutex.new
25
27
  connect
26
28
  end
27
29
 
@@ -32,23 +34,28 @@ module DBus
32
34
  # @raise EOFError
33
35
  # @todo failure modes
34
36
  def pop(blocking: true)
35
- buffer_from_socket_nonblock
36
- message = message_from_buffer_nonblock
37
- if blocking
38
- # we can block
39
- while message.nil?
40
- r, _d, _d = IO.select([@socket])
41
- if r && r[0] == @socket
42
- buffer_from_socket_nonblock
43
- message = message_from_buffer_nonblock
37
+ # FIXME: this is not enough, the R/W test deadlocks on shared connections
38
+ @mutex.synchronize do
39
+ buffer_from_socket_nonblock
40
+ message = message_from_buffer_nonblock
41
+ if blocking
42
+ # we can block
43
+ while message.nil?
44
+ r, _d, _d = IO.select([@socket])
45
+ if r && r[0] == @socket
46
+ buffer_from_socket_nonblock
47
+ message = message_from_buffer_nonblock
48
+ end
44
49
  end
45
50
  end
51
+ message
46
52
  end
47
- message
48
53
  end
49
54
 
50
55
  def push(message)
51
- @socket.write(message.marshall)
56
+ @mutex.synchronize do
57
+ @socket.write(message.marshall)
58
+ end
52
59
  end
53
60
  alias << push
54
61
 
@@ -129,7 +136,7 @@ module DBus
129
136
 
130
137
  # Initialize the connection to the bus.
131
138
  def init_connection
132
- client = Client.new(@socket)
139
+ client = Authentication::Client.new(@socket)
133
140
  client.authenticate
134
141
  end
135
142
 
data/lib/dbus/object.rb CHANGED
@@ -89,8 +89,11 @@ module DBus
89
89
  # while the superclass keeps the old one.
90
90
  self.intfs = intfs.merge(name => @@cur_intf)
91
91
  end
92
- yield
93
- @@cur_intf = nil
92
+ begin
93
+ yield
94
+ ensure
95
+ @@cur_intf = nil
96
+ end
94
97
  end
95
98
  end
96
99
 
@@ -122,7 +125,8 @@ module DBus
122
125
  #
123
126
  # @param ruby_name [Symbol] :foo_bar is exposed as FooBar;
124
127
  # use dbus_name to override
125
- # @param type a signature like "s" or "a(uus)" or Type::STRING
128
+ # @param type [Type,SingleCompleteType]
129
+ # a signature like "s" or "a(uus)" or Type::STRING
126
130
  # @param dbus_name [String] if not given it is made
127
131
  # by CamelCasing the ruby_name. foo_bar becomes FooBar
128
132
  # to convert the Ruby convention to the DBus convention.
@@ -387,6 +391,23 @@ module DBus
387
391
  property
388
392
  end
389
393
 
394
+ # Generates information about interfaces and properties of the object
395
+ #
396
+ # Returns a hash containing interfaces names as keys. Each value is the
397
+ # same hash that would be returned by the
398
+ # org.freedesktop.DBus.Properties.GetAll() method for that combination of
399
+ # object path and interface. If an interface has no properties, the empty
400
+ # hash is returned.
401
+ #
402
+ # @return [Hash{String => Hash{String => Data::Base}}] interface -> property -> value
403
+ def interfaces_and_properties
404
+ get_all_method = self.class.make_method_name("org.freedesktop.DBus.Properties", :GetAll)
405
+
406
+ intfs.keys.each_with_object({}) do |interface, hash|
407
+ hash[interface] = public_send(get_all_method, interface).first
408
+ end
409
+ end
410
+
390
411
  ####################################################################
391
412
 
392
413
  # use the above defined methods to declare the property-handling
@@ -459,5 +480,12 @@ module DBus
459
480
 
460
481
  dbus_signal :PropertiesChanged, "interface:s, changed_properties:a{sv}, invalidated_properties:as"
461
482
  end
483
+
484
+ dbus_interface "org.freedesktop.DBus.Introspectable" do
485
+ dbus_method :Introspect, "out xml_data:s" do
486
+ # The body is not used, Connection#process handles it instead
487
+ # which is more efficient and handles paths without objects.
488
+ end
489
+ end
462
490
  end
463
491
  end
@@ -0,0 +1,58 @@
1
+ # frozen_string_literal: true
2
+
3
+ # This file is part of the ruby-dbus project
4
+ # Copyright (C) 2022 José Iván López González
5
+ # Copyright (C) 2022 Martin Vidner
6
+ #
7
+ # This library is free software; you can redistribute it and/or
8
+ # modify it under the terms of the GNU Lesser General Public
9
+ # License, version 2.1 as published by the Free Software Foundation.
10
+ # See the file "COPYING" for the exact licensing terms.
11
+
12
+ module DBus
13
+ # A mixin for {DBus::Object} implementing
14
+ # {https://dbus.freedesktop.org/doc/dbus-specification.html#standard-interfaces-objectmanager
15
+ # org.freedesktop.DBus.ObjectManager}.
16
+ #
17
+ # {Service#export} and {Service#unexport} will look for an ObjectManager
18
+ # parent in the path hierarchy. If found, it will emit InterfacesAdded
19
+ # or InterfacesRemoved, as appropriate.
20
+ module ObjectManager
21
+ OBJECT_MANAGER_INTERFACE = "org.freedesktop.DBus.ObjectManager"
22
+
23
+ # @return [Hash{ObjectPath => Hash{String => Hash{String => Data::Base}}}]
24
+ # object -> interface -> property -> value
25
+ def managed_objects
26
+ # FIXME: also fix the "service" concept
27
+ descendant_objects = @service.descendants_for(path)
28
+ descendant_objects.each_with_object({}) do |obj, hash|
29
+ hash[obj.path] = obj.interfaces_and_properties
30
+ end
31
+ end
32
+
33
+ # @param object [DBus::Object]
34
+ # @return [void]
35
+ def object_added(object)
36
+ InterfacesAdded(object.path, object.interfaces_and_properties)
37
+ end
38
+
39
+ # @param object [DBus::Object]
40
+ # @return [void]
41
+ def object_removed(object)
42
+ InterfacesRemoved(object.path, object.intfs.keys)
43
+ end
44
+
45
+ def self.included(base)
46
+ base.class_eval do
47
+ dbus_interface OBJECT_MANAGER_INTERFACE do
48
+ dbus_method :GetManagedObjects, "out res:a{oa{sa{sv}}}" do
49
+ [managed_objects]
50
+ end
51
+
52
+ dbus_signal :InterfacesAdded, "object:o, interfaces_and_properties:a{sa{sv}}"
53
+ dbus_signal :InterfacesRemoved, "object:o, interfaces:as"
54
+ end
55
+ end
56
+ end
57
+ end
58
+ end
data/lib/dbus.rb CHANGED
@@ -24,6 +24,7 @@ require_relative "dbus/matchrule"
24
24
  require_relative "dbus/message"
25
25
  require_relative "dbus/message_queue"
26
26
  require_relative "dbus/object"
27
+ require_relative "dbus/object_manager"
27
28
  require_relative "dbus/object_path"
28
29
  require_relative "dbus/proxy_object"
29
30
  require_relative "dbus/proxy_object_factory"
data/spec/auth_spec.rb ADDED
@@ -0,0 +1,225 @@
1
+ #!/usr/bin/env rspec
2
+ # frozen_string_literal: true
3
+
4
+ require_relative "spec_helper"
5
+ require "dbus"
6
+
7
+ describe DBus::Authentication::Client do
8
+ let(:socket) { instance_double("Socket") }
9
+ let(:subject) { described_class.new(socket) }
10
+
11
+ before(:each) do
12
+ allow(Process).to receive(:uid).and_return(999)
13
+ allow(subject).to receive(:send_nul_byte)
14
+ end
15
+
16
+ describe "#next_state" do
17
+ it "raises when I forget to handle a state" do
18
+ subject.instance_variable_set(:@state, :Denmark)
19
+ expect { subject.__send__(:next_state, []) }.to raise_error(RuntimeError, /unhandled state :Denmark/)
20
+ end
21
+ end
22
+
23
+ def expect_protocol(pairs)
24
+ pairs.each do |we_say, server_says|
25
+ expect(subject).to receive(:write_line).with(we_say)
26
+ next if server_says.nil?
27
+
28
+ expect(subject).to receive(:read_line).and_return(server_says)
29
+ end
30
+ end
31
+
32
+ context "with ANONYMOUS" do
33
+ let(:subject) { described_class.new(socket, [DBus::Authentication::Anonymous]) }
34
+
35
+ it "authentication passes" do
36
+ expect_protocol [
37
+ ["AUTH ANONYMOUS 527562792044427573\r\n", "OK ffffffffffffffffffffffffffffffff\r\n"],
38
+ ["NEGOTIATE_UNIX_FD\r\n", "ERROR not for anonymous\r\n"],
39
+ ["BEGIN\r\n"]
40
+ ]
41
+
42
+ expect { subject.authenticate }.to_not raise_error
43
+ end
44
+ end
45
+
46
+ context "with EXTERNAL" do
47
+ let(:subject) { described_class.new(socket, [DBus::Authentication::External]) }
48
+
49
+ it "authentication passes, and address_uuid is set" do
50
+ expect_protocol [
51
+ ["AUTH EXTERNAL 393939\r\n", "OK ffffffffffffffffffffffffffffffff\r\n"],
52
+ ["NEGOTIATE_UNIX_FD\r\n", "AGREE_UNIX_FD\r\n"],
53
+ ["BEGIN\r\n"]
54
+ ]
55
+
56
+ expect { subject.authenticate }.to_not raise_error
57
+ expect(subject.address_uuid).to eq "ffffffffffffffffffffffffffffffff"
58
+ end
59
+
60
+ context "when the server says superfluous things before an OK" do
61
+ it "authentication passes" do
62
+ expect_protocol [
63
+ ["AUTH EXTERNAL 393939\r\n", "WOULD_YOU_LIKE_SOME_TEA\r\n"],
64
+ ["ERROR\r\n", "OK ffffffffffffffffffffffffffffffff\r\n"],
65
+ ["NEGOTIATE_UNIX_FD\r\n", "AGREE_UNIX_FD\r\n"],
66
+ ["BEGIN\r\n"]
67
+ ]
68
+
69
+ expect { subject.authenticate }.to_not raise_error
70
+ end
71
+ end
72
+
73
+ context "when the server messes up NEGOTIATE_UNIX_FD" do
74
+ it "authentication fails orderly" do
75
+ expect_protocol [
76
+ ["AUTH EXTERNAL 393939\r\n", "OK ffffffffffffffffffffffffffffffff\r\n"],
77
+ ["NEGOTIATE_UNIX_FD\r\n", "I_DONT_NEGOTIATE_WITH_TENORISTS\r\n"]
78
+ ]
79
+
80
+ allow(socket).to receive(:close) # want to get rid of this
81
+ # TODO: quote the server error message?
82
+ expect { subject.authenticate }.to raise_error(DBus::AuthenticationFailed, /Unknown server reply/)
83
+ end
84
+ end
85
+
86
+ context "when the server replies with ERROR" do
87
+ it "authentication fails orderly" do
88
+ expect_protocol [
89
+ ["AUTH EXTERNAL 393939\r\n", "ERROR something failed\r\n"],
90
+ ["CANCEL\r\n", "REJECTED DBUS_COOKIE_SHA1\r\n"]
91
+ ]
92
+
93
+ allow(socket).to receive(:close) # want to get rid of this
94
+ # TODO: quote the server error message?
95
+ expect { subject.authenticate }.to raise_error(DBus::AuthenticationFailed, /exhausted/)
96
+ end
97
+ end
98
+ end
99
+
100
+ context "with EXTERNAL without uid" do
101
+ let(:subject) do
102
+ described_class.new(socket, [DBus::Authentication::External, DBus::Authentication::ExternalWithoutUid])
103
+ end
104
+
105
+ it "authentication passes" do
106
+ expect_protocol [
107
+ ["AUTH EXTERNAL 393939\r\n", "REJECTED EXTERNAL\r\n"],
108
+ # this succeeds when we connect to a privileged container,
109
+ # where outside-non-root becomes inside-root
110
+ ["AUTH EXTERNAL\r\n", "DATA\r\n"],
111
+ ["DATA\r\n", "OK ffffffffffffffffffffffffffffffff\r\n"],
112
+ ["NEGOTIATE_UNIX_FD\r\n", "AGREE_UNIX_FD\r\n"],
113
+ ["BEGIN\r\n"]
114
+ ]
115
+
116
+ expect { subject.authenticate }.to_not raise_error
117
+ end
118
+ end
119
+
120
+ context "with a rejected mechanism and then EXTERNAL" do
121
+ let(:rejected_mechanism) do
122
+ double("Mechanism", name: "WIMP", call: [:MechContinue, "I expect to be rejected"])
123
+ end
124
+
125
+ let(:subject) { described_class.new(socket, [rejected_mechanism, DBus::Authentication::External]) }
126
+
127
+ it "authentication eventually passes" do
128
+ expect_protocol [
129
+ [/^AUTH WIMP .*\r\n/, "REJECTED EXTERNAL\r\n"],
130
+ ["AUTH EXTERNAL 393939\r\n", "OK ffffffffffffffffffffffffffffffff\r\n"],
131
+ ["NEGOTIATE_UNIX_FD\r\n", "AGREE_UNIX_FD\r\n"],
132
+ ["BEGIN\r\n"]
133
+ ]
134
+
135
+ expect { subject.authenticate }.to_not raise_error
136
+ end
137
+ end
138
+
139
+ context "with a DATA-using mechanism" do
140
+ let(:mechanism) do
141
+ double("Mechanism", name: "CHALLENGE_ME", call: [:MechContinue, "1"])
142
+ end
143
+
144
+ # try it twice to test calling #use_next_mechanism
145
+ let(:subject) { described_class.new(socket, [mechanism, mechanism]) }
146
+
147
+ it "authentication fails orderly when the server says ERROR" do
148
+ expect_protocol [
149
+ ["AUTH CHALLENGE_ME 31\r\n", "ERROR something failed\r\n"],
150
+ ["CANCEL\r\n", "REJECTED DBUS_COOKIE_SHA1\r\n"],
151
+ ["AUTH CHALLENGE_ME 31\r\n", "ERROR something failed\r\n"],
152
+ ["CANCEL\r\n", "REJECTED DBUS_COOKIE_SHA1\r\n"]
153
+ ]
154
+
155
+ allow(socket).to receive(:close) # want to get rid of this
156
+ # TODO: quote the server error message?
157
+ expect { subject.authenticate }.to raise_error(DBus::AuthenticationFailed, /exhausted/)
158
+ end
159
+
160
+ it "authentication fails orderly when the server says ERROR and then changes its mind" do
161
+ expect_protocol [
162
+ ["AUTH CHALLENGE_ME 31\r\n", "ERROR something failed\r\n"],
163
+ ["CANCEL\r\n", "I_CHANGED_MY_MIND please come back\r\n"]
164
+ ]
165
+
166
+ allow(socket).to receive(:close) # want to get rid of this
167
+ # TODO: quote the server error message?
168
+ expect { subject.authenticate }.to raise_error(DBus::AuthenticationFailed, /Unknown.*MIND.*REJECTED/)
169
+ end
170
+
171
+ it "authentication passes when the server says superfluous things before DATA" do
172
+ expect_protocol [
173
+ ["AUTH CHALLENGE_ME 31\r\n", "WOULD_YOU_LIKE_SOME_TEA\r\n"],
174
+ ["ERROR\r\n", "DATA\r\n"],
175
+ ["DATA 31\r\n", "OK ffffffffffffffffffffffffffffffff\r\n"],
176
+ ["NEGOTIATE_UNIX_FD\r\n", "AGREE_UNIX_FD\r\n"],
177
+ ["BEGIN\r\n"]
178
+ ]
179
+
180
+ expect { subject.authenticate }.to_not raise_error
181
+ end
182
+
183
+ it "authentication passes when the server decides not to need the DATA" do
184
+ expect_protocol [
185
+ ["AUTH CHALLENGE_ME 31\r\n", "OK ffffffffffffffffffffffffffffffff\r\n"],
186
+ ["NEGOTIATE_UNIX_FD\r\n", "AGREE_UNIX_FD\r\n"],
187
+ ["BEGIN\r\n"]
188
+ ]
189
+
190
+ expect { subject.authenticate }.to_not raise_error
191
+ end
192
+ end
193
+
194
+ context "with a mechanism returning :MechError" do
195
+ let(:fallible_mechanism) do
196
+ double(name: "FALLIBLE", call: [:MechError, "not my best day"])
197
+ end
198
+
199
+ let(:subject) { described_class.new(socket, [fallible_mechanism]) }
200
+
201
+ it "authentication fails orderly" do
202
+ expect_protocol [
203
+ ["ERROR not my best day\r\n", "REJECTED DBUS_COOKIE_SHA1\r\n"]
204
+ ]
205
+
206
+ allow(socket).to receive(:close) # want to get rid of thise
207
+ expect { subject.authenticate }.to raise_error(DBus::AuthenticationFailed, /exhausted/)
208
+ end
209
+ end
210
+
211
+ context "with a badly implemented mechanism" do
212
+ let(:buggy_mechanism) do
213
+ double(name: "buggy", call: [:smurf, nil])
214
+ end
215
+
216
+ let(:subject) { described_class.new(socket, [buggy_mechanism]) }
217
+
218
+ it "authentication fails before protoxol is exchanged" do
219
+ expect(subject).to_not receive(:write_line)
220
+ expect(subject).to_not receive(:read_line)
221
+
222
+ expect { subject.authenticate }.to raise_error(DBus::AuthenticationFailed, /smurf/)
223
+ end
224
+ end
225
+ end
data/spec/node_spec.rb CHANGED
@@ -20,4 +20,50 @@ describe DBus::Node do
20
20
  expect(parent.inspect).to match(/<DBus::Node [0-9a-f]+ {child0 => {},child1 => {},child2 => {}}>/)
21
21
  end
22
22
  end
23
+
24
+ describe "#descendant_objects" do
25
+ let(:manager_path) { "/org/example/FooManager" }
26
+ let(:child_paths) do
27
+ [
28
+ # note that "/org/example/FooManager/good"
29
+ # is a path under a managed object but there is no object there
30
+ "/org/example/FooManager/good/1",
31
+ "/org/example/FooManager/good/2",
32
+ "/org/example/FooManager/good/3",
33
+ "/org/example/FooManager/bad/1",
34
+ "/org/example/FooManager/bad/2"
35
+ ]
36
+ end
37
+
38
+ let(:non_child_paths) do
39
+ [
40
+ "/org/example/BarManager/good/1",
41
+ "/org/example/BarManager/good/2"
42
+ ]
43
+ end
44
+
45
+ context "on the bus" do
46
+ let(:bus) { DBus::ASessionBus.new }
47
+ let(:service) do
48
+ # if we used org.ruby.service it would be a name collision
49
+ # ... which would not break the test for lucky reasons
50
+ bus.request_service("org.ruby.service.scratch")
51
+ end
52
+
53
+ before do
54
+ service.export(DBus::Object.new(manager_path))
55
+ non_child_paths.each do |p|
56
+ service.export(DBus::Object.new(p))
57
+ end
58
+ end
59
+
60
+ it "returns just the descendants of the specified objects" do
61
+ child_exported_objects = child_paths.map { |p| DBus::Object.new(p) }
62
+ child_exported_objects.each { |obj| service.export(obj) }
63
+
64
+ node = service.get_node(manager_path, create: false)
65
+ expect(node.descendant_objects).to eq child_exported_objects
66
+ end
67
+ end
68
+ end
23
69
  end
@@ -0,0 +1,33 @@
1
+ #!/usr/bin/env rspec
2
+ # frozen_string_literal: true
3
+
4
+ require_relative "spec_helper"
5
+ require "dbus"
6
+
7
+ describe DBus::ObjectManager do
8
+ describe "GetManagedObjects" do
9
+ let(:bus) { DBus::ASessionBus.new }
10
+ let(:service) { bus["org.ruby.service"] }
11
+ let(:obj) { service["/org/ruby/MyInstance"] }
12
+ let(:parent_iface) { obj["org.ruby.TestParent"] }
13
+ let(:om_iface) { obj["org.freedesktop.DBus.ObjectManager"] }
14
+
15
+ it "returns the interfaces and properties of currently managed objects" do
16
+ c1_opath = parent_iface.New("child1")
17
+ c2_opath = parent_iface.New("child2")
18
+
19
+ parent_iface.Delete(c1_opath)
20
+ expected_gmo = {
21
+ "/org/ruby/MyInstance/child2" => {
22
+ "org.freedesktop.DBus.Introspectable" => {},
23
+ "org.freedesktop.DBus.Properties" => {},
24
+ "org.ruby.TestChild" => { "Name" => "Child2" }
25
+ }
26
+ }
27
+ expect(om_iface.GetManagedObjects).to eq(expected_gmo)
28
+
29
+ parent_iface.Delete(c2_opath)
30
+ expect(om_iface.GetManagedObjects).to eq({})
31
+ end
32
+ end
33
+ end
data/spec/object_spec.rb CHANGED
@@ -61,6 +61,16 @@ describe DBus::Object do
61
61
  end
62
62
  end.to raise_error(DBus::Object::UndefinedInterface)
63
63
  end
64
+
65
+ it "fails when the signature is invalid" do
66
+ expect do
67
+ ObjectTest.instance_exec do
68
+ dbus_interface "org.ruby.ServerTest" do
69
+ dbus_reader :foo2, "!"
70
+ end
71
+ end
72
+ end.to raise_error(DBus::Type::SignatureException)
73
+ end
64
74
  end
65
75
 
66
76
  describe ".dbus_reader, when paired with attr_accessor" do