[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