ruby-dbus 0.16.0 → 0.25.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (108) hide show
  1. checksums.yaml +4 -4
  2. data/NEWS.md +288 -3
  3. data/README.md +3 -5
  4. data/Rakefile +27 -12
  5. data/VERSION +1 -1
  6. data/doc/Reference.md +109 -10
  7. data/examples/doc/_extract_examples +7 -0
  8. data/examples/no-bus/pulseaudio.rb +50 -0
  9. data/examples/no-introspect/nm-test.rb +2 -0
  10. data/examples/no-introspect/tracker-test.rb +3 -1
  11. data/examples/rhythmbox/playpause.rb +2 -1
  12. data/examples/service/call_service.rb +2 -1
  13. data/examples/service/complex-property.rb +21 -0
  14. data/examples/service/service_newapi.rb +3 -3
  15. data/examples/simple/call_introspect.rb +1 -0
  16. data/examples/simple/get_id.rb +6 -3
  17. data/examples/simple/properties.rb +2 -0
  18. data/examples/utils/listnames.rb +10 -1
  19. data/examples/utils/notify.rb +1 -0
  20. data/lib/dbus/api_options.rb +9 -0
  21. data/lib/dbus/auth.rb +307 -217
  22. data/lib/dbus/bus.rb +155 -588
  23. data/lib/dbus/bus_name.rb +12 -8
  24. data/lib/dbus/connection.rb +363 -0
  25. data/lib/dbus/core_ext/class/attribute.rb +1 -1
  26. data/lib/dbus/data.rb +821 -0
  27. data/lib/dbus/emits_changed_signal.rb +83 -0
  28. data/lib/dbus/error.rb +4 -2
  29. data/lib/dbus/introspect.rb +133 -31
  30. data/lib/dbus/logger.rb +6 -3
  31. data/lib/dbus/main.rb +66 -0
  32. data/lib/dbus/marshall.rb +247 -296
  33. data/lib/dbus/matchrule.rb +16 -16
  34. data/lib/dbus/message.rb +66 -57
  35. data/lib/dbus/message_queue.rb +40 -25
  36. data/lib/dbus/node_tree.rb +105 -0
  37. data/lib/dbus/object.rb +442 -33
  38. data/lib/dbus/object_manager.rb +61 -0
  39. data/lib/dbus/object_path.rb +11 -6
  40. data/lib/dbus/object_server.rb +149 -0
  41. data/lib/dbus/org.freedesktop.DBus.xml +97 -0
  42. data/lib/dbus/platform.rb +26 -0
  43. data/lib/dbus/proxy_object.rb +30 -11
  44. data/lib/dbus/proxy_object_factory.rb +13 -7
  45. data/lib/dbus/proxy_object_interface.rb +63 -30
  46. data/lib/dbus/proxy_service.rb +107 -0
  47. data/lib/dbus/raw_message.rb +91 -0
  48. data/lib/dbus/type.rb +319 -86
  49. data/lib/dbus/xml.rb +40 -19
  50. data/lib/dbus.rb +28 -17
  51. data/ruby-dbus.gemspec +13 -6
  52. data/spec/async_spec.rb +2 -0
  53. data/spec/auth_spec.rb +225 -0
  54. data/spec/binding_spec.rb +2 -0
  55. data/spec/bus_and_xml_backend_spec.rb +5 -1
  56. data/spec/bus_connection_spec.rb +93 -0
  57. data/spec/bus_name_spec.rb +3 -1
  58. data/spec/bus_spec.rb +2 -0
  59. data/spec/byte_array_spec.rb +2 -0
  60. data/spec/client_robustness_spec.rb +4 -2
  61. data/spec/connection_spec.rb +37 -0
  62. data/spec/coverage_helper.rb +39 -0
  63. data/spec/data/marshall.yaml +1667 -0
  64. data/spec/data_spec.rb +673 -0
  65. data/spec/dbus_spec.rb +22 -0
  66. data/spec/emits_changed_signal_spec.rb +58 -0
  67. data/spec/err_msg_spec.rb +2 -0
  68. data/spec/introspect_xml_parser_spec.rb +2 -0
  69. data/spec/introspection_spec.rb +2 -0
  70. data/spec/main_loop_spec.rb +17 -1
  71. data/spec/message_spec.rb +21 -0
  72. data/spec/mock-service/cockpit-dbustests.rb +29 -0
  73. data/spec/mock-service/com.redhat.Cockpit.DBusTests.xml +180 -0
  74. data/spec/mock-service/org.ruby.service.service +4 -0
  75. data/spec/mock-service/org.rubygems.ruby_dbus.DBusTests.service +4 -0
  76. data/spec/{service_newapi.rb → mock-service/spaghetti-monster.rb} +122 -75
  77. data/spec/node_spec.rb +65 -0
  78. data/spec/object_manager_spec.rb +33 -0
  79. data/spec/object_path_spec.rb +3 -0
  80. data/spec/object_server_spec.rb +138 -0
  81. data/spec/object_spec.rb +194 -0
  82. data/spec/packet_marshaller_spec.rb +41 -0
  83. data/spec/packet_unmarshaller_spec.rb +248 -0
  84. data/spec/platform_spec.rb +14 -0
  85. data/spec/property_spec.rb +214 -12
  86. data/spec/proxy_object_interface_spec.rb +35 -0
  87. data/spec/proxy_object_spec.rb +2 -0
  88. data/spec/{bus_driver_spec.rb → proxy_service_spec.rb} +15 -8
  89. data/spec/raw_message_spec.rb +32 -0
  90. data/spec/server_robustness_spec.rb +20 -6
  91. data/spec/session_bus_spec.rb +36 -1
  92. data/spec/session_bus_spec_manual.rb +2 -0
  93. data/spec/signal_spec.rb +16 -3
  94. data/spec/spec_helper.rb +25 -33
  95. data/spec/thread_safety_spec.rb +55 -12
  96. data/spec/tools/dbus-launch-simple +9 -6
  97. data/spec/tools/dbus-limited-session.conf +32 -0
  98. data/spec/tools/test_env +26 -6
  99. data/spec/type_spec.rb +214 -6
  100. data/spec/value_spec.rb +16 -1
  101. data/spec/variant_spec.rb +4 -2
  102. data/spec/zzz_quit_spec.rb +16 -0
  103. metadata +92 -20
  104. data/examples/gdbus/gdbus +0 -257
  105. data/examples/gdbus/gdbus.glade +0 -98
  106. data/examples/gdbus/launch.sh +0 -4
  107. data/spec/server_spec.rb +0 -53
  108. data/spec/tools/test_server +0 -39
data/spec/node_spec.rb ADDED
@@ -0,0 +1,65 @@
1
+ #!/usr/bin/env rspec
2
+ # frozen_string_literal: true
3
+
4
+ require_relative "spec_helper"
5
+ require "dbus"
6
+
7
+ describe DBus::Node do
8
+ describe "#inspect" do
9
+ # the behavior needs improvement
10
+ it "shows the node, poorly" do
11
+ parent = described_class.new("parent")
12
+ parent.object = DBus::Object.new("/parent")
13
+
14
+ 3.times do |i|
15
+ child_name = "child#{i}"
16
+ child = described_class.new(child_name)
17
+ parent[child_name] = child
18
+ end
19
+
20
+ expect(parent.inspect).to match(/<DBus::Node [0-9a-f]+ {child0 => {},child1 => {},child2 => {}}>/)
21
+ end
22
+ end
23
+
24
+ describe "#descendant_objects" do
25
+ let(:manager_path) { "/org/example/FooManager" }
26
+ let(:child_paths) do
27
+ [
28
+ # NOTE: "/org/example/FooManager/good"
29
+ # is a path under a managed object but there is no object there
30
+ "/org/example/FooManager/good/1",
31
+ "/org/example/FooManager/good/2",
32
+ "/org/example/FooManager/good/3",
33
+ "/org/example/FooManager/bad/1",
34
+ "/org/example/FooManager/bad/2"
35
+ ]
36
+ end
37
+
38
+ let(:non_child_paths) do
39
+ [
40
+ "/org/example/BarManager/good/1",
41
+ "/org/example/BarManager/good/2"
42
+ ]
43
+ end
44
+
45
+ context "on the bus" do
46
+ let(:bus) { DBus::ASessionBus.new }
47
+ let(:service) { bus.object_server }
48
+
49
+ before do
50
+ service.export(DBus::Object.new(manager_path))
51
+ non_child_paths.each do |p|
52
+ service.export(DBus::Object.new(p))
53
+ end
54
+ end
55
+
56
+ it "returns just the descendants of the specified objects" do
57
+ child_exported_objects = child_paths.map { |p| DBus::Object.new(p) }
58
+ child_exported_objects.each { |obj| service.export(obj) }
59
+
60
+ node = service.get_node(manager_path, create: false)
61
+ expect(node.descendant_objects).to eq child_exported_objects
62
+ end
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,33 @@
1
+ #!/usr/bin/env rspec
2
+ # frozen_string_literal: true
3
+
4
+ require_relative "spec_helper"
5
+ require "dbus"
6
+
7
+ describe DBus::ObjectManager do
8
+ describe "GetManagedObjects" do
9
+ let(:bus) { DBus::ASessionBus.new }
10
+ let(:service) { bus["org.ruby.service"] }
11
+ let(:obj) { service["/org/ruby/MyInstance"] }
12
+ let(:parent_iface) { obj["org.ruby.TestParent"] }
13
+ let(:om_iface) { obj["org.freedesktop.DBus.ObjectManager"] }
14
+
15
+ it "returns the interfaces and properties of currently managed objects" do
16
+ c1_opath = parent_iface.New("child1")
17
+ c2_opath = parent_iface.New("child2")
18
+
19
+ parent_iface.Delete(c1_opath)
20
+ expected_gmo = {
21
+ "/org/ruby/MyInstance/child2" => {
22
+ "org.freedesktop.DBus.Introspectable" => {},
23
+ "org.freedesktop.DBus.Properties" => {},
24
+ "org.ruby.TestChild" => { "Name" => "Child2" }
25
+ }
26
+ }
27
+ expect(om_iface.GetManagedObjects).to eq(expected_gmo)
28
+
29
+ parent_iface.Delete(c2_opath)
30
+ expect(om_iface.GetManagedObjects).to eq({})
31
+ end
32
+ end
33
+ end
@@ -1,4 +1,6 @@
1
1
  #!/usr/bin/env rspec
2
+ # frozen_string_literal: true
3
+
2
4
  require_relative "spec_helper"
3
5
  require "dbus"
4
6
 
@@ -18,6 +20,7 @@ describe DBus::ObjectPath do
18
20
  expect(described_class.valid?("/EmptyLastComponent/")).to be_falsey
19
21
  expect(described_class.valid?("/Invalid Character")).to be_falsey
20
22
  expect(described_class.valid?("/Invalid-Character")).to be_falsey
23
+ expect(described_class.valid?("/InválídCháráctér")).to be_falsey
21
24
  end
22
25
  end
23
26
  end
@@ -0,0 +1,138 @@
1
+ #!/usr/bin/env rspec
2
+ # frozen_string_literal: true
3
+
4
+ require_relative "spec_helper"
5
+ require "dbus"
6
+
7
+ describe DBus::ObjectServer do
8
+ let(:bus) { DBus::ASessionBus.new }
9
+ let(:server) { bus.object_server }
10
+
11
+ describe "#descendants_for" do
12
+ it "raises for not existing path" do
13
+ expect { server.descendants_for("/notthere") }.to raise_error(ArgumentError, /notthere doesn't exist/)
14
+ end
15
+ end
16
+
17
+ # tag_bus MEANS that the test needs a session bus
18
+ # tag_service MEANS that it needs a session bus with our testing services
19
+
20
+ describe "#export, #object, #[]", tag_bus: true do
21
+ it "object and [] return the object if it was exported" do
22
+ path = "/org/ruby/Foo"
23
+ obj = DBus::Object.new(path)
24
+ server.export(obj)
25
+ expect(server.object(path)).to be_equal(obj)
26
+ expect(server[path]).to be_equal(obj)
27
+ end
28
+
29
+ it "object and [] return nil if the path was not found or has no object" do
30
+ path = "/org/ruby/Bar"
31
+ obj = DBus::Object.new(path)
32
+ server.export(obj)
33
+
34
+ path2 = "/org/ruby/nosuch"
35
+ expect(server.object(path2)).to be_nil
36
+ expect(server[path2]).to be_nil
37
+
38
+ path3 = "/org"
39
+ expect(server.object(path3)).to be_nil
40
+ expect(server[path3]).to be_nil
41
+ end
42
+ end
43
+
44
+ describe "#export", tag_bus: true do
45
+ context "when exporting at a path where an object exists already" do
46
+ let(:path) { "/org/ruby/Same" }
47
+ let(:obj1) do
48
+ o = DBus::Object.new(path)
49
+ o.define_singleton_method(:which) { 1 }
50
+ o
51
+ end
52
+ let(:obj2) do
53
+ o = DBus::Object.new(path)
54
+ o.define_singleton_method(:which) { 2 }
55
+ o
56
+ end
57
+
58
+ it "raises an error" do
59
+ server.export(obj1)
60
+ expect { server.export(obj2) }.to raise_error(RuntimeError, /there is already an object/)
61
+ end
62
+ end
63
+ end
64
+
65
+ describe "#unexport", tag_bus: true do
66
+ before(:each) do
67
+ bus = DBus::ASessionBus.new
68
+ @svc = bus.object_server
69
+ end
70
+
71
+ it "returns the unexported leaf object" do
72
+ obj = DBus::Object.new "/org/ruby/Foo"
73
+ @svc.export obj
74
+ expect(@svc.unexport(obj)).to be_equal(obj)
75
+ end
76
+
77
+ it "returns the unexported leaf object, if specified by its path" do
78
+ obj = DBus::Object.new "/org/ruby/Foo"
79
+ @svc.export obj
80
+ expect(@svc.unexport(obj.path)).to be_equal(obj)
81
+
82
+ obj = DBus::Object.new "/org/ruby/Foo"
83
+ @svc.export obj
84
+ expect(@svc.unexport(DBus::ObjectPath.new(obj.path))).to be_equal(obj)
85
+ end
86
+
87
+ it "returns false if the object was never exported" do
88
+ obj = DBus::Object.new "/org/ruby/Foo"
89
+ expect(@svc.unexport(obj)).to be false
90
+ end
91
+
92
+ it "raises false if the path has no node" do
93
+ obj = DBus::Object.new "/org/ruby/Foo"
94
+ @svc.export obj
95
+ expect { @svc.unexport("/org/ruby/NotFoo") }.to raise_error(ArgumentError)
96
+ @svc.unexport obj
97
+ end
98
+
99
+ it "raises false if the path has no object" do
100
+ obj = DBus::Object.new "/org/ruby/Foo"
101
+ @svc.export obj
102
+ expect { @svc.unexport("/org/ruby") }.to raise_error(ArgumentError)
103
+ @svc.unexport obj
104
+ end
105
+
106
+ it "raises when argument is not usable" do
107
+ expect { @svc.unexport(:foo) }.to raise_error(ArgumentError)
108
+ end
109
+
110
+ context "/child_of_root" do
111
+ it "returns the unexported object" do
112
+ obj = DBus::Object.new "/child_of_root"
113
+ @svc.export obj
114
+ expect(@svc.unexport(obj)).to be_equal(obj)
115
+ end
116
+ end
117
+
118
+ context "/ (root)" do
119
+ it "returns the unexported object" do
120
+ obj = DBus::Object.new "/"
121
+ @svc.export obj
122
+ expect(@svc.unexport(obj)).to be_equal(obj)
123
+ end
124
+ end
125
+
126
+ context "not a leaf object" do
127
+ it "maintains objects on child paths" do
128
+ obj = DBus::Object.new "/org/ruby"
129
+ @svc.export obj
130
+ obj2 = DBus::Object.new "/org/ruby/Foo"
131
+ @svc.export obj2
132
+
133
+ @svc.unexport(obj)
134
+ expect(@svc.object("/org/ruby/Foo")).to be_a DBus::Object
135
+ end
136
+ end
137
+ end
138
+ end
@@ -0,0 +1,194 @@
1
+ #!/usr/bin/env rspec
2
+ # frozen_string_literal: true
3
+
4
+ require_relative "spec_helper"
5
+ require "dbus"
6
+
7
+ class ObjectTest < DBus::Object
8
+ T = DBus::Type unless const_defined? "T"
9
+
10
+ dbus_interface "org.ruby.ServerTest" do
11
+ dbus_attr_writer :write_me, T::Struct[String, String]
12
+
13
+ attr_accessor :read_only_for_dbus
14
+
15
+ dbus_reader :read_only_for_dbus, T::STRING, emits_changed_signal: :invalidates
16
+ end
17
+ end
18
+
19
+ describe DBus::Object do
20
+ describe ".dbus_attr_writer" do
21
+ describe "the declared assignment method" do
22
+ # Slightly advanced RSpec:
23
+ # https://rspec.info/documentation/3.9/rspec-expectations/RSpec/Matchers.html#satisfy-instance_method
24
+ let(:a_struct_in_a_variant) do
25
+ satisfying { |x| x.is_a?(DBus::Data::Variant) && x.member_type.to_s == "(ss)" }
26
+ # ^ This formatting keeps the matcher on a single line
27
+ # which enables RSpec to cite it if it fails, instead of saying "block".
28
+ end
29
+
30
+ it "emits PropertyChanged with correctly typed argument" do
31
+ obj = ObjectTest.new("/test")
32
+ expect(obj).to receive(:PropertiesChanged).with(
33
+ "org.ruby.ServerTest",
34
+ {
35
+ "WriteMe" => a_struct_in_a_variant
36
+ },
37
+ []
38
+ )
39
+ # bug: call PC with simply the assigned value,
40
+ # which will need type guessing
41
+ obj.write_me = ["two", "strings"]
42
+ end
43
+ end
44
+ end
45
+
46
+ describe ".dbus_accessor" do
47
+ it "can only be used within a dbus_interface" do
48
+ expect do
49
+ ObjectTest.instance_exec do
50
+ dbus_accessor :foo, DBus::Type::STRING
51
+ end
52
+ end.to raise_error(DBus::Object::UndefinedInterface)
53
+ end
54
+ end
55
+
56
+ describe ".dbus_reader" do
57
+ it "can only be used within a dbus_interface" do
58
+ expect do
59
+ ObjectTest.instance_exec do
60
+ dbus_reader :foo, DBus::Type::STRING
61
+ end
62
+ end.to raise_error(DBus::Object::UndefinedInterface)
63
+ end
64
+
65
+ it "fails when the signature is invalid" do
66
+ expect do
67
+ ObjectTest.instance_exec do
68
+ dbus_interface "org.ruby.ServerTest" do
69
+ dbus_reader :foo2, "!"
70
+ end
71
+ end
72
+ end.to raise_error(DBus::Type::SignatureException)
73
+ end
74
+ end
75
+
76
+ describe ".dbus_reader, when paired with attr_accessor" do
77
+ describe "the declared assignment method" do
78
+ it "emits PropertyChanged" do
79
+ obj = ObjectTest.new("/test")
80
+ expect(obj).to receive(:PropertiesChanged).with(
81
+ "org.ruby.ServerTest",
82
+ {},
83
+ ["ReadOnlyForDbus"]
84
+ )
85
+ obj.read_only_for_dbus = "myvalue"
86
+ end
87
+ end
88
+ end
89
+
90
+ describe ".dbus_writer" do
91
+ it "can only be used within a dbus_interface" do
92
+ expect do
93
+ ObjectTest.instance_exec do
94
+ dbus_writer :foo, DBus::Type::STRING
95
+ end
96
+ end.to raise_error(DBus::Object::UndefinedInterface)
97
+ end
98
+ end
99
+
100
+ describe ".dbus_watcher" do
101
+ it "can only be used within a dbus_interface" do
102
+ expect do
103
+ ObjectTest.instance_exec do
104
+ dbus_watcher :foo
105
+ end
106
+ end.to raise_error(DBus::Object::UndefinedInterface)
107
+ end
108
+ end
109
+
110
+ describe ".dbus_method" do
111
+ it "can only be used within a dbus_interface" do
112
+ expect do
113
+ ObjectTest.instance_exec do
114
+ dbus_method :foo do
115
+ end
116
+ end
117
+ end.to raise_error(DBus::Object::UndefinedInterface)
118
+ end
119
+ end
120
+
121
+ describe ".dbus_signal" do
122
+ it "can only be used within a dbus_interface" do
123
+ expect do
124
+ ObjectTest.instance_exec do
125
+ dbus_signal :signal_without_interface
126
+ end
127
+ end.to raise_error(DBus::Object::UndefinedInterface)
128
+ end
129
+
130
+ it "cannot be named with a bang" do
131
+ expect do
132
+ ObjectTest.instance_exec do
133
+ dbus_interface "org.ruby.ServerTest" do
134
+ # a valid Ruby symbol but an invalid DBus name; Ticket#38
135
+ dbus_signal :signal_with_a_bang!
136
+ end
137
+ end
138
+ end.to raise_error(DBus::InvalidMethodName)
139
+ end
140
+ end
141
+
142
+ describe ".emits_changed_signal" do
143
+ it "raises UndefinedInterface when so" do
144
+ expect { ObjectTest.emits_changed_signal = false }
145
+ .to raise_error DBus::Object::UndefinedInterface
146
+ end
147
+
148
+ it "assigns to the current interface" do
149
+ ObjectTest.instance_exec do
150
+ dbus_interface "org.ruby.Interface" do
151
+ self.emits_changed_signal = false
152
+ end
153
+ end
154
+ ecs = ObjectTest.intfs["org.ruby.Interface"].emits_changed_signal
155
+ expect(ecs).to eq false
156
+ end
157
+
158
+ it "only can be assigned once" do
159
+ expect do
160
+ Class.new(DBus::Object) do
161
+ dbus_interface "org.ruby.Interface" do
162
+ self.emits_changed_signal = false
163
+ self.emits_changed_signal = :invalidates
164
+ end
165
+ end
166
+ end.to raise_error(RuntimeError, /assigned more than once/)
167
+ end
168
+ end
169
+
170
+ # coverage obsession
171
+ describe "#dispatch" do
172
+ it "survives being called with a non-METHOD_CALL, doing nothing" do
173
+ obj = ObjectTest.new("/test")
174
+ msg = DBus::MethodReturnMessage.new
175
+ expect { obj.dispatch(msg) }.to_not raise_error
176
+ end
177
+ end
178
+
179
+ describe "#emit" do
180
+ context "before the object has been exported" do
181
+ it "raises an explanatory error" do
182
+ obj = ObjectTest.new("/test")
183
+
184
+ intf = DBus::Interface.new("org.example.Test")
185
+ signal = DBus::Signal.new("Ring")
186
+ expect { obj.emit(intf, signal) }
187
+ .to raise_error(
188
+ RuntimeError,
189
+ %r{Cannot emit signal org.example.Test.Ring before /test is exported}
190
+ )
191
+ end
192
+ end
193
+ end
194
+ end
@@ -0,0 +1,41 @@
1
+ #!/usr/bin/env rspec
2
+ # frozen_string_literal: true
3
+
4
+ require_relative "spec_helper"
5
+ require "dbus"
6
+ require "ostruct"
7
+ require "yaml"
8
+
9
+ data_dir = File.expand_path("data", __dir__)
10
+ marshall_yaml_s = File.read("#{data_dir}/marshall.yaml")
11
+ marshall_yaml = YAML.safe_load(marshall_yaml_s)
12
+
13
+ describe DBus::PacketMarshaller do
14
+ context "marshall.yaml" do
15
+ marshall_yaml.each do |test|
16
+ t = OpenStruct.new(test)
17
+ next if t.marshall == false
18
+ # skip test cases for invalid unmarshalling
19
+ next if t.val.nil?
20
+
21
+ # while the marshaller can use only native endianness, skip the other
22
+ endianness = t.end.to_sym
23
+
24
+ signature = t.sig
25
+ expected = buffer_from_yaml(t.buf)
26
+
27
+ it "writes a '#{signature}' with value #{t.val.inspect} (#{endianness})" do
28
+ subject = described_class.new(endianness: endianness)
29
+ subject.append(signature, t.val)
30
+ expect(subject.packet).to eq(expected)
31
+ end
32
+
33
+ it "writes a '#{signature}' with typed value #{t.val.inspect} (#{endianness})" do
34
+ subject = described_class.new(endianness: endianness)
35
+ typed_val = DBus::Data.make_typed(signature, t.val)
36
+ subject.append(signature, typed_val)
37
+ expect(subject.packet).to eq(expected)
38
+ end
39
+ end
40
+ end
41
+ end