ruby-dbus 0.10.0 → 0.11.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (50) hide show
  1. checksums.yaml +4 -4
  2. data/NEWS +8 -0
  3. data/README.md +41 -4
  4. data/Rakefile +15 -16
  5. data/VERSION +1 -1
  6. data/lib/dbus.rb +18 -14
  7. data/lib/dbus/bus.rb +32 -168
  8. data/lib/dbus/error.rb +4 -2
  9. data/lib/dbus/export.rb +3 -2
  10. data/lib/dbus/introspect.rb +0 -294
  11. data/lib/dbus/marshall.rb +1 -1
  12. data/lib/dbus/message.rb +8 -0
  13. data/lib/dbus/message_queue.rb +166 -0
  14. data/lib/dbus/proxy_object.rb +149 -0
  15. data/lib/dbus/proxy_object_factory.rb +41 -0
  16. data/lib/dbus/proxy_object_interface.rb +128 -0
  17. data/ruby-dbus.gemspec +1 -0
  18. data/test/{async_test.rb → async_spec.rb} +10 -11
  19. data/test/{binding_test.rb → binding_spec.rb} +23 -25
  20. data/test/bus_and_xml_backend_spec.rb +39 -0
  21. data/test/bus_driver_spec.rb +20 -0
  22. data/test/{bus_test.rb → bus_spec.rb} +8 -7
  23. data/test/byte_array_spec.rb +38 -0
  24. data/test/err_msg_spec.rb +42 -0
  25. data/test/{introspect_xml_parser_test.rb → introspect_xml_parser_spec.rb} +5 -6
  26. data/test/introspection_spec.rb +32 -0
  27. data/test/{main_loop_test.rb → main_loop_spec.rb} +10 -7
  28. data/test/property_spec.rb +53 -0
  29. data/test/server_robustness_spec.rb +66 -0
  30. data/test/{server_test.rb → server_spec.rb} +10 -11
  31. data/test/service_newapi.rb +1 -1
  32. data/test/session_bus_spec_manual.rb +15 -0
  33. data/test/{signal_test.rb → signal_spec.rb} +13 -14
  34. data/test/spec_helper.rb +33 -0
  35. data/test/{thread_safety_test.rb → thread_safety_spec.rb} +5 -6
  36. data/test/type_spec.rb +19 -0
  37. data/test/{value_test.rb → value_spec.rb} +23 -24
  38. data/test/variant_spec.rb +66 -0
  39. metadata +40 -22
  40. data/test/bus_and_xml_backend_test.rb +0 -43
  41. data/test/bus_driver_test.rb +0 -19
  42. data/test/byte_array_test.rb +0 -42
  43. data/test/err_msg_test.rb +0 -59
  44. data/test/introspection_test.rb +0 -32
  45. data/test/property_test.rb +0 -65
  46. data/test/server_robustness_test.rb +0 -73
  47. data/test/session_bus_test_manual.rb +0 -17
  48. data/test/test_helper.rb +0 -14
  49. data/test/type_test.rb +0 -23
  50. data/test/variant_test.rb +0 -67
@@ -0,0 +1,149 @@
1
+ # This file is part of the ruby-dbus project
2
+ # Copyright (C) 2007 Arnaud Cornet and Paul van Tilburg
3
+ # Copyright (C) 2009-2014 Martin Vidner
4
+ #
5
+ # This library is free software; you can redistribute it and/or
6
+ # modify it under the terms of the GNU Lesser General Public
7
+ # License, version 2.1 as published by the Free Software Foundation.
8
+ # See the file "COPYING" for the exact licensing terms.
9
+
10
+ module DBus
11
+ # D-Bus proxy object class
12
+ #
13
+ # Class representing a remote object in an external application.
14
+ # Typically, calling a method on an instance of a ProxyObject sends a message
15
+ # over the bus so that the method is executed remotely on the correctponding
16
+ # object.
17
+ class ProxyObject
18
+ # The names of direct subnodes of the object in the tree.
19
+ attr_accessor :subnodes
20
+ # Flag determining whether the object has been introspected.
21
+ attr_accessor :introspected
22
+ # The (remote) destination of the object.
23
+ attr_reader :destination
24
+ # The path to the object.
25
+ attr_reader :path
26
+ # The bus the object is reachable via.
27
+ attr_reader :bus
28
+ # @return [String] The name of the default interface of the object.
29
+ attr_accessor :default_iface
30
+
31
+ # Creates a new proxy object living on the given _bus_ at destination _dest_
32
+ # on the given _path_.
33
+ def initialize(bus, dest, path)
34
+ @bus, @destination, @path = bus, dest, path
35
+ @interfaces = Hash.new
36
+ @subnodes = Array.new
37
+ end
38
+
39
+ # Returns the interfaces of the object.
40
+ def interfaces
41
+ @interfaces.keys
42
+ end
43
+
44
+ # Retrieves an interface of the proxy object
45
+ # @param [String] intfname
46
+ # @return [ProxyObjectInterface]
47
+ def [](intfname)
48
+ @interfaces[intfname]
49
+ end
50
+
51
+ # Maps the given interface name _intfname_ to the given interface _intf.
52
+ # @param [String] intfname
53
+ # @param [ProxyObjectInterface] intf
54
+ # @return [ProxyObjectInterface]
55
+ def []=(intfname, intf)
56
+ @interfaces[intfname] = intf
57
+ end
58
+
59
+ # Introspects the remote object. Allows you to find and select
60
+ # interfaces on the object.
61
+ def introspect
62
+ # Synchronous call here.
63
+ xml = @bus.introspect_data(@destination, @path)
64
+ ProxyObjectFactory.introspect_into(self, xml)
65
+ define_shortcut_methods()
66
+ xml
67
+ end
68
+
69
+ # For each non duplicated method name in any interface present on the
70
+ # caller, defines a shortcut method dynamically.
71
+ # This function is automatically called when a {ProxyObject} is
72
+ # introspected.
73
+ def define_shortcut_methods
74
+ # builds a list of duplicated methods
75
+ dup_meths, univocal_meths = [],{}
76
+ @interfaces.each_value do |intf|
77
+ intf.methods.each_value do |meth|
78
+ name = meth.name.to_sym
79
+ # don't overwrite instance methods!
80
+ if dup_meths.include? name or self.class.instance_methods.include? name
81
+ next
82
+ elsif univocal_meths.include? name
83
+ univocal_meths.delete name
84
+ dup_meths << name
85
+ else
86
+ univocal_meths[name] = intf
87
+ end
88
+ end
89
+ end
90
+ univocal_meths.each do |name, intf|
91
+ # creates a shortcut function that forwards each call to the method on
92
+ # the appropriate intf
93
+ singleton_class.class_eval do
94
+ define_method name do |*args, &reply_handler|
95
+ intf.method(name).call(*args, &reply_handler)
96
+ end
97
+ end
98
+ end
99
+ end
100
+
101
+ # Returns whether the object has an interface with the given _name_.
102
+ def has_iface?(name)
103
+ raise "Cannot call has_iface? if not introspected" if not @introspected
104
+ @interfaces.member?(name)
105
+ end
106
+
107
+ # Registers a handler, the code block, for a signal with the given _name_.
108
+ # It uses _default_iface_ which must have been set.
109
+ # @return [void]
110
+ def on_signal(name, &block)
111
+ if @default_iface and has_iface?(@default_iface)
112
+ @interfaces[@default_iface].on_signal(name, &block)
113
+ else
114
+ # TODO improve
115
+ raise NoMethodError
116
+ end
117
+ end
118
+
119
+ ####################################################
120
+ private
121
+
122
+ # Handles all unkown methods, mostly to route method calls to the
123
+ # default interface.
124
+ def method_missing(name, *args, &reply_handler)
125
+ if @default_iface and has_iface?(@default_iface)
126
+ begin
127
+ @interfaces[@default_iface].method(name).call(*args, &reply_handler)
128
+ rescue NameError => e
129
+ # interesting, foo.method("unknown")
130
+ # raises NameError, not NoMethodError
131
+ raise unless e.to_s =~ /undefined method `#{name}'/
132
+ # BTW e.exception("...") would preserve the class.
133
+ raise NoMethodError,"undefined method `#{name}' for DBus interface `#{@default_iface}' on object `#{@path}'"
134
+ end
135
+ else
136
+ # TODO distinguish:
137
+ # - di not specified
138
+ #TODO
139
+ # - di is specified but not found in introspection data
140
+ raise NoMethodError, "undefined method `#{name}' for DBus interface `#{@default_iface}' on object `#{@path}'"
141
+ end
142
+ end
143
+
144
+ # Returns the singleton class of the object.
145
+ def singleton_class
146
+ (class << self ; self ; end)
147
+ end
148
+ end # class ProxyObject
149
+ end
@@ -0,0 +1,41 @@
1
+ # This file is part of the ruby-dbus project
2
+ # Copyright (C) 2007 Arnaud Cornet and Paul van Tilburg
3
+ # Copyright (C) 2009-2014 Martin Vidner
4
+ #
5
+ # This library is free software; you can redistribute it and/or
6
+ # modify it under the terms of the GNU Lesser General Public
7
+ # License, version 2.1 as published by the Free Software Foundation.
8
+ # See the file "COPYING" for the exact licensing terms.
9
+
10
+ module DBus
11
+ # = D-Bus proxy object factory class
12
+ #
13
+ # Class that generates and sets up a proxy object based on introspection data.
14
+ class ProxyObjectFactory
15
+ # Creates a new proxy object factory for the given introspection XML _xml_,
16
+ # _bus_, destination _dest_, and _path_.
17
+ def initialize(xml, bus, dest, path)
18
+ @xml, @bus, @path, @dest = xml, bus, path, dest
19
+ end
20
+
21
+ # Investigates the sub-nodes of the proxy object _po_ based on the
22
+ # introspection XML data _xml_ and sets them up recursively.
23
+ def ProxyObjectFactory.introspect_into(po, xml)
24
+ intfs, po.subnodes = IntrospectXMLParser.new(xml).parse
25
+ intfs.each do |i|
26
+ poi = ProxyObjectInterface.new(po, i.name)
27
+ i.methods.each_value { |m| poi.define(m) }
28
+ i.signals.each_value { |s| poi.define(s) }
29
+ po[i.name] = poi
30
+ end
31
+ po.introspected = true
32
+ end
33
+
34
+ # Generates, sets up and returns the proxy object.
35
+ def build
36
+ po = ProxyObject.new(@bus, @dest, @path)
37
+ ProxyObjectFactory.introspect_into(po, @xml)
38
+ po
39
+ end
40
+ end # class ProxyObjectFactory
41
+ end
@@ -0,0 +1,128 @@
1
+ # This file is part of the ruby-dbus project
2
+ # Copyright (C) 2007 Arnaud Cornet and Paul van Tilburg
3
+ # Copyright (C) 2009-2014 Martin Vidner
4
+ #
5
+ # This library is free software; you can redistribute it and/or
6
+ # modify it under the terms of the GNU Lesser General Public
7
+ # License, version 2.1 as published by the Free Software Foundation.
8
+ # See the file "COPYING" for the exact licensing terms.
9
+
10
+ module DBus
11
+ # = D-Bus proxy object interface class
12
+ #
13
+ # A class similar to the normal Interface used as a proxy for remote
14
+ # object interfaces.
15
+ class ProxyObjectInterface
16
+ # The proxied methods contained in the interface.
17
+ attr_accessor :methods
18
+ # The proxied signals contained in the interface.
19
+ attr_accessor :signals
20
+ # The proxy object to which this interface belongs.
21
+ attr_reader :object
22
+ # The name of the interface.
23
+ attr_reader :name
24
+
25
+ # Creates a new proxy interface for the given proxy _object_
26
+ # and the given _name_.
27
+ def initialize(object, name)
28
+ @object, @name = object, name
29
+ @methods, @signals = Hash.new, Hash.new
30
+ end
31
+
32
+ # Returns the string representation of the interface (the name).
33
+ def to_str
34
+ @name
35
+ end
36
+
37
+ # Returns the singleton class of the interface.
38
+ def singleton_class
39
+ (class << self ; self ; end)
40
+ end
41
+
42
+ # Defines a method on the interface from the Method descriptor _m_.
43
+ def define_method_from_descriptor(m)
44
+ m.params.each do |fpar|
45
+ par = fpar.type
46
+ # This is the signature validity check
47
+ Type::Parser.new(par).parse
48
+ end
49
+
50
+ singleton_class.class_eval do
51
+ define_method m.name do |*args, &reply_handler|
52
+ if m.params.size != args.size
53
+ raise ArgumentError, "wrong number of arguments (#{args.size} for #{m.params.size})"
54
+ end
55
+
56
+ msg = Message.new(Message::METHOD_CALL)
57
+ msg.path = @object.path
58
+ msg.interface = @name
59
+ msg.destination = @object.destination
60
+ msg.member = m.name
61
+ msg.sender = @object.bus.unique_name
62
+ m.params.each do |fpar|
63
+ par = fpar.type
64
+ msg.add_param(par, args.shift)
65
+ end
66
+ @object.bus.send_sync_or_async(msg, &reply_handler)
67
+ end
68
+ end
69
+
70
+ @methods[m.name] = m
71
+ end
72
+
73
+ # Defines a signal from the descriptor _s_.
74
+ def define_signal_from_descriptor(s)
75
+ @signals[s.name] = s
76
+ end
77
+
78
+ # Defines a signal or method based on the descriptor _m_.
79
+ def define(m)
80
+ if m.kind_of?(Method)
81
+ define_method_from_descriptor(m)
82
+ elsif m.kind_of?(Signal)
83
+ define_signal_from_descriptor(m)
84
+ end
85
+ end
86
+
87
+ # Defines a proxied method on the interface.
88
+ def define_method(methodname, prototype)
89
+ m = Method.new(methodname)
90
+ m.from_prototype(prototype)
91
+ define(m)
92
+ end
93
+
94
+ # @overload on_signal(name, &block)
95
+ # @overload on_signal(bus, name, &block)
96
+ # Registers a handler (code block) for a signal with _name_ arriving
97
+ # over the given _bus_. If no block is given, the signal is unregistered.
98
+ # Note that specifying _bus_ is discouraged and the option is kept only for
99
+ # backward compatibility.
100
+ # @return [void]
101
+ def on_signal(bus = @object.bus, name, &block)
102
+ mr = DBus::MatchRule.new.from_signal(self, name)
103
+ if block.nil?
104
+ bus.remove_match(mr)
105
+ else
106
+ bus.add_match(mr) { |msg| block.call(*msg.params) }
107
+ end
108
+ end
109
+
110
+ PROPERTY_INTERFACE = "org.freedesktop.DBus.Properties"
111
+
112
+ # Read a property.
113
+ def [](propname)
114
+ self.object[PROPERTY_INTERFACE].Get(self.name, propname)[0]
115
+ end
116
+
117
+ # Write a property.
118
+ def []=(propname, value)
119
+ self.object[PROPERTY_INTERFACE].Set(self.name, propname, value)
120
+ end
121
+
122
+ # Read all properties at once, as a hash.
123
+ # @return [Hash{String}]
124
+ def all_properties
125
+ self.object[PROPERTY_INTERFACE].GetAll(self.name)[0]
126
+ end
127
+ end # class ProxyObjectInterface
128
+ end
@@ -16,4 +16,5 @@ GEMSPEC = Gem::Specification.new do |s|
16
16
  s.require_path = "lib"
17
17
  s.required_ruby_version = ">= 1.9.3"
18
18
  s.add_development_dependency("packaging_rake_tasks")
19
+ s.add_development_dependency("rspec")
19
20
  end
@@ -1,11 +1,10 @@
1
- #!/usr/bin/env ruby
1
+ #!/usr/bin/env rspec
2
2
  # Test the binding of dbus concepts to ruby concepts
3
- require File.expand_path("../test_helper", __FILE__)
4
- require "test/unit"
3
+ require_relative "spec_helper"
5
4
  require "dbus"
6
5
 
7
- class AsyncTest < Test::Unit::TestCase
8
- def setup
6
+ describe "AsyncTest" do
7
+ before(:each) do
9
8
  @bus = DBus::ASessionBus.new
10
9
  @svc = @bus.service("org.ruby.service")
11
10
  @obj = @svc.object "/org/ruby/MyInstance"
@@ -14,32 +13,32 @@ class AsyncTest < Test::Unit::TestCase
14
13
  end
15
14
 
16
15
  # https://github.com/mvidner/ruby-dbus/issues/13
17
- def test_async_call_to_default_interface
16
+ it "tests async_call_to_default_interface" do
18
17
  loop = DBus::Main.new
19
18
  loop << @bus
20
19
 
21
20
  immediate_answer = @obj.the_answer do |msg, retval|
22
- assert_equal 42, retval
21
+ expect(retval).to eq(42)
23
22
  loop.quit
24
23
  end
25
24
 
26
- assert_nil immediate_answer
25
+ expect(immediate_answer).to be_nil
27
26
 
28
27
  # wait for the async reply
29
28
  loop.run
30
29
  end
31
30
 
32
- def test_async_call_to_explicit_interface
31
+ it "tests async_call_to_explicit_interface" do
33
32
  loop = DBus::Main.new
34
33
  loop << @bus
35
34
 
36
35
  ifc = @obj["org.ruby.AnotherInterface"]
37
36
  immediate_answer = ifc.Reverse("abcd") do |msg, retval|
38
- assert_equal "dcba", retval
37
+ expect(retval).to eq("dcba")
39
38
  loop.quit
40
39
  end
41
40
 
42
- assert_nil immediate_answer
41
+ expect(immediate_answer).to be_nil
43
42
 
44
43
  # wait for the async reply
45
44
  loop.run
@@ -1,11 +1,11 @@
1
- #!/usr/bin/env ruby
1
+ #!/usr/bin/env rspec
2
2
  # Test the binding of dbus concepts to ruby concepts
3
- require File.expand_path("../test_helper", __FILE__)
4
- require "test/unit"
3
+ require_relative "spec_helper"
4
+
5
5
  require "dbus"
6
6
 
7
- class BindingTest < Test::Unit::TestCase
8
- def setup
7
+ describe "BindingTest" do
8
+ before(:each) do
9
9
  @bus = DBus::ASessionBus.new
10
10
  @svc = @bus.service("org.ruby.service")
11
11
  @base = @svc.object "/org/ruby/MyInstance"
@@ -14,48 +14,46 @@ class BindingTest < Test::Unit::TestCase
14
14
  end
15
15
 
16
16
  # https://trac.luon.net/ruby-dbus/ticket/36#comment:3
17
- def test_class_inheritance
17
+ it "tests class inheritance" do
18
18
  derived = @svc.object "/org/ruby/MyDerivedInstance"
19
19
  derived.introspect
20
20
 
21
21
  # it should inherit from the parent
22
- assert_not_nil derived["org.ruby.SampleInterface"]
22
+ expect(derived["org.ruby.SampleInterface"]).not_to be_nil
23
23
  end
24
24
 
25
25
  # https://trac.luon.net/ruby-dbus/ticket/36
26
26
  # Interfaces and methods/signals appeared on all classes
27
- def test_separation_of_classes
27
+ it "tests separation of classes" do
28
28
  test2 = @svc.object "/org/ruby/MyInstance2"
29
29
  test2.introspect
30
30
 
31
31
  # it should have its own interface
32
- assert_not_nil test2["org.ruby.Test2"]
32
+ expect(test2["org.ruby.Test2"]).not_to be_nil
33
33
  # but not an interface of the Test class
34
- assert_nil test2["org.ruby.SampleInterface"]
34
+ expect(test2["org.ruby.SampleInterface"]).to be_nil
35
35
 
36
36
  # and the parent should not get polluted by the child
37
- assert_nil @base["org.ruby.Test2"]
37
+ expect(@base["org.ruby.Test2"]).to be_nil
38
38
  end
39
39
 
40
- def test_translating_errors_into_exceptions
40
+ it "tests translating errors into exceptions" do
41
41
  # this is a generic call that will reply with the specified error
42
- @base.Error "org.example.Fail", "as you wish"
43
- assert false, "should have raised"
44
- rescue DBus::Error => e
45
- assert_equal "org.example.Fail", e.name
46
- assert_equal "as you wish", e.message
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
47
46
  end
48
47
 
49
- def test_generic_dbus_error
48
+ it "tests generic dbus error" do
50
49
  # this is a generic call that will reply with the specified error
51
- @base.will_raise_error_failed
52
- assert false, "should have raised"
53
- rescue DBus::Error => e
54
- assert_equal "org.freedesktop.DBus.Error.Failed", e.name
55
- assert_equal "failed as designed", e.message
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
56
54
  end
57
55
 
58
- def test_dynamic_interface_definition
56
+ it "tests dynamic interface definition" do
59
57
  # interfaces can be defined dynamicaly
60
58
  derived = DBus::Object.new "/org/ruby/MyDerivedInstance"
61
59
 
@@ -70,7 +68,7 @@ class BindingTest < Test::Unit::TestCase
70
68
 
71
69
  # the object should have the new iface
72
70
  ifaces = derived.intfs
73
- assert ifaces and ifaces.include?("org.ruby.DynamicInterface")
71
+ expect(ifaces && ifaces.include?("org.ruby.DynamicInterface")).to be_true
74
72
  end
75
73
 
76
74
  end