ruby-dbus 0.18.1 → 0.20.0

Sign up to get free protection for your applications and to get access to all the features.
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