em-ruby-dbus 0.11.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/COPYING +504 -0
- data/NEWS +253 -0
- data/README.md +93 -0
- data/Rakefile +58 -0
- data/VERSION +1 -0
- data/doc/Reference.md +207 -0
- data/doc/Tutorial.md +480 -0
- data/doc/ex-calling-methods.body.rb +8 -0
- data/doc/ex-calling-methods.rb +3 -0
- data/doc/ex-properties.body.rb +9 -0
- data/doc/ex-properties.rb +3 -0
- data/doc/ex-setup.rb +7 -0
- data/doc/ex-signal.body.rb +20 -0
- data/doc/ex-signal.rb +3 -0
- data/doc/example-helper.rb +6 -0
- data/em-ruby-dbus.gemspec +20 -0
- data/examples/gdbus/gdbus +255 -0
- data/examples/gdbus/gdbus.glade +184 -0
- data/examples/gdbus/launch.sh +4 -0
- data/examples/no-introspect/nm-test.rb +21 -0
- data/examples/no-introspect/tracker-test.rb +16 -0
- data/examples/rhythmbox/playpause.rb +25 -0
- data/examples/service/call_service.rb +25 -0
- data/examples/service/service_newapi.rb +51 -0
- data/examples/simple/call_introspect.rb +34 -0
- data/examples/simple/properties.rb +19 -0
- data/examples/utils/listnames.rb +11 -0
- data/examples/utils/notify.rb +19 -0
- data/lib/dbus.rb +82 -0
- data/lib/dbus/auth.rb +269 -0
- data/lib/dbus/bus.rb +739 -0
- data/lib/dbus/core_ext/array/extract_options.rb +31 -0
- data/lib/dbus/core_ext/class/attribute.rb +129 -0
- data/lib/dbus/core_ext/kernel/singleton_class.rb +8 -0
- data/lib/dbus/core_ext/module/remove_method.rb +14 -0
- data/lib/dbus/error.rb +46 -0
- data/lib/dbus/export.rb +128 -0
- data/lib/dbus/introspect.rb +219 -0
- data/lib/dbus/logger.rb +31 -0
- data/lib/dbus/loop-em.rb +19 -0
- data/lib/dbus/marshall.rb +434 -0
- data/lib/dbus/matchrule.rb +101 -0
- data/lib/dbus/message.rb +276 -0
- data/lib/dbus/message_queue.rb +166 -0
- data/lib/dbus/proxy_object.rb +149 -0
- data/lib/dbus/proxy_object_factory.rb +41 -0
- data/lib/dbus/proxy_object_interface.rb +128 -0
- data/lib/dbus/type.rb +193 -0
- data/lib/dbus/xml.rb +161 -0
- data/test/async_spec.rb +47 -0
- data/test/binding_spec.rb +74 -0
- data/test/bus_and_xml_backend_spec.rb +39 -0
- data/test/bus_driver_spec.rb +20 -0
- data/test/bus_spec.rb +20 -0
- data/test/byte_array_spec.rb +38 -0
- data/test/err_msg_spec.rb +42 -0
- data/test/introspect_xml_parser_spec.rb +26 -0
- data/test/introspection_spec.rb +32 -0
- data/test/main_loop_spec.rb +82 -0
- data/test/property_spec.rb +53 -0
- data/test/server_robustness_spec.rb +66 -0
- data/test/server_spec.rb +53 -0
- data/test/service_newapi.rb +217 -0
- data/test/session_bus_spec_manual.rb +15 -0
- data/test/signal_spec.rb +90 -0
- data/test/spec_helper.rb +33 -0
- data/test/thread_safety_spec.rb +31 -0
- data/test/tools/dbus-launch-simple +35 -0
- data/test/tools/dbus-limited-session.conf +28 -0
- data/test/tools/test_env +13 -0
- data/test/tools/test_server +39 -0
- data/test/type_spec.rb +19 -0
- data/test/value_spec.rb +81 -0
- data/test/variant_spec.rb +66 -0
- metadata +145 -0
data/lib/dbus/xml.rb
ADDED
@@ -0,0 +1,161 @@
|
|
1
|
+
# dbus/xml.rb - introspection parser, rexml/nokogiri abstraction
|
2
|
+
#
|
3
|
+
# This file is part of the ruby-dbus project
|
4
|
+
# Copyright (C) 2007 Arnaud Cornet and Paul van Tilburg
|
5
|
+
# Copyright (C) 2012 Geoff Youngs
|
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
|
+
# TODO check if it is slow, make replaceable
|
13
|
+
require 'rexml/document'
|
14
|
+
begin
|
15
|
+
require 'nokogiri'
|
16
|
+
rescue LoadError
|
17
|
+
end
|
18
|
+
|
19
|
+
module DBus
|
20
|
+
# = D-Bus introspect XML parser class
|
21
|
+
#
|
22
|
+
# This class parses introspection XML of an object and constructs a tree
|
23
|
+
# of Node, Interface, Method, Signal instances.
|
24
|
+
class IntrospectXMLParser
|
25
|
+
class << self
|
26
|
+
attr_accessor :backend
|
27
|
+
end
|
28
|
+
# Creates a new parser for XML data in string _xml_.
|
29
|
+
def initialize(xml)
|
30
|
+
@xml = xml
|
31
|
+
end
|
32
|
+
|
33
|
+
class AbstractXML
|
34
|
+
def self.have_nokogiri?
|
35
|
+
Object.const_defined?('Nokogiri')
|
36
|
+
end
|
37
|
+
class Node
|
38
|
+
def initialize(node)
|
39
|
+
@node = node
|
40
|
+
end
|
41
|
+
# required methods
|
42
|
+
# returns node attribute value
|
43
|
+
def [](key)
|
44
|
+
end
|
45
|
+
# yields child nodes which match xpath of type AbstractXML::Node
|
46
|
+
def each(xpath)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
# required methods
|
50
|
+
# initialize parser with xml string
|
51
|
+
def initialize(xml)
|
52
|
+
end
|
53
|
+
# yields nodes which match xpath of type AbstractXML::Node
|
54
|
+
def each(xpath)
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
class NokogiriParser < AbstractXML
|
59
|
+
class NokogiriNode < AbstractXML::Node
|
60
|
+
def [](key)
|
61
|
+
@node[key]
|
62
|
+
end
|
63
|
+
def each(path, &block)
|
64
|
+
@node.search(path).each { |node| block.call NokogiriNode.new(node) }
|
65
|
+
end
|
66
|
+
end
|
67
|
+
def initialize(xml)
|
68
|
+
@doc = Nokogiri.XML(xml)
|
69
|
+
end
|
70
|
+
def each(path, &block)
|
71
|
+
@doc.search("//#{path}").each { |node| block.call NokogiriNode.new(node) }
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
class REXMLParser < AbstractXML
|
76
|
+
class REXMLNode < AbstractXML::Node
|
77
|
+
def [](key)
|
78
|
+
@node.attributes[key]
|
79
|
+
end
|
80
|
+
def each(path, &block)
|
81
|
+
@node.elements.each(path) { |node| block.call REXMLNode.new(node) }
|
82
|
+
end
|
83
|
+
end
|
84
|
+
def initialize(xml)
|
85
|
+
@doc = REXML::Document.new(xml)
|
86
|
+
end
|
87
|
+
def each(path, &block)
|
88
|
+
@doc.elements.each(path) { |node| block.call REXMLNode.new(node) }
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
if AbstractXML.have_nokogiri?
|
93
|
+
@backend = NokogiriParser
|
94
|
+
else
|
95
|
+
@backend = REXMLParser
|
96
|
+
end
|
97
|
+
|
98
|
+
# return a pair: [list of Interfaces, list of direct subnode names]
|
99
|
+
def parse
|
100
|
+
# Using a Hash instead of a list helps merge split-up interfaces,
|
101
|
+
# a quirk observed in ModemManager (I#41).
|
102
|
+
interfaces = Hash.new do |hash, missing_key|
|
103
|
+
hash[missing_key] = Interface.new(missing_key)
|
104
|
+
end
|
105
|
+
subnodes = []
|
106
|
+
t = Time.now
|
107
|
+
|
108
|
+
|
109
|
+
d = IntrospectXMLParser.backend.new(@xml)
|
110
|
+
d.each("node/node") do |e|
|
111
|
+
subnodes << e["name"]
|
112
|
+
end
|
113
|
+
d.each("node/interface") do |e|
|
114
|
+
i = interfaces[e["name"]]
|
115
|
+
e.each("method") do |me|
|
116
|
+
m = Method.new(me["name"])
|
117
|
+
parse_methsig(me, m)
|
118
|
+
i << m
|
119
|
+
end
|
120
|
+
e.each("signal") do |se|
|
121
|
+
s = Signal.new(se["name"])
|
122
|
+
parse_methsig(se, s)
|
123
|
+
i << s
|
124
|
+
end
|
125
|
+
end
|
126
|
+
d = Time.now - t
|
127
|
+
if d > 2
|
128
|
+
DBus.logger.debug "Some XML took more that two secs to parse. Optimize me!"
|
129
|
+
end
|
130
|
+
[interfaces.values, subnodes]
|
131
|
+
end
|
132
|
+
|
133
|
+
######################################################################
|
134
|
+
private
|
135
|
+
|
136
|
+
# Parses a method signature XML element _e_ and initialises
|
137
|
+
# method/signal _m_.
|
138
|
+
def parse_methsig(e, m)
|
139
|
+
e.each("arg") do |ae|
|
140
|
+
name = ae["name"]
|
141
|
+
dir = ae["direction"]
|
142
|
+
sig = ae["type"]
|
143
|
+
if m.is_a?(DBus::Signal)
|
144
|
+
# Direction can only be "out", ignore it
|
145
|
+
m.add_fparam(name, sig)
|
146
|
+
elsif m.is_a?(DBus::Method)
|
147
|
+
case dir
|
148
|
+
# This is a method, so dir defaults to "in"
|
149
|
+
when "in", nil
|
150
|
+
m.add_fparam(name, sig)
|
151
|
+
when "out"
|
152
|
+
m.add_return(name, sig)
|
153
|
+
end
|
154
|
+
else
|
155
|
+
raise NotImplementedError, dir
|
156
|
+
end
|
157
|
+
end
|
158
|
+
end
|
159
|
+
end # class IntrospectXMLParser
|
160
|
+
end # module DBus
|
161
|
+
|
data/test/async_spec.rb
ADDED
@@ -0,0 +1,47 @@
|
|
1
|
+
#!/usr/bin/env rspec
|
2
|
+
# Test the binding of dbus concepts to ruby concepts
|
3
|
+
require_relative "spec_helper"
|
4
|
+
require "dbus"
|
5
|
+
|
6
|
+
describe "AsyncTest" do
|
7
|
+
before(:each) do
|
8
|
+
@bus = DBus::ASessionBus.new
|
9
|
+
@svc = @bus.service("org.ruby.service")
|
10
|
+
@obj = @svc.object "/org/ruby/MyInstance"
|
11
|
+
@obj.introspect
|
12
|
+
@obj.default_iface = "org.ruby.SampleInterface"
|
13
|
+
end
|
14
|
+
|
15
|
+
# https://github.com/mvidner/ruby-dbus/issues/13
|
16
|
+
it "tests async_call_to_default_interface" do
|
17
|
+
loop = DBus::Main.new
|
18
|
+
loop << @bus
|
19
|
+
|
20
|
+
immediate_answer = @obj.the_answer do |msg, retval|
|
21
|
+
expect(retval).to eq(42)
|
22
|
+
loop.quit
|
23
|
+
end
|
24
|
+
|
25
|
+
expect(immediate_answer).to be_nil
|
26
|
+
|
27
|
+
# wait for the async reply
|
28
|
+
loop.run
|
29
|
+
end
|
30
|
+
|
31
|
+
it "tests async_call_to_explicit_interface" do
|
32
|
+
loop = DBus::Main.new
|
33
|
+
loop << @bus
|
34
|
+
|
35
|
+
ifc = @obj["org.ruby.AnotherInterface"]
|
36
|
+
immediate_answer = ifc.Reverse("abcd") do |msg, retval|
|
37
|
+
expect(retval).to eq("dcba")
|
38
|
+
loop.quit
|
39
|
+
end
|
40
|
+
|
41
|
+
expect(immediate_answer).to be_nil
|
42
|
+
|
43
|
+
# wait for the async reply
|
44
|
+
loop.run
|
45
|
+
end
|
46
|
+
|
47
|
+
end
|
@@ -0,0 +1,74 @@
|
|
1
|
+
#!/usr/bin/env rspec
|
2
|
+
# Test the binding of dbus concepts to ruby concepts
|
3
|
+
require_relative "spec_helper"
|
4
|
+
|
5
|
+
require "dbus"
|
6
|
+
|
7
|
+
describe "BindingTest" do
|
8
|
+
before(:each) do
|
9
|
+
@bus = DBus::ASessionBus.new
|
10
|
+
@svc = @bus.service("org.ruby.service")
|
11
|
+
@base = @svc.object "/org/ruby/MyInstance"
|
12
|
+
@base.introspect
|
13
|
+
@base.default_iface = "org.ruby.SampleInterface"
|
14
|
+
end
|
15
|
+
|
16
|
+
# https://trac.luon.net/ruby-dbus/ticket/36#comment:3
|
17
|
+
it "tests class inheritance" do
|
18
|
+
derived = @svc.object "/org/ruby/MyDerivedInstance"
|
19
|
+
derived.introspect
|
20
|
+
|
21
|
+
# it should inherit from the parent
|
22
|
+
expect(derived["org.ruby.SampleInterface"]).not_to be_nil
|
23
|
+
end
|
24
|
+
|
25
|
+
# https://trac.luon.net/ruby-dbus/ticket/36
|
26
|
+
# Interfaces and methods/signals appeared on all classes
|
27
|
+
it "tests separation of classes" do
|
28
|
+
test2 = @svc.object "/org/ruby/MyInstance2"
|
29
|
+
test2.introspect
|
30
|
+
|
31
|
+
# it should have its own interface
|
32
|
+
expect(test2["org.ruby.Test2"]).not_to be_nil
|
33
|
+
# but not an interface of the Test class
|
34
|
+
expect(test2["org.ruby.SampleInterface"]).to be_nil
|
35
|
+
|
36
|
+
# and the parent should not get polluted by the child
|
37
|
+
expect(@base["org.ruby.Test2"]).to be_nil
|
38
|
+
end
|
39
|
+
|
40
|
+
it "tests translating errors into exceptions" do
|
41
|
+
# this is a generic call that will reply with the specified error
|
42
|
+
expect { @base.Error "org.example.Fail", "as you wish" }.to raise_error(DBus::Error) do |e|
|
43
|
+
expect(e.name).to eq("org.example.Fail")
|
44
|
+
expect(e.message).to match(/as you wish/)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
it "tests generic dbus error" do
|
49
|
+
# this is a generic call that will reply with the specified error
|
50
|
+
expect { @base.will_raise_error_failed }.to raise_error(DBus::Error) do |e|
|
51
|
+
expect(e.name).to eq("org.freedesktop.DBus.Error.Failed")
|
52
|
+
expect(e.message).to match(/failed as designed/)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
it "tests dynamic interface definition" do
|
57
|
+
# interfaces can be defined dynamicaly
|
58
|
+
derived = DBus::Object.new "/org/ruby/MyDerivedInstance"
|
59
|
+
|
60
|
+
#define a new interface
|
61
|
+
derived.singleton_class.instance_eval do
|
62
|
+
dbus_interface "org.ruby.DynamicInterface" do
|
63
|
+
dbus_method :hello2, "in name:s, in name2:s" do |name, name2|
|
64
|
+
puts "hello(#{name}, #{name2})"
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
# the object should have the new iface
|
70
|
+
ifaces = derived.intfs
|
71
|
+
expect(ifaces && ifaces.include?("org.ruby.DynamicInterface")).to be_true
|
72
|
+
end
|
73
|
+
|
74
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
#!/usr/bin/env rspec
|
2
|
+
# Test the bus class
|
3
|
+
require_relative "spec_helper"
|
4
|
+
|
5
|
+
require 'rubygems'
|
6
|
+
require 'nokogiri'
|
7
|
+
require "dbus"
|
8
|
+
|
9
|
+
describe "BusAndXmlBackendTest" do
|
10
|
+
before(:each) do
|
11
|
+
@bus = DBus::ASessionBus.new
|
12
|
+
end
|
13
|
+
|
14
|
+
it "tests introspection reading rexml" do
|
15
|
+
DBus::IntrospectXMLParser.backend = DBus::IntrospectXMLParser::REXMLParser
|
16
|
+
@svc = @bus.service("org.ruby.service")
|
17
|
+
obj = @svc.object("/org/ruby/MyInstance")
|
18
|
+
obj.default_iface = 'org.ruby.SampleInterface'
|
19
|
+
obj.introspect
|
20
|
+
# "should respond to :the_answer"
|
21
|
+
expect(obj.the_answer[0]).to eq(42)
|
22
|
+
# "should work with multiple interfaces"
|
23
|
+
expect(obj["org.ruby.AnotherInterface"].Reverse('foo')[0]).to eq("oof")
|
24
|
+
end
|
25
|
+
|
26
|
+
it "tests introspection reading nokogiri" do
|
27
|
+
# peek inside the object to see if a cleanup step worked or not
|
28
|
+
DBus::IntrospectXMLParser.backend = DBus::IntrospectXMLParser::NokogiriParser
|
29
|
+
@svc = @bus.service("org.ruby.service")
|
30
|
+
obj = @svc.object("/org/ruby/MyInstance")
|
31
|
+
obj.default_iface = 'org.ruby.SampleInterface'
|
32
|
+
obj.introspect
|
33
|
+
# "should respond to :the_answer"
|
34
|
+
expect(obj.the_answer[0]).to eq(42)
|
35
|
+
# "should work with multiple interfaces"
|
36
|
+
expect(obj["org.ruby.AnotherInterface"].Reverse('foo')[0]).to eq("oof")
|
37
|
+
end
|
38
|
+
|
39
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
#!/usr/bin/env rspec
|
2
|
+
require_relative "spec_helper"
|
3
|
+
require "dbus"
|
4
|
+
|
5
|
+
describe DBus::Service do
|
6
|
+
let(:bus) { DBus::ASessionBus.new }
|
7
|
+
|
8
|
+
describe "#exists?" do
|
9
|
+
it "is true for an existing service" do
|
10
|
+
svc = bus.service("org.ruby.service")
|
11
|
+
svc.object("/").introspect # must activate the service first :-/
|
12
|
+
expect(svc.exists?).to be_true
|
13
|
+
end
|
14
|
+
|
15
|
+
it "is false for a nonexisting service" do
|
16
|
+
svc = bus.service("org.ruby.nosuchservice")
|
17
|
+
expect(svc.exists?).to be_false
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
data/test/bus_spec.rb
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
#!/usr/bin/env rspec
|
2
|
+
# Test the bus class
|
3
|
+
require_relative "spec_helper"
|
4
|
+
|
5
|
+
require "dbus"
|
6
|
+
|
7
|
+
describe "BusTest" do
|
8
|
+
before(:each) do
|
9
|
+
@bus = DBus::ASessionBus.new
|
10
|
+
@svc = @bus.service("org.ruby.service")
|
11
|
+
@svc.object("/").introspect
|
12
|
+
end
|
13
|
+
|
14
|
+
it "tests introspection not leaking" do
|
15
|
+
# peek inside the object to see if a cleanup step worked or not
|
16
|
+
some_hash = @bus.instance_eval { @method_call_replies || Hash.new }
|
17
|
+
# fail: "there are leftover method handlers"
|
18
|
+
expect(some_hash.size).to eq(0)
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
#!/usr/bin/env rspec
|
2
|
+
require_relative "spec_helper"
|
3
|
+
|
4
|
+
require "dbus"
|
5
|
+
|
6
|
+
describe "ByteArrayTest" do
|
7
|
+
before(:each) do
|
8
|
+
@bus = DBus::ASessionBus.new
|
9
|
+
@svc = @bus.service("org.ruby.service")
|
10
|
+
@obj = @svc.object("/org/ruby/MyInstance")
|
11
|
+
@obj.introspect
|
12
|
+
@obj.default_iface = "org.ruby.SampleInterface"
|
13
|
+
end
|
14
|
+
|
15
|
+
|
16
|
+
it "tests passing byte array" do
|
17
|
+
data = [0, 77, 255]
|
18
|
+
result = @obj.mirror_byte_array(data).first
|
19
|
+
expect(result).to eq(data)
|
20
|
+
end
|
21
|
+
|
22
|
+
it "tests passing byte array from string" do
|
23
|
+
data = "AAA"
|
24
|
+
result = @obj.mirror_byte_array(data).first
|
25
|
+
expect(result).to eq([65, 65, 65])
|
26
|
+
end
|
27
|
+
|
28
|
+
it "tests passing byte array from hash" do
|
29
|
+
# Hash is an Enumerable, but is caught earlier
|
30
|
+
data = { "this will" => "fail" }
|
31
|
+
expect { @obj.mirror_byte_array(data).first }.to raise_error(DBus::TypeException)
|
32
|
+
end
|
33
|
+
|
34
|
+
it "tests passing byte array from nonenumerable" do
|
35
|
+
data = Time.now
|
36
|
+
expect { @obj.mirror_byte_array(data).first }.to raise_error(DBus::TypeException)
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
#!/usr/bin/env rspec
|
2
|
+
# should report it missing on org.ruby.SampleInterface
|
3
|
+
# (on object...) instead of on DBus::Proxy::ObjectInterface
|
4
|
+
require_relative "spec_helper"
|
5
|
+
require "dbus"
|
6
|
+
|
7
|
+
describe "ErrMsgTest" do
|
8
|
+
before(:each) do
|
9
|
+
session_bus = DBus::ASessionBus.new
|
10
|
+
svc = session_bus.service("org.ruby.service")
|
11
|
+
@obj = svc.object("/org/ruby/MyInstance")
|
12
|
+
@obj.introspect # necessary
|
13
|
+
@obj.default_iface = "org.ruby.SampleInterface"
|
14
|
+
end
|
15
|
+
|
16
|
+
it "tests report dbus interface" do
|
17
|
+
# a specific exception...
|
18
|
+
# mentioning DBus and the interface
|
19
|
+
expect { @obj.NoSuchMethod }.to raise_error(NameError, /DBus interface.*#{@obj.default_iface}/)
|
20
|
+
end
|
21
|
+
|
22
|
+
it "tests report short struct" do
|
23
|
+
expect { @obj.test_variant ["(ss)", ["too few"] ] }.to raise_error(DBus::TypeException, /1 elements but type info for 2/)
|
24
|
+
end
|
25
|
+
|
26
|
+
it "tests report long struct" do
|
27
|
+
expect { @obj.test_variant ["(ss)", ["a", "b", "too many"] ] }.to raise_error(DBus::TypeException, /3 elements but type info for 2/)
|
28
|
+
end
|
29
|
+
|
30
|
+
it "tests report nil" do
|
31
|
+
nils = [
|
32
|
+
["(s)", [nil] ], # would get disconnected
|
33
|
+
["i", nil ],
|
34
|
+
["a{ss}", {"foo" => nil} ],
|
35
|
+
]
|
36
|
+
nils.each do |has_nil|
|
37
|
+
# TODO want backtrace from the perspective of the caller:
|
38
|
+
# rescue/reraise in send_sync?
|
39
|
+
expect { @obj.test_variant has_nil }.to raise_error(DBus::TypeException, /Cannot send nil/)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|