ruby-dbus 0.10.0 → 0.11.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 (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