[DRE-commits] [ruby-dbus] 01/10: Imported Upstream version 0.11.0
Paul van Tilburg
paulvt at moszumanska.debian.org
Fri Feb 21 21:21:09 UTC 2014
This is an automated email from the git hooks/post-receive script.
paulvt pushed a commit to branch master
in repository ruby-dbus.
commit 05aaed0ca3994dee4c18316661a03b995ba66476
Author: Paul van Tilburg <paulvt at debian.org>
Date: Fri Feb 21 20:57:16 2014 +0100
Imported Upstream version 0.11.0
---
NEWS | 75 +++++
README | 42 ---
README.md | 81 +++++
Rakefile | 74 +++--
VERSION | 2 +-
checksums.yaml.gz | Bin 0 -> 267 bytes
doc/Reference.md | 207 +++++++++++++
doc/{tutorial/index.markdown => Tutorial.md} | 0
doc/ex-calling-methods.body.rb | 8 +
doc/ex-calling-methods.rb | 3 +
doc/ex-properties.body.rb | 9 +
doc/ex-properties.rb | 3 +
doc/ex-setup.rb | 7 +
doc/ex-signal.body.rb | 20 ++
doc/ex-signal.rb | 3 +
doc/example-helper.rb | 6 +
lib/dbus.rb | 36 +--
lib/dbus/auth.rb | 33 +-
lib/dbus/bus.rb | 238 ++++-----------
lib/dbus/core_ext/array/extract_options.rb | 31 ++
lib/dbus/core_ext/class/attribute.rb | 129 ++++++++
lib/dbus/core_ext/kernel/singleton_class.rb | 8 +
lib/dbus/core_ext/module/remove_method.rb | 14 +
lib/dbus/error.rb | 6 +-
lib/dbus/export.rb | 31 +-
lib/dbus/introspect.rb | 333 ---------------------
lib/dbus/logger.rb | 31 ++
lib/dbus/marshall.rb | 28 +-
lib/dbus/message.rb | 86 ++----
lib/dbus/message_queue.rb | 166 ++++++++++
lib/dbus/proxy_object.rb | 149 +++++++++
lib/dbus/proxy_object_factory.rb | 41 +++
lib/dbus/proxy_object_interface.rb | 128 ++++++++
lib/dbus/type.rb | 93 ++----
lib/dbus/xml.rb | 161 ++++++++++
metadata.yml | 169 ++++++-----
ruby-dbus.gemspec | 10 +-
test/{async_test.rb => async_spec.rb} | 20 +-
test/binding_spec.rb | 74 +++++
test/binding_test.rb | 56 ----
test/bus_and_xml_backend_spec.rb | 39 +++
test/bus_driver_spec.rb | 20 ++
test/bus_driver_test.rb | 22 --
test/{bus_test.rb => bus_spec.rb} | 14 +-
test/byte_array_spec.rb | 38 +++
test/err_msg_spec.rb | 42 +++
test/introspect_xml_parser_spec.rb | 26 ++
test/introspection_spec.rb | 32 ++
test/{t6-loop.rb => main_loop_spec.rb} | 30 +-
test/property_spec.rb | 53 ++++
test/property_test.rb | 64 ----
test/server_robustness_spec.rb | 66 ++++
test/server_robustness_test.rb | 72 -----
test/{server_test.rb => server_spec.rb} | 22 +-
test/service_newapi.rb | 33 +-
test/session_bus_spec_manual.rb | 15 +
test/session_bus_test_manual.rb | 20 --
test/signal_spec.rb | 90 ++++++
test/signal_test.rb | 64 ----
test/spec_helper.rb | 33 ++
test/t1 | 4 -
test/t2.rb | 72 -----
test/t3-ticket27.rb | 18 --
test/t5-report-dbus-interface.rb | 58 ----
...thread_safety_test.rb => thread_safety_spec.rb} | 14 +-
test/{ => tools}/dbus-launch-simple | 0
test/{ => tools}/dbus-limited-session.conf | 2 +-
test/{ => tools}/test_env | 2 +-
test/{ => tools}/test_server | 0
test/type_spec.rb | 19 ++
test/value_spec.rb | 81 +++++
test/variant_spec.rb | 66 ++++
test/variant_test.rb | 66 ----
73 files changed, 2360 insertions(+), 1448 deletions(-)
diff --git a/NEWS b/NEWS
index 76f58b0..6a61326 100644
--- a/NEWS
+++ b/NEWS
@@ -5,6 +5,81 @@ Note about bug numbers:
Issue#1 - http://github.com/mvidner/ruby-dbus/issues#issue/1
bnc#1 - https://bugzilla.novell.com/show_bug.cgi?id=1
+== Ruby D-Bus 0.11.0 - 2014-02-17
+
+API:
+ * Connection: split off MessageQueue, marked other methods as private.
+
+Requirements:
+ * converted tests to RSpec, rather mechanically for now
+
+== Ruby D-Bus 0.10.0 - 2014-01-10
+
+Bug fixes:
+ * fixed "Interfaces added with singleton_class.instance_eval aren't
+ exported" (Issue#22, by miaoufkirsh)
+
+Requirements:
+ * Require ruby 1.9.3, stopped supporting 1.8.7.
+
+== Ruby D-Bus 0.9.3 - 2014-01-02
+
+Bug fixes:
+ * re-added COPYING, NEWS, README.md to the gem (Issue#47,
+ by Cédric Boutillier)
+
+Packaging:
+ * use packaging_rake_tasks
+
+== Ruby D-Bus 0.9.2 - 2013-05-08
+
+Features:
+ * Ruby strings can be passed where byte arrays ("ay") are expected
+ (Issue#40, by Jesper B. Rosenkilde)
+
+Bug fixes:
+ * Fixed accessing ModemManager properties (Issue#41, reported
+ by Ernest Bursa). MM introspection produces two elements
+ for a single interface; merge them.
+
+== Ruby D-Bus 0.9.1 - 2013-04-23
+
+Bug fixes:
+ * Prefer /etc/machine-id to /var/lib/dbus/machine-id
+ when DBUS_SESSION_BUS_ADDRESS is unset (Issue#39, by WU Jun).
+
+== Ruby D-Bus 0.9.0 - 2012-11-06
+
+Features:
+ * When calling methods, the interface can be left unspecified if unambiguous
+ (Damiano Stoffie)
+ * YARD documentation, Reference.md
+
+Bug fixes:
+ * Introspection attribute "direction" can be omitted
+ as allowed by the specification (Noah Meyerhans).
+ * ProxyObjectInterface#on_signal no longer needs the "bus" parameter
+ (Issue#31, by Damiano Stoffie)
+
+== Ruby D-Bus 0.8.0 - 2012-09-20
+
+Features:
+ * Add Anonymous authentication (Issue#27, by Walter Brebels).
+ * Use Nokogiri for XML parsing when available (Issue#24, by Geoff Youngs).
+
+Bug fixes:
+ * Use SCM_CREDS authentication only on FreeBSD, not on OpenBSD (Issue#21,
+ reported by Adde Nilsson).
+ * Recognize signature "h" (UNIX_FD) used eg. by Upstart (Issue#23,
+ by Bernd Ahlers).
+ * Find the session bus also via launchd, on OS X (Issue#20, reported
+ by Paul Sturgess).
+
+Other:
+ * Now doing continuous integration with Travis:
+ http://travis-ci.org/#!/mvidner/ruby-dbus
+
+
== Ruby D-Bus 0.7.2 - 2012-04-05
A brown-paper-bag release.
diff --git a/README b/README
deleted file mode 100644
index a331c2d..0000000
--- a/README
+++ /dev/null
@@ -1,42 +0,0 @@
-= Ruby D-Bus README
-
-Ruby D-Bus provides an implementation of the D-Bus protocol such that the
-D-Bus system can be used in the Ruby programming language.
-
-== Requirements
-
- * Ruby 1.8.7 or 1.9
-
-== Installation
-
- * gem install ruby-dbus
-
-== Feature
-
-Ruby D-Bus currently supports the following features:
-
- * Connecting to local buses.
- * Accessing remote services, objects and interfaces.
- * Invoking methods on remote objects synchronously and asynchronously.
- * Catch signals on remote objects and handle them via callbacks.
- * Remote object introspection.
- * Walking object trees.
- * Creating services and registering them on the bus.
- * Exporting objects with interfaces on a bus for remote use.
- * Rubyish D-Bus object and interface syntax support that automatically
- allows for introspection.
- * Emitting signals on exported objects.
-
-== Usage
-
- See some of the examples in the examples/ subdirectory of the tarball.
- Also, check out the included tutorial (in Markdown format) in doc/tutorial/
- or view it online on
- https://github.com/mvidner/ruby-dbus/blob/master/doc/tutorial/index.markdown .
-
-== License
-
- Ruby D-Bus is free software; you can redistribute it and/or modify it
- under the terms of the GNU Lesser General Public License as published by the
- Free Software Foundation; either version 2.1 of the License, or (at
- your option) any later version.
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..b197d7b
--- /dev/null
+++ b/README.md
@@ -0,0 +1,81 @@
+# Ruby D-Bus
+
+[D-Bus](http://dbus.freedesktop.org) is an interprocess communication
+mechanism for Linux.
+Ruby D-Bus is a pure Ruby library for writing clients and services for D-Bus.
+
+[![Gem Version][GV img]][Gem Version]
+[![Build Status][BS img]][Build Status]
+[![Dependency Status][DS img]][Dependency Status]
+[![Code Climate][CC img]][Code Climate]
+[![Coverage Status][CS img]][Coverage Status]
+
+[Gem Version]: https://rubygems.org/gems/ruby-dbus
+[Build Status]: https://travis-ci.org/mvidner/ruby-dbus
+[travis pull requests]: https://travis-ci.org/mvidner/ruby-dbus/pull_requests
+[Dependency Status]: https://gemnasium.com/mvidner/ruby-dbus
+[Code Climate]: https://codeclimate.com/github/mvidner/ruby-dbus
+[Coverage Status]: https://coveralls.io/r/mvidner/ruby-dbus
+
+[GV img]: https://badge.fury.io/rb/ruby-dbus.png
+[BS img]: https://travis-ci.org/mvidner/ruby-dbus.png
+[DS img]: https://gemnasium.com/mvidner/ruby-dbus.png
+[CC img]: https://codeclimate.com/github/mvidner/ruby-dbus.png
+[CS img]: https://coveralls.io/repos/mvidner/ruby-dbus/badge.png?branch=master
+
+## Example
+
+Check whether the system is on battery power
+via [UPower](http://upower.freedesktop.org/docs/UPower.html#UPower:OnBattery)
+
+ require "dbus"
+ sysbus = DBus.system_bus
+ upower_service = sysbus["org.freedesktop.UPower"]
+ upower_object = upower_service.object "/org/freedesktop/UPower"
+ upower_object.introspect
+ upower_interface = upower_object["org.freedesktop.UPower"]
+ on_battery = upower_interface["OnBattery"]
+ if on_battery
+ puts "The computer IS on battery power."
+ else
+ puts "The computer IS NOT on battery power."
+ end
+
+## Requirements
+
+- Ruby 1.9.3 or 2.0
+
+
+## Installation
+
+- `gem install ruby-dbus`
+
+## Features
+
+Ruby D-Bus currently supports the following features:
+
+ * Connecting to local buses.
+ * Accessing remote services, objects and interfaces.
+ * Invoking methods on remote objects synchronously and asynchronously.
+ * Catch signals on remote objects and handle them via callbacks.
+ * Remote object introspection.
+ * Walking object trees.
+ * Creating services and registering them on the bus.
+ * Exporting objects with interfaces on a bus for remote use.
+ * Rubyish D-Bus object and interface syntax support that automatically
+ allows for introspection.
+ * Emitting signals on exported objects.
+
+## Usage
+
+ See some of the examples in the examples/ subdirectory of the tarball.
+ Also, check out the included tutorial (in Markdown format) in doc/Tutorial.md
+ or view it online on
+ <https://github.com/mvidner/ruby-dbus/blob/master/doc/Tutorial.md> .
+
+## License
+
+ Ruby D-Bus is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by the
+ Free Software Foundation; either version 2.1 of the License, or (at
+ your option) any later version.
diff --git a/Rakefile b/Rakefile
index c97c01f..e049ce7 100644
--- a/Rakefile
+++ b/Rakefile
@@ -1,64 +1,58 @@
#! /usr/bin/env ruby
require 'rake'
-require 'rake/gempackagetask'
require 'fileutils'
include FileUtils
require 'tmpdir'
-require 'rake/rdoctask'
-require 'rake/testtask'
-
-desc 'Default: run tests in the proper environment'
-task :default => :test
-
-def common_test_task(t)
- t.libs << "lib"
- t.test_files = FileList['test/*_test.rb', 'test/t[0-9]*.rb']
- t.verbose = true
+require 'rspec/core/rake_task'
+
+require "packaging"
+
+Packaging.configuration do |conf|
+ conf.obs_project = "devel:languages:ruby:extensions"
+ conf.package_name = "rubygem-ruby-dbus"
+ conf.obs_sr_project = "openSUSE:Factory"
+ conf.skip_license_check << /^[^\/]*$/
+ conf.skip_license_check << /^(doc|examples|test)\/.*/
+ # "Ruby on Rails is released under the MIT License."
+ # but the files are missing copyright headers
+ conf.skip_license_check << /^lib\/dbus\/core_ext\//
end
-Rake::TestTask.new("bare:test") {|t| common_test_task t }
-begin
- require 'rcov/rcovtask'
- Rcov::RcovTask.new("bare:rcov") {|t| common_test_task t }
-rescue LoadError
- # no rcov, never mind
+desc 'Default: run specs in the proper environment'
+task :default => :spec
+task :test => :spec
+
+RSpec::Core::RakeTask.new("bare:spec") do |t|
+ t.pattern = "**/test/**/*_spec.rb"
+ t.rspec_opts = "--color --format doc"
end
-%w(test rcov).each do |tname|
+%w(spec).each do |tname|
desc "Run bare:#{tname} in the proper environment"
task tname do |t|
- cd "test" do
- system "./test_env rake bare:#{tname}"
+ cd "test/tools" do
+ sh "./test_env rake bare:#{tname}"
end
end
end
-load "ruby-dbus.gemspec"
-
-Rake::GemPackageTask.new(GEMSPEC) do |pkg|
- # no other formats needed
+if ENV["TRAVIS"]
+ require "coveralls/rake/task"
+ Coveralls::RakeTask.new
+ task :default => "coveralls:push"
end
+#remove tarball implementation and create gem for this gemfile
+Rake::Task[:tarball].clear
+
desc "Build a package from a clone of the local Git repo"
-task :package_git do |t|
+task :tarball do |t|
Dir.mktmpdir do |temp|
sh "git clone . #{temp}"
cd temp do
- sh "rake package"
+ sh "gem build ruby-dbus.gemspec"
end
- cp_r "#{temp}/pkg", "."
+ sh "rm -f package/*.gem"
+ cp Dir.glob("#{temp}/*.gem"), "package"
end
end
-
-Rake::RDocTask.new do |rd|
- rd.rdoc_dir = 'doc/rdoc'
- rd.rdoc_files.include("README", "lib/**/*.rb")
-# rd.options << "--diagram"
-# rd.options << "--all"
-end
-
-desc "Render the tutorial in HTML"
-task :tutorial => "doc/tutorial/index.html"
-file "doc/tutorial/index.html" => "doc/tutorial/index.markdown" do |t|
- sh "markdown #{t.prerequisites[0]} > #{t.name}"
-end
diff --git a/VERSION b/VERSION
index 7486fdb..d9df1bb 100644
--- a/VERSION
+++ b/VERSION
@@ -1 +1 @@
-0.7.2
+0.11.0
diff --git a/checksums.yaml.gz b/checksums.yaml.gz
new file mode 100644
index 0000000..02dfed5
Binary files /dev/null and b/checksums.yaml.gz differ
diff --git a/doc/Reference.md b/doc/Reference.md
new file mode 100644
index 0000000..307451c
--- /dev/null
+++ b/doc/Reference.md
@@ -0,0 +1,207 @@
+Ruby D-Bus Reference
+====================
+
+This is a reference-style documentation. It's not [a tutorial for
+beginners](http://dbus.freedesktop.org/doc/dbus-tutorial.html), the
+reader should have knowledge of basic DBus concepts.
+
+Client Side
+-----------
+
+This section should be enough if you only want to consume DBus APIs.
+
+### Basic Concepts
+
+#### Setting Up
+
+The following code is assumed as a prolog to all following ones
+
+{include:file:doc/ex-setup.rb}
+
+#### Calling Methods
+
+1. {DBus.session_bus Connect to the session bus};
+ {DBus::Connection#[] get the screensaver service}
+ {DBus::Service#object and its screensaver object}.
+2. Perform {DBus::ProxyObject#introspect explicit introspection}
+ to define the interfaces and methods
+ on the {DBus::ProxyObject object proxy}
+([I#28](https://github.com/mvidner/ruby-dbus/issues/28)).
+3. Call one of its methods in a loop, solving [xkcd#196](http://xkcd.com/196).
+
+{include:file:doc/ex-calling-methods.body.rb}
+
+##### Retrieving Return Values
+
+A method proxy always returns an array of values. This is to
+accomodate the rare cases of a DBus method specifying more than one
+*out* parameter. For nearly all methods you should use `Method[0]` or
+`Method.first`
+([I#30](https://github.com/mvidner/ruby-dbus/issues/30)).
+
+
+ # wrong
+ if upower_i.SuspendAllowed # [false] is true!
+ upower_i.Suspend
+ end
+
+ # right
+ if upower_i.SuspendAllowed[0]
+ upower_i.Suspend
+ end
+
+#### Accessing Properties
+
+To access properties, think of the {DBus::ProxyObjectInterface interface} as a
+{DBus::ProxyObjectInterface#[] hash} keyed by strings,
+or use {DBus::ProxyObjectInterface#all_properties} to get
+an actual Hash of them.
+
+{include:file:doc/ex-properties.body.rb}
+
+(TODO a writable property example)
+
+Note that unlike for methods where the interface is inferred if unambiguous,
+for properties the interface must be explicitly chosen.
+That is because {DBus::ProxyObject} uses the {DBus::ProxyObject Hash#[]} API
+to provide the {DBus::ProxyObjectInterface interfaces}, not the properties.
+
+#### Asynchronous Operation
+
+If a method call has a block attached, it is asynchronous and the block
+is invoked on receiving a method_return message or an error message
+
+##### Main Loop
+
+For asynchronous operation an event loop is necessary. Use {DBus::Main}:
+
+ # [set up signal handlers...]
+ main = DBus::Main.new
+ main << mybus
+ main.run
+
+Alternately, run the GLib main loop and add your DBus connections to it via
+{DBus::Connection#glibize}.
+
+#### Receiving Signals
+
+To receive signals for a specific object and interface, use
+{DBus::ProxyObjectInterface#on\_signal}(name, &block) or
+{DBus::ProxyObject#on_signal}(name, &block), for the default interface.
+
+{include:file:doc/ex-signal.body.rb}
+
+### Intermediate Concepts
+#### Names
+#### Types and Values, D-Bus -> Ruby
+
+D-Bus booleans, numbers, strings, arrays and dictionaries become their straightforward Ruby counterparts.
+
+Structs become arrays.
+
+Object paths become strings.
+
+Variants are simply unpacked to become their contained type.
+(ISSUE: prevents proper round-tripping!)
+
+#### Types and Values, Ruby -> D-Bus
+
+D-Bus has stricter typing than Ruby, so the library must decide
+which D-Bus type to choose. Most of the time the choice is dictated
+by the D-Bus signature.
+
+##### Variants
+
+If the signature expects a Variant
+(which is the case for all Properties!) then an explicit mechanism is needed.
+
+1. A pair [{DBus::Type::Type}, value] specifies to marshall *value* as
+ that specified type.
+ The pair can be produced by {DBus.variant}(signature, value) which
+ gives the same result as [{DBus.type}(signature), value].
+
+ ISSUE: using something else than cryptic signatures is even more painful
+ than remembering the signatures!
+
+ foo_i['Bar'] = DBus.variant("au", [0, 1, 1, 2, 3, 5, 8])
+
+2. Other values are tried to fit one of these:
+ Boolean, Double, Array of Variants, Hash of String keyed Variants,
+ String, Int32, Int64.
+
+3. **Deprecated:** A pair [String, value], where String is a valid
+ signature of a single complete type, marshalls value as that
+ type. This will hit you when you rely on method (2) but happen to have
+ a particular string value in an array.
+
+##### Byte Arrays
+
+If a byte array (`ay`) is expected you can pass a String too.
+The bytes sent are according to the string's
+[encoding](http://ruby-doc.org/core-1.9.3/Encoding.html).
+
+##### nil
+
+`nil` is not allowed by D-Bus and attempting to send it raises an exception
+(but see [I#16](https://github.com/mvidner/ruby-dbus/issues/16)).
+
+
+#### Errors
+
+D-Bus calls can reply with an error instead of a return value. An error is
+translated to a Ruby exception, an instance of {DBus::Error}.
+
+ begin
+ network_manager.sleep
+ rescue DBus::Error => e
+ puts e unless e.name == "org.freedesktop.NetworkManager.AlreadyAsleepOrAwake"
+ end
+
+#### Interfaces
+
+Methods, properties and signals of a D-Bus object always belong to one of its interfaces.
+
+Methods can be called without specifying their interface, as long as there is no ambiguity.
+There are two ways to resolve ambiguities:
+
+1. assign an interface name to {DBus::ProxyObject#default_iface}.
+
+2. get a specific {DBus::ProxyObjectInterface interface} of the object,
+with {DBus::ProxyObject#[]} and call methods from there.
+
+Signals and properties only work with a specific interface.
+
+#### Thread Safety
+Not there. An [incomplete attempt](https://github.com/mvidner/ruby-dbus/tree/multithreading) was made.
+### Advanced Concepts
+#### Bus Addresses
+#### Without Introspection
+#### Name Overloading
+
+Service Side
+------------
+
+When you want to provide a DBus API.
+
+(check that client and service side have their counterparts)
+
+### Basic
+#### Exporting a Method
+##### Interfaces
+##### Methods
+##### Bus Names
+##### Errors
+#### Exporting Properties
+### Advanced
+#### Inheritance
+#### Names
+
+Specification Conformance
+-------------------------
+
+This section lists the known deviations from version 0.19 of
+[the specification][spec].
+
+[spec]: http://dbus.freedesktop.org/doc/dbus-specification.html
+
+1. Properties support is basic.
diff --git a/doc/tutorial/index.markdown b/doc/Tutorial.md
similarity index 100%
rename from doc/tutorial/index.markdown
rename to doc/Tutorial.md
diff --git a/doc/ex-calling-methods.body.rb b/doc/ex-calling-methods.body.rb
new file mode 100644
index 0000000..f383ead
--- /dev/null
+++ b/doc/ex-calling-methods.body.rb
@@ -0,0 +1,8 @@
+mybus = DBus.session_bus
+service = mybus['org.freedesktop.ScreenSaver']
+object = service.object '/ScreenSaver'
+object.introspect
+loop do
+ object.SimulateUserActivity
+ sleep 5 * 60
+end
diff --git a/doc/ex-calling-methods.rb b/doc/ex-calling-methods.rb
new file mode 100755
index 0000000..bcffc11
--- /dev/null
+++ b/doc/ex-calling-methods.rb
@@ -0,0 +1,3 @@
+#! /usr/bin/env ruby
+require 'example-helper.rb'
+example 'ex-calling-methods.body.rb'
diff --git a/doc/ex-properties.body.rb b/doc/ex-properties.body.rb
new file mode 100644
index 0000000..715c284
--- /dev/null
+++ b/doc/ex-properties.body.rb
@@ -0,0 +1,9 @@
+sysbus = DBus.system_bus
+upower_s = sysbus['org.freedesktop.UPower']
+upower_o = upower_s.object '/org/freedesktop/UPower'
+upower_o.introspect
+upower_i = upower_o['org.freedesktop.UPower']
+
+on_battery = upower_i['OnBattery']
+
+puts "Is the computer on battery now? #{on_battery}"
diff --git a/doc/ex-properties.rb b/doc/ex-properties.rb
new file mode 100755
index 0000000..59229f7
--- /dev/null
+++ b/doc/ex-properties.rb
@@ -0,0 +1,3 @@
+#! /usr/bin/env ruby
+require 'example-helper.rb'
+example 'ex-properties.body.rb'
diff --git a/doc/ex-setup.rb b/doc/ex-setup.rb
new file mode 100644
index 0000000..5dae79e
--- /dev/null
+++ b/doc/ex-setup.rb
@@ -0,0 +1,7 @@
+#! /usr/bin/env ruby
+require 'rubygems' # Not needed since Ruby 1.9
+require 'dbus' # The gem is 'ruby-dbus' but the require is 'dbus'
+
+# Connect to a well-known address. Most apps need only one of them.
+mybus = DBus.session_bus
+sysbus = DBus.system_bus
diff --git a/doc/ex-signal.body.rb b/doc/ex-signal.body.rb
new file mode 100644
index 0000000..a1a30a8
--- /dev/null
+++ b/doc/ex-signal.body.rb
@@ -0,0 +1,20 @@
+sysbus = DBus.system_bus
+login_s = sysbus['org.freedesktop.login1'] # part of systemd
+login_o = login_s.object '/org/freedesktop/login1'
+login_o.introspect
+login_o.default_iface = 'org.freedesktop.login1.Manager'
+
+# to trigger this signal, login on the Linux console
+login_o.on_signal("SessionNew") do |name, opath|
+ puts "New session: #{name}"
+
+ session_o = login_s.object(opath)
+ session_o.introspect
+ session_i = session_o['org.freedesktop.login1.Session']
+ uid, user_opath = session_i['User']
+ puts "Its UID: #{uid}"
+end
+
+main = DBus::Main.new
+main << sysbus
+main.run
diff --git a/doc/ex-signal.rb b/doc/ex-signal.rb
new file mode 100755
index 0000000..524557d
--- /dev/null
+++ b/doc/ex-signal.rb
@@ -0,0 +1,3 @@
+#! /usr/bin/env ruby
+require 'example-helper.rb'
+example 'ex-signal.body.rb'
diff --git a/doc/example-helper.rb b/doc/example-helper.rb
new file mode 100644
index 0000000..c128b92
--- /dev/null
+++ b/doc/example-helper.rb
@@ -0,0 +1,6 @@
+# Fin
+$:.unshift File.expand_path("../../lib", __FILE__)
+load 'ex-setup.rb'
+def example(filename)
+ eval(File.read(filename), binding, filename, 1)
+end
diff --git a/lib/dbus.rb b/lib/dbus.rb
index e49df8c..18110be 100644
--- a/lib/dbus.rb
+++ b/lib/dbus.rb
@@ -8,25 +8,25 @@
# License, version 2.1 as published by the Free Software Foundation.
# See the file "COPYING" for the exact licensing terms.
-require 'dbus/type'
-require 'dbus/introspect'
-require 'dbus/error'
-require 'dbus/export'
-require 'dbus/bus.rb'
-require 'dbus/marshall'
-require 'dbus/message'
-require 'dbus/matchrule'
-require 'dbus/auth'
+require_relative "dbus/auth"
+require_relative "dbus/bus"
+require_relative "dbus/core_ext/class/attribute"
+require_relative "dbus/error"
+require_relative "dbus/export"
+require_relative "dbus/introspect"
+require_relative "dbus/logger"
+require_relative "dbus/marshall"
+require_relative "dbus/matchrule"
+require_relative "dbus/message"
+require_relative "dbus/message_queue"
+require_relative "dbus/proxy_object"
+require_relative "dbus/proxy_object_factory"
+require_relative "dbus/proxy_object_interface"
+require_relative "dbus/type"
+require_relative "dbus/xml"
-require 'socket'
-require 'thread'
-
-unless 0.respond_to?(:ord)
- # Backward compatibility with Ruby 1.8.6, see http://www.pubbs.net/ruby/200907/65871/
- class Integer
- def ord; self; end
- end
-end
+require "socket"
+require "thread"
# = D-Bus main module
#
diff --git a/lib/dbus/auth.rb b/lib/dbus/auth.rb
index cebaab6..762b05d 100644
--- a/lib/dbus/auth.rb
+++ b/lib/dbus/auth.rb
@@ -6,8 +6,6 @@
# License, version 2.1 as published by the Free Software Foundation.
# See the file "COPYING" for the exact licensing terms.
-$debug = $DEBUG #it's all over the state machine
-
require 'rbconfig'
module DBus
@@ -23,6 +21,13 @@ module DBus
end
end
+ # = Anonymous authentication class
+ class Anonymous < Authenticator
+ def authenticate
+ '527562792044427573' # Hex encoded version of "Ruby DBus"
+ end
+ end
+
# = External authentication class
#
# Class for 'external' type authentication.
@@ -67,12 +72,12 @@ module DBus
c_challenge = Array.new(s_challenge.bytesize/2).map{|obj|obj=rand(255).to_s}.join
# Search cookie file for id
path = File.join(ENV['HOME'], '.dbus-keyrings', context)
- puts "DEBUG: path: #{path.inspect}" if $debug
+ DBus.logger.debug "path: #{path.inspect}"
File.foreach(path) do |line|
if line.index(id) == 0
# Right line of file, read cookie
cookie = line.split(' ')[2].chomp
- puts "DEBUG: cookie: #{cookie.inspect}" if $debug
+ DBus.logger.debug "cookie: #{cookie.inspect}"
# Concatenate and encrypt
to_encrypt = [s_challenge, c_challenge, cookie].join(':')
sha = Digest::SHA1.hexdigest(to_encrypt)
@@ -115,12 +120,12 @@ module DBus
def initialize(socket)
@socket = socket
@state = nil
- @auth_list = [External,DBusCookieSHA1]
+ @auth_list = [External,DBusCookieSHA1,Anonymous]
end
# Start the authentication process.
def authenticate
- if (RbConfig::CONFIG["target_os"] =~ /bsd/)
+ if (RbConfig::CONFIG["target_os"] =~ /freebsd/)
@socket.sendmsg(0.chr, 0, nil, [:SOCKET, :SCM_CREDS, ""])
else
@socket.write(0.chr)
@@ -151,7 +156,7 @@ module DBus
raise AuthenticationFailed if @auth_list.size == 0
@authenticator = @auth_list.shift.new
auth_msg = ["AUTH", @authenticator.name, @authenticator.authenticate]
- puts "DEBUG: auth_msg: #{auth_msg.inspect}" if $debug
+ DBus.logger.debug "auth_msg: #{auth_msg.inspect}"
send(auth_msg)
rescue AuthenticationFailed
@socket.close
@@ -172,7 +177,7 @@ module DBus
break if data.include? crlf #crlf means line finished, the TCP socket keeps on listening, so we break
end
readline = data.chomp.split(" ")
- puts "DEBUG: readline: #{readline.inspect}" if $debug
+ DBus.logger.debug "readline: #{readline.inspect}"
return readline
end
@@ -188,7 +193,7 @@ module DBus
def next_state
msg = next_msg
if @state == :Starting
- puts "DEBUG: :Starting msg: #{msg[0].inspect}" if $debug
+ DBus.logger.debug ":Starting msg: #{msg[0].inspect}"
case msg[0]
when "OK"
@state = :WaitingForOk
@@ -198,15 +203,15 @@ module DBus
@state = :WaitingForData
end
end
- puts "DEBUG: state: #{@state}" if $debug
+ DBus.logger.debug "state: #{@state}"
case @state
when :WaitingForData
- puts "DEBUG: :WaitingForData msg: #{msg[0].inspect}" if $debug
+ DBus.logger.debug ":WaitingForData msg: #{msg[0].inspect}"
case msg[0]
when "DATA"
chall = msg[1]
resp, chall = @authenticator.data(chall)
- puts "DEBUG: :WaitingForData/DATA resp: #{resp.inspect}" if $debug
+ DBus.logger.debug ":WaitingForData/DATA resp: #{resp.inspect}"
case resp
when :AuthContinue
send("DATA", chall)
@@ -232,7 +237,7 @@ module DBus
@state = :WaitingForData
end
when :WaitingForOk
- puts "DEBUG: :WaitingForOk msg: #{msg[0].inspect}" if $debug
+ DBus.logger.debug ":WaitingForOk msg: #{msg[0].inspect}"
case msg[0]
when "OK"
send("BEGIN")
@@ -248,7 +253,7 @@ module DBus
@state = :WaitingForOk
end
when :WaitingForReject
- puts "DEBUG: :WaitingForReject msg: #{msg[0].inspect}" if $debug
+ DBus.logger.debug ":WaitingForReject msg: #{msg[0].inspect}"
case msg[0]
when "REJECT"
next_authenticator
diff --git a/lib/dbus/bus.rb b/lib/dbus/bus.rb
index f522a8f..4e0946a 100644
--- a/lib/dbus/bus.rb
+++ b/lib/dbus/bus.rb
@@ -3,7 +3,7 @@
# This file is part of the ruby-dbus project
# Copyright (C) 2007 Arnaud Cornet and Paul van Tilburg
#
-# This library is free software; you can redistribute it and/or
+# This library is free software; you caan redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License, version 2.1 as published by the Free Software Foundation.
# See the file "COPYING" for the exact licensing terms.
@@ -11,7 +11,6 @@
require 'socket'
require 'thread'
require 'singleton'
-require 'fcntl'
# = D-Bus main module
#
@@ -49,7 +48,8 @@ module DBus
self
end
- # Retrieves an object (ProxyObject) at the given _path_.
+ # Retrieves an object at the given _path_.
+ # @return [ProxyObject]
def object(path)
node = get_node(path, true)
if node.object.nil?
@@ -95,7 +95,7 @@ module DBus
n = n[elem]
end
if n.nil?
- puts "Warning, unknown object #{path}" if $DEBUG
+ DBus.logger.debug "Warning, unknown object #{path}"
end
n
end
@@ -186,8 +186,8 @@ module DBus
class Connection
# The unique name (by specification) of the message.
attr_reader :unique_name
- # The socket that is used to connect with the bus.
- attr_reader :socket
+ # pop and push messages here
+ attr_reader :message_queue
# Create a new connection to the bus for a given connect _path_. _path_
# format is described in the D-Bus specification:
@@ -196,84 +196,22 @@ module DBus
# "transport1:key1=value1,key2=value2;transport2:key1=value1,key2=value2"
# e.g. "unix:path=/tmp/dbus-test" or "tcp:host=localhost,port=2687"
def initialize(path)
- @path = path
+ @message_queue = MessageQueue.new(path)
@unique_name = nil
- @buffer = ""
@method_call_replies = Hash.new
@method_call_msgs = Hash.new
@signal_matchrules = Hash.new
@proxy = nil
@object_root = Node.new("/")
- @is_tcp = false
- end
-
- # Connect to the bus and initialize the connection.
- def connect
- addresses = @path.split ";"
- # connect to first one that succeeds
- worked = addresses.find do |a|
- transport, keyvaluestring = a.split ":"
- kv_list = keyvaluestring.split ","
- kv_hash = Hash.new
- kv_list.each do |kv|
- key, escaped_value = kv.split "="
- value = escaped_value.gsub(/%(..)/) {|m| [$1].pack "H2" }
- kv_hash[key] = value
- end
- case transport
- when "unix"
- connect_to_unix kv_hash
- when "tcp"
- connect_to_tcp kv_hash
- else
- # ignore, report?
- end
- end
- worked
- # returns the address that worked or nil.
- # how to report failure?
- end
-
- # Connect to a bus over tcp and initialize the connection.
- def connect_to_tcp(params)
- #check if the path is sufficient
- if params.key?("host") and params.key?("port")
- begin
- #initialize the tcp socket
- @socket = TCPSocket.new(params["host"],params["port"].to_i)
- @socket.fcntl(Fcntl::F_SETFD, Fcntl::FD_CLOEXEC)
- init_connection
- @is_tcp = true
- rescue
- puts "Error: Could not establish connection to: #{@path}, will now exit."
- exit(0) #a little harsh
- end
- else
- #Danger, Will Robinson: the specified "path" is not usable
- puts "Error: supplied path: #{@path}, unusable! sorry."
- end
end
- # Connect to an abstract unix bus and initialize the connection.
- def connect_to_unix(params)
- @socket = Socket.new(Socket::Constants::PF_UNIX,Socket::Constants::SOCK_STREAM, 0)
- @socket.fcntl(Fcntl::F_SETFD, Fcntl::FD_CLOEXEC)
- if ! params['abstract'].nil?
- if HOST_END == LIL_END
- sockaddr = "\1\0\0#{params['abstract']}"
- else
- sockaddr = "\0\1\0#{params['abstract']}"
- end
- elsif ! params['path'].nil?
- sockaddr = Socket.pack_sockaddr_un(params['path'])
+ # Dispatch all messages that are available in the queue,
+ # but do not block on the queue.
+ # Called by a main loop when something is available in the queue
+ def dispatch_message_queue
+ while (msg = @message_queue.pop(:non_block)) # FIXME EOFError
+ process(msg)
end
- @socket.connect(sockaddr)
- init_connection
- end
-
- # Send the buffer _buf_ to the bus using Connection#writel.
- def send(buf)
- @socket.write(buf) unless @socket.nil?
end
# Tell a bus to register itself on the glib main loop
@@ -282,13 +220,10 @@ module DBus
# Circumvent a ruby-glib bug
@channels ||= Array.new
- gio = GLib::IOChannel.new(@socket.fileno)
+ gio = GLib::IOChannel.new(@message_queue.socket.fileno)
@channels << gio
gio.add_watch(GLib::IOChannel::IN) do |c, ch|
- update_buffer
- messages.each do |msg|
- process(msg)
- end
+ dispatch_message_queue
true
end
end
@@ -384,6 +319,7 @@ module DBus
'
# This apostroph is for syntax highlighting editors confused by above xml: "
+ # @api private
# Send a _message_.
# If _reply_handler_ is not given, wait for the reply
# and return the reply, or raise the error.
@@ -408,11 +344,12 @@ module DBus
reply_handler.call(rmsg, * rmsg.params)
end
end
- send(message.marshall)
+ @message_queue.push(message)
end
ret
end
+ # @api private
def introspect_data(dest, path, &reply_handler)
m = DBus::Message.new(DBus::Message::METHOD_CALL)
m.path = path
@@ -432,6 +369,7 @@ module DBus
end
end
+ # @api private
# Issues a call to the org.freedesktop.DBus.Introspectable.Introspect method
# _dest_ is the service and _path_ the object path you want to introspect
# If a code block is given, the introspect call in asynchronous. If not
@@ -461,6 +399,7 @@ module DBus
#
# FIXME, NameRequestError cannot really be rescued as it will be raised
# when dispatching a later call. Rework the API to better match the spec.
+ # @return [Service]
def request_service(name)
# Use RequestName, but asynchronously!
# A synchronous call would not work with service activation, where
@@ -489,83 +428,22 @@ module DBus
@proxy
end
- # Fill (append) the buffer from data that might be available on the
- # socket.
- def update_buffer
- @buffer += @socket.read_nonblock(MSG_BUF_SIZE)
- rescue EOFError
- raise # the caller expects it
- rescue Exception => e
- puts "Oops:", e
- raise if @is_tcp # why?
- puts "WARNING: read_nonblock failed, falling back to .recv"
- @buffer += @socket.recv(MSG_BUF_SIZE)
- end
-
- # Get one message from the bus and remove it from the buffer.
- # Return the message.
- def pop_message
- return nil if @buffer.empty?
- ret = nil
- begin
- ret, size = Message.new.unmarshall_buffer(@buffer)
- @buffer.slice!(0, size)
- rescue IncompleteBufferException => e
- # fall through, let ret be null
- end
- ret
- end
-
- # Retrieve all the messages that are currently in the buffer.
- def messages
- ret = Array.new
- while msg = pop_message
- ret << msg
- end
- ret
- end
-
- # The buffer size for messages.
- MSG_BUF_SIZE = 4096
-
- # Update the buffer and retrieve all messages using Connection#messages.
- # Return the messages.
- def poll_messages
- ret = nil
- r, d, d = IO.select([@socket], nil, nil, 0)
- if r and r.size > 0
- update_buffer
- end
- messages
- end
-
+ # @api private
# Wait for a message to arrive. Return it once it is available.
def wait_for_message
- if @socket.nil?
- puts "ERROR: Can't wait for messages, @socket is nil."
- return
- end
- ret = pop_message
- while ret == nil
- r, d, d = IO.select([@socket])
- if r and r[0] == @socket
- update_buffer
- ret = pop_message
- end
- end
- ret
+ @message_queue.pop # FIXME EOFError
end
+ # @api private
# Send a message _m_ on to the bus. This is done synchronously, thus
# the call will block until a reply message arrives.
def send_sync(m, &retc) # :yields: reply/return message
return if m.nil? #check if somethings wrong
- send(m.marshall)
+ @message_queue.push(m)
@method_call_msgs[m.serial] = m
@method_call_replies[m.serial] = retc
retm = wait_for_message
-
return if retm.nil? #check if somethings wrong
process(retm)
@@ -575,6 +453,7 @@ module DBus
end
end
+ # @api private
# Specify a code block that has to be executed when a reply for
# message _m_ is received.
def on_return(m, &retc)
@@ -591,10 +470,10 @@ module DBus
def add_match(mr, &slot)
# check this is a signal.
mrs = mr.to_s
- puts "#{@signal_matchrules.size} rules, adding #{mrs.inspect}" if $DEBUG
+ DBus.logger.debug "#{@signal_matchrules.size} rules, adding #{mrs.inspect}"
# don't ask for the same match if we override it
unless @signal_matchrules.key?(mrs)
- puts "Asked for a new match" if $DEBUG
+ DBus.logger.debug "Asked for a new match"
proxy.AddMatch(mrs)
end
@signal_matchrules[mrs] = slot
@@ -610,6 +489,7 @@ module DBus
end
end
+ # @api private
# Process a message _m_ based on its type.
def process(m)
return if m.nil? #check if somethings wrong
@@ -618,7 +498,7 @@ module DBus
raise InvalidPacketException if m.reply_serial == nil
mcs = @method_call_replies[m.reply_serial]
if not mcs
- puts "DEBUG: no return code for mcs: #{mcs.inspect} m: #{m.inspect}" if $DEBUG
+ DBus.logger.debug "no return code for mcs: #{mcs.inspect} m: #{m.inspect}"
else
if m.message_type == Message::ERROR
mcs.call(Error.new(m))
@@ -630,23 +510,23 @@ module DBus
end
when DBus::Message::METHOD_CALL
if m.path == "/org/freedesktop/DBus"
- puts "DEBUG: Got method call on /org/freedesktop/DBus" if $DEBUG
+ DBus.logger.debug "Got method call on /org/freedesktop/DBus"
end
node = @service.get_node(m.path)
if not node
reply = Message.error(m, "org.freedesktop.DBus.Error.UnknownObject",
"Object #{m.path} doesn't exist")
- send(reply.marshall)
+ @message_queue.push(reply)
# handle introspectable as an exception:
elsif m.interface == "org.freedesktop.DBus.Introspectable" and
m.member == "Introspect"
reply = Message.new(Message::METHOD_RETURN).reply_to(m)
reply.sender = @unique_name
reply.add_param(Type::STRING, node.to_xml)
- send(reply.marshall)
+ @message_queue.push(reply)
else
obj = node.object
- return if obj.nil? # FIXME, sends no reply
+ return if obj.nil? # FIXME, pushes no reply
obj.dispatch(m) if obj
end
when DBus::Message::SIGNAL
@@ -657,11 +537,14 @@ module DBus
end
end
else
- puts "DEBUG: Unknown message type: #{m.message_type}" if $DEBUG
+ DBus.logger.debug "Unknown message type: #{m.message_type}"
end
+ rescue Exception => ex
+ raise m.annotate_exception(ex)
end
# Retrieves the Service with the given _name_.
+ # @return [Service]
def service(name)
# The service might not exist at this time so we cannot really check
# anything
@@ -669,6 +552,7 @@ module DBus
end
alias :[] :service
+ # @api private
# Emit a signal event for the given _service_, object _obj_, interface
# _intf_ and signal _sig_ with arguments _args_.
def emit(service, obj, intf, sig, *args)
@@ -682,7 +566,7 @@ module DBus
m.add_param(par.type, args[i])
i += 1
end
- send(m.marshall)
+ @message_queue.push(m)
end
###########################################################################
@@ -697,16 +581,10 @@ module DBus
m.member = "Hello"
send_sync(m) do |rmsg|
@unique_name = rmsg.destination
- puts "Got hello reply. Our unique_name is #{@unique_name}" if $DEBUG
+ DBus.logger.debug "Got hello reply. Our unique_name is #{@unique_name}"
end
@service = Service.new(@unique_name, self)
end
-
- # Initialize the connection to the bus.
- def init_connection
- @client = Client.new(@socket)
- @client.authenticate
- end
end # class Connection
@@ -719,17 +597,23 @@ module DBus
class ASessionBus < Connection
# Get the the default session bus.
def initialize
- super(ENV["DBUS_SESSION_BUS_ADDRESS"] || address_from_file)
- connect
+ super(ENV["DBUS_SESSION_BUS_ADDRESS"] || address_from_file || "launchd:env=DBUS_LAUNCHD_SESSION_BUS_SOCKET")
send_hello
end
def address_from_file
- f = File.new("/var/lib/dbus/machine-id")
- machine_id = f.readline.chomp
- f.close
- display = ENV["DISPLAY"].gsub(/.*:([0-9]*).*/, '\1')
- File.open(ENV["HOME"] + "/.dbus/session-bus/#{machine_id}-#{display}").each do |line|
+ # systemd uses /etc/machine-id
+ # traditional dbus uses /var/lib/dbus/machine-id
+ machine_id_path = Dir['{/etc,/var/lib/dbus}/machine-id'].first
+ return nil unless machine_id_path
+ machine_id = File.read(machine_id_path).chomp
+
+ display = ENV["DISPLAY"][/:(\d+)\.?/, 1]
+
+ bus_file_path = File.join(ENV["HOME"], "/.dbus/session-bus/#{machine_id}-#{display}")
+ return nil unless File.exists?(bus_file_path)
+
+ File.open(bus_file_path).each_line do |line|
if line =~ /^DBUS_SESSION_BUS_ADDRESS=(.*)/
return $1
end
@@ -754,7 +638,6 @@ module DBus
# Get the default system bus.
def initialize
super(SystemSocketName)
- connect
send_hello
end
end
@@ -775,7 +658,6 @@ module DBus
# Get the remote bus.
def initialize socket_name
super(socket_name)
- connect
send_hello
end
end
@@ -785,12 +667,14 @@ module DBus
include Singleton
end
- # Shortcut for the SystemBus instance
+ # Shortcut for the {SystemBus} instance
+ # @return [Connection]
def DBus.system_bus
SystemBus.instance
end
- # Shortcut for the SessionBus instance
+ # Shortcut for the {SessionBus} instance
+ # @return [Connection]
def DBus.session_bus
SessionBus.instance
end
@@ -809,7 +693,7 @@ module DBus
# Add a _bus_ to the list of buses to watch for events.
def <<(bus)
- @buses[bus.socket] = bus
+ @buses[bus.message_queue.socket] = bus
end
# Quit a running main loop, to be used eg. from a signal handler
@@ -822,7 +706,7 @@ module DBus
# before blocking, empty the buffers
# https://bugzilla.novell.com/show_bug.cgi?id=537401
@buses.each_value do |b|
- while m = b.pop_message
+ while m = b.message_queue.message_from_buffer_nonblock
b.process(m)
end
end
@@ -832,12 +716,12 @@ module DBus
ready.first.each do |socket|
b = @buses[socket]
begin
- b.update_buffer
+ b.message_queue.buffer_from_socket_nonblock
rescue EOFError, SystemCallError
@buses.delete socket # this bus died
next
end
- while m = b.pop_message
+ while m = b.message_queue.message_from_buffer_nonblock
b.process(m)
end
end
diff --git a/lib/dbus/core_ext/array/extract_options.rb b/lib/dbus/core_ext/array/extract_options.rb
new file mode 100644
index 0000000..8a2736d
--- /dev/null
+++ b/lib/dbus/core_ext/array/extract_options.rb
@@ -0,0 +1,31 @@
+# copied from activesupport/core_ext from Rails, MIT license
+# https://github.com/rails/rails/tree/5aa869861c192daceafe3a3ee50eb93f5a2b7bd2/activesupport/lib/active_support/core_ext
+class Hash
+ # By default, only instances of Hash itself are extractable.
+ # Subclasses of Hash may implement this method and return
+ # true to declare themselves as extractable. If a Hash
+ # is extractable, Array#extract_options! pops it from
+ # the Array when it is the last element of the Array.
+ def extractable_options?
+ instance_of?(Hash)
+ end
+end
+
+class Array
+ # Extracts options from a set of arguments. Removes and returns the last
+ # element in the array if it's a hash, otherwise returns a blank hash.
+ #
+ # def options(*args)
+ # args.extract_options!
+ # end
+ #
+ # options(1, 2) # => {}
+ # options(1, 2, a: :b) # => {:a=>:b}
+ def extract_options!
+ if last.is_a?(Hash) && last.extractable_options?
+ pop
+ else
+ {}
+ end
+ end
+end
diff --git a/lib/dbus/core_ext/class/attribute.rb b/lib/dbus/core_ext/class/attribute.rb
new file mode 100644
index 0000000..9de0cc2
--- /dev/null
+++ b/lib/dbus/core_ext/class/attribute.rb
@@ -0,0 +1,129 @@
+# copied from activesupport/core_ext from Rails, MIT license
+# https://github.com/rails/rails/tree/5aa869861c192daceafe3a3ee50eb93f5a2b7bd2/activesupport/lib/active_support/core_ext
+require 'dbus/core_ext/kernel/singleton_class'
+require 'dbus/core_ext/module/remove_method'
+require 'dbus/core_ext/array/extract_options'
+
+class Class
+ # Declare a class-level attribute whose value is inheritable by subclasses.
+ # Subclasses can change their own value and it will not impact parent class.
+ #
+ # class Base
+ # class_attribute :setting
+ # end
+ #
+ # class Subclass < Base
+ # end
+ #
+ # Base.setting = true
+ # Subclass.setting # => true
+ # Subclass.setting = false
+ # Subclass.setting # => false
+ # Base.setting # => true
+ #
+ # In the above case as long as Subclass does not assign a value to setting
+ # by performing <tt>Subclass.setting = _something_ </tt>, <tt>Subclass.setting</tt>
+ # would read value assigned to parent class. Once Subclass assigns a value then
+ # the value assigned by Subclass would be returned.
+ #
+ # This matches normal Ruby method inheritance: think of writing an attribute
+ # on a subclass as overriding the reader method. However, you need to be aware
+ # when using +class_attribute+ with mutable structures as +Array+ or +Hash+.
+ # In such cases, you don't want to do changes in places but use setters:
+ #
+ # Base.setting = []
+ # Base.setting # => []
+ # Subclass.setting # => []
+ #
+ # # Appending in child changes both parent and child because it is the same object:
+ # Subclass.setting << :foo
+ # Base.setting # => [:foo]
+ # Subclass.setting # => [:foo]
+ #
+ # # Use setters to not propagate changes:
+ # Base.setting = []
+ # Subclass.setting += [:foo]
+ # Base.setting # => []
+ # Subclass.setting # => [:foo]
+ #
+ # For convenience, an instance predicate method is defined as well.
+ # To skip it, pass <tt>instance_predicate: false</tt>.
+ #
+ # Subclass.setting? # => false
+ #
+ # Instances may overwrite the class value in the same way:
+ #
+ # Base.setting = true
+ # object = Base.new
+ # object.setting # => true
+ # object.setting = false
+ # object.setting # => false
+ # Base.setting # => true
+ #
+ # To opt out of the instance reader method, pass <tt>instance_reader: false</tt>.
+ #
+ # object.setting # => NoMethodError
+ # object.setting? # => NoMethodError
+ #
+ # To opt out of the instance writer method, pass <tt>instance_writer: false</tt>.
+ #
+ # object.setting = false # => NoMethodError
+ #
+ # To opt out of both instance methods, pass <tt>instance_accessor: false</tt>.
+ def class_attribute(*attrs)
+ options = attrs.extract_options!
+ instance_reader = options.fetch(:instance_accessor, true) && options.fetch(:instance_reader, true)
+ instance_writer = options.fetch(:instance_accessor, true) && options.fetch(:instance_writer, true)
+ instance_predicate = options.fetch(:instance_predicate, true)
+
+ attrs.each do |name|
+ define_singleton_method(name) { nil }
+ define_singleton_method("#{name}?") { !!public_send(name) } if instance_predicate
+
+ ivar = "@#{name}"
+
+ define_singleton_method("#{name}=") do |val|
+ singleton_class.class_eval do
+ remove_possible_method(name)
+ define_method(name) { val }
+ end
+
+ if singleton_class?
+ class_eval do
+ remove_possible_method(name)
+ define_method(name) do
+ if instance_variable_defined? ivar
+ instance_variable_get ivar
+ else
+ singleton_class.send name
+ end
+ end
+ end
+ end
+ val
+ end
+
+ if instance_reader
+ remove_possible_method name
+ define_method(name) do
+ if instance_variable_defined?(ivar)
+ instance_variable_get ivar
+ else
+ self.class.public_send name
+ end
+ end
+ define_method("#{name}?") { !!public_send(name) } if instance_predicate
+ end
+
+ attr_writer name if instance_writer
+ end
+ end
+
+ private
+
+ unless respond_to?(:singleton_class?)
+ def singleton_class?
+ ancestors.first != self
+ end
+ end
+end
diff --git a/lib/dbus/core_ext/kernel/singleton_class.rb b/lib/dbus/core_ext/kernel/singleton_class.rb
new file mode 100644
index 0000000..57c0770
--- /dev/null
+++ b/lib/dbus/core_ext/kernel/singleton_class.rb
@@ -0,0 +1,8 @@
+# copied from activesupport/core_ext from Rails, MIT license
+# https://github.com/rails/rails/tree/5aa869861c192daceafe3a3ee50eb93f5a2b7bd2/activesupport/lib/active_support/core_ext
+module Kernel
+ # class_eval on an object acts like singleton_class.class_eval.
+ def class_eval(*args, &block)
+ singleton_class.class_eval(*args, &block)
+ end
+end
diff --git a/lib/dbus/core_ext/module/remove_method.rb b/lib/dbus/core_ext/module/remove_method.rb
new file mode 100644
index 0000000..cc75eb0
--- /dev/null
+++ b/lib/dbus/core_ext/module/remove_method.rb
@@ -0,0 +1,14 @@
+# copied from activesupport/core_ext from Rails, MIT license
+# https://github.com/rails/rails/tree/5aa869861c192daceafe3a3ee50eb93f5a2b7bd2/activesupport/lib/active_support/core_ext
+class Module
+ def remove_possible_method(method)
+ if method_defined?(method) || private_method_defined?(method)
+ undef_method(method)
+ end
+ end
+
+ def redefine_method(method, &block)
+ remove_possible_method(method)
+ define_method(method, &block)
+ end
+end
diff --git a/lib/dbus/error.rb b/lib/dbus/error.rb
index 3a918dc..02ad253 100644
--- a/lib/dbus/error.rb
+++ b/lib/dbus/error.rb
@@ -34,8 +34,10 @@ module DBus
end
end # class Error
- # raise DBus.error, "message"
- # raise DBus.error("org.example.Error.SeatOccupied"), "Seat #{n} is occupied"
+ # @example raise a generic error
+ # raise DBus.error, "message"
+ # @example raise a specific error
+ # raise DBus.error("org.example.Error.SeatOccupied"), "Seat #{n} is occupied"
def error(name = "org.freedesktop.DBus.Error.Failed")
# message will be set by Kernel.raise
DBus::Error.new(nil, name)
diff --git a/lib/dbus/export.rb b/lib/dbus/export.rb
index 691aabb..79c2441 100644
--- a/lib/dbus/export.rb
+++ b/lib/dbus/export.rb
@@ -19,10 +19,8 @@ module DBus
class Object
# The path of the object.
attr_reader :path
- # The interfaces that the object supports.
- # intfs: Hash: String => Interface
- # @@intfs_hash simulates a class attribute by being a hash Class => intfs
- @@intfs_hash = {DBus::Object => nil}
+ # The interfaces that the object supports. Hash: String => Interface
+ class_attribute :intfs
# The service that the object is exported by.
attr_writer :service
@@ -36,26 +34,6 @@ module DBus
@service = nil
end
- def self.intfs
- if self.equal? DBus::Object
- @@intfs_hash[DBus::Object]
- else
- @@intfs_hash[self] || self.superclass.intfs
- end
- end
-
- def self.intfs= param
- @@intfs_hash[self] = param
- end
-
- def intfs
- self.class.intfs
- end
-
- def intfs= param
- self.class.intfs = param
- end
-
# State that the object implements the given _intf_.
def implements(intf)
# use a setter
@@ -86,9 +64,10 @@ module DBus
reply.add_param(rsig.type, rdata)
end
rescue => ex
- reply = ErrorMessage.from_exception(ex).reply_to(msg)
+ dbus_msg_exc = msg.annotate_exception(ex)
+ reply = ErrorMessage.from_exception(dbus_msg_exc).reply_to(msg)
end
- @service.bus.send(reply.marshall)
+ @service.bus.message_queue.push(reply)
end
end
diff --git a/lib/dbus/introspect.rb b/lib/dbus/introspect.rb
index 7a3cfe8..205a68a 100644
--- a/lib/dbus/introspect.rb
+++ b/lib/dbus/introspect.rb
@@ -8,9 +8,6 @@
# License, version 2.1 as published by the Free Software Foundation.
# See the file "COPYING" for the exact licensing terms.
-# TODO check if it is slow, make replaceable
-require 'rexml/document'
-
module DBus
# Regular expressions that should match all method names.
MethodSignalRE = /^[A-Za-z][A-Za-z0-9_]*$/
@@ -218,335 +215,5 @@ module DBus
xml
end
end # class Signal
-
- # = D-Bus introspect XML parser class
- #
- # This class parses introspection XML of an object and constructs a tree
- # of Node, Interface, Method, Signal instances.
- class IntrospectXMLParser
- # Creates a new parser for XML data in string _xml_.
- def initialize(xml)
- @xml = xml
- end
-
- # return the names of direct subnodes
- def parse_subnodes
- subnodes = Array.new
- d = REXML::Document.new(@xml)
- d.elements.each("node/node") do |e|
- subnodes << e.attributes["name"]
- end
- subnodes
- end
-
- # return a pair: [list of Interfaces, list of direct subnode names]
- def parse
- interfaces = Array.new
- subnodes = Array.new
- t = Time.now
- d = REXML::Document.new(@xml)
- d.elements.each("node/node") do |e|
- subnodes << e.attributes["name"]
- end
- d.elements.each("node/interface") do |e|
- i = Interface.new(e.attributes["name"])
- e.elements.each("method") do |me|
- m = Method.new(me.attributes["name"])
- parse_methsig(me, m)
- i << m
- end
- e.elements.each("signal") do |se|
- s = Signal.new(se.attributes["name"])
- parse_methsig(se, s)
- i << s
- end
- interfaces << i
- end
- d = Time.now - t
- if d > 2
- puts "Some XML took more that two secs to parse. Optimize me!" if $DEBUG
- end
- [interfaces, subnodes]
- end
-
- ######################################################################
- private
-
- # Parses a method signature XML element _e_ and initialises
- # method/signal _m_.
- def parse_methsig(e, m)
- e.elements.each("arg") do |ae|
- name = ae.attributes["name"]
- dir = ae.attributes["direction"]
- sig = ae.attributes["type"]
- if m.is_a?(DBus::Signal)
- m.add_fparam(name, sig)
- elsif m.is_a?(DBus::Method)
- case dir
- when "in"
- m.add_fparam(name, sig)
- when "out"
- m.add_return(name, sig)
- end
- else
- raise NotImplementedError, dir
- end
- end
- end
- end # class IntrospectXMLParser
-
- # = D-Bus proxy object interface class
- #
- # A class similar to the normal Interface used as a proxy for remote
- # object interfaces.
- class ProxyObjectInterface
- # The proxied methods contained in the interface.
- attr_accessor :methods
- # The proxied signals contained in the interface.
- attr_accessor :signals
- # The proxy object to which this interface belongs.
- attr_reader :object
- # The name of the interface.
- attr_reader :name
-
- # Creates a new proxy interface for the given proxy _object_
- # and the given _name_.
- def initialize(object, name)
- @object, @name = object, name
- @methods, @signals = Hash.new, Hash.new
- end
-
- # Returns the string representation of the interface (the name).
- def to_str
- @name
- end
-
- # Returns the singleton class of the interface.
- def singleton_class
- (class << self ; self ; end)
- end
-
- # FIXME
- def check_for_eval(s)
- raise RuntimeError, "invalid internal data '#{s}'" if not s.to_s =~ /^[A-Za-z0-9_]*$/
- end
-
- # FIXME
- def check_for_quoted_eval(s)
- raise RuntimeError, "invalid internal data '#{s}'" if not s.to_s =~ /^[^"]+$/
- end
-
- # Defines a method on the interface from the Method descriptor _m_.
- def define_method_from_descriptor(m)
- check_for_eval(m.name)
- check_for_quoted_eval(@name)
- methdef = "def #{m.name}("
- methdef += (0..(m.params.size - 1)).to_a.collect { |n|
- "arg#{n}"
- }.push("&reply_handler").join(", ")
- methdef += %{)
- msg = Message.new(Message::METHOD_CALL)
- msg.path = @object.path
- msg.interface = "#{@name}"
- msg.destination = @object.destination
- msg.member = "#{m.name}"
- msg.sender = @object.bus.unique_name
- }
- idx = 0
- m.params.each do |fpar|
- par = fpar.type
- check_for_quoted_eval(par)
-
- # This is the signature validity check
- Type::Parser.new(par).parse
-
- methdef += %{
- msg.add_param("#{par}", arg#{idx})
- }
- idx += 1
- end
- methdef += "
- @object.bus.send_sync_or_async(msg, &reply_handler)
- end
- "
- singleton_class.class_eval(methdef)
- @methods[m.name] = m
- end
-
- # Defines a signal from the descriptor _s_.
- def define_signal_from_descriptor(s)
- @signals[s.name] = s
- end
-
- # Defines a signal or method based on the descriptor _m_.
- def define(m)
- if m.kind_of?(Method)
- define_method_from_descriptor(m)
- elsif m.kind_of?(Signal)
- define_signal_from_descriptor(m)
- end
- end
-
- # Defines a proxied method on the interface.
- def define_method(methodname, prototype)
- m = Method.new(methodname)
- m.from_prototype(prototype)
- define(m)
- end
-
- # Registers a handler (code block) for a signal with _name_ arriving
- # over the given _bus_. If no block is given, the signal is unregistered.
- def on_signal(bus, name, &block)
- mr = DBus::MatchRule.new.from_signal(self, name)
- if block.nil?
- bus.remove_match(mr)
- else
- bus.add_match(mr) { |msg| block.call(*msg.params) }
- end
- end
-
- PROPERTY_INTERFACE = "org.freedesktop.DBus.Properties"
-
- # Read a property.
- def [](propname)
- self.object[PROPERTY_INTERFACE].Get(self.name, propname)[0]
- end
-
- # Write a property.
- def []=(propname, value)
- self.object[PROPERTY_INTERFACE].Set(self.name, propname, value)
- end
-
- # Read all properties at once, as a hash.
- def all_properties
- self.object[PROPERTY_INTERFACE].GetAll(self.name)[0]
- end
- end # class ProxyObjectInterface
-
- # D-Bus proxy object class
- #
- # Class representing a remote object in an external application.
- # Typically, calling a method on an instance of a ProxyObject sends a message
- # over the bus so that the method is executed remotely on the correctponding
- # object.
- class ProxyObject
- # The names of direct subnodes of the object in the tree.
- attr_accessor :subnodes
- # Flag determining whether the object has been introspected.
- attr_accessor :introspected
- # The (remote) destination of the object.
- attr_reader :destination
- # The path to the object.
- attr_reader :path
- # The bus the object is reachable via.
- attr_reader :bus
- # The default interface of the object, as String.
- attr_accessor :default_iface
-
- # Creates a new proxy object living on the given _bus_ at destination _dest_
- # on the given _path_.
- def initialize(bus, dest, path)
- @bus, @destination, @path = bus, dest, path
- @interfaces = Hash.new
- @subnodes = Array.new
- end
-
- # Returns the interfaces of the object.
- def interfaces
- @interfaces.keys
- end
-
- # Retrieves an interface of the proxy object (ProxyObjectInterface instance).
- def [](intfname)
- @interfaces[intfname]
- end
-
- # Maps the given interface name _intfname_ to the given interface _intf.
- def []=(intfname, intf)
- @interfaces[intfname] = intf
- end
-
- # Introspects the remote object. Allows you to find and select
- # interfaces on the object.
- def introspect
- # Synchronous call here.
- xml = @bus.introspect_data(@destination, @path)
- ProxyObjectFactory.introspect_into(self, xml)
- xml
- end
-
- # Returns whether the object has an interface with the given _name_.
- def has_iface?(name)
- raise "Cannot call has_iface? if not introspected" if not @introspected
- @interfaces.member?(name)
- end
-
- # Registers a handler, the code block, for a signal with the given _name_.
- # It uses _default_iface_ which must have been set.
- def on_signal(name, &block)
- if @default_iface and has_iface?(@default_iface)
- @interfaces[@default_iface].on_signal(@bus, name, &block)
- else
- # TODO improve
- raise NoMethodError
- end
- end
-
- ####################################################
- private
-
- # Handles all unkown methods, mostly to route method calls to the
- # default interface.
- def method_missing(name, *args, &reply_handler)
- if @default_iface and has_iface?(@default_iface)
- begin
- @interfaces[@default_iface].method(name).call(*args, &reply_handler)
- rescue NameError => e
- # interesting, foo.method("unknown")
- # raises NameError, not NoMethodError
- raise unless e.to_s =~ /undefined method `#{name}'/
- # BTW e.exception("...") would preserve the class.
- raise NoMethodError,"undefined method `#{name}' for DBus interface `#{@default_iface}' on object `#{@path}'"
- end
- else
- # TODO distinguish:
- # - di not specified
- #TODO
- # - di is specified but not found in introspection data
- raise NoMethodError, "undefined method `#{name}' for DBus interface `#{@default_iface}' on object `#{@path}'"
- end
- end
- end # class ProxyObject
-
- # = D-Bus proxy object factory class
- #
- # Class that generates and sets up a proxy object based on introspection data.
- class ProxyObjectFactory
- # Creates a new proxy object factory for the given introspection XML _xml_,
- # _bus_, destination _dest_, and _path_.
- def initialize(xml, bus, dest, path)
- @xml, @bus, @path, @dest = xml, bus, path, dest
- end
-
- # Investigates the sub-nodes of the proxy object _po_ based on the
- # introspection XML data _xml_ and sets them up recursively.
- def ProxyObjectFactory.introspect_into(po, xml)
- intfs, po.subnodes = IntrospectXMLParser.new(xml).parse
- intfs.each do |i|
- poi = ProxyObjectInterface.new(po, i.name)
- i.methods.each_value { |m| poi.define(m) }
- i.signals.each_value { |s| poi.define(s) }
- po[i.name] = poi
- end
- po.introspected = true
- end
-
- # Generates, sets up and returns the proxy object.
- def build
- po = ProxyObject.new(@bus, @dest, @path)
- ProxyObjectFactory.introspect_into(po, @xml)
- po
- end
- end # class ProxyObjectFactory
end # module DBus
diff --git a/lib/dbus/logger.rb b/lib/dbus/logger.rb
new file mode 100644
index 0000000..0703093
--- /dev/null
+++ b/lib/dbus/logger.rb
@@ -0,0 +1,31 @@
+# dbus/logger.rb - debug logging
+#
+# This file is part of the ruby-dbus project
+# Copyright (C) 2012 Martin Vidner
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License, version 2.1 as published by the Free Software Foundation.
+# See the file "COPYING" for the exact licensing terms.
+
+require 'logger'
+
+module DBus
+ # Get the logger for the DBus module.
+ # The default one logs to STDERR,
+ # with DEBUG if $DEBUG is set, otherwise INFO.
+ def logger
+ unless defined? @logger
+ @logger = Logger.new(STDERR)
+ @logger.level = $DEBUG ? Logger::DEBUG : Logger::INFO
+ end
+ @logger
+ end
+ module_function :logger
+
+ # Set the logger for the DBus module
+ def logger=(logger)
+ @logger = logger
+ end
+ module_function :logger=
+end
diff --git a/lib/dbus/marshall.rb b/lib/dbus/marshall.rb
index be5031a..185bd69 100644
--- a/lib/dbus/marshall.rb
+++ b/lib/dbus/marshall.rb
@@ -90,15 +90,6 @@ module DBus
ret
end
- # Retrieve the series of bytes until the next NULL (\0) byte.
- def get_nul_terminated
- raise IncompleteBufferException if not @buffy[@idx..-1] =~ /^([^\0]*)\0/
- str = $1
- raise IncompleteBufferException if @idx + str.bytesize + 1 > @buffy.bytesize
- @idx += str.bytesize + 1
- str
- end
-
# Get the string length and string itself from the buffer.
# Return the string.
def get_string
@@ -146,7 +137,7 @@ module DBus
if (packet & 0x8000) != 0
packet -= 0x10000
end
- when Type::UINT32
+ when Type::UINT32, Type::UNIX_FD
align(4)
packet = get(4).unpack(@uint32)[0]
when Type::INT32
@@ -221,7 +212,7 @@ module DBus
packet = get_string
when Type::STRING
packet = get_string
- packet.force_encoding('UTF-8') if RUBY_VERSION >= '1.9'
+ packet.force_encoding('UTF-8')
when Type::SIGNATURE
packet = get_signature
when Type::DICT_ENTRY
@@ -301,11 +292,6 @@ module DBus
yield
end
- # Append a string of bytes without type.
- def append_simple_string(s)
- @packet += s + "\0"
- end
-
# Append a value _val_ to the packet based on its _type_.
#
# Host native endianness is used, declared in Message#marshall
@@ -317,7 +303,7 @@ module DBus
case type.sigtype
when Type::BYTE
@packet += val.chr
- when Type::UINT32
+ when Type::UINT32, Type::UNIX_FD
align(4)
@packet += [val].pack("L")
when Type::UINT64
@@ -382,8 +368,12 @@ module DBus
# Damn ruby rocks here
val = val.to_a
end
- if not val.kind_of?(Array)
- raise TypeException, "Expected an Array but got a #{val.class}"
+ # If string is recieved and ay is expected, explode the string
+ if val.kind_of?(String) && type.child.sigtype == Type::BYTE
+ val = val.bytes
+ end
+ if not val.kind_of?(Enumerable)
+ raise TypeException, "Expected an Enumerable of #{type.child.inspect} but got a #{val.class}"
end
array(type.child) do
val.each do |elem|
diff --git a/lib/dbus/message.rb b/lib/dbus/message.rb
index 378e0d5..0fee107 100644
--- a/lib/dbus/message.rb
+++ b/lib/dbus/message.rb
@@ -101,6 +101,10 @@ module DBus
end
end
+ def to_s
+ "#{message_type} sender=#{sender} -> dest=#{destination} serial=#{serial} reply_serial=#{reply_serial} path=#{path}; interface=#{interface}; member=#{member} error_name=#{error_name}"
+ end
+
# Create a regular reply to a message _m_.
def self.method_return(m)
MethodReturnMessage.new.reply_to(m)
@@ -161,64 +165,18 @@ module DBus
marshaller.append(Type::BYTE, @protocol)
marshaller.append(Type::UINT32, @body_length)
marshaller.append(Type::UINT32, @serial)
- marshaller.array(Type::Parser.new("y").parse[0]) do
- if @path
- marshaller.struct do
- marshaller.append(Type::BYTE, PATH)
- marshaller.append(Type::BYTE, 1)
- marshaller.append_simple_string("o")
- marshaller.append(Type::OBJECT_PATH, @path)
- end
- end
- if @interface
- marshaller.struct do
- marshaller.append(Type::BYTE, INTERFACE)
- marshaller.append(Type::BYTE, 1)
- marshaller.append_simple_string("s")
- marshaller.append(Type::STRING, @interface)
- end
- end
- if @member
- marshaller.struct do
- marshaller.append(Type::BYTE, MEMBER)
- marshaller.append(Type::BYTE, 1)
- marshaller.append_simple_string("s")
- marshaller.append(Type::STRING, @member)
- end
- end
- if @error_name
- marshaller.struct do
- marshaller.append(Type::BYTE, ERROR_NAME)
- marshaller.append(Type::BYTE, 1)
- marshaller.append_simple_string("s")
- marshaller.append(Type::STRING, @error_name)
- end
- end
- if @reply_serial
- marshaller.struct do
- marshaller.append(Type::BYTE, REPLY_SERIAL)
- marshaller.append(Type::BYTE, 1)
- marshaller.append_simple_string("u")
- marshaller.append(Type::UINT32, @reply_serial)
- end
- end
- if @destination
- marshaller.struct do
- marshaller.append(Type::BYTE, DESTINATION)
- marshaller.append(Type::BYTE, 1)
- marshaller.append_simple_string("s")
- marshaller.append(Type::STRING, @destination)
- end
- end
- if @signature != ""
- marshaller.struct do
- marshaller.append(Type::BYTE, SIGNATURE)
- marshaller.append(Type::BYTE, 1)
- marshaller.append_simple_string("g")
- marshaller.append(Type::SIGNATURE, @signature)
- end
- end
- end
+
+ headers = []
+ headers << [PATH, ["o", @path]] if @path
+ headers << [INTERFACE, ["s", @interface]] if @interface
+ headers << [MEMBER, ["s", @member]] if @member
+ headers << [ERROR_NAME, ["s", @error_name]] if @error_name
+ headers << [REPLY_SERIAL, ["u", @reply_serial]] if @reply_serial
+ headers << [DESTINATION, ["s", @destination]] if @destination
+ # SENDER is not sent, the message bus fills it in instead
+ headers << [SIGNATURE, ["g", @signature]] if @signature != ""
+ marshaller.append("a(yv)", headers)
+
marshaller.align(8)
@params.each do |param|
marshaller.append(param[0], param[1])
@@ -240,7 +198,7 @@ module DBus
end
pu = PacketUnmarshaller.new(buf, endianness)
mdata = pu.unmarshall(MESSAGE_SIGNATURE)
- dummy, @message_type, @flags, @protocol, @body_length, @serial,
+ _, @message_type, @flags, @protocol, @body_length, @serial,
headers = mdata
headers.each do |struct|
@@ -274,9 +232,17 @@ module DBus
# Message#unmarshall_buf.
# Return the message.
def unmarshall(buf)
- ret, size = unmarshall_buffer(buf)
+ ret, _ = unmarshall_buffer(buf)
ret
end
+
+ # Make a new exception from ex, mark it as being caused by this message
+ # @api private
+ def annotate_exception(ex)
+ new_ex = ex.exception("#{ex}; caused by #{self}")
+ new_ex.set_backtrace(ex.backtrace)
+ new_ex
+ end
end # class Message
class MethodReturnMessage < Message
diff --git a/lib/dbus/message_queue.rb b/lib/dbus/message_queue.rb
new file mode 100644
index 0000000..1d2de58
--- /dev/null
+++ b/lib/dbus/message_queue.rb
@@ -0,0 +1,166 @@
+# This file is part of the ruby-dbus project
+# Copyright (C) 2007 Arnaud Cornet and Paul van Tilburg
+# Copyright (C) 2009-2014 Martin Vidner
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License, version 2.1 as published by the Free Software Foundation.
+# See the file "COPYING" for the exact licensing terms.
+
+require "fcntl"
+require "socket"
+
+module DBus
+ class MessageQueue
+ # The socket that is used to connect with the bus.
+ attr_reader :socket
+
+ def initialize(address)
+ @address = address
+ @buffer = ""
+ @is_tcp = false
+ connect
+ end
+
+ # TODO failure modes
+ #
+ # If _non_block_ is true, return nil instead of waiting
+ # EOFError may be raised
+ def pop(non_block = false)
+ buffer_from_socket_nonblock
+ message = message_from_buffer_nonblock
+ unless non_block
+ # we can block
+ while message.nil?
+ r, d, d = IO.select([@socket])
+ if r and r[0] == @socket
+ buffer_from_socket_nonblock
+ message = message_from_buffer_nonblock
+ end
+ end
+ end
+ message
+ end
+
+ def push(message)
+ @socket.write(message.marshall)
+ end
+ alias :<< :push
+
+ private
+
+ # Connect to the bus and initialize the connection.
+ def connect
+ addresses = @address.split ";"
+ # connect to first one that succeeds
+ worked = addresses.find do |a|
+ transport, keyvaluestring = a.split ":"
+ kv_list = keyvaluestring.split ","
+ kv_hash = Hash.new
+ kv_list.each do |kv|
+ key, escaped_value = kv.split "="
+ value = escaped_value.gsub(/%(..)/) {|m| [$1].pack "H2" }
+ kv_hash[key] = value
+ end
+ case transport
+ when "unix"
+ connect_to_unix kv_hash
+ when "tcp"
+ connect_to_tcp kv_hash
+ when "launchd"
+ connect_to_launchd kv_hash
+ else
+ # ignore, report?
+ end
+ end
+ worked
+ # returns the address that worked or nil.
+ # how to report failure?
+ end
+
+ # Connect to a bus over tcp and initialize the connection.
+ def connect_to_tcp(params)
+ #check if the path is sufficient
+ if params.key?("host") and params.key?("port")
+ begin
+ #initialize the tcp socket
+ @socket = TCPSocket.new(params["host"],params["port"].to_i)
+ @socket.fcntl(Fcntl::F_SETFD, Fcntl::FD_CLOEXEC)
+ init_connection
+ @is_tcp = true
+ rescue Exception => e
+ puts "Oops:", e
+ puts "Error: Could not establish connection to: #{@path}, will now exit."
+ exit(1) #a little harsh
+ end
+ else
+ #Danger, Will Robinson: the specified "path" is not usable
+ puts "Error: supplied path: #{@path}, unusable! sorry."
+ end
+ end
+
+ # Connect to an abstract unix bus and initialize the connection.
+ def connect_to_unix(params)
+ @socket = Socket.new(Socket::Constants::PF_UNIX,Socket::Constants::SOCK_STREAM, 0)
+ @socket.fcntl(Fcntl::F_SETFD, Fcntl::FD_CLOEXEC)
+ if ! params['abstract'].nil?
+ if HOST_END == LIL_END
+ sockaddr = "\1\0\0#{params['abstract']}"
+ else
+ sockaddr = "\0\1\0#{params['abstract']}"
+ end
+ elsif ! params['path'].nil?
+ sockaddr = Socket.pack_sockaddr_un(params['path'])
+ end
+ @socket.connect(sockaddr)
+ init_connection
+ end
+
+ def connect_to_launchd(params)
+ socket_var = params['env']
+ socket = `launchctl getenv #{socket_var}`.chomp
+ connect_to_unix 'path' => socket
+ end
+
+ # Initialize the connection to the bus.
+ def init_connection
+ @client = Client.new(@socket)
+ @client.authenticate
+ end
+
+ public # FIXME: fix Main loop instead
+
+ # Get and remove one message from the buffer.
+ # Return the message or nil.
+ def message_from_buffer_nonblock
+ return nil if @buffer.empty?
+ ret = nil
+ begin
+ ret, size = Message.new.unmarshall_buffer(@buffer)
+ @buffer.slice!(0, size)
+ rescue IncompleteBufferException
+ # fall through, let ret be null
+ end
+ ret
+ end
+
+ # The buffer size for messages.
+ MSG_BUF_SIZE = 4096
+
+ # Fill (append) the buffer from data that might be available on the
+ # socket.
+ # EOFError may be raised
+ def buffer_from_socket_nonblock
+ @buffer += @socket.read_nonblock(MSG_BUF_SIZE)
+ rescue EOFError
+ raise # the caller expects it
+ rescue Errno::EAGAIN
+ # fine, would block
+ rescue Exception => e
+ puts "Oops:", e
+ raise if @is_tcp # why?
+ puts "WARNING: read_nonblock failed, falling back to .recv"
+ @buffer += @socket.recv(MSG_BUF_SIZE)
+ end
+ end
+end
diff --git a/lib/dbus/proxy_object.rb b/lib/dbus/proxy_object.rb
new file mode 100644
index 0000000..e5588ad
--- /dev/null
+++ b/lib/dbus/proxy_object.rb
@@ -0,0 +1,149 @@
+# This file is part of the ruby-dbus project
+# Copyright (C) 2007 Arnaud Cornet and Paul van Tilburg
+# Copyright (C) 2009-2014 Martin Vidner
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License, version 2.1 as published by the Free Software Foundation.
+# See the file "COPYING" for the exact licensing terms.
+
+module DBus
+ # D-Bus proxy object class
+ #
+ # Class representing a remote object in an external application.
+ # Typically, calling a method on an instance of a ProxyObject sends a message
+ # over the bus so that the method is executed remotely on the correctponding
+ # object.
+ class ProxyObject
+ # The names of direct subnodes of the object in the tree.
+ attr_accessor :subnodes
+ # Flag determining whether the object has been introspected.
+ attr_accessor :introspected
+ # The (remote) destination of the object.
+ attr_reader :destination
+ # The path to the object.
+ attr_reader :path
+ # The bus the object is reachable via.
+ attr_reader :bus
+ # @return [String] The name of the default interface of the object.
+ attr_accessor :default_iface
+
+ # Creates a new proxy object living on the given _bus_ at destination _dest_
+ # on the given _path_.
+ def initialize(bus, dest, path)
+ @bus, @destination, @path = bus, dest, path
+ @interfaces = Hash.new
+ @subnodes = Array.new
+ end
+
+ # Returns the interfaces of the object.
+ def interfaces
+ @interfaces.keys
+ end
+
+ # Retrieves an interface of the proxy object
+ # @param [String] intfname
+ # @return [ProxyObjectInterface]
+ def [](intfname)
+ @interfaces[intfname]
+ end
+
+ # Maps the given interface name _intfname_ to the given interface _intf.
+ # @param [String] intfname
+ # @param [ProxyObjectInterface] intf
+ # @return [ProxyObjectInterface]
+ def []=(intfname, intf)
+ @interfaces[intfname] = intf
+ end
+
+ # Introspects the remote object. Allows you to find and select
+ # interfaces on the object.
+ def introspect
+ # Synchronous call here.
+ xml = @bus.introspect_data(@destination, @path)
+ ProxyObjectFactory.introspect_into(self, xml)
+ define_shortcut_methods()
+ xml
+ end
+
+ # For each non duplicated method name in any interface present on the
+ # caller, defines a shortcut method dynamically.
+ # This function is automatically called when a {ProxyObject} is
+ # introspected.
+ def define_shortcut_methods
+ # builds a list of duplicated methods
+ dup_meths, univocal_meths = [],{}
+ @interfaces.each_value do |intf|
+ intf.methods.each_value do |meth|
+ name = meth.name.to_sym
+ # don't overwrite instance methods!
+ if dup_meths.include? name or self.class.instance_methods.include? name
+ next
+ elsif univocal_meths.include? name
+ univocal_meths.delete name
+ dup_meths << name
+ else
+ univocal_meths[name] = intf
+ end
+ end
+ end
+ univocal_meths.each do |name, intf|
+ # creates a shortcut function that forwards each call to the method on
+ # the appropriate intf
+ singleton_class.class_eval do
+ define_method name do |*args, &reply_handler|
+ intf.method(name).call(*args, &reply_handler)
+ end
+ end
+ end
+ end
+
+ # Returns whether the object has an interface with the given _name_.
+ def has_iface?(name)
+ raise "Cannot call has_iface? if not introspected" if not @introspected
+ @interfaces.member?(name)
+ end
+
+ # Registers a handler, the code block, for a signal with the given _name_.
+ # It uses _default_iface_ which must have been set.
+ # @return [void]
+ def on_signal(name, &block)
+ if @default_iface and has_iface?(@default_iface)
+ @interfaces[@default_iface].on_signal(name, &block)
+ else
+ # TODO improve
+ raise NoMethodError
+ end
+ end
+
+ ####################################################
+ private
+
+ # Handles all unkown methods, mostly to route method calls to the
+ # default interface.
+ def method_missing(name, *args, &reply_handler)
+ if @default_iface and has_iface?(@default_iface)
+ begin
+ @interfaces[@default_iface].method(name).call(*args, &reply_handler)
+ rescue NameError => e
+ # interesting, foo.method("unknown")
+ # raises NameError, not NoMethodError
+ raise unless e.to_s =~ /undefined method `#{name}'/
+ # BTW e.exception("...") would preserve the class.
+ raise NoMethodError,"undefined method `#{name}' for DBus interface `#{@default_iface}' on object `#{@path}'"
+ end
+ else
+ # TODO distinguish:
+ # - di not specified
+ #TODO
+ # - di is specified but not found in introspection data
+ raise NoMethodError, "undefined method `#{name}' for DBus interface `#{@default_iface}' on object `#{@path}'"
+ end
+ end
+
+ # Returns the singleton class of the object.
+ def singleton_class
+ (class << self ; self ; end)
+ end
+ end # class ProxyObject
+end
diff --git a/lib/dbus/proxy_object_factory.rb b/lib/dbus/proxy_object_factory.rb
new file mode 100644
index 0000000..afbffd0
--- /dev/null
+++ b/lib/dbus/proxy_object_factory.rb
@@ -0,0 +1,41 @@
+# This file is part of the ruby-dbus project
+# Copyright (C) 2007 Arnaud Cornet and Paul van Tilburg
+# Copyright (C) 2009-2014 Martin Vidner
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License, version 2.1 as published by the Free Software Foundation.
+# See the file "COPYING" for the exact licensing terms.
+
+module DBus
+ # = D-Bus proxy object factory class
+ #
+ # Class that generates and sets up a proxy object based on introspection data.
+ class ProxyObjectFactory
+ # Creates a new proxy object factory for the given introspection XML _xml_,
+ # _bus_, destination _dest_, and _path_.
+ def initialize(xml, bus, dest, path)
+ @xml, @bus, @path, @dest = xml, bus, path, dest
+ end
+
+ # Investigates the sub-nodes of the proxy object _po_ based on the
+ # introspection XML data _xml_ and sets them up recursively.
+ def ProxyObjectFactory.introspect_into(po, xml)
+ intfs, po.subnodes = IntrospectXMLParser.new(xml).parse
+ intfs.each do |i|
+ poi = ProxyObjectInterface.new(po, i.name)
+ i.methods.each_value { |m| poi.define(m) }
+ i.signals.each_value { |s| poi.define(s) }
+ po[i.name] = poi
+ end
+ po.introspected = true
+ end
+
+ # Generates, sets up and returns the proxy object.
+ def build
+ po = ProxyObject.new(@bus, @dest, @path)
+ ProxyObjectFactory.introspect_into(po, @xml)
+ po
+ end
+ end # class ProxyObjectFactory
+end
diff --git a/lib/dbus/proxy_object_interface.rb b/lib/dbus/proxy_object_interface.rb
new file mode 100644
index 0000000..b943ee0
--- /dev/null
+++ b/lib/dbus/proxy_object_interface.rb
@@ -0,0 +1,128 @@
+# This file is part of the ruby-dbus project
+# Copyright (C) 2007 Arnaud Cornet and Paul van Tilburg
+# Copyright (C) 2009-2014 Martin Vidner
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License, version 2.1 as published by the Free Software Foundation.
+# See the file "COPYING" for the exact licensing terms.
+
+module DBus
+ # = D-Bus proxy object interface class
+ #
+ # A class similar to the normal Interface used as a proxy for remote
+ # object interfaces.
+ class ProxyObjectInterface
+ # The proxied methods contained in the interface.
+ attr_accessor :methods
+ # The proxied signals contained in the interface.
+ attr_accessor :signals
+ # The proxy object to which this interface belongs.
+ attr_reader :object
+ # The name of the interface.
+ attr_reader :name
+
+ # Creates a new proxy interface for the given proxy _object_
+ # and the given _name_.
+ def initialize(object, name)
+ @object, @name = object, name
+ @methods, @signals = Hash.new, Hash.new
+ end
+
+ # Returns the string representation of the interface (the name).
+ def to_str
+ @name
+ end
+
+ # Returns the singleton class of the interface.
+ def singleton_class
+ (class << self ; self ; end)
+ end
+
+ # Defines a method on the interface from the Method descriptor _m_.
+ def define_method_from_descriptor(m)
+ m.params.each do |fpar|
+ par = fpar.type
+ # This is the signature validity check
+ Type::Parser.new(par).parse
+ end
+
+ singleton_class.class_eval do
+ define_method m.name do |*args, &reply_handler|
+ if m.params.size != args.size
+ raise ArgumentError, "wrong number of arguments (#{args.size} for #{m.params.size})"
+ end
+
+ msg = Message.new(Message::METHOD_CALL)
+ msg.path = @object.path
+ msg.interface = @name
+ msg.destination = @object.destination
+ msg.member = m.name
+ msg.sender = @object.bus.unique_name
+ m.params.each do |fpar|
+ par = fpar.type
+ msg.add_param(par, args.shift)
+ end
+ @object.bus.send_sync_or_async(msg, &reply_handler)
+ end
+ end
+
+ @methods[m.name] = m
+ end
+
+ # Defines a signal from the descriptor _s_.
+ def define_signal_from_descriptor(s)
+ @signals[s.name] = s
+ end
+
+ # Defines a signal or method based on the descriptor _m_.
+ def define(m)
+ if m.kind_of?(Method)
+ define_method_from_descriptor(m)
+ elsif m.kind_of?(Signal)
+ define_signal_from_descriptor(m)
+ end
+ end
+
+ # Defines a proxied method on the interface.
+ def define_method(methodname, prototype)
+ m = Method.new(methodname)
+ m.from_prototype(prototype)
+ define(m)
+ end
+
+ # @overload on_signal(name, &block)
+ # @overload on_signal(bus, name, &block)
+ # Registers a handler (code block) for a signal with _name_ arriving
+ # over the given _bus_. If no block is given, the signal is unregistered.
+ # Note that specifying _bus_ is discouraged and the option is kept only for
+ # backward compatibility.
+ # @return [void]
+ def on_signal(bus = @object.bus, name, &block)
+ mr = DBus::MatchRule.new.from_signal(self, name)
+ if block.nil?
+ bus.remove_match(mr)
+ else
+ bus.add_match(mr) { |msg| block.call(*msg.params) }
+ end
+ end
+
+ PROPERTY_INTERFACE = "org.freedesktop.DBus.Properties"
+
+ # Read a property.
+ def [](propname)
+ self.object[PROPERTY_INTERFACE].Get(self.name, propname)[0]
+ end
+
+ # Write a property.
+ def []=(propname, value)
+ self.object[PROPERTY_INTERFACE].Set(self.name, propname, value)
+ end
+
+ # Read all properties at once, as a hash.
+ # @return [Hash{String}]
+ def all_properties
+ self.object[PROPERTY_INTERFACE].GetAll(self.name)[0]
+ end
+ end # class ProxyObjectInterface
+end
diff --git a/lib/dbus/type.rb b/lib/dbus/type.rb
index 2a5d4f5..34f6bc8 100644
--- a/lib/dbus/type.rb
+++ b/lib/dbus/type.rb
@@ -15,45 +15,31 @@ module DBus
# This module containts the constants of the types specified in the D-Bus
# protocol.
module Type
- # The types.
- INVALID = 0
- BYTE = ?y
- BOOLEAN = ?b
- INT16 = ?n
- UINT16 = ?q
- INT32 = ?i
- UINT32 = ?u
- INT64 = ?x
- UINT64 = ?t
- DOUBLE = ?d
- STRUCT = ?r
- ARRAY = ?a
- VARIANT = ?v
- OBJECT_PATH = ?o
- STRING = ?s
- SIGNATURE = ?g
- DICT_ENTRY = ?e
-
- # Mapping from type number to name.
- TypeName = {
- INVALID => "INVALID",
- BYTE => "BYTE",
- BOOLEAN => "BOOLEAN",
- INT16 => "INT16",
- UINT16 => "UINT16",
- INT32 => "INT32",
- UINT32 => "UINT32",
- INT64 => "INT64",
- UINT64 => "UINT64",
- DOUBLE => "DOUBLE",
- STRUCT => "STRUCT",
- ARRAY => "ARRAY",
- VARIANT => "VARIANT",
- OBJECT_PATH => "OBJECT_PATH",
- STRING => "STRING",
- SIGNATURE => "SIGNATURE",
- DICT_ENTRY => "DICT_ENTRY"
+ # Mapping from type number to name and alignment.
+ TypeMapping = {
+ 0 => ["INVALID", nil],
+ ?y => ["BYTE", 1],
+ ?b => ["BOOLEAN", 4],
+ ?n => ["INT16", 2],
+ ?q => ["UINT16", 2],
+ ?i => ["INT32", 4],
+ ?u => ["UINT32", 4],
+ ?x => ["INT64", 8],
+ ?t => ["UINT64", 8],
+ ?d => ["DOUBLE", 8],
+ ?r => ["STRUCT", 8],
+ ?a => ["ARRAY", 4],
+ ?v => ["VARIANT", 1],
+ ?o => ["OBJECT_PATH", 4],
+ ?s => ["STRING", 4],
+ ?g => ["SIGNATURE", 1],
+ ?e => ["DICT_ENTRY", 8],
+ ?h => ["UNIX_FD", 4],
}
+ # Defines the set of constants
+ TypeMapping.each_pair do |key, value|
+ Type.const_set(value.first, key)
+ end
# Exception raised when an unknown/incorrect type is encountered.
class SignatureException < Exception
@@ -70,7 +56,7 @@ module Type
# Create a new type instance for type number _sigtype_.
def initialize(sigtype)
- if not TypeName.keys.member?(sigtype)
+ if not TypeMapping.keys.member?(sigtype)
raise SignatureException, "Unknown key in signature: #{sigtype.chr}"
end
@sigtype = sigtype
@@ -79,24 +65,7 @@ module Type
# Return the required alignment for the type.
def alignment
- {
- BYTE => 1,
- BOOLEAN => 4,
- INT16 => 2,
- UINT16 => 2,
- INT32 => 4,
- UINT32 => 4,
- INT64 => 8,
- UINT64 => 8,
- STRUCT => 8,
- DICT_ENTRY => 8,
- DOUBLE => 8,
- ARRAY => 4,
- VARIANT => 1,
- OBJECT_PATH => 4,
- STRING => 4,
- SIGNATURE => 1,
- }[@sigtype]
+ TypeMapping[@sigtype].last
end
# Return a string representation of the type according to the
@@ -110,7 +79,7 @@ module Type
when DICT_ENTRY
"{" + @members.collect { |t| t.to_s }.join + "}"
else
- if not TypeName.keys.member?(@sigtype)
+ if not TypeMapping.keys.member?(@sigtype)
raise NotImplementedError
end
@sigtype.chr
@@ -120,7 +89,7 @@ module Type
# Add a new member type _a_.
def <<(a)
if not [STRUCT, ARRAY, DICT_ENTRY].member?(@sigtype)
- raise SignatureException
+ raise SignatureException
end
raise SignatureException if @sigtype == ARRAY and @members.size > 0
if @sigtype == DICT_ENTRY
@@ -142,7 +111,7 @@ module Type
end
def inspect
- s = TypeName[@sigtype]
+ s = TypeMapping[@sigtype].first
if [STRUCT, ARRAY].member?(@sigtype)
s += ": " + @members.inspect
end
@@ -173,7 +142,9 @@ module Type
case c
when ?a
res = Type.new(ARRAY)
- child = parse_one(nextchar)
+ c = nextchar
+ raise SignatureException, "Parse error in #{@signature}" if c == nil
+ child = parse_one(c)
res << child
when ?(
res = Type.new(STRUCT)
diff --git a/lib/dbus/xml.rb b/lib/dbus/xml.rb
new file mode 100644
index 0000000..17a9f16
--- /dev/null
+++ b/lib/dbus/xml.rb
@@ -0,0 +1,161 @@
+# dbus/xml.rb - introspection parser, rexml/nokogiri abstraction
+#
+# This file is part of the ruby-dbus project
+# Copyright (C) 2007 Arnaud Cornet and Paul van Tilburg
+# Copyright (C) 2012 Geoff Youngs
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License, version 2.1 as published by the Free Software Foundation.
+# See the file "COPYING" for the exact licensing terms.
+
+# TODO check if it is slow, make replaceable
+require 'rexml/document'
+begin
+require 'nokogiri'
+rescue LoadError
+end
+
+module DBus
+ # = D-Bus introspect XML parser class
+ #
+ # This class parses introspection XML of an object and constructs a tree
+ # of Node, Interface, Method, Signal instances.
+ class IntrospectXMLParser
+ class << self
+ attr_accessor :backend
+ end
+ # Creates a new parser for XML data in string _xml_.
+ def initialize(xml)
+ @xml = xml
+ end
+
+ class AbstractXML
+ def self.have_nokogiri?
+ Object.const_defined?('Nokogiri')
+ end
+ class Node
+ def initialize(node)
+ @node = node
+ end
+ # required methods
+ # returns node attribute value
+ def [](key)
+ end
+ # yields child nodes which match xpath of type AbstractXML::Node
+ def each(xpath)
+ end
+ end
+ # required methods
+ # initialize parser with xml string
+ def initialize(xml)
+ end
+ # yields nodes which match xpath of type AbstractXML::Node
+ def each(xpath)
+ end
+ end
+
+ class NokogiriParser < AbstractXML
+ class NokogiriNode < AbstractXML::Node
+ def [](key)
+ @node[key]
+ end
+ def each(path, &block)
+ @node.search(path).each { |node| block.call NokogiriNode.new(node) }
+ end
+ end
+ def initialize(xml)
+ @doc = Nokogiri.XML(xml)
+ end
+ def each(path, &block)
+ @doc.search("//#{path}").each { |node| block.call NokogiriNode.new(node) }
+ end
+ end
+
+ class REXMLParser < AbstractXML
+ class REXMLNode < AbstractXML::Node
+ def [](key)
+ @node.attributes[key]
+ end
+ def each(path, &block)
+ @node.elements.each(path) { |node| block.call REXMLNode.new(node) }
+ end
+ end
+ def initialize(xml)
+ @doc = REXML::Document.new(xml)
+ end
+ def each(path, &block)
+ @doc.elements.each(path) { |node| block.call REXMLNode.new(node) }
+ end
+ end
+
+ if AbstractXML.have_nokogiri?
+ @backend = NokogiriParser
+ else
+ @backend = REXMLParser
+ end
+
+ # return a pair: [list of Interfaces, list of direct subnode names]
+ def parse
+ # Using a Hash instead of a list helps merge split-up interfaces,
+ # a quirk observed in ModemManager (I#41).
+ interfaces = Hash.new do |hash, missing_key|
+ hash[missing_key] = Interface.new(missing_key)
+ end
+ subnodes = []
+ t = Time.now
+
+
+ d = IntrospectXMLParser.backend.new(@xml)
+ d.each("node/node") do |e|
+ subnodes << e["name"]
+ end
+ d.each("node/interface") do |e|
+ i = interfaces[e["name"]]
+ e.each("method") do |me|
+ m = Method.new(me["name"])
+ parse_methsig(me, m)
+ i << m
+ end
+ e.each("signal") do |se|
+ s = Signal.new(se["name"])
+ parse_methsig(se, s)
+ i << s
+ end
+ end
+ d = Time.now - t
+ if d > 2
+ DBus.logger.debug "Some XML took more that two secs to parse. Optimize me!"
+ end
+ [interfaces.values, subnodes]
+ end
+
+ ######################################################################
+ private
+
+ # Parses a method signature XML element _e_ and initialises
+ # method/signal _m_.
+ def parse_methsig(e, m)
+ e.each("arg") do |ae|
+ name = ae["name"]
+ dir = ae["direction"]
+ sig = ae["type"]
+ if m.is_a?(DBus::Signal)
+ # Direction can only be "out", ignore it
+ m.add_fparam(name, sig)
+ elsif m.is_a?(DBus::Method)
+ case dir
+ # This is a method, so dir defaults to "in"
+ when "in", nil
+ m.add_fparam(name, sig)
+ when "out"
+ m.add_return(name, sig)
+ end
+ else
+ raise NotImplementedError, dir
+ end
+ end
+ end
+ end # class IntrospectXMLParser
+end # module DBus
+
diff --git a/metadata.yml b/metadata.yml
index f2fe070..5952189 100644
--- a/metadata.yml
+++ b/metadata.yml
@@ -1,37 +1,64 @@
---- !ruby/object:Gem::Specification
+--- !ruby/object:Gem::Specification
name: ruby-dbus
-version: !ruby/object:Gem::Version
- hash: 7
- prerelease:
- segments:
- - 0
- - 7
- - 2
- version: 0.7.2
+version: !ruby/object:Gem::Version
+ version: 0.11.0
platform: ruby
-authors:
+authors:
- Ruby DBus Team
autorequire:
bindir: bin
cert_chain: []
-
-date: 2012-04-05 00:00:00 Z
-dependencies: []
-
-description:
+date: 2014-02-17 00:00:00.000000000 Z
+dependencies:
+- !ruby/object:Gem::Dependency
+ name: packaging_rake_tasks
+ requirement: !ruby/object:Gem::Requirement
+ requirements:
+ - - '>='
+ - !ruby/object:Gem::Version
+ version: '0'
+ type: :development
+ prerelease: false
+ version_requirements: !ruby/object:Gem::Requirement
+ requirements:
+ - - '>='
+ - !ruby/object:Gem::Version
+ version: '0'
+- !ruby/object:Gem::Dependency
+ name: rspec
+ requirement: !ruby/object:Gem::Requirement
+ requirements:
+ - - '>='
+ - !ruby/object:Gem::Version
+ version: '0'
+ type: :development
+ prerelease: false
+ version_requirements: !ruby/object:Gem::Requirement
+ requirements:
+ - - '>='
+ - !ruby/object:Gem::Version
+ version: '0'
+description: Pure Ruby module for interaction with D-Bus IPC system
email: ruby-dbus-devel at lists.luon.net
executables: []
-
extensions: []
-
-extra_rdoc_files:
+extra_rdoc_files: []
+files:
- COPYING
-- README
- NEWS
-files:
+- README.md
- Rakefile
- VERSION
-- doc/tutorial/index.markdown
+- doc/Reference.md
+- doc/Tutorial.md
+- doc/ex-calling-methods.body.rb
+- doc/ex-calling-methods.rb
+- doc/ex-properties.body.rb
+- doc/ex-properties.rb
+- doc/ex-setup.rb
+- doc/ex-signal.body.rb
+- doc/ex-signal.rb
+- doc/example-helper.rb
- examples/gdbus/gdbus
- examples/gdbus/gdbus.glade
- examples/gdbus/launch.sh
@@ -47,72 +74,72 @@ files:
- lib/dbus.rb
- lib/dbus/auth.rb
- lib/dbus/bus.rb
+- lib/dbus/core_ext/array/extract_options.rb
+- lib/dbus/core_ext/class/attribute.rb
+- lib/dbus/core_ext/kernel/singleton_class.rb
+- lib/dbus/core_ext/module/remove_method.rb
- lib/dbus/error.rb
- lib/dbus/export.rb
- lib/dbus/introspect.rb
+- lib/dbus/logger.rb
- lib/dbus/marshall.rb
- lib/dbus/matchrule.rb
- lib/dbus/message.rb
+- lib/dbus/message_queue.rb
+- lib/dbus/proxy_object.rb
+- lib/dbus/proxy_object_factory.rb
+- lib/dbus/proxy_object_interface.rb
- lib/dbus/type.rb
+- lib/dbus/xml.rb
- ruby-dbus.gemspec
-- test/async_test.rb
-- test/binding_test.rb
-- test/bus_driver_test.rb
-- test/bus_test.rb
-- test/dbus-launch-simple
-- test/dbus-limited-session.conf
-- test/property_test.rb
-- test/server_robustness_test.rb
-- test/server_test.rb
+- test/async_spec.rb
+- test/binding_spec.rb
+- test/bus_and_xml_backend_spec.rb
+- test/bus_driver_spec.rb
+- test/bus_spec.rb
+- test/byte_array_spec.rb
+- test/err_msg_spec.rb
+- test/introspect_xml_parser_spec.rb
+- test/introspection_spec.rb
+- test/main_loop_spec.rb
+- test/property_spec.rb
+- test/server_robustness_spec.rb
+- test/server_spec.rb
- test/service_newapi.rb
-- test/session_bus_test_manual.rb
-- test/signal_test.rb
-- test/t1
-- test/t2.rb
-- test/t3-ticket27.rb
-- test/t5-report-dbus-interface.rb
-- test/t6-loop.rb
-- test/test_env
-- test/test_server
-- test/thread_safety_test.rb
-- test/variant_test.rb
-- COPYING
-- README
-- NEWS
+- test/session_bus_spec_manual.rb
+- test/signal_spec.rb
+- test/spec_helper.rb
+- test/thread_safety_spec.rb
+- test/tools/dbus-launch-simple
+- test/tools/dbus-limited-session.conf
+- test/tools/test_env
+- test/tools/test_server
+- test/type_spec.rb
+- test/value_spec.rb
+- test/variant_spec.rb
homepage: https://trac.luon.net/ruby-dbus
-licenses:
+licenses:
- LGPL v2.1
+metadata: {}
post_install_message:
rdoc_options: []
-
-require_paths:
+require_paths:
- lib
-required_ruby_version: !ruby/object:Gem::Requirement
- none: false
- requirements:
- - - ">="
- - !ruby/object:Gem::Version
- hash: 57
- segments:
- - 1
- - 8
- - 7
- version: 1.8.7
-required_rubygems_version: !ruby/object:Gem::Requirement
- none: false
- requirements:
- - - ">="
- - !ruby/object:Gem::Version
- hash: 3
- segments:
- - 0
- version: "0"
+required_ruby_version: !ruby/object:Gem::Requirement
+ requirements:
+ - - '>='
+ - !ruby/object:Gem::Version
+ version: 1.9.3
+required_rubygems_version: !ruby/object:Gem::Requirement
+ requirements:
+ - - '>='
+ - !ruby/object:Gem::Version
+ version: '0'
requirements: []
-
rubyforge_project:
-rubygems_version: 1.8.15
+rubygems_version: 2.0.3
signing_key:
-specification_version: 3
+specification_version: 4
summary: Ruby module for interaction with D-Bus
test_files: []
-
+has_rdoc:
diff --git a/ruby-dbus.gemspec b/ruby-dbus.gemspec
index dc41f5f..d19afd4 100644
--- a/ruby-dbus.gemspec
+++ b/ruby-dbus.gemspec
@@ -6,15 +6,15 @@ GEMSPEC = Gem::Specification.new do |s|
s.name = "ruby-dbus"
# s.rubyforge_project = nil
s.summary = "Ruby module for interaction with D-Bus"
- # s.description = FIXME
+ s.description = "Pure Ruby module for interaction with D-Bus IPC system"
s.version = File.read("VERSION").strip
s.license = "LGPL v2.1"
s.author = "Ruby DBus Team"
s.email = "ruby-dbus-devel at lists.luon.net"
s.homepage = "https://trac.luon.net/ruby-dbus"
- s.files = FileList["{doc/tutorial,examples,lib,test}/**/*", "Rakefile", "ruby-dbus.gemspec", "VERSION"].to_a.sort
+ s.files = FileList["{doc,examples,lib,test}/**/*", "COPYING", "NEWS", "Rakefile", "README.md", "ruby-dbus.gemspec", "VERSION"].to_a.sort
s.require_path = "lib"
- s.has_rdoc = true
- s.extra_rdoc_files = ["COPYING", "README", "NEWS"]
- s.required_ruby_version = ">= 1.8.7"
+ s.required_ruby_version = ">= 1.9.3"
+ s.add_development_dependency("packaging_rake_tasks")
+ s.add_development_dependency("rspec")
end
diff --git a/test/async_test.rb b/test/async_spec.rb
similarity index 68%
rename from test/async_test.rb
rename to test/async_spec.rb
index 5582794..33b4241 100755
--- a/test/async_test.rb
+++ b/test/async_spec.rb
@@ -1,10 +1,10 @@
-#!/usr/bin/env ruby
+#!/usr/bin/env rspec
# Test the binding of dbus concepts to ruby concepts
-require "test/unit"
+require_relative "spec_helper"
require "dbus"
-class AsyncTest < Test::Unit::TestCase
- def setup
+describe "AsyncTest" do
+ before(:each) do
@bus = DBus::ASessionBus.new
@svc = @bus.service("org.ruby.service")
@obj = @svc.object "/org/ruby/MyInstance"
@@ -13,32 +13,32 @@ class AsyncTest < Test::Unit::TestCase
end
# https://github.com/mvidner/ruby-dbus/issues/13
- def test_async_call_to_default_interface
+ it "tests async_call_to_default_interface" do
loop = DBus::Main.new
loop << @bus
immediate_answer = @obj.the_answer do |msg, retval|
- assert_equal 42, retval
+ expect(retval).to eq(42)
loop.quit
end
- assert_nil immediate_answer
+ expect(immediate_answer).to be_nil
# wait for the async reply
loop.run
end
- def test_async_call_to_explicit_interface
+ it "tests async_call_to_explicit_interface" do
loop = DBus::Main.new
loop << @bus
ifc = @obj["org.ruby.AnotherInterface"]
immediate_answer = ifc.Reverse("abcd") do |msg, retval|
- assert_equal "dcba", retval
+ expect(retval).to eq("dcba")
loop.quit
end
- assert_nil immediate_answer
+ expect(immediate_answer).to be_nil
# wait for the async reply
loop.run
diff --git a/test/binding_spec.rb b/test/binding_spec.rb
new file mode 100755
index 0000000..da56ff5
--- /dev/null
+++ b/test/binding_spec.rb
@@ -0,0 +1,74 @@
+#!/usr/bin/env rspec
+# Test the binding of dbus concepts to ruby concepts
+require_relative "spec_helper"
+
+require "dbus"
+
+describe "BindingTest" do
+ before(:each) do
+ @bus = DBus::ASessionBus.new
+ @svc = @bus.service("org.ruby.service")
+ @base = @svc.object "/org/ruby/MyInstance"
+ @base.introspect
+ @base.default_iface = "org.ruby.SampleInterface"
+ end
+
+ # https://trac.luon.net/ruby-dbus/ticket/36#comment:3
+ it "tests class inheritance" do
+ derived = @svc.object "/org/ruby/MyDerivedInstance"
+ derived.introspect
+
+ # it should inherit from the parent
+ expect(derived["org.ruby.SampleInterface"]).not_to be_nil
+ end
+
+ # https://trac.luon.net/ruby-dbus/ticket/36
+ # Interfaces and methods/signals appeared on all classes
+ it "tests separation of classes" do
+ test2 = @svc.object "/org/ruby/MyInstance2"
+ test2.introspect
+
+ # it should have its own interface
+ expect(test2["org.ruby.Test2"]).not_to be_nil
+ # but not an interface of the Test class
+ expect(test2["org.ruby.SampleInterface"]).to be_nil
+
+ # and the parent should not get polluted by the child
+ expect(@base["org.ruby.Test2"]).to be_nil
+ end
+
+ it "tests translating errors into exceptions" do
+ # this is a generic call that will reply with the specified error
+ expect { @base.Error "org.example.Fail", "as you wish" }.to raise_error(DBus::Error) do |e|
+ expect(e.name).to eq("org.example.Fail")
+ expect(e.message).to match(/as you wish/)
+ end
+ end
+
+ it "tests generic dbus error" do
+ # this is a generic call that will reply with the specified error
+ expect { @base.will_raise_error_failed }.to raise_error(DBus::Error) do |e|
+ expect(e.name).to eq("org.freedesktop.DBus.Error.Failed")
+ expect(e.message).to match(/failed as designed/)
+ end
+ end
+
+ it "tests dynamic interface definition" do
+ # interfaces can be defined dynamicaly
+ derived = DBus::Object.new "/org/ruby/MyDerivedInstance"
+
+ #define a new interface
+ derived.singleton_class.instance_eval do
+ dbus_interface "org.ruby.DynamicInterface" do
+ dbus_method :hello2, "in name:s, in name2:s" do |name, name2|
+ puts "hello(#{name}, #{name2})"
+ end
+ end
+ end
+
+ # the object should have the new iface
+ ifaces = derived.intfs
+ expect(ifaces && ifaces.include?("org.ruby.DynamicInterface")).to be_true
+ end
+
+end
diff --git a/test/binding_test.rb b/test/binding_test.rb
deleted file mode 100755
index a9bce2e..0000000
--- a/test/binding_test.rb
+++ /dev/null
@@ -1,56 +0,0 @@
-#!/usr/bin/env ruby
-# Test the binding of dbus concepts to ruby concepts
-require "test/unit"
-require "dbus"
-
-class BindingTest < Test::Unit::TestCase
- def setup
- @bus = DBus::ASessionBus.new
- @svc = @bus.service("org.ruby.service")
- @base = @svc.object "/org/ruby/MyInstance"
- @base.introspect
- @base.default_iface = "org.ruby.SampleInterface"
- end
-
- # https://trac.luon.net/ruby-dbus/ticket/36#comment:3
- def test_class_inheritance
- derived = @svc.object "/org/ruby/MyDerivedInstance"
- derived.introspect
-
- # it should inherit from the parent
- assert_not_nil derived["org.ruby.SampleInterface"]
- end
-
- # https://trac.luon.net/ruby-dbus/ticket/36
- # Interfaces and methods/signals appeared on all classes
- def test_separation_of_classes
- test2 = @svc.object "/org/ruby/MyInstance2"
- test2.introspect
-
- # it should have its own interface
- assert_not_nil test2["org.ruby.Test2"]
- # but not an interface of the Test class
- assert_nil test2["org.ruby.SampleInterface"]
-
- # and the parent should not get polluted by the child
- assert_nil @base["org.ruby.Test2"]
- end
-
- def test_translating_errors_into_exceptions
- # this is a generic call that will reply with the specified error
- @base.Error "org.example.Fail", "as you wish"
- assert false, "should have raised"
- rescue DBus::Error => e
- assert_equal "org.example.Fail", e.name
- assert_equal "as you wish", e.message
- end
-
- def test_generic_dbus_error
- # this is a generic call that will reply with the specified error
- @base.will_raise_error_failed
- assert false, "should have raised"
- rescue DBus::Error => e
- assert_equal "org.freedesktop.DBus.Error.Failed", e.name
- assert_equal "failed as designed", e.message
- end
-end
diff --git a/test/bus_and_xml_backend_spec.rb b/test/bus_and_xml_backend_spec.rb
new file mode 100755
index 0000000..8eb325a
--- /dev/null
+++ b/test/bus_and_xml_backend_spec.rb
@@ -0,0 +1,39 @@
+#!/usr/bin/env rspec
+# Test the bus class
+require_relative "spec_helper"
+
+require 'rubygems'
+require 'nokogiri'
+require "dbus"
+
+describe "BusAndXmlBackendTest" do
+ before(:each) do
+ @bus = DBus::ASessionBus.new
+ end
+
+ it "tests introspection reading rexml" do
+ DBus::IntrospectXMLParser.backend = DBus::IntrospectXMLParser::REXMLParser
+ @svc = @bus.service("org.ruby.service")
+ obj = @svc.object("/org/ruby/MyInstance")
+ obj.default_iface = 'org.ruby.SampleInterface'
+ obj.introspect
+ # "should respond to :the_answer"
+ expect(obj.the_answer[0]).to eq(42)
+ # "should work with multiple interfaces"
+ expect(obj["org.ruby.AnotherInterface"].Reverse('foo')[0]).to eq("oof")
+ end
+
+ it "tests introspection reading nokogiri" do
+ # peek inside the object to see if a cleanup step worked or not
+ DBus::IntrospectXMLParser.backend = DBus::IntrospectXMLParser::NokogiriParser
+ @svc = @bus.service("org.ruby.service")
+ obj = @svc.object("/org/ruby/MyInstance")
+ obj.default_iface = 'org.ruby.SampleInterface'
+ obj.introspect
+ # "should respond to :the_answer"
+ expect(obj.the_answer[0]).to eq(42)
+ # "should work with multiple interfaces"
+ expect(obj["org.ruby.AnotherInterface"].Reverse('foo')[0]).to eq("oof")
+ end
+
+end
diff --git a/test/bus_driver_spec.rb b/test/bus_driver_spec.rb
new file mode 100755
index 0000000..62c8e4d
--- /dev/null
+++ b/test/bus_driver_spec.rb
@@ -0,0 +1,20 @@
+#!/usr/bin/env rspec
+require_relative "spec_helper"
+require "dbus"
+
+describe DBus::Service do
+ let(:bus) { DBus::ASessionBus.new }
+
+ describe "#exists?" do
+ it "is true for an existing service" do
+ svc = bus.service("org.ruby.service")
+ svc.object("/").introspect # must activate the service first :-/
+ expect(svc.exists?).to be_true
+ end
+
+ it "is false for a nonexisting service" do
+ svc = bus.service("org.ruby.nosuchservice")
+ expect(svc.exists?).to be_false
+ end
+ end
+end
diff --git a/test/bus_driver_test.rb b/test/bus_driver_test.rb
deleted file mode 100755
index 33e9a24..0000000
--- a/test/bus_driver_test.rb
+++ /dev/null
@@ -1,22 +0,0 @@
-#!/usr/bin/env ruby
-# Test the methods of the bus driver
-require "test/unit"
-require "dbus"
-
-def d(msg)
- puts msg if $DEBUG
-end
-
-class BusDriverTest < Test::Unit::TestCase
- def setup
- @bus = DBus::ASessionBus.new
- @svc = @bus.service("org.ruby.service")
- @svc.object("/").introspect
- end
-
- def test_exists
- assert @svc.exists?, "could not find the service"
- nonsvc = @bus.service "org.ruby.nosuchservice"
- assert ! nonsvc.exists?, "found a service that should not exist"
- end
-end
diff --git a/test/bus_test.rb b/test/bus_spec.rb
similarity index 57%
rename from test/bus_test.rb
rename to test/bus_spec.rb
index 90b92d6..b547105 100755
--- a/test/bus_test.rb
+++ b/test/bus_spec.rb
@@ -1,18 +1,20 @@
-#!/usr/bin/env ruby
+#!/usr/bin/env rspec
# Test the bus class
-require "test/unit"
+require_relative "spec_helper"
+
require "dbus"
-class BusTest < Test::Unit::TestCase
- def setup
+describe "BusTest" do
+ before(:each) do
@bus = DBus::ASessionBus.new
@svc = @bus.service("org.ruby.service")
@svc.object("/").introspect
end
- def test_introspection_not_leaking
+ it "tests introspection not leaking" do
# peek inside the object to see if a cleanup step worked or not
some_hash = @bus.instance_eval { @method_call_replies || Hash.new }
- assert_equal 0, some_hash.size, "there are leftover method handlers"
+ # fail: "there are leftover method handlers"
+ expect(some_hash.size).to eq(0)
end
end
diff --git a/test/byte_array_spec.rb b/test/byte_array_spec.rb
new file mode 100755
index 0000000..807c5ed
--- /dev/null
+++ b/test/byte_array_spec.rb
@@ -0,0 +1,38 @@
+#!/usr/bin/env rspec
+require_relative "spec_helper"
+
+require "dbus"
+
+describe "ByteArrayTest" do
+ before(:each) do
+ @bus = DBus::ASessionBus.new
+ @svc = @bus.service("org.ruby.service")
+ @obj = @svc.object("/org/ruby/MyInstance")
+ @obj.introspect
+ @obj.default_iface = "org.ruby.SampleInterface"
+ end
+
+
+ it "tests passing byte array" do
+ data = [0, 77, 255]
+ result = @obj.mirror_byte_array(data).first
+ expect(result).to eq(data)
+ end
+
+ it "tests passing byte array from string" do
+ data = "AAA"
+ result = @obj.mirror_byte_array(data).first
+ expect(result).to eq([65, 65, 65])
+ end
+
+ it "tests passing byte array from hash" do
+ # Hash is an Enumerable, but is caught earlier
+ data = { "this will" => "fail" }
+ expect { @obj.mirror_byte_array(data).first }.to raise_error(DBus::TypeException)
+ end
+
+ it "tests passing byte array from nonenumerable" do
+ data = Time.now
+ expect { @obj.mirror_byte_array(data).first }.to raise_error(DBus::TypeException)
+ end
+end
diff --git a/test/err_msg_spec.rb b/test/err_msg_spec.rb
new file mode 100755
index 0000000..d40fb4d
--- /dev/null
+++ b/test/err_msg_spec.rb
@@ -0,0 +1,42 @@
+#!/usr/bin/env rspec
+# should report it missing on org.ruby.SampleInterface
+# (on object...) instead of on DBus::Proxy::ObjectInterface
+require_relative "spec_helper"
+require "dbus"
+
+describe "ErrMsgTest" do
+ before(:each) do
+ session_bus = DBus::ASessionBus.new
+ svc = session_bus.service("org.ruby.service")
+ @obj = svc.object("/org/ruby/MyInstance")
+ @obj.introspect # necessary
+ @obj.default_iface = "org.ruby.SampleInterface"
+ end
+
+ it "tests report dbus interface" do
+ # a specific exception...
+ # mentioning DBus and the interface
+ expect { @obj.NoSuchMethod }.to raise_error(NameError, /DBus interface.*#{@obj.default_iface}/)
+ end
+
+ it "tests report short struct" do
+ expect { @obj.test_variant ["(ss)", ["too few"] ] }.to raise_error(DBus::TypeException, /1 elements but type info for 2/)
+ end
+
+ it "tests report long struct" do
+ expect { @obj.test_variant ["(ss)", ["a", "b", "too many"] ] }.to raise_error(DBus::TypeException, /3 elements but type info for 2/)
+ end
+
+ it "tests report nil" do
+ nils = [
+ ["(s)", [nil] ], # would get disconnected
+ ["i", nil ],
+ ["a{ss}", {"foo" => nil} ],
+ ]
+ nils.each do |has_nil|
+ # TODO want backtrace from the perspective of the caller:
+ # rescue/reraise in send_sync?
+ expect { @obj.test_variant has_nil }.to raise_error(DBus::TypeException, /Cannot send nil/)
+ end
+ end
+end
diff --git a/test/introspect_xml_parser_spec.rb b/test/introspect_xml_parser_spec.rb
new file mode 100755
index 0000000..225f931
--- /dev/null
+++ b/test/introspect_xml_parser_spec.rb
@@ -0,0 +1,26 @@
+#!/usr/bin/env rspec
+require_relative "spec_helper"
+require "dbus"
+
+describe "IntrospectXMLParserTest" do
+ it "tests split interfaces" do
+ xml = <<EOS
+<node>
+ <interface name="org.example.Foo">
+ <method name="Dwim"/>
+ </interface>
+ <interface name="org.example.Bar">
+ <method name="Drink"/>
+ </interface>
+ <interface name="org.example.Foo">
+ <method name="Smurf"/>
+ </interface>
+</node>
+EOS
+
+ interfaces, _ = DBus::IntrospectXMLParser.new(xml).parse
+
+ foo = interfaces.find {|i| i.name == "org.example.Foo" }
+ expect(foo.methods.keys.size).to eq(2)
+ end
+end
diff --git a/test/introspection_spec.rb b/test/introspection_spec.rb
new file mode 100755
index 0000000..61e537b
--- /dev/null
+++ b/test/introspection_spec.rb
@@ -0,0 +1,32 @@
+#!/usr/bin/env rspec
+require_relative "spec_helper"
+require "dbus"
+
+describe "IntrospectionTest" do
+ before(:each) do
+ session_bus = DBus::ASessionBus.new
+ svc = session_bus.service("org.ruby.service")
+ @obj = svc.object("/org/ruby/MyInstance")
+ @obj.introspect
+ @obj.default_iface = "org.ruby.SampleInterface"
+ end
+
+ it "tests wrong number of arguments" do
+ expect { @obj.test_variant "too","many","args" }.to raise_error(ArgumentError)
+ # not enough
+ expect { @obj.test_variant }.to raise_error(ArgumentError)
+ end
+
+ it "tests shortcut methods" do
+ @obj.default_iface = nil
+ expect(@obj.bounce_variant("varargs")).to eq(["varargs"])
+ # test for a duplicated method name
+ expect { @obj.the_answer }.to raise_error(NoMethodError)
+ # ensure istance methods of ProxyObject aren't overwritten by remote
+ # methods
+ expect { @obj.interfaces }.not_to raise_error
+
+ @obj.default_iface = "org.ruby.SampleInterface"
+ expect(@obj.the_answer).to eq([42])
+ end
+end
diff --git a/test/t6-loop.rb b/test/main_loop_spec.rb
similarity index 81%
rename from test/t6-loop.rb
rename to test/main_loop_spec.rb
index d2a412f..ff702d5 100755
--- a/test/t6-loop.rb
+++ b/test/main_loop_spec.rb
@@ -1,14 +1,10 @@
-#!/usr/bin/env ruby
+#!/usr/bin/env rspec
# Test the main loop
-require "test/unit"
+require_relative "spec_helper"
require "dbus"
-def d(msg)
- puts "#{$$} #{msg}" if $DEBUG
-end
-
-class MainLoopTest < Test::Unit::TestCase
- def setup
+describe "MainLoopTest" do
+ before(:each) do
@session_bus = DBus::ASessionBus.new
svc = @session_bus.service("org.ruby.service")
@obj = svc.object("/org/ruby/MyInstance")
@@ -26,7 +22,7 @@ class MainLoopTest < Test::Unit::TestCase
class << @session_bus
alias :wait_for_message_orig :wait_for_message
def wait_for_message_lazy
- d "I am so lazy"
+ DBus.logger.debug "I am so lazy"
sleep 1 # Give the server+bus a chance to join the messages
wait_for_message_orig
end
@@ -43,9 +39,9 @@ class MainLoopTest < Test::Unit::TestCase
end
end
- def test_loop_quit(delay = 1)
+ def test_loop_quit(delay)
@obj.on_signal "LongTaskEnd" do
- d "Telling loop to quit"
+ DBus.logger.debug "Telling loop to quit"
@loop.quit
end
@@ -57,9 +53,9 @@ class MainLoopTest < Test::Unit::TestCase
# this thread will make the test fail if @loop.run does not return
dynamite = Thread.new do
- d "Dynamite burning"
+ DBus.logger.debug "Dynamite burning"
sleep 2
- d "Dynamite explodes"
+ DBus.logger.debug "Dynamite explodes"
# We need to raise in the main thread.
# Simply raising here means the exception is ignored
# (until dynamite.join which we don't call) or
@@ -68,15 +64,19 @@ class MainLoopTest < Test::Unit::TestCase
end
@loop.run
- d "Defusing dynamite"
+ DBus.logger.debug "Defusing dynamite"
# if we get here, defuse the bomb
dynamite.exit
# remove signal handler
@obj.on_signal "LongTaskEnd"
end
+ it "tests loop quit" do
+ test_loop_quit 1
+ end
+
# https://bugzilla.novell.com/show_bug.cgi?id=537401
- def test_loop_drained_socket
+ it "tests loop drained socket" do
test_loop_quit 0
end
end
diff --git a/test/property_spec.rb b/test/property_spec.rb
new file mode 100755
index 0000000..27a15b9
--- /dev/null
+++ b/test/property_spec.rb
@@ -0,0 +1,53 @@
+#!/usr/bin/env rspec
+require_relative "spec_helper"
+require "dbus"
+
+describe "PropertyTest" do
+ before(:each) do
+ session_bus = DBus::ASessionBus.new
+ svc = session_bus.service("org.ruby.service")
+ @obj = svc.object("/org/ruby/MyInstance")
+ @obj.introspect
+ @iface = @obj["org.ruby.SampleInterface"]
+ end
+
+ it "tests property reading" do
+ expect(@iface["ReadMe"]).to eq("READ ME")
+ end
+
+ it "tests property nonreading" do
+ expect { @iface["WriteMe"] }.to raise_error(DBus::Error, /not readable/)
+ end
+
+ it "tests property writing" do
+ @iface["ReadOrWriteMe"] = "VALUE"
+ expect(@iface["ReadOrWriteMe"]).to eq("VALUE")
+ end
+
+ # https://github.com/mvidner/ruby-dbus/pull/19
+ it "tests service select timeout" do
+ @iface["ReadOrWriteMe"] = "VALUE"
+ expect(@iface["ReadOrWriteMe"]).to eq("VALUE")
+ # wait for the service to become idle
+ sleep 6
+ # fail: "Property value changed; perhaps the service died and got restarted"
+ expect(@iface["ReadOrWriteMe"]).to eq("VALUE")
+ end
+
+ it "tests property nonwriting" do
+ expect { @iface["ReadMe"] = "WROTE" }.to raise_error(DBus::Error, /not writable/)
+ end
+
+ it "tests get all" do
+ all = @iface.all_properties
+ expect(all.keys.sort).to eq(["ReadMe", "ReadOrWriteMe"])
+ end
+
+ it "tests unknown property reading" do
+ expect { @iface["Spoon"] }.to raise_error(DBus::Error, /not found/)
+ end
+
+ it "tests unknown property writing" do
+ expect { @iface["Spoon"] = "FPRK" }.to raise_error(DBus::Error, /not found/)
+ end
+end
diff --git a/test/property_test.rb b/test/property_test.rb
deleted file mode 100755
index 5f13e8c..0000000
--- a/test/property_test.rb
+++ /dev/null
@@ -1,64 +0,0 @@
-#!/usr/bin/env ruby
-require "test/unit"
-require "dbus"
-
-class PropertyTest < Test::Unit::TestCase
- def setup
- session_bus = DBus::ASessionBus.new
- svc = session_bus.service("org.ruby.service")
- @obj = svc.object("/org/ruby/MyInstance")
- @obj.introspect
- @iface = @obj["org.ruby.SampleInterface"]
- end
-
- def test_property_reading
- assert_equal "READ ME", @iface["ReadMe"]
- end
-
- def test_property_nonreading
- e = assert_raises DBus::Error do
- @iface["WriteMe"]
- end
- assert_match(/not readable/, e.to_s)
- end
-
- def test_property_writing
- @iface["ReadOrWriteMe"] = "VALUE"
- assert_equal "VALUE", @iface["ReadOrWriteMe"]
- end
-
- # https://github.com/mvidner/ruby-dbus/pull/19
- def test_service_select_timeout
- @iface["ReadOrWriteMe"] = "VALUE"
- assert_equal "VALUE", @iface["ReadOrWriteMe"]
- # wait for the service to become idle
- sleep 6
- assert_equal "VALUE", @iface["ReadOrWriteMe"], "Property value changed; perhaps the service died and got restarted"
- end
-
- def test_property_nonwriting
- e = assert_raises DBus::Error do
- @iface["ReadMe"] = "WROTE"
- end
- assert_match(/not writable/, e.to_s)
- end
-
- def test_get_all
- all = @iface.all_properties
- assert_equal ["ReadMe", "ReadOrWriteMe"], all.keys.sort
- end
-
- def test_unknown_property_reading
- e = assert_raises DBus::Error do
- @iface["Spoon"]
- end
- assert_match(/not found/, e.to_s)
- end
-
- def test_unknown_property_writing
- e = assert_raises DBus::Error do
- @iface["Spoon"] = "FORK"
- end
- assert_match(/not found/, e.to_s)
- end
-end
diff --git a/test/server_robustness_spec.rb b/test/server_robustness_spec.rb
new file mode 100755
index 0000000..054c262
--- /dev/null
+++ b/test/server_robustness_spec.rb
@@ -0,0 +1,66 @@
+#!/usr/bin/env rspec
+# Test that a server survives various error cases
+require_relative "spec_helper"
+require "dbus"
+
+describe "ServerRobustnessTest" do
+ before(:each) do
+ @bus = DBus::ASessionBus.new
+ @svc = @bus.service("org.ruby.service")
+ end
+
+ # https://trac.luon.net/ruby-dbus/ticket/31
+ # the server should not crash
+ it "tests no such path with introspection" do
+ obj = @svc.object "/org/ruby/NotMyInstance"
+ expect { obj.introspect }.to raise_error(DBus::Error) do |e|
+ expect(e).to_not match(/timeout/)
+ end
+ end
+
+ it "tests no such path without introspection" do
+ obj = @svc.object "/org/ruby/NotMyInstance"
+ ifc = DBus::ProxyObjectInterface.new(obj, "org.ruby.SampleInterface")
+ ifc.define_method("the_answer", "out n:i")
+ expect { ifc.the_answer }.to raise_error(DBus::Error) do |e|
+ expect(e).to_not match(/timeout/)
+ end
+ end
+
+ it "tests a method that raises" do
+ obj = @svc.object "/org/ruby/MyInstance"
+ obj.introspect
+ obj.default_iface = "org.ruby.SampleInterface"
+ expect { obj.will_raise }.to raise_error(DBus::Error) do |e|
+ expect(e).to_not match(/timeout/)
+ end
+ end
+
+ it "tests a method that raises name error" do
+ obj = @svc.object "/org/ruby/MyInstance"
+ obj.introspect
+ obj.default_iface = "org.ruby.SampleInterface"
+ expect { obj.will_raise_name_error }.to raise_error(DBus::Error) do |e|
+ expect(e).to_not match(/timeout/)
+ end
+ end
+
+ # https://trac.luon.net/ruby-dbus/ticket/31#comment:3
+ it "tests no such method without introspection" do
+ obj = @svc.object "/org/ruby/MyInstance"
+ ifc = DBus::ProxyObjectInterface.new(obj, "org.ruby.SampleInterface")
+ ifc.define_method("not_the_answer", "out n:i")
+ expect { ifc.not_the_answer }.to raise_error(DBus::Error) do |e|
+ expect(e).to_not match(/timeout/)
+ end
+ end
+
+ it "tests no such interface without introspection" do
+ obj = @svc.object "/org/ruby/MyInstance"
+ ifc = DBus::ProxyObjectInterface.new(obj, "org.ruby.NoSuchInterface")
+ ifc.define_method("the_answer", "out n:i")
+ expect { ifc.the_answer }.to raise_error(DBus::Error) do |e|
+ expect(e).to_not match(/timeout/)
+ end
+ end
+end
diff --git a/test/server_robustness_test.rb b/test/server_robustness_test.rb
deleted file mode 100755
index 0dffa1e..0000000
--- a/test/server_robustness_test.rb
+++ /dev/null
@@ -1,72 +0,0 @@
-#!/usr/bin/env ruby
-# Test that a server survives various error cases
-require "test/unit"
-require "dbus"
-
-class ServerRobustnessTest < Test::Unit::TestCase
- def setup
- @bus = DBus::ASessionBus.new
- @svc = @bus.service("org.ruby.service")
- end
-
- # https://trac.luon.net/ruby-dbus/ticket/31
- # the server should not crash
- def test_no_such_path_with_introspection
- obj = @svc.object "/org/ruby/NotMyInstance"
- obj.introspect
- assert false, "should have raised"
- rescue DBus::Error => e
- assert_no_match(/timeout/, e.to_s)
- end
-
- def test_no_such_path_without_introspection
- obj = @svc.object "/org/ruby/NotMyInstance"
- ifc = DBus::ProxyObjectInterface.new(obj, "org.ruby.SampleInterface")
- ifc.define_method("the_answer", "out n:i")
- ifc.the_answer
- assert false, "should have raised"
- rescue DBus::Error => e
- assert_no_match(/timeout/, e.to_s)
- end
-
- def test_a_method_that_raises
- obj = @svc.object "/org/ruby/MyInstance"
- obj.introspect
- obj.default_iface = "org.ruby.SampleInterface"
- obj.will_raise
- assert false, "should have raised"
- rescue DBus::Error => e
- assert_no_match(/timeout/, e.to_s)
- end
-
- def test_a_method_that_raises_name_error
- obj = @svc.object "/org/ruby/MyInstance"
- obj.introspect
- obj.default_iface = "org.ruby.SampleInterface"
- obj.will_raise_name_error
- assert false, "should have raised"
- rescue DBus::Error => e
- assert_no_match(/timeout/, e.to_s)
- end
-
- # https://trac.luon.net/ruby-dbus/ticket/31#comment:3
- def test_no_such_method_without_introspection
- obj = @svc.object "/org/ruby/MyInstance"
- ifc = DBus::ProxyObjectInterface.new(obj, "org.ruby.SampleInterface")
- ifc.define_method("not_the_answer", "out n:i")
- ifc.not_the_answer
- assert false, "should have raised"
- rescue DBus::Error => e
- assert_no_match(/timeout/, e.to_s)
- end
-
- def test_no_such_interface_without_introspection
- obj = @svc.object "/org/ruby/MyInstance"
- ifc = DBus::ProxyObjectInterface.new(obj, "org.ruby.NoSuchInterface")
- ifc.define_method("the_answer", "out n:i")
- ifc.the_answer
- assert false, "should have raised"
- rescue DBus::Error => e
- assert_no_match(/timeout/, e.to_s)
- end
-end
diff --git a/test/server_test.rb b/test/server_spec.rb
similarity index 72%
rename from test/server_test.rb
rename to test/server_spec.rb
index 0e3f631..671de1a 100755
--- a/test/server_test.rb
+++ b/test/server_spec.rb
@@ -1,6 +1,6 @@
-#!/usr/bin/env ruby
+#!/usr/bin/env rspec
# Test that a server survives various error cases
-require "test/unit"
+require_relative "spec_helper"
require "dbus"
class Foo < DBus::Object
@@ -10,7 +10,7 @@ class Foo < DBus::Object
end
dbus_signal :signal_without_interface
-rescue DBus::Object::UndefinedInterface => e
+rescue DBus::Object::UndefinedInterface
# raised by the preceding signal declaration
end
@@ -23,28 +23,28 @@ rescue DBus::InvalidMethodName
# raised by the preceding signal declaration
end
-class ServerTest < Test::Unit::TestCase
- def setup
+describe "ServerTest" do
+ before(:each) do
@bus = DBus::ASessionBus.new
@svc = @bus.request_service "org.ruby.server-test"
end
- def teardown
+ after(:each) do
@bus.proxy.ReleaseName "org.ruby.server-test"
end
- def test_unexporting_an_object
+ it "tests unexporting an object" do
obj = Foo.new "/org/ruby/Foo"
@svc.export obj
- assert @svc.unexport(obj)
+ expect(@svc.unexport(obj)).to be_true
end
- def test_unexporting_an_object_not_exported
+ it "tests unexporting an object not exported" do
obj = Foo.new "/org/ruby/Foo"
- assert !@svc.unexport(obj)
+ expect(@svc.unexport(obj)).to be_false
end
- def test_emiting_signals
+ it "tests emiting signals" do
obj = Foo.new "/org/ruby/Foo"
@svc.export obj
obj.signal_without_arguments
diff --git a/test/service_newapi.rb b/test/service_newapi.rb
index bc1d069..85e10ac 100755
--- a/test/service_newapi.rb
+++ b/test/service_newapi.rb
@@ -1,15 +1,13 @@
#!/usr/bin/env ruby
# -*- coding: utf-8 -*-
+require_relative "spec_helper"
+SimpleCov.command_name "Service Tests" if Object.const_defined? "SimpleCov"
# find the library without external help
$:.unshift File.expand_path("../../lib", __FILE__)
require 'dbus'
-def d(msg)
- puts "#{$$} #{msg}" if $DEBUG
-end
-
PROPERTY_INTERFACE = "org.freedesktop.DBus.Properties"
class Test < DBus::Object
@@ -27,7 +25,7 @@ class Test < DBus::Object
end
dbus_method :test_variant, "in stuff:v" do |variant|
- p variant
+ DBus.logger.debug variant.inspect
end
dbus_method :bounce_variant, "in stuff:v, out chaff:v" do |variant|
@@ -57,6 +55,10 @@ class Test < DBus::Object
dbus_method :Error, "in name:s, in description:s" do |name, description|
raise DBus.error(name), description
end
+
+ dbus_method :mirror_byte_array, "in bytes:ay, out mirrored:ay" do |bytes|
+ [bytes]
+ end
end
# closing and reopening the same interface
@@ -84,21 +86,32 @@ class Test < DBus::Object
end
end
+ dbus_interface "org.ruby.Duplicates" do
+ dbus_method :the_answer, "out answer:i" do
+ [0]
+ end
+ dbus_method :interfaces, "out answer:i" do
+ raise "This DBus method is currently shadowed by ProxyObject#interfaces"
+ end
+ end
+
dbus_interface "org.ruby.Loop" do
# starts doing something long, but returns immediately
# and sends a signal when done
dbus_method :LongTaskBegin, 'in delay:i' do |delay|
# FIXME did not complain about mismatch between signature and block args
- d "Long task began"
+ self.LongTaskStart
+ DBus.logger.debug "Long task began"
task = Thread.new do
- d "Long task thread started (#{delay}s)"
+ DBus.logger.debug "Long task thread started (#{delay}s)"
sleep delay
- d "Long task will signal end"
+ DBus.logger.debug "Long task will signal end"
self.LongTaskEnd
end
task.abort_on_exception = true # protect from test case bugs
end
+ dbus_signal :LongTaskStart
dbus_signal :LongTaskEnd
end
@@ -176,7 +189,7 @@ service.export(myobj)
derived = Derived.new "/org/ruby/MyDerivedInstance"
service.export derived
test2 = Test2.new "/org/ruby/MyInstance2"
-service.export test2
+service.export test2
# introspect every other connection, Ticket #34
# (except the one that activates us - it has already emitted
@@ -186,7 +199,7 @@ mr = DBus::MatchRule.new.from_s "type='signal',interface='org.freedesktop.DBus',
bus.add_match(mr) do |msg|
new_unique_name = msg.params[2]
unless new_unique_name.empty?
- d "RRRING #{new_unique_name}"
+ DBus.logger.debug "RRRING #{new_unique_name}"
bus.introspect_data(new_unique_name, "/") do
# ignore the result
end
diff --git a/test/session_bus_spec_manual.rb b/test/session_bus_spec_manual.rb
new file mode 100755
index 0000000..70a35b5
--- /dev/null
+++ b/test/session_bus_spec_manual.rb
@@ -0,0 +1,15 @@
+#!/usr/bin/env rspec
+require_relative "spec_helper"
+require "dbus"
+
+describe DBus::ASessionBus do
+ context "when DBUS_SESSION_BUS_ADDRESS is unset in ENV (Issue#4)" do
+ ENV.delete "DBUS_SESSION_BUS_ADDRESS"
+
+ it "can connect" do
+ bus = DBus::ASessionBus.new
+ svc = bus.service("org.freedesktop.DBus")
+ expect(svc.exists?).to be_true
+ end
+ end
+end
diff --git a/test/session_bus_test_manual.rb b/test/session_bus_test_manual.rb
deleted file mode 100755
index b1495eb..0000000
--- a/test/session_bus_test_manual.rb
+++ /dev/null
@@ -1,20 +0,0 @@
-#!/usr/bin/env ruby
-require "test/unit"
-require "dbus"
-
-def d(msg)
- puts msg if $DEBUG
-end
-
-class SessionBusAddressTest < Test::Unit::TestCase
- def setup
- # test getting the session bus address even if unset in ENV (Issue#4)
- ENV.delete "DBUS_SESSION_BUS_ADDRESS"
- @bus = DBus::ASessionBus.new
- @svc = @bus.service("org.freedesktop.DBus")
- end
-
- def test_connection
- assert @svc.exists?
- end
-end
diff --git a/test/signal_spec.rb b/test/signal_spec.rb
new file mode 100755
index 0000000..e8be3bd
--- /dev/null
+++ b/test/signal_spec.rb
@@ -0,0 +1,90 @@
+#!/usr/bin/env rspec
+# Test the signal handlers
+require_relative "spec_helper"
+require "dbus"
+
+describe "SignalHandlerTest" do
+ before(:each) do
+ @session_bus = DBus::ASessionBus.new
+ svc = @session_bus.service("org.ruby.service")
+ @obj = svc.object("/org/ruby/MyInstance")
+ @obj.introspect # necessary
+ @obj.default_iface = "org.ruby.Loop"
+ @intf = @obj["org.ruby.Loop"]
+
+ @loop = DBus::Main.new
+ @loop << @session_bus
+ end
+
+ # testing for commit 017c83 (kkaempf)
+ it "tests overriding a handler" do
+ DBus.logger.debug "Inside test_overriding_a_handler"
+ counter = 0
+
+ @obj.on_signal "LongTaskEnd" do
+ DBus.logger.debug "+10"
+ counter += 10
+ end
+ @obj.on_signal "LongTaskEnd" do
+ DBus.logger.debug "+1"
+ counter += 1
+ end
+
+ DBus.logger.debug "will begin"
+ @obj.LongTaskBegin 3
+
+ quitter = Thread.new do
+ DBus.logger.debug "sleep before quit"
+ # FIXME if we sleep for too long
+ # the socket will be drained and we deadlock in a select.
+ # It could be worked around by sending ourselves a Unix signal
+ # (with a dummy handler) to interrupt the select
+ sleep 1
+ DBus.logger.debug "will quit"
+ @loop.quit
+ end
+ @loop.run
+ quitter.join
+
+ expect(counter).to eq(1)
+ end
+
+ it "tests on signal overload" do
+ DBus.logger.debug "Inside test_on_signal_overload"
+ counter = 0
+ started = false
+ @intf.on_signal "LongTaskStart" do
+ started = true
+ end
+ # Same as intf.on_signal("LongTaskEnd"), just the old way
+ @intf.on_signal @obj.bus, "LongTaskEnd" do
+ counter += 1
+ end
+ @obj.LongTaskBegin 3
+ quitter = Thread.new do
+ DBus.logger.debug "sleep before quit"
+ sleep 1
+ DBus.logger.debug "will quit"
+ @loop.quit
+ end
+ @loop.run
+ quitter.join
+
+ expect(started).to eq(true)
+ expect(counter).to eq(1)
+ expect { @intf.on_signal }.to raise_error(ArgumentError) # not enough
+ expect { @intf.on_signal 'to', 'many', 'yarrrrr!' }.to raise_error(ArgumentError)
+ end
+
+ it "tests too many rules" do
+ 100.times do
+ @obj.on_signal "Whichever" do
+ puts "not called"
+ end
+ end
+ end
+
+ it "tests removing a nonexistent rule" do
+ @obj.on_signal "DoesNotExist"
+ end
+end
diff --git a/test/signal_test.rb b/test/signal_test.rb
deleted file mode 100755
index 3f423ea..0000000
--- a/test/signal_test.rb
+++ /dev/null
@@ -1,64 +0,0 @@
-#!/usr/bin/env ruby
-# Test the signal handlers
-require "test/unit"
-require "dbus"
-
-def d(msg)
- puts "#{$$} #{msg}" if $DEBUG
-end
-
-class SignalHandlerTest < Test::Unit::TestCase
- def setup
- @session_bus = DBus::ASessionBus.new
- svc = @session_bus.service("org.ruby.service")
- @obj = svc.object("/org/ruby/MyInstance")
- @obj.introspect # necessary
- @obj.default_iface = "org.ruby.Loop"
-
- @loop = DBus::Main.new
- @loop << @session_bus
- end
-
- # testing for commit 017c83 (kkaempf)
- def test_overriding_a_handler
- counter = 0
-
- @obj.on_signal "LongTaskEnd" do
- d "+10"
- counter += 10
- end
- @obj.on_signal "LongTaskEnd" do
- d "+1"
- counter += 1
- end
-
- d "will begin"
- @obj.LongTaskBegin 3
-
- quitter = Thread.new do
- d "sleep before quit"
- # FIXME if we sleep for too long
- # the socket will be drained and we deadlock in a select.
- # It could be worked around by sending ourselves a Unix signal
- # (with a dummy handler) to interrupt the select
- sleep 1
- d "will quit"
- @loop.quit
- end
- @loop.run
-
- assert_equal 1, counter
- end
-
- def test_too_many_rules
- 100.times do
- @obj.on_signal "Whichever" do
- puts "not called"
- end
- end
- end
-
- def test_removing_a_nonexistent_rule
- @obj.on_signal "DoesNotExist"
- end
-end
diff --git a/test/spec_helper.rb b/test/spec_helper.rb
new file mode 100644
index 0000000..d750296
--- /dev/null
+++ b/test/spec_helper.rb
@@ -0,0 +1,33 @@
+if ENV["COVERAGE"]
+ coverage = ENV["COVERAGE"] == "true"
+else
+ # heuristics: enable for interactive builds (but not in OBS) or in Travis
+ coverage = !!ENV["DISPLAY"] || ENV["TRAVIS"]
+end
+
+if coverage
+ require "simplecov"
+ SimpleCov.root File.expand_path("../..", __FILE__)
+
+ # do not cover specs
+ SimpleCov.add_filter "_spec.rb"
+ # do not cover the activesupport helpers
+ SimpleCov.add_filter "/core_ext/"
+
+ # use coveralls for on-line code coverage reporting at Travis CI
+ if ENV["TRAVIS"]
+ require "coveralls"
+ end
+ SimpleCov.start
+end
+
+$:.unshift File.expand_path("../../lib", __FILE__)
+
+if Object.const_defined? "RSpec"
+ # http://betterspecs.org/#expect
+ RSpec.configure do |config|
+ config.expect_with :rspec do |c|
+ c.syntax = :expect
+ end
+ end
+end
diff --git a/test/t1 b/test/t1
deleted file mode 100755
index 42ce5e9..0000000
--- a/test/t1
+++ /dev/null
@@ -1,4 +0,0 @@
-#! /bin/sh
-SEND0="dbus-send --session --dest=org.ruby.service"
-CALL="$SEND0 --type=method_call --print-reply"
-$CALL /org/ruby/MyInstance org.ruby.AnotherInterface.Reverse string:Hello
diff --git a/test/t2.rb b/test/t2.rb
deleted file mode 100755
index 18eb429..0000000
--- a/test/t2.rb
+++ /dev/null
@@ -1,72 +0,0 @@
-#!/usr/bin/env ruby
-# -*- coding: utf-8 -*-
-require "test/unit"
-require "dbus"
-
-class ValueTest < Test::Unit::TestCase
- def setup
- session_bus = DBus::ASessionBus.new
- svc = session_bus.service("org.ruby.service")
- @obj = svc.object("/org/ruby/MyInstance")
- @obj.introspect # necessary
- @obj.default_iface = "org.ruby.SampleInterface"
- end
-
- def test_passing_an_array_through_a_variant
- # old explicit typing
- @obj.test_variant(["as", ["coucou", "kuku"]])
- # automatic typing
- @obj.test_variant(["coucou", "kuku"])
- @obj.test_variant(["saint", "was that a word or a signature?"])
- end
-
- def test_bouncing_a_variant
- assert_equal "cuckoo", @obj.bounce_variant("cuckoo")[0]
- assert_equal ["coucou", "kuku"], @obj.bounce_variant(["coucou", "kuku"])[0]
- assert_equal [], @obj.bounce_variant([])[0]
- empty_hash = {}
- assert_equal empty_hash, @obj.bounce_variant(empty_hash)[0]
- end
-
- # these are ambiguous
- def test_pairs_with_a_string
-
- # deprecated
- assert_equal "foo", @obj.bounce_variant(["s", "foo"])[0]
-
- assert_equal "foo", @obj.bounce_variant(DBus.variant("s", "foo"))[0]
- assert_equal "foo", @obj.bounce_variant([DBus.type("s"), "foo"])[0]
-
- # does not work, because the server side forgets the explicit typing
-# assert_equal ["s", "foo"], @obj.bounce_variant(["av", ["s", "foo"]])[0]
-# assert_equal ["s", "foo"], @obj.bounce_variant(["as", ["s", "foo"]])[0]
-
- # instead, use this to demonstrate that the variant is passed as expected
- assert_equal 4, @obj.variant_size(["s", "four"])[0]
- # "av" is the simplest thing that will work,
- # shifting the heuristic from a pair to the individual items
- assert_equal 2, @obj.variant_size(["av", ["s", "four"]])[0]
- end
-
- def test_marshalling_an_array_of_variants
- # https://trac.luon.net/ruby-dbus/ticket/30
- @obj.default_iface = "org.ruby.Ticket30"
- choices = []
- choices << ['s', 'Plan A']
- choices << ['s', 'Plan B']
- # old explicit typing
- assert_equal "Do Plan A", @obj.Sybilla(choices)[0]
- # automatic typing
- assert_equal "Do Plan A", @obj.Sybilla(["Plan A", "Plan B"])[0]
- end
-
- def test_service_returning_nonarray
- # "warning: default `to_a' will be obsolete"
- @obj.the_answer
- end
-
- def test_multibyte_string
- str = @obj.multibyte_string[0]
- assert_equal "あいうえお", str
- end
-end
diff --git a/test/t3-ticket27.rb b/test/t3-ticket27.rb
deleted file mode 100755
index 2e35580..0000000
--- a/test/t3-ticket27.rb
+++ /dev/null
@@ -1,18 +0,0 @@
-#!/usr/bin/env ruby
-# Test passing a particular struct array through a variant
-# https://trac.luon.net/ruby-dbus/ticket/27
-require "dbus"
-session_bus = DBus::ASessionBus.new
-svc = session_bus.service("org.ruby.service")
-obj = svc.object("/org/ruby/MyInstance")
-obj.introspect # necessary
-obj.default_iface = "org.ruby.SampleInterface"
-# The bug is probably alignment related so whether it triggers
-# depends also on the combined length of service, interface,
-# and method names. Luckily here it works out.
-triple = ['a(uuu)', []]
-obj.test_variant(triple)
-quadruple = ['a(uuuu)', []] # a(uuu) works fine
-# The bus disconnects us because of malformed message,
-# code 12: DBUS_INVALID_TOO_MUCH_DATA
-obj.test_variant(quadruple)
diff --git a/test/t5-report-dbus-interface.rb b/test/t5-report-dbus-interface.rb
deleted file mode 100755
index aa83ce8..0000000
--- a/test/t5-report-dbus-interface.rb
+++ /dev/null
@@ -1,58 +0,0 @@
-#!/usr/bin/env ruby
-# should report it missing on org.ruby.SampleInterface
-# (on object...) instead of on DBus::Proxy::ObjectInterface
-require "test/unit"
-require "dbus"
-
-class ErrMsgTest < Test::Unit::TestCase
- def setup
- session_bus = DBus::ASessionBus.new
- svc = session_bus.service("org.ruby.service")
- @obj = svc.object("/org/ruby/MyInstance")
- @obj.introspect # necessary
- @obj.default_iface = "org.ruby.SampleInterface"
- end
-
- def test_report_dbus_interface
- begin
- @obj.NoSuchMethod
- # a specific exception...
- rescue NameError => e
- # mentioning DBus and the interface
- assert_match(/DBus interface.*#{@obj.default_iface}/, e.to_s)
- end
- end
-
- def test_report_short_struct
- begin
- @obj.test_variant ["(ss)", ["too few"] ]
- rescue DBus::TypeException => e
- assert_match(/1 elements but type info for 2/, e.to_s)
- end
- end
-
- def test_report_long_struct
- begin
- @obj.test_variant ["(ss)", ["a", "b", "too many"] ]
- rescue DBus::TypeException => e
- assert_match(/3 elements but type info for 2/, e.to_s)
- end
- end
-
- def test_report_nil
- nils = [
- ["(s)", [nil] ], # would get disconnected
- ["i", nil ],
- ["a{ss}", {"foo" => nil} ],
- ]
- nils.each do |has_nil|
- begin
- @obj.test_variant has_nil
- rescue DBus::TypeException => e
- # TODO want backtrace from the perspective of the caller:
- # rescue/reraise in send_sync?
- assert_match(/Cannot send nil/, e.to_s)
- end
- end
- end
-end
diff --git a/test/thread_safety_test.rb b/test/thread_safety_spec.rb
similarity index 74%
rename from test/thread_safety_test.rb
rename to test/thread_safety_spec.rb
index c774ff2..5b10623 100755
--- a/test/thread_safety_test.rb
+++ b/test/thread_safety_spec.rb
@@ -1,14 +1,10 @@
-#!/usr/bin/env ruby
+#!/usr/bin/env rspec
# Test thread safety
-require "test/unit"
+require_relative "spec_helper"
require "dbus"
-def d(msg)
- puts "#{$$} #{msg}" if $DEBUG
-end
-
-class ThreadSafetyTest < Test::Unit::TestCase
- def test_thread_competition
+describe "ThreadSafetyTest" do
+ it "tests thread competition" do
print "Thread competition: "
jobs = []
5.times do
@@ -25,7 +21,7 @@ class ThreadSafetyTest < Test::Unit::TestCase
10.times do |i|
print "#{i} "
$stdout.flush
- assert_equal 42, obj.the_answer[0]
+ expect(obj.the_answer[0]).to eq(42)
sleep 0.1 * rand
end
end
diff --git a/test/dbus-launch-simple b/test/tools/dbus-launch-simple
similarity index 100%
rename from test/dbus-launch-simple
rename to test/tools/dbus-launch-simple
diff --git a/test/dbus-limited-session.conf b/test/tools/dbus-limited-session.conf
similarity index 93%
rename from test/dbus-limited-session.conf
rename to test/tools/dbus-limited-session.conf
index 1ef9f2d..3ade98b 100644
--- a/test/dbus-limited-session.conf
+++ b/test/tools/dbus-limited-session.conf
@@ -8,7 +8,7 @@
<type>session</type>
<listen>unix:tmpdir=/tmp</listen>
- <listen>tcp:host=localhost,port=0,family=ipv4</listen>
+ <listen>tcp:host=127.0.0.1</listen>
<standard_session_servicedirs />
diff --git a/test/test_env b/test/tools/test_env
similarity index 91%
rename from test/test_env
rename to test/tools/test_env
index 75c63bd..5f08f72 100755
--- a/test/test_env
+++ b/test/tools/test_env
@@ -7,7 +7,7 @@
#export RUBYOPT="-d"
export RUBYOPT="$RUBYOPT -w"
./test_server \
- ./service_newapi.rb \
+ ../service_newapi.rb \
-- \
./dbus-launch-simple \
"$@"
diff --git a/test/test_server b/test/tools/test_server
similarity index 100%
rename from test/test_server
rename to test/tools/test_server
diff --git a/test/type_spec.rb b/test/type_spec.rb
new file mode 100755
index 0000000..5cab430
--- /dev/null
+++ b/test/type_spec.rb
@@ -0,0 +1,19 @@
+#!/usr/bin/env rspec
+require_relative "spec_helper"
+require "dbus"
+
+describe DBus do
+ describe ".type" do
+ %w{i ai a(ii) aai}.each do |s|
+ it "parses some type #{s}" do
+ expect(DBus::type(s).to_s).to be_eql s
+ end
+ end
+
+ %w{aa (ii ii) hrmp}.each do |s|
+ it "raises exception for invalid type #{s}" do
+ expect {DBus::type(s).to_s}.to raise_error DBus::Type::SignatureException
+ end
+ end
+ end
+end
diff --git a/test/value_spec.rb b/test/value_spec.rb
new file mode 100755
index 0000000..6136515
--- /dev/null
+++ b/test/value_spec.rb
@@ -0,0 +1,81 @@
+#!/usr/bin/env rspec
+# -*- coding: utf-8 -*-
+require_relative "spec_helper"
+require "dbus"
+
+describe "ValueTest" do
+ before(:each) do
+ session_bus = DBus::ASessionBus.new
+ svc = session_bus.service("org.ruby.service")
+ @obj = svc.object("/org/ruby/MyInstance")
+ @obj.introspect # necessary
+ @obj.default_iface = "org.ruby.SampleInterface"
+ end
+
+ it "tests passing an array of structs through a variant" do
+ triple = ['a(uuu)', []]
+ @obj.test_variant(triple)
+ quadruple = ['a(uuuu)', []] # a(uuu) works fine
+ # The bus disconnects us because of malformed message,
+ # code 12: DBUS_INVALID_TOO_MUCH_DATA
+ @obj.test_variant(quadruple)
+ end
+
+ it "tests passing an array through a variant" do
+ # old explicit typing
+ @obj.test_variant(["as", ["coucou", "kuku"]])
+ # automatic typing
+ @obj.test_variant(["coucou", "kuku"])
+ @obj.test_variant(["saint", "was that a word or a signature?"])
+ end
+
+ it "tests bouncing a variant" do
+ expect(@obj.bounce_variant("cuckoo")[0]).to eq("cuckoo")
+ expect(@obj.bounce_variant(["coucou", "kuku"])[0]).to eq(["coucou", "kuku"])
+ expect(@obj.bounce_variant([])[0]).to eq([])
+ empty_hash = {}
+ expect(@obj.bounce_variant(empty_hash)[0]).to eq(empty_hash)
+ end
+
+ # these are ambiguous
+ it "tests pairs with a string" do
+
+ # deprecated
+ expect(@obj.bounce_variant(["s", "foo"])[0]).to eq("foo")
+
+ expect(@obj.bounce_variant(DBus.variant("s", "foo"))[0]).to eq("foo")
+ expect(@obj.bounce_variant([DBus.type("s"), "foo"])[0]).to eq("foo")
+
+ # does not work, because the server side forgets the explicit typing
+# assert_equal ["s", "foo"], @obj.bounce_variant(["av", ["s", "foo"]])[0]
+# assert_equal ["s", "foo"], @obj.bounce_variant(["as", ["s", "foo"]])[0]
+
+ # instead, use this to demonstrate that the variant is passed as expected
+ expect(@obj.variant_size(["s", "four"])[0]).to eq(4)
+ # "av" is the simplest thing that will work,
+ # shifting the heuristic from a pair to the individual items
+ expect(@obj.variant_size(["av", ["s", "four"]])[0]).to eq(2)
+ end
+
+ it "tests marshalling an array of variants" do
+ # https://trac.luon.net/ruby-dbus/ticket/30
+ @obj.default_iface = "org.ruby.Ticket30"
+ choices = []
+ choices << ['s', 'Plan A']
+ choices << ['s', 'Plan B']
+ # old explicit typing
+ expect(@obj.Sybilla(choices)[0]).to eq("Do Plan A")
+ # automatic typing
+ expect(@obj.Sybilla(["Plan A", "Plan B"])[0]).to eq("Do Plan A")
+ end
+
+ it "tests service returning nonarray" do
+ # "warning: default `to_a' will be obsolete"
+ @obj.the_answer
+ end
+
+ it "tests multibyte string" do
+ str = @obj.multibyte_string[0]
+ expect(str).to eq("あいうえお")
+ end
+end
diff --git a/test/variant_spec.rb b/test/variant_spec.rb
new file mode 100755
index 0000000..24e9591
--- /dev/null
+++ b/test/variant_spec.rb
@@ -0,0 +1,66 @@
+#!/usr/bin/env rspec
+# Test marshalling variants according to ruby types
+require_relative "spec_helper"
+require "dbus"
+
+describe "VariantTest" do
+ before(:each) do
+ @bus = DBus::ASessionBus.new
+ @svc = @bus.service("org.ruby.service")
+ end
+
+ def make_variant(a)
+ DBus::PacketMarshaller.make_variant(a)
+ end
+
+ it "tests make variant scalar" do
+ # special case: do not fail immediately, marshaller will do that
+ expect(make_variant(nil)).to eq(["b", nil])
+
+ expect(make_variant(true)).to eq(["b", true])
+ # Integers
+ # no byte
+ expect(make_variant(42)).to eq(["i", 42])
+ # 3_000_000_000 can be u or x.
+ # less specific test: just run it thru a loopback
+ expect(make_variant(3_000_000_000)).to eq(["x", 3_000_000_000])
+ expect(make_variant(5_000_000_000)).to eq(["x", 5_000_000_000])
+
+ expect(make_variant(3.14)).to eq(["d", 3.14])
+
+ expect(make_variant("foo")).to eq(["s", "foo"])
+ expect(make_variant(:bar)).to eq(["s", "bar"])
+
+ # left: strruct, array, dict
+ # object path: detect exported objects?, signature
+
+# # by Ruby types
+# class Foo
+# end
+# make_variant(Foo.new)
+# if we don;t understand a class, the error should be informative -> new exception
+ end
+
+ it "tests make variant array" do
+ ai = [1, 2, 3]
+# as = ["one", "two", "three"]
+ # which?
+# expect(make_variant(ai)).to eq(["ai", [1, 2, 3]])
+ expect(make_variant(ai)).to eq(["av", [["i", 1],
+ ["i", 2],
+ ["i", 3]]])
+ a0 = []
+ expect(make_variant(a0)).to eq(["av", []])
+
+ end
+
+ it "tests make variant hash" do
+ h = {"k1" => "v1", "k2" => "v2"}
+ expect(make_variant(h)).to eq(["a{sv}", {
+ "k1" => ["s", "v1"],
+ "k2" => ["s", "v2"],
+ }])
+ h0 = {}
+ expect(make_variant(h0)).to eq(["a{sv}", {}])
+ end
+end
diff --git a/test/variant_test.rb b/test/variant_test.rb
deleted file mode 100755
index 9379658..0000000
--- a/test/variant_test.rb
+++ /dev/null
@@ -1,66 +0,0 @@
-#!/usr/bin/env ruby
-# Test marshalling variants according to ruby types
-require "test/unit"
-require "dbus"
-
-class VariantTest < Test::Unit::TestCase
- def setup
- @bus = DBus::ASessionBus.new
- @svc = @bus.service("org.ruby.service")
- end
-
- def make_variant(a)
- DBus::PacketMarshaller.make_variant(a)
- end
-
- def test_make_variant_scalar
- # special case: do not fail immediately, marshaller will do that
- assert_equal ["b", nil], make_variant(nil)
-
- assert_equal ["b", true], make_variant(true)
- # Integers
- # no byte
- assert_equal ["i", 42], make_variant(42)
- # 3_000_000_000 can be u or x.
- # less specific test: just run it thru a loopback
- assert_equal ["x", 3_000_000_000], make_variant(3_000_000_000)
- assert_equal ["x", 5_000_000_000], make_variant(5_000_000_000)
-
- assert_equal ["d", 3.14], make_variant(3.14)
-
- assert_equal ["s", "foo"], make_variant("foo")
- assert_equal ["s", "bar"], make_variant(:bar)
-
- # left: strruct, array, dict
- # object path: detect exported objects?, signature
-
-# # by Ruby types
-# class Foo
-# end
-# make_variant(Foo.new)
-# if we don;t understand a class, the error should be informative -> new exception
- end
-
- def test_make_variant_array
- ai = [1, 2, 3]
-# as = ["one", "two", "three"]
- # which?
-# assert_equal ["ai", [1, 2, 3]], make_variant(ai)
- assert_equal ["av", [["i", 1],
- ["i", 2],
- ["i", 3]]], make_variant(ai)
- a0 = []
- assert_equal ["av", []], make_variant(a0)
-
- end
-
- def test_make_variant_hash
- h = {"k1" => "v1", "k2" => "v2"}
- assert_equal ["a{sv}", {
- "k1" => ["s", "v1"],
- "k2" => ["s", "v2"],
- }], make_variant(h)
- h0 = {}
- assert_equal ["a{sv}", {}], make_variant(h0)
- end
-end
--
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/pkg-ruby-extras/ruby-dbus.git
More information about the Pkg-ruby-extras-commits
mailing list