openhab-jrubyscripting 5.0.0.rc1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (164) hide show
  1. checksums.yaml +7 -0
  2. data/lib/openhab/core/actions.rb +163 -0
  3. data/lib/openhab/core/entity_lookup.rb +144 -0
  4. data/lib/openhab/core/events/abstract_event.rb +17 -0
  5. data/lib/openhab/core/events/item_channel_link.rb +36 -0
  6. data/lib/openhab/core/events/item_command_event.rb +78 -0
  7. data/lib/openhab/core/events/item_event.rb +22 -0
  8. data/lib/openhab/core/events/item_state_changed_event.rb +52 -0
  9. data/lib/openhab/core/events/item_state_event.rb +51 -0
  10. data/lib/openhab/core/events/thing.rb +29 -0
  11. data/lib/openhab/core/events/thing_status_info_event.rb +53 -0
  12. data/lib/openhab/core/events.rb +10 -0
  13. data/lib/openhab/core/items/accepted_data_types.rb +29 -0
  14. data/lib/openhab/core/items/color_item.rb +52 -0
  15. data/lib/openhab/core/items/contact_item.rb +52 -0
  16. data/lib/openhab/core/items/date_time_item.rb +58 -0
  17. data/lib/openhab/core/items/dimmer_item.rb +148 -0
  18. data/lib/openhab/core/items/generic_item.rb +344 -0
  19. data/lib/openhab/core/items/group_item.rb +174 -0
  20. data/lib/openhab/core/items/image_item.rb +109 -0
  21. data/lib/openhab/core/items/location_item.rb +34 -0
  22. data/lib/openhab/core/items/metadata/hash.rb +390 -0
  23. data/lib/openhab/core/items/metadata/namespace_hash.rb +469 -0
  24. data/lib/openhab/core/items/metadata.rb +11 -0
  25. data/lib/openhab/core/items/number_item.rb +62 -0
  26. data/lib/openhab/core/items/numeric_item.rb +22 -0
  27. data/lib/openhab/core/items/persistence.rb +327 -0
  28. data/lib/openhab/core/items/player_item.rb +66 -0
  29. data/lib/openhab/core/items/proxy.rb +59 -0
  30. data/lib/openhab/core/items/registry.rb +66 -0
  31. data/lib/openhab/core/items/rollershutter_item.rb +68 -0
  32. data/lib/openhab/core/items/semantics/enumerable.rb +152 -0
  33. data/lib/openhab/core/items/semantics.rb +476 -0
  34. data/lib/openhab/core/items/state_storage.rb +53 -0
  35. data/lib/openhab/core/items/string_item.rb +28 -0
  36. data/lib/openhab/core/items/switch_item.rb +78 -0
  37. data/lib/openhab/core/items.rb +114 -0
  38. data/lib/openhab/core/lazy_array.rb +52 -0
  39. data/lib/openhab/core/profile_factory.rb +118 -0
  40. data/lib/openhab/core/script_handling.rb +55 -0
  41. data/lib/openhab/core/things/channel.rb +48 -0
  42. data/lib/openhab/core/things/channel_uid.rb +51 -0
  43. data/lib/openhab/core/things/item_channel_link.rb +33 -0
  44. data/lib/openhab/core/things/profile_callback.rb +52 -0
  45. data/lib/openhab/core/things/proxy.rb +69 -0
  46. data/lib/openhab/core/things/registry.rb +46 -0
  47. data/lib/openhab/core/things/thing.rb +194 -0
  48. data/lib/openhab/core/things.rb +22 -0
  49. data/lib/openhab/core/timer.rb +128 -0
  50. data/lib/openhab/core/types/comparable_type.rb +23 -0
  51. data/lib/openhab/core/types/date_time_type.rb +259 -0
  52. data/lib/openhab/core/types/decimal_type.rb +192 -0
  53. data/lib/openhab/core/types/hsb_type.rb +183 -0
  54. data/lib/openhab/core/types/increase_decrease_type.rb +34 -0
  55. data/lib/openhab/core/types/next_previous_type.rb +34 -0
  56. data/lib/openhab/core/types/numeric_type.rb +52 -0
  57. data/lib/openhab/core/types/on_off_type.rb +46 -0
  58. data/lib/openhab/core/types/open_closed_type.rb +41 -0
  59. data/lib/openhab/core/types/percent_type.rb +95 -0
  60. data/lib/openhab/core/types/play_pause_type.rb +38 -0
  61. data/lib/openhab/core/types/point_type.rb +117 -0
  62. data/lib/openhab/core/types/quantity_type.rb +327 -0
  63. data/lib/openhab/core/types/raw_type.rb +26 -0
  64. data/lib/openhab/core/types/refresh_type.rb +27 -0
  65. data/lib/openhab/core/types/rewind_fastforward_type.rb +38 -0
  66. data/lib/openhab/core/types/stop_move_type.rb +34 -0
  67. data/lib/openhab/core/types/string_type.rb +76 -0
  68. data/lib/openhab/core/types/type.rb +117 -0
  69. data/lib/openhab/core/types/un_def_type.rb +38 -0
  70. data/lib/openhab/core/types/up_down_type.rb +50 -0
  71. data/lib/openhab/core/types.rb +69 -0
  72. data/lib/openhab/core/uid.rb +36 -0
  73. data/lib/openhab/core.rb +85 -0
  74. data/lib/openhab/core_ext/java/duration.rb +115 -0
  75. data/lib/openhab/core_ext/java/local_date.rb +93 -0
  76. data/lib/openhab/core_ext/java/local_time.rb +106 -0
  77. data/lib/openhab/core_ext/java/month.rb +59 -0
  78. data/lib/openhab/core_ext/java/month_day.rb +105 -0
  79. data/lib/openhab/core_ext/java/period.rb +103 -0
  80. data/lib/openhab/core_ext/java/temporal_amount.rb +34 -0
  81. data/lib/openhab/core_ext/java/time.rb +58 -0
  82. data/lib/openhab/core_ext/java/unit.rb +15 -0
  83. data/lib/openhab/core_ext/java/zoned_date_time.rb +116 -0
  84. data/lib/openhab/core_ext/ruby/array.rb +21 -0
  85. data/lib/openhab/core_ext/ruby/class.rb +15 -0
  86. data/lib/openhab/core_ext/ruby/date.rb +89 -0
  87. data/lib/openhab/core_ext/ruby/numeric.rb +190 -0
  88. data/lib/openhab/core_ext/ruby/range.rb +70 -0
  89. data/lib/openhab/core_ext/ruby/time.rb +104 -0
  90. data/lib/openhab/core_ext.rb +18 -0
  91. data/lib/openhab/dsl/events/watch_event.rb +18 -0
  92. data/lib/openhab/dsl/events.rb +9 -0
  93. data/lib/openhab/dsl/gems.rb +3 -0
  94. data/lib/openhab/dsl/items/builder.rb +618 -0
  95. data/lib/openhab/dsl/items/ensure.rb +93 -0
  96. data/lib/openhab/dsl/items/timed_command.rb +236 -0
  97. data/lib/openhab/dsl/rules/automation_rule.rb +308 -0
  98. data/lib/openhab/dsl/rules/builder.rb +1373 -0
  99. data/lib/openhab/dsl/rules/guard.rb +115 -0
  100. data/lib/openhab/dsl/rules/name_inference.rb +160 -0
  101. data/lib/openhab/dsl/rules/property.rb +76 -0
  102. data/lib/openhab/dsl/rules/rule_triggers.rb +96 -0
  103. data/lib/openhab/dsl/rules/terse.rb +63 -0
  104. data/lib/openhab/dsl/rules/triggers/changed.rb +169 -0
  105. data/lib/openhab/dsl/rules/triggers/channel.rb +57 -0
  106. data/lib/openhab/dsl/rules/triggers/command.rb +107 -0
  107. data/lib/openhab/dsl/rules/triggers/conditions/duration.rb +161 -0
  108. data/lib/openhab/dsl/rules/triggers/conditions/proc.rb +164 -0
  109. data/lib/openhab/dsl/rules/triggers/cron/cron.rb +195 -0
  110. data/lib/openhab/dsl/rules/triggers/cron/cron_handler.rb +127 -0
  111. data/lib/openhab/dsl/rules/triggers/trigger.rb +56 -0
  112. data/lib/openhab/dsl/rules/triggers/updated.rb +130 -0
  113. data/lib/openhab/dsl/rules/triggers/watch/watch.rb +55 -0
  114. data/lib/openhab/dsl/rules/triggers/watch/watch_handler.rb +155 -0
  115. data/lib/openhab/dsl/rules/triggers.rb +12 -0
  116. data/lib/openhab/dsl/rules.rb +29 -0
  117. data/lib/openhab/dsl/script_handling.rb +55 -0
  118. data/lib/openhab/dsl/things/builder.rb +263 -0
  119. data/lib/openhab/dsl/thread_local.rb +48 -0
  120. data/lib/openhab/dsl/timer_manager.rb +191 -0
  121. data/lib/openhab/dsl/version.rb +9 -0
  122. data/lib/openhab/dsl.rb +686 -0
  123. data/lib/openhab/log.rb +348 -0
  124. data/lib/openhab/osgi.rb +70 -0
  125. data/lib/openhab/rspec/configuration.rb +56 -0
  126. data/lib/openhab/rspec/example_group.rb +90 -0
  127. data/lib/openhab/rspec/helpers.rb +439 -0
  128. data/lib/openhab/rspec/hooks.rb +93 -0
  129. data/lib/openhab/rspec/jruby.rb +46 -0
  130. data/lib/openhab/rspec/karaf.rb +811 -0
  131. data/lib/openhab/rspec/mocks/bundle_install_support.rb +25 -0
  132. data/lib/openhab/rspec/mocks/bundle_resolver.rb +30 -0
  133. data/lib/openhab/rspec/mocks/event_admin.rb +146 -0
  134. data/lib/openhab/rspec/mocks/metadata_provider.rb +75 -0
  135. data/lib/openhab/rspec/mocks/persistence_service.rb +140 -0
  136. data/lib/openhab/rspec/mocks/safe_caller.rb +40 -0
  137. data/lib/openhab/rspec/mocks/synchronous_executor.rb +56 -0
  138. data/lib/openhab/rspec/mocks/thing_handler.rb +76 -0
  139. data/lib/openhab/rspec/mocks/timer.rb +95 -0
  140. data/lib/openhab/rspec/openhab/core/actions.rb +26 -0
  141. data/lib/openhab/rspec/openhab/core/items/proxy.rb +27 -0
  142. data/lib/openhab/rspec/openhab/core/things/proxy.rb +27 -0
  143. data/lib/openhab/rspec/shell.rb +31 -0
  144. data/lib/openhab/rspec/suspend_rules.rb +60 -0
  145. data/lib/openhab/rspec.rb +17 -0
  146. data/lib/openhab/yard/cli/stats.rb +23 -0
  147. data/lib/openhab/yard/code_objects/group_object.rb +17 -0
  148. data/lib/openhab/yard/code_objects/java/base.rb +31 -0
  149. data/lib/openhab/yard/code_objects/java/class_object.rb +11 -0
  150. data/lib/openhab/yard/code_objects/java/field_object.rb +15 -0
  151. data/lib/openhab/yard/code_objects/java/interface_object.rb +15 -0
  152. data/lib/openhab/yard/code_objects/java/package_object.rb +11 -0
  153. data/lib/openhab/yard/code_objects/java/proxy.rb +23 -0
  154. data/lib/openhab/yard/handlers/jruby/base.rb +49 -0
  155. data/lib/openhab/yard/handlers/jruby/class_handler.rb +18 -0
  156. data/lib/openhab/yard/handlers/jruby/constant_handler.rb +18 -0
  157. data/lib/openhab/yard/handlers/jruby/java_import_handler.rb +27 -0
  158. data/lib/openhab/yard/handlers/jruby/mixin_handler.rb +23 -0
  159. data/lib/openhab/yard/html_helper.rb +44 -0
  160. data/lib/openhab/yard/tags/constant_directive.rb +20 -0
  161. data/lib/openhab/yard/tags/group_directive.rb +24 -0
  162. data/lib/openhab/yard/tags/library.rb +3 -0
  163. data/lib/openhab/yard.rb +32 -0
  164. metadata +504 -0
@@ -0,0 +1,439 @@
1
+ # frozen_string_literal: true
2
+
3
+ module OpenHAB
4
+ #
5
+ # Contains loaded Ruby transformations as class methods
6
+ #
7
+ # Only during testing.
8
+ #
9
+ # @example Corresponds to `transform/compass.script`
10
+ # OpenHAB::Transform.compass("59 °")
11
+ #
12
+ # @example Corresponds to `transform/compass.script`
13
+ # OpenHAB::Transform.compass("30", param: "7")
14
+ #
15
+ # @example Corresponds to `transform/ruby/compass.script`
16
+ # OpenHAB::Transform::Ruby.compass("59 °")
17
+
18
+ module Transform
19
+ class << self
20
+ # @!visibility private
21
+ def add_script(modules, script)
22
+ full_name = modules.join("/")
23
+ name = modules.pop
24
+ (@scripts ||= {})[full_name] = engine_factory.script_engine.compile(script)
25
+
26
+ mod = modules.inject(self) { |m, n| m.const_get(n, false) }
27
+ mod.singleton_class.define_method(name) do |input, **kwargs|
28
+ Transform.send(:transform, full_name, input, kwargs)
29
+ end
30
+ end
31
+
32
+ private
33
+
34
+ def engine_factory
35
+ @engine_factory ||= org.jruby.embed.jsr223.JRubyEngineFactory.new
36
+ end
37
+
38
+ def transform(name, input, kwargs)
39
+ script = @scripts[name]
40
+ ctx = script.engine.context
41
+ ctx.set_attribute("input", input.to_s, javax.script.ScriptContext::ENGINE_SCOPE)
42
+ kwargs.each do |(k, v)|
43
+ ctx.set_attribute(k.to_s, v.to_s, javax.script.ScriptContext::ENGINE_SCOPE)
44
+ end
45
+ script.eval
46
+ end
47
+ end
48
+ end
49
+ end
50
+
51
+ module OpenHAB
52
+ module RSpec
53
+ #
54
+ # Provides helper methods for use in specs, to easily work with and adjust
55
+ # the OpenHAB environment.
56
+ #
57
+ # These methods are automatically available in RSpec spec blocks, as well
58
+ # as other per-spec hooks like `before` and `after`. You can also call them
59
+ # explicitly.
60
+ #
61
+ module Helpers
62
+ module BindingHelper
63
+ # @!visibility private
64
+ def add_kwargs_to_current_binding(binding, kwargs)
65
+ kwargs.each { |(k, v)| binding.local_variable_set(k, v) }
66
+ end
67
+ end
68
+ private_constant :BindingHelper
69
+
70
+ # Yard crashes on this; be tricky so it doesn't realize what's going on
71
+ s = singleton_class
72
+ s.include(Helpers)
73
+
74
+ module_function
75
+
76
+ #
77
+ # Reconfigure all items to autoupdate
78
+ #
79
+ # To bypass any items configured to not autoupdate, waiting for the binding to update them.
80
+ #
81
+ # @return [void]
82
+ #
83
+ def autoupdate_all_items
84
+ if instance_variable_defined?(:@autoupdated_items)
85
+ raise RuntimeError "You should only call `autoupdate_all_items` once per spec"
86
+ end
87
+
88
+ @autoupdated_items = []
89
+
90
+ $ir.for_each do |_provider, item|
91
+ if (hash = item.metadata.delete("autoupdate"))
92
+ @autoupdated_items << hash
93
+ item.metadata["autoupdate"] = "true"
94
+ end
95
+ end
96
+ end
97
+
98
+ #
99
+ # Execute all pending timers
100
+ #
101
+ # @return [void]
102
+ #
103
+ def execute_timers
104
+ raise "Cannot execute timers when timers aren't mocked" unless self.class.mock_timers?
105
+
106
+ now = ZonedDateTime.now
107
+ DSL::TimerManager.instance.instance_variable_get(:@timers).each_key do |t|
108
+ t.execute if t.active? && t.execution_time <= now
109
+ end
110
+ end
111
+
112
+ #
113
+ # Wait `duration` seconds, then execute any pending timers
114
+ #
115
+ # If timers are mocked, it will use Timecop. If they're not mocked, it
116
+ # will just sleep for `duration`
117
+ #
118
+ # @return [void]
119
+ #
120
+ def time_travel_and_execute_timers(duration)
121
+ if self.class.mock_timers?
122
+ Timecop.travel(duration)
123
+ execute_timers
124
+ else
125
+ sleep duration
126
+ end
127
+ end
128
+
129
+ #
130
+ # Suspend rules for the duration of the block
131
+ #
132
+ # @return [Object] The return value from the block.
133
+ #
134
+ def suspend_rules(&block)
135
+ SuspendRules.suspend_rules(&block)
136
+ end
137
+
138
+ #
139
+ # Calls the block repeatedly until the expectations inside pass.
140
+ #
141
+ # @param [Duration] how_long how long to keep trying before giving up
142
+ # @yield
143
+ # @return [void]
144
+ def wait(how_long = 2.seconds)
145
+ now = Time.now
146
+
147
+ begin
148
+ yield
149
+ rescue ::RSpec::Expectations::ExpectationNotMetError,
150
+ ::RSpec::Mocks::MockExpectationError
151
+ raise if Time.now > now + how_long
152
+
153
+ sleep 0.1
154
+ retry
155
+ end
156
+ end
157
+
158
+ #
159
+ # Manually send an event to a trigger channel
160
+ #
161
+ # @param [String, Core::Things::Channel, Core::Things::ChannelUID] channel The channel to trigger.
162
+ # @param [String] event The event data to send to the channel.
163
+ # @return [void]
164
+ #
165
+ def trigger_channel(channel, event = "")
166
+ channel = org.openhab.core.thing.ChannelUID.new(channel) if channel.is_a?(String)
167
+ channel = channel.uid if channel.is_a?(org.openhab.core.thing.Channel)
168
+ thing = channel.thing
169
+ thing.handler.callback.channel_triggered(nil, channel, event)
170
+ end
171
+
172
+ #
173
+ # Require all files configured to be autorequired with the jrubyscripting addon in OpenHAB.
174
+ #
175
+ # This method is normally called by RSpec hooks.
176
+ #
177
+ # @return [void]
178
+ #
179
+ def autorequires
180
+ requires = jrubyscripting_config&.get("require") || ""
181
+ requires.split(",").each do |f|
182
+ require f.strip
183
+ end
184
+ end
185
+
186
+ #
187
+ # Launch the karaf instance
188
+ #
189
+ # This method is normally called by RSpec hooks.
190
+ #
191
+ # @return [void]
192
+ # @see Configuration
193
+ #
194
+ def launch_karaf(include_bindings: true,
195
+ include_jsondb: true,
196
+ private_confdir: false,
197
+ use_root_instance: false)
198
+ karaf = Karaf.new("#{Dir.pwd}/.karaf")
199
+ karaf.include_bindings = include_bindings
200
+ karaf.include_jsondb = include_jsondb
201
+ karaf.private_confdir = private_confdir
202
+ karaf.use_root_instance = use_root_instance
203
+ main = karaf.launch
204
+
205
+ ENV["RUBYLIB"] ||= ""
206
+ ENV["RUBYLIB"] += ":" unless ENV["RUBYLIB"].empty?
207
+ ENV["RUBYLIB"] += rubylib_dir
208
+ require "openhab/dsl"
209
+
210
+ require_relative "mocks/persistence_service"
211
+ require_relative "mocks/metadata_provider"
212
+ require_relative "mocks/timer"
213
+
214
+ # override several DSL methods
215
+ require_relative "openhab/core/items/proxy"
216
+ require_relative "openhab/core/things/proxy"
217
+ require_relative "openhab/core/actions"
218
+
219
+ ps = Mocks::PersistenceService.instance
220
+ bundle = org.osgi.framework.FrameworkUtil.get_bundle(org.openhab.core.persistence.PersistenceService)
221
+ bundle.bundle_context.register_service(org.openhab.core.persistence.PersistenceService.java_class, ps, nil)
222
+
223
+ # wait for the rule engine
224
+ rs = OSGi.service("org.openhab.core.service.ReadyService")
225
+ filter = org.openhab.core.service.ReadyMarkerFilter.new
226
+ .with_type(org.openhab.core.service.StartLevelService::STARTLEVEL_MARKER_TYPE)
227
+ .with_identifier(org.openhab.core.service.StartLevelService::STARTLEVEL_RULEENGINE.to_s)
228
+
229
+ karaf.send(:wait) do |continue|
230
+ rs.register_tracker(org.openhab.core.service.ReadyService::ReadyTracker.impl { continue.call }, filter)
231
+ end
232
+
233
+ # RSpec additions
234
+ require_relative "suspend_rules"
235
+
236
+ if defined?(::RSpec)
237
+ ::RSpec.configure do |config|
238
+ config.include OpenHAB::DSL
239
+ end
240
+ end
241
+ main
242
+ rescue Exception => e
243
+ puts e.inspect
244
+ puts e.backtrace
245
+ raise
246
+ end
247
+
248
+ #
249
+ # Load all Ruby rules in the config/automation directory
250
+ #
251
+ # This method is normally called by RSpec hooks.
252
+ #
253
+ # @return [void]
254
+ #
255
+ def load_rules
256
+ automation_path = "#{org.openhab.core.OpenHAB.config_folder}/automation/jsr223"
257
+
258
+ SuspendRules.suspend_rules do
259
+ files = Dir["#{automation_path}/**/*.rb"]
260
+ files.sort_by { |f| [get_start_level(f), f] }.each do |f|
261
+ load f
262
+ rescue Exception => e
263
+ warn "Failed loading #{f}: #{e.inspect}"
264
+ warn e.backtrace
265
+ end
266
+ end
267
+ end
268
+
269
+ #
270
+ # Load all Ruby transformations in the config/transform directory
271
+ #
272
+ # Since Ruby transformations must end with the .script extension, you must include
273
+ # an Emacs modeline comment (`# -*- mode: ruby -*-`) in your script for it to be
274
+ # recognized.
275
+ #
276
+ # This method is normally called by RSpec hooks.
277
+ #
278
+ # @return [void]
279
+ #
280
+ def load_transforms
281
+ transform_path = "#{org.openhab.core.OpenHAB.config_folder}/transform"
282
+ Dir["#{transform_path}/**/*.script"].each do |filename|
283
+ script = File.read(filename)
284
+ next unless ruby_file?(script)
285
+
286
+ filename.slice!(0..transform_path.length)
287
+ dir = File.dirname(filename)
288
+ modules = (dir == ".") ? [] : moduleize(dir)
289
+ basename = File.basename(filename)
290
+ method = basename[0...-7]
291
+ modules << method
292
+ Transform.add_script(modules, script)
293
+ end
294
+ end
295
+
296
+ #
297
+ # Install an OpenHAB addon
298
+ #
299
+ # @param [String] addon_id The addon id, such as "binding-mqtt"
300
+ # @param [true,false] wait Wait until OSGi has confirmed the bundle is installed and running before returning.
301
+ # @param [String,Array<String>] ready_markers Array of ready marker types to wait for.
302
+ # The addon's bundle id is used as the identifier.
303
+ # @return [void]
304
+ #
305
+ def install_addon(addon_id, wait: true, ready_markers: nil)
306
+ addon_service = OSGi.service("org.openhab.core.addon.AddonService")
307
+ addon_service.install(addon_id)
308
+ return unless wait
309
+
310
+ addon = nil
311
+ loop do
312
+ addon = addon_service.get_addon(addon_id, nil)
313
+ break if addon.installed?
314
+
315
+ sleep 0.25
316
+ end
317
+
318
+ return unless ready_markers
319
+
320
+ package_id = addon.logger_packages.first
321
+
322
+ ready_markers = Array(ready_markers).map do |marker|
323
+ case marker
324
+ when String
325
+ org.openhab.core.service.ReadyMarker.new(marker, package_id)
326
+ else
327
+ marker
328
+ end
329
+ end
330
+
331
+ rs = OSGi.service("org.openhab.core.service.ReadyService")
332
+ loop do
333
+ break if ready_markers.all? { |rm| rs.ready?(rm) }
334
+
335
+ sleep 0.25
336
+ end
337
+ end
338
+
339
+ # @return [String] The filename of the OpenHAB log.
340
+ def log_file
341
+ "#{java.lang.System.get_property("openhab.logdir", nil)}/openhab.log"
342
+ end
343
+
344
+ #
345
+ # @return [Array<String>] The log lines since this spec started.
346
+ #
347
+ # @example
348
+ # it "logs" do
349
+ # logger.trace("log line")
350
+ # expect(spec_log_lines).to include(match(/TRACE.*log line/))
351
+ # end
352
+ #
353
+ def spec_log_lines
354
+ File.open(log_file, "rb") do |f|
355
+ f.seek(@log_index) if @log_index
356
+ f.read.split("\n")
357
+ end
358
+ end
359
+
360
+ private
361
+
362
+ def jrubyscripting_config
363
+ ca = OSGi.service("org.osgi.service.cm.ConfigurationAdmin")
364
+ ca.get_configuration("org.openhab.automation.jrubyscripting", nil)&.properties
365
+ end
366
+
367
+ def rubylib_dir
368
+ jrubyscripting_config&.get("rubylib") || "#{org.openhab.core.OpenHAB.config_folder}/automation/lib/ruby"
369
+ end
370
+
371
+ def get_start_level(file)
372
+ return ($1 || $2).to_i if file =~ %r{/sl(\d{2})/[^/]+$|\.sl(\d{2})\.[^/.]+$}
373
+
374
+ 50
375
+ end
376
+
377
+ EMACS_MODELINE_REGEXP = /# -\*-(.+)-\*-/.freeze
378
+ private_constant :EMACS_MODELINE_REGEXP
379
+
380
+ def parse_emacs_modeline(line)
381
+ line[EMACS_MODELINE_REGEXP, 1]
382
+ &.split(";")
383
+ &.map(&:strip)
384
+ &.map { |l| l.split(":", 2).map(&:strip).tap { |a| a[1] ||= nil } }
385
+ &.to_h
386
+ end
387
+
388
+ def ruby_file?(script)
389
+ # check the first 1KB for an emacs magic comment
390
+ script[0..1024].split("\n").any? { |line| parse_emacs_modeline(line)&.dig("mode") == "ruby" }
391
+ end
392
+
393
+ def moduleize(term)
394
+ term
395
+ .sub(/^[a-z\d]*/, &:capitalize)
396
+ .gsub(%r{(?:_|(/))([a-z\d]*)}) { "#{$1}#{$2.capitalize}" }
397
+ .split("/")
398
+ end
399
+
400
+ # need to transfer autoupdate metadata from GenericMetadataProvider to ManagedMetadataProvider
401
+ # so that we can mutate it in the future
402
+ def set_up_autoupdates
403
+ gmp = OSGi.service("org.openhab.core.model.item.internal.GenericMetadataProvider")
404
+ mr = Core::Items::Metadata::NamespaceHash.registry
405
+ mmp = mr.managed_provider.get
406
+ to_add = []
407
+ gmp.all.each do |metadata|
408
+ next unless metadata.uid.namespace == "autoupdate"
409
+
410
+ to_add << metadata
411
+ end
412
+ gmp.remove_metadata_by_namespace("autoupdate")
413
+
414
+ to_add.each do |m|
415
+ # we can't just update; we need to remove and add
416
+ # because this was a duplicate key, so the ManagedProvider
417
+ # knows about it, but the registry does not. So
418
+ # removing and adding gets it to notify the registry
419
+ # that it has it
420
+ mmp.remove(m.uid) if mmp.get(m.uid)
421
+ mmp.add(m)
422
+ end
423
+ end
424
+
425
+ def restore_autoupdate_items
426
+ return unless instance_variable_defined?(:@autoupdated_items)
427
+
428
+ @autoupdated_items&.each(&:commit)
429
+ @autoupdated_items = nil
430
+ end
431
+ end
432
+
433
+ if defined?(::RSpec)
434
+ ::RSpec.configure do |config|
435
+ config.include Helpers
436
+ end
437
+ end
438
+ end
439
+ end
@@ -0,0 +1,93 @@
1
+ # frozen_string_literal: true
2
+
3
+ module OpenHAB
4
+ #
5
+ # This module contains helper methods, hooks, and infrastracture to
6
+ # boot OpenHAB inside of JRuby, and run RSpec (or other Ruby processes)
7
+ # in that context.
8
+ #
9
+ # @see file:testing.md Testing Your Rules
10
+ #
11
+ module RSpec
12
+ Object.include Helpers if defined?(IRB)
13
+
14
+ Helpers.launch_karaf(
15
+ include_bindings: Configuration.include_bindings,
16
+ include_jsondb: Configuration.include_jsondb,
17
+ private_confdir: Configuration.private_confdir,
18
+ use_root_instance: Configuration.use_root_instance
19
+ )
20
+
21
+ if defined?(::RSpec)
22
+ ::RSpec.configure do |config|
23
+ require_relative "example_group"
24
+ config.include ExampleGroup
25
+
26
+ config.before(:suite) do
27
+ Helpers.autorequires unless Configuration.private_confdir
28
+ Helpers.send(:set_up_autoupdates)
29
+ Helpers.load_transforms
30
+ Helpers.load_rules
31
+ end
32
+
33
+ config.before do
34
+ suspend_rules do
35
+ $ir.for_each do |_provider, item|
36
+ next if item.is_a?(GroupItem) # groups only have calculated states
37
+
38
+ item.state = NULL unless item.raw_state == NULL
39
+ end
40
+ end
41
+ @known_rules = Core.rule_registry.all.map(&:uid)
42
+ end
43
+
44
+ config.before do |example|
45
+ @item_provider = DSL::Items::ItemProvider.send(:new)
46
+ allow(DSL::Items::ItemProvider).to receive(:instance).and_return(@item_provider)
47
+ @thing_provider = DSL::Things::ThingProvider.send(:new)
48
+ allow(DSL::Things::ThingProvider).to receive(:instance).and_return(@thing_provider)
49
+ @item_channel_link_provider = DSL::Items::ItemChannelLinkProvider.send(:new)
50
+ allow(DSL::Items::ItemChannelLinkProvider).to receive(:instance).and_return(@item_channel_link_provider)
51
+ mr = Core::Items::Metadata::NamespaceHash.registry
52
+ @metadata_provider = Mocks::MetadataProvider.new(mr.managed_provider.get)
53
+ mr.add_provider(@metadata_provider)
54
+ mr.set_managed_provider(@metadata_provider)
55
+ tm = OSGi.service("org.openhab.core.thing.ThingManager")
56
+ tm.class.field_reader :storage
57
+ tm.storage.keys.each { |k| tm.storage.remove(k) } # rubocop:disable Style/HashEachMethods not a hash
58
+ @log_index = File.size(log_file)
59
+ profile_factory = Core::ProfileFactory.send(:new)
60
+ @profile_factory_registration = OSGi.register_service(profile_factory)
61
+ allow(Core::ProfileFactory).to receive(:instance).and_return(profile_factory)
62
+ stub_const("OpenHAB::Core::Timer", Mocks::Timer) if self.class.mock_timers?
63
+ log_line = "rspec #{example.location} # #{example.full_description}"
64
+ logger.info(log_line)
65
+ Logger.events.info(log_line)
66
+ end
67
+
68
+ config.after do
69
+ # remove rules created during the spec
70
+ (Core.rule_registry.all.map(&:uid) - @known_rules).each do |uid|
71
+ remove_rule(uid) if defined?(remove_rule)
72
+ end
73
+ $ir.remove_provider(@item_provider)
74
+ Core::Items::Proxy.reset_cache
75
+ $things.remove_provider(@thing_provider)
76
+ Core::Things::Proxy.reset_cache
77
+ registry = OSGi.service("org.openhab.core.thing.link.ItemChannelLinkRegistry")
78
+ registry.remove_provider(@item_channel_link_provider)
79
+ Core::Items::Metadata::NamespaceHash.registry.remove_provider(@metadata_provider)
80
+ @metadata_provider.restore_parent
81
+ @profile_factory_registration.unregister
82
+ timers.cancel_all
83
+ # timers and rules have already been canceled, so we can safely just
84
+ # wipe this
85
+ DSL::Items::TimedCommand.timed_commands.clear
86
+ Timecop.return
87
+ restore_autoupdate_items
88
+ Mocks::PersistenceService.instance.reset
89
+ end
90
+ end
91
+ end
92
+ end
93
+ end
@@ -0,0 +1,46 @@
1
+ # frozen_string_literal: true
2
+
3
+ module OpenHAB
4
+ module RSpec
5
+ # @!visibility private
6
+ module JRuby
7
+ # Basically org.jruby.embed.osgi.OSGiIsolatedScriptingContainer$BundleGetResources,
8
+ # but implemented in Ruby so that it doesn't have a hard dependency on
9
+ # org.osgi.bundle.Bundle -- which we may need to load!
10
+ class OSGiBundleClassLoader
11
+ include org.jruby.util.Loader
12
+
13
+ def initialize(bundle)
14
+ @bundle = bundle
15
+ end
16
+
17
+ def get_resource(path)
18
+ @bundle.get_resource(path)
19
+ end
20
+
21
+ def get_resources(path)
22
+ @bundle.get_resources(path)
23
+ end
24
+
25
+ def load_class(name)
26
+ @bundle.load_class(name)
27
+ end
28
+
29
+ def get_class_loader # rubocop:disable Naming/AccessorMethodName
30
+ @bundle&.adapt(org.osgi.framework.wiring.BundleWiring.java_class)&.class_loader
31
+ end
32
+ end
33
+
34
+ module InstanceConfig
35
+ def add_loader(loader)
36
+ # have to use Ruby-style class reference for the defined? check
37
+ if defined?(Java::OrgOsgiFramework::Bundle) && loader.is_a?(org.osgi.framework.Bundle)
38
+ loader = OSGiBundleClassLoader.new(loader)
39
+ end
40
+ super(loader)
41
+ end
42
+ end
43
+ org.jruby.RubyInstanceConfig.prepend(InstanceConfig)
44
+ end
45
+ end
46
+ end