rspec-openhab-scripting 0.0.10-java → 0.0.11-java

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: ec06e2cfb8832f84f5bd953e22904dbf99ec22a97e5ba0b900ee0e09a163a566
4
- data.tar.gz: 959d078c55a2147e8b1899a8efabc6e8d8065fa9c869cda1c1d5ff057784de3c
3
+ metadata.gz: 3ef6a42e87b4466cf53972b65523d3a810060cbeac84497796ab4514b377d445
4
+ data.tar.gz: 0b3be611dddf1a4e5643cdb48fd3f8a05d35eda32e50e9b4062a063f49f6ad17
5
5
  SHA512:
6
- metadata.gz: d806b85fd33ce2c42b09f5442bd7dc752327a21c1aae8df5fa57b615dd4935a96c62274ffab8d5dcad9bc94fe034c3073d0af20221cd59e647e00568ae7acc64
7
- data.tar.gz: a2565829aa4ddd62cdd1e3ac34e23f55ec9794dcbdeac806bb7175c546f2dcca483cddd72116b1f7726d249c983b7407939de8906e0ec442320d2f2b7b515947
6
+ metadata.gz: 0ffaf54c3e64d9a4f1be5ff0ecec757976db0e5ebbddec2d916d254fb9f9f730f3e1e1245fc478a970e8e42509613877ac535f9983aa94288cbb9c1f7abfa826
7
+ data.tar.gz: 3333117270aa1e6b2de597ad9570df51a9f829d0c5da345cdbb1aed5f50d5bf841cdd1b9a140a94cf06695982acce024280ec814f4cbe5b46ab94dce88566b40
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "singleton"
4
+
3
5
  require "openhab/core/osgi"
4
6
 
5
7
  module OpenHAB
@@ -20,6 +22,7 @@ module OpenHAB
20
22
 
21
23
  def initialize(event_manager)
22
24
  @event_manager = event_manager
25
+ @pending_events = nil
23
26
  end
24
27
 
25
28
  def post_event(event)
@@ -27,7 +30,17 @@ module OpenHAB
27
30
  end
28
31
 
29
32
  def send_event(event)
33
+ if @pending_events
34
+ @pending_events << event
35
+ return
36
+ end
37
+
38
+ @pending_events = []
30
39
  @event_manager.handle_event(event)
40
+
41
+ @event_manager.handle_event(@pending_events.shift) until @pending_events.empty?
42
+
43
+ @pending_events = nil
31
44
  end
32
45
  end
33
46
 
@@ -123,6 +136,119 @@ module OpenHAB
123
136
  field_reader :typedEventFactories, :typedEventSubscribers
124
137
  end
125
138
 
139
+ # reimplement to not use a thread
140
+ class EventHandler
141
+ def initialize(typed_event_subscribers, typed_event_factories)
142
+ @typed_event_subscribers = typed_event_subscribers
143
+ @typed_event_factories = typed_event_factories
144
+ end
145
+
146
+ def handle_event(osgi_event)
147
+ type = osgi_event.get_property("type")
148
+ payload = osgi_event.get_property("payload")
149
+ topic = osgi_event.get_property("topic")
150
+ source = osgi_event.get_property("source")
151
+
152
+ if type.is_a?(String) && payload.is_a?(String) && topic.is_a?(String)
153
+ handle_event_internal(type, payload, topic, source) unless type.empty? || payload.empty? || topic.empty?
154
+ else
155
+ logger.error("The handled OSGi event is invalid. " \
156
+ "Expect properties as string named 'type', 'payload', and 'topic'. " \
157
+ "Received event properties are: #{properties.keys.inspect}")
158
+ end
159
+ end
160
+
161
+ private
162
+
163
+ def handle_event_internal(type, payload, topic, source)
164
+ event_factory = @typed_event_factories[type]
165
+ unless event_factory
166
+ logger.debug("Could not find an Event Factory for the event type '#{type}'.")
167
+ return
168
+ end
169
+
170
+ event_subscribers = event_subscribers(type)
171
+ return if event_subscribers.empty?
172
+
173
+ event = create_event(event_factory, type, payload, topic, source)
174
+ return unless event
175
+
176
+ dispatch_event(event_subscribers, event)
177
+ end
178
+
179
+ def event_subscribers(event_type)
180
+ event_type_subscribers = @typed_event_subscribers[event_type]
181
+ all_event_type_subscribers = @typed_event_subscribers["ALL"]
182
+
183
+ subscribers = java.util.HashSet.new
184
+ subscribers.add_all(event_type_subscribers) if event_type_subscribers
185
+ subscribers.add_all(all_event_type_subscribers) if all_event_type_subscribers
186
+ subscribers
187
+ end
188
+
189
+ def create_event(event_factory, type, payload, topic, source)
190
+ event_factory.create_event(type, topic, payload, source)
191
+ rescue Exception => e
192
+ logger.warn("Creation of event failed, because one of the " \
193
+ "registered event factories has thrown an exception: #{e}")
194
+ nil
195
+ end
196
+
197
+ def dispatch_event(event_subscribers, event)
198
+ event_subscribers.each do |event_subscriber|
199
+ filter = event_subscriber.event_filter
200
+ if filter.nil? || filter.apply(event)
201
+ begin
202
+ event_subscriber.receive(event)
203
+ rescue Exception => e
204
+ logger.warn("Dispatching/filtering event for subscriber '#{event_subscriber.class}' failed: #{e}")
205
+ end
206
+ else
207
+ logger.trace("Skip event subscriber (#{event_subscriber.class}) because of its filter.")
208
+ end
209
+ end
210
+ end
211
+ end
212
+
213
+ org.openhab.core.automation.internal.TriggerHandlerCallbackImpl.field_accessor :executor
214
+ org.openhab.core.automation.internal.TriggerHandlerCallbackImpl.field_reader :ruleUID
215
+ class SynchronousExecutor
216
+ include java.util.concurrent.ScheduledExecutorService
217
+ include Singleton
218
+
219
+ def submit(runnable)
220
+ runnable.run
221
+
222
+ java.util.concurrent.CompletableFuture.completed_future(nil)
223
+ end
224
+
225
+ def execute(runnable)
226
+ runnable.run
227
+ end
228
+
229
+ def shutdown_now; end
230
+
231
+ def shutdown?
232
+ false
233
+ end
234
+ end
235
+
236
+ class CallbacksMap < java.util.HashMap
237
+ def put(_rule_uid, trigger_handler)
238
+ trigger_handler.executor.shutdown_now
239
+ trigger_handler.executor = SynchronousExecutor.instance
240
+ super
241
+ end
242
+ end
243
+
244
+ class SynchronousExecutorMap
245
+ include java.util.Map
246
+
247
+ def get(_key)
248
+ SynchronousExecutor.instance
249
+ end
250
+ end
251
+
126
252
  @imported = false
127
253
 
128
254
  class << self
@@ -133,18 +259,13 @@ module OpenHAB
133
259
 
134
260
  @imported = true
135
261
 
136
- # some background java threads get created; kill them at_exit
137
- at_exit do
138
- status = 0
139
- status = $!.status if $!.is_a?(SystemExit)
140
- exit!(status)
141
- end
262
+ org.openhab.core.common.ThreadPoolManager.field_accessor :pools
263
+ org.openhab.core.common.ThreadPoolManager.pools = SynchronousExecutorMap.new
142
264
 
143
265
  # OSGiEventManager will create a ThreadedEventHandler on OSGi activation;
144
266
  # we're skipping that, and directly sending to a non-threaded event handler.
145
267
  em = EventManager.new
146
- eh = org.openhab.core.internal.events.EventHandler.new(em.typedEventSubscribers, em.typedEventFactories)
147
- at_exit { eh.close }
268
+ eh = EventHandler.new(em.typedEventSubscribers, em.typedEventFactories)
148
269
  ea = EventAdmin.new(eh)
149
270
  ep = org.openhab.core.internal.events.OSGiEventPublisher.new(ea)
150
271
  bc = BundleContext.new(em)
@@ -234,8 +355,7 @@ module OpenHAB
234
355
  $se = $scriptExtension = sew
235
356
 
236
357
  # need to create some singletons referencing registries
237
- scheduler = org.openhab.core.internal.scheduler.SchedulerImpl.new
238
- org.openhab.core.model.script.ScriptServiceUtil.new(ir, tr, ep, nil, scheduler)
358
+ org.openhab.core.model.script.ScriptServiceUtil.new(ir, tr, ep, nil, nil)
239
359
  org.openhab.core.model.script.internal.engine.action.SemanticsActionService.new(ir)
240
360
 
241
361
  # link up event bus infrastructure
@@ -265,6 +385,12 @@ module OpenHAB
265
385
 
266
386
  rs = org.openhab.core.internal.service.ReadyServiceImpl.new
267
387
  re = org.openhab.core.automation.internal.RuleEngineImpl.new(mtr, rr, ss, rs)
388
+ # overwrite thCallbacks to one that will spy to remove threading
389
+ field = re.class.java_class.declared_field("thCallbacks")
390
+ field.accessible = true
391
+ field.set(re, CallbacksMap.new)
392
+ re.class.field_accessor :executor
393
+ re.executor = SynchronousExecutor.instance
268
394
  re.add_module_handler_factory(cmhf)
269
395
  re.add_module_handler_factory(scmhf)
270
396
  re.add_module_handler_factory(spmhf)
@@ -0,0 +1,85 @@
1
+ # frozen_string_literal: true
2
+
3
+ module OpenHAB
4
+ module DSL
5
+ class Timer
6
+ module MockedZonedDateTime
7
+ def now
8
+ mocked_time_stack_item = Timecop.top_stack_item
9
+ return super unless mocked_time_stack_item
10
+
11
+ instant = java.time.Instant.of_epoch_milli((Time.now.to_f * 1000).to_i)
12
+ ZonedDateTime.of_instant(instant, java.time.ZoneId.system_default)
13
+ end
14
+ end
15
+ ZonedDateTime.singleton_class.prepend(MockedZonedDateTime)
16
+
17
+ # extend Timecop to support java time classes
18
+ module TimeCopStackItem
19
+ def parse_time(*args)
20
+ if args.length == 1 && args.first.is_a?(Duration)
21
+ return time_klass.at(ZonedDateTime.now.plus(args.first).to_instant.to_epoch_milli / 1000.0)
22
+ end
23
+
24
+ super
25
+ end
26
+ end
27
+ Timecop::TimeStackItem.prepend(TimeCopStackItem)
28
+
29
+ attr_reader :execution_time
30
+
31
+ def initialize(duration:, thread_locals: {}, &block) # rubocop:disable Lint/UnusedMethodArgument
32
+ @block = block
33
+ reschedule(duration)
34
+ end
35
+
36
+ def reschedule(duration = nil)
37
+ @duration = duration || @duration
38
+ @execution_time = ZonedDateTime.now.plus(@duration)
39
+ @executed = @cancelled = false
40
+
41
+ Timers.timer_manager.add(self)
42
+ end
43
+
44
+ def execute
45
+ raise "Timer already cancelled" if cancelled?
46
+ raise "Timer already executed" if terminated?
47
+
48
+ @block.call
49
+ Timers.timer_manager.delete(self)
50
+ @executed = true
51
+ end
52
+
53
+ def cancel
54
+ Timers.timer_manager.delete(self)
55
+ @executed = false
56
+ @cancelled = true
57
+ true
58
+ end
59
+
60
+ def cancelled?
61
+ @cancelled
62
+ end
63
+
64
+ def terminated?
65
+ @executed || @cancelled
66
+ end
67
+
68
+ def running?
69
+ active? && @execution_time > ZonedDateTime.now
70
+ end
71
+
72
+ def active?
73
+ !terminated?
74
+ end
75
+ end
76
+
77
+ module Support
78
+ class TimerManager
79
+ def execute_timers
80
+ @timers.each { |t| t.execute if t.active? && t.execution_time < ZonedDateTime.now }
81
+ end
82
+ end
83
+ end
84
+ end
85
+ end
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RSpec
4
+ module OpenHAB
5
+ module Helpers
6
+ def autoupdate_all_items
7
+ @autoupdated_items ||= {}
8
+ $ir.for_each do |_provider, item|
9
+ @autoupdated_items[item] = item.meta.delete("autoupdate") if item.meta.key?("autoupdate")
10
+ end
11
+ end
12
+
13
+ def execute_timers
14
+ OpenHAB::DSL::Timers.timer_manager.execute_timers
15
+ end
16
+
17
+ def suspend_rules(&block)
18
+ SuspendRules.suspend_rules(&block)
19
+ end
20
+
21
+ def trigger_rule(rule_name, event = nil)
22
+ @rules ||= ::OpenHAB::DSL::Rules::Rule.script_rules.each_with_object({}) { |r, obj| obj[r.name] = r }
23
+
24
+ @rules.fetch(rule_name).execute(nil, { "event" => event })
25
+ end
26
+
27
+ private
28
+
29
+ def restore_autoupdate_items
30
+ return unless instance_variable_defined?(:@autoupdated_items)
31
+
32
+ @autoupdated_items.each do |(item, meta)|
33
+ item.meta["autoupdate"] = meta
34
+ end
35
+ end
36
+ end
37
+
38
+ RSpec.configure do |config|
39
+ config.include Helpers
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.configure do |config|
4
+ config.before(:all) { OpenHAB::DSL::Timers.timer_manager.cancel_all }
5
+
6
+ config.before do
7
+ suspend_rules do
8
+ $ir.for_each do |_provider, item|
9
+ item.state = NULL unless item.raw_state == NULL
10
+ end
11
+ end
12
+ end
13
+ config.after do
14
+ OpenHAB::DSL::Timers.timer_manager.cancel_all
15
+ Timecop.return
16
+ restore_autoupdate_items
17
+ end
18
+ end
@@ -49,30 +49,6 @@ module RSpec
49
49
  end
50
50
  end
51
51
  end
52
-
53
- def autoupdate_all_items
54
- @autoupdated_items ||= {}
55
- $ir.for_each do |_provider, item|
56
- @autoupdated_items[item] = item.meta.delete("autoupdate") if item.meta.key?("autoupdate")
57
- end
58
- end
59
-
60
- private
61
-
62
- def restore_autoupdate_items
63
- return unless instance_variable_defined?(:@autoupdated_items)
64
-
65
- @autoupdated_items.each do |(item, meta)|
66
- item.meta["autoupdate"] = meta
67
- end
68
- end
69
-
70
- ::RSpec.configure do |config|
71
- config.include(self)
72
- config.after do
73
- restore_autoupdate_items
74
- end
75
- end
76
52
  end
77
53
  end
78
54
  end
@@ -0,0 +1,56 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RSpec
4
+ module OpenHAB
5
+ module SuspendRules
6
+ # I'd prefer to prepend a module, but I can't because of
7
+ # https://github.com/jruby/jruby/issues/6966#issuecomment-1172983776
8
+ class ::OpenHAB::DSL::Rules::AutomationRule # rubocop:disable Style/ClassAndModuleChildren
9
+ def execute(mod = nil, inputs = nil)
10
+ if SuspendRules.suspended?
11
+ logger.debug("Skipping execution of #{name} because rules are suspended.")
12
+ return
13
+ end
14
+
15
+ # super
16
+ ::OpenHAB::DSL.import_presets
17
+ thread_local(RULE_NAME: name) do
18
+ logger.trace { "Execute called with mod (#{mod&.to_string}) and inputs (#{inputs.inspect})" }
19
+ logger.trace { "Event details #{inputs["event"].inspect}" } if inputs&.key?("event")
20
+ trigger_conditions(inputs).process(mod: mod, inputs: inputs) do
21
+ process_queue(create_queue(inputs), mod, inputs)
22
+ end
23
+ end
24
+ end
25
+ end
26
+ # private_constant :AutomationRule
27
+ # ::OpenHAB::DSL::Rules::AutomationRule.prepend(AutomationRule)
28
+
29
+ module Timers
30
+ def after(*)
31
+ return if SuspendRules.suspended?
32
+
33
+ super
34
+ end
35
+ end
36
+ private_constant :Timers
37
+ ::Object.prepend(Timers)
38
+
39
+ @suspended = false
40
+
41
+ class << self
42
+ def suspend_rules
43
+ old_suspended = @suspended
44
+ @suspended = true
45
+ yield
46
+ ensure
47
+ @suspended = old_suspended
48
+ end
49
+
50
+ def suspended?
51
+ @suspended
52
+ end
53
+ end
54
+ end
55
+ end
56
+ end
@@ -2,6 +2,6 @@
2
2
 
3
3
  module RSpec
4
4
  module OpenHAB
5
- VERSION = "0.0.10"
5
+ VERSION = "0.0.11"
6
6
  end
7
7
  end
@@ -57,10 +57,13 @@ def oh.config_folder
57
57
  end
58
58
 
59
59
  # global variables need to be set up before openhab-scripting loads
60
+ require "timecop"
60
61
  require "openhab/log/logger"
61
62
  require "rspec/openhab/core/logger"
63
+
62
64
  # during testing, we don't want "regular" output from rules
63
65
  OpenHAB::Log.logger("org.openhab.automation.jruby.runtime").level = :warn
66
+ OpenHAB::Log.logger("org.openhab.automation.jruby.logger").level = :warn
64
67
  require "openhab/dsl/imports"
65
68
  OpenHAB::DSL::Imports.api = api
66
69
  OpenHAB::DSL::Imports.import_presets
@@ -70,19 +73,24 @@ require "openhab"
70
73
  require "rspec/openhab/actions"
71
74
  require "rspec/openhab/core/cron_scheduler"
72
75
 
76
+ # override several timer methods
77
+ require_relative "rspec/openhab/dsl/timers/timer"
78
+
73
79
  # RSpec additions
74
80
  require "rspec/core"
75
81
  require "rspec/openhab/dsl/rules/rspec"
76
82
  require "rspec/openhab/items"
77
- require "rspec/openhab/state"
78
- require "rspec/openhab/timer"
79
- require "rspec/openhab/trigger"
80
- require "rspec/openhab/wait"
83
+ require "rspec/openhab/helpers"
84
+ require "rspec/openhab/hooks"
85
+ require "rspec/openhab/suspend_rules"
86
+
81
87
  RSpec.configure do |config|
82
88
  config.include OpenHAB::Core::EntityLookup
83
89
  end
84
90
 
85
- RSpec::OpenHAB::Items.populate_items_from_api(api)
91
+ RSpec::OpenHAB::SuspendRules.suspend_rules do
92
+ RSpec::OpenHAB::Items.populate_items_from_api(api)
93
+ end
86
94
 
87
95
  # make bundler/inline _not_ destroy the already existing load path
88
96
  module Bundler
@@ -96,7 +104,7 @@ OPENHAB_AUTOMATION_PATH = "#{org.openhab.core.OpenHAB.config_folder}/automation/
96
104
 
97
105
  Dir["#{OPENHAB_AUTOMATION_PATH}/*.rb"].each do |f|
98
106
  load f
99
- rescue Exception => e # rubocop:disable Lint/RescueException
107
+ rescue Exception => e
100
108
  warn "Failed loading #{f}: #{e.inspect}"
101
109
  warn e.backtrace
102
110
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rspec-openhab-scripting
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.10
4
+ version: 0.0.11
5
5
  platform: java
6
6
  authors:
7
7
  - Cody Cutrer
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2022-07-02 00:00:00.000000000 Z
11
+ date: 2022-07-03 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  requirement: !ruby/object:Gem::Requirement
@@ -94,6 +94,20 @@ dependencies:
94
94
  - - "~>"
95
95
  - !ruby/object:Gem::Version
96
96
  version: '3.3'
97
+ - !ruby/object:Gem::Dependency
98
+ requirement: !ruby/object:Gem::Requirement
99
+ requirements:
100
+ - - "~>"
101
+ - !ruby/object:Gem::Version
102
+ version: '0.9'
103
+ name: timecop
104
+ prerelease: false
105
+ type: :runtime
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - "~>"
109
+ - !ruby/object:Gem::Version
110
+ version: '0.9'
97
111
  - !ruby/object:Gem::Dependency
98
112
  requirement: !ruby/object:Gem::Requirement
99
113
  requirements:
@@ -182,12 +196,12 @@ files:
182
196
  - lib/rspec/openhab/core/script_handling.rb
183
197
  - lib/rspec/openhab/dsl/imports.rb
184
198
  - lib/rspec/openhab/dsl/rules/rspec.rb
199
+ - lib/rspec/openhab/dsl/timers/timer.rb
200
+ - lib/rspec/openhab/helpers.rb
201
+ - lib/rspec/openhab/hooks.rb
185
202
  - lib/rspec/openhab/items.rb
186
- - lib/rspec/openhab/state.rb
187
- - lib/rspec/openhab/timer.rb
188
- - lib/rspec/openhab/trigger.rb
203
+ - lib/rspec/openhab/suspend_rules.rb
189
204
  - lib/rspec/openhab/version.rb
190
- - lib/rspec/openhab/wait.rb
191
205
  - vendor/gems/jar-dependencies-1.0.0/lib/jar-dependencies.rb
192
206
  - vendor/gems/jar-dependencies-1.0.0/lib/jar_dependencies.rb
193
207
  - vendor/gems/jar-dependencies-1.0.0/lib/jar_install_post_install_hook.rb
@@ -1,20 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- RSpec.configure do |config|
4
- config.before(:each) do
5
- ep = $ir.event_publisher
6
-
7
- # stash event publishers to avoid triggering any rules
8
- $ir.for_each do |_provider, item|
9
- item.event_publisher = nil
10
- end
11
-
12
- $ir.for_each do |_provider, item| # rubocop:disable Style/CombinableLoops
13
- item.state = NULL # don't use update, to avoid triggering any rules
14
- end
15
- ensure
16
- $ir.for_each do |_provider, item|
17
- item.event_publisher = ep
18
- end
19
- end
20
- end
@@ -1,12 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- RSpec.configure do |config|
4
- config.before(:each) do
5
- OpenHAB::DSL::Timers.timer_manager.cancel_all
6
- wait_for_background_tasks
7
- end
8
- config.after(:each) do
9
- OpenHAB::DSL::Timers.timer_manager.cancel_all
10
- wait_for_background_tasks
11
- end
12
- end
@@ -1,17 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module RSpec
4
- module OpenHAB
5
- module Trigger
6
- def trigger_rule(rule_name, event = nil)
7
- @rules ||= ::OpenHAB::DSL::Rules::Rule.script_rules.each_with_object({}) { |r, obj| obj[r.name] = r }
8
-
9
- @rules.fetch(rule_name).execute(nil, { "event" => event })
10
- end
11
- end
12
- end
13
- end
14
-
15
- RSpec.configure do |config|
16
- config.include RSpec::OpenHAB::Trigger
17
- end
@@ -1,82 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module RSpec
4
- module OpenHAB
5
- module Wait
6
- def wait_for_rules
7
- wait_for_background_tasks(timers: false)
8
- end
9
-
10
- def wait_for_timers
11
- wait_for_background_tasks(rules: false)
12
- end
13
-
14
- def wait_for_background_tasks(rules: true, timers: true)
15
- loop do
16
- sleep 0.1
17
- next if java.lang.Thread.all_stack_traces.any? do |(t, stack)|
18
- # this is just an estimate. I see 9 when it's parked waiting
19
- # for an event, but once it hits ruby it gets real big real quick
20
- min_frames = 15
21
-
22
- case t.name
23
- when /^OH-scheduler-/
24
- # timer thread; born and die for each timer
25
- if thread_running?(t) || stack.length > min_frames
26
- logger.debug "thread #{t.name} is running (#{stack.length})"
27
- stack.each do |frame|
28
- logger.trace " #{frame}"
29
- end
30
- next timers
31
- end
32
- when /^OH-rule-/
33
-
34
- if thread_running?(t) || stack.length > min_frames
35
- logger.debug "thread #{t.name} is running (#{stack.length})"
36
- stack.each do |frame|
37
- logger.trace " #{frame}"
38
- end
39
-
40
- next rules
41
- end
42
- when /^OH-(?:eventwatcher|eventexecutor)-/
43
- # an event is making its way through the system
44
- if thread_running?(t)
45
- logger.debug "thread #{t.name} is running"
46
- next rules
47
- end
48
- end
49
- end
50
-
51
- # no need to retry if there were no timers
52
- break unless timers && wait_for_next_timer
53
- end
54
- end
55
-
56
- private
57
-
58
- def thread_running?(thread)
59
- ![java.lang.Thread::State::WAITING,
60
- java.lang.Thread::State::TIMED_WAITING].include?(thread.state)
61
- end
62
-
63
- def wait_for_next_timer
64
- latest = ::OpenHAB::DSL::Timers.timer_manager.instance_variable_get(:@timers).min_by(&:execution_time)
65
- return false unless latest
66
-
67
- delta = (latest.execution_time.to_instant.to_epoch_milli - java.time.Instant.now.to_epoch_milli) / 1000.0
68
- # in the past? it's probably executing
69
- return true if delta.negative?
70
-
71
- logger.info("Waiting #{delta}s for next timer") if delta > 5
72
-
73
- sleep(delta)
74
- true
75
- end
76
- end
77
- end
78
- end
79
-
80
- RSpec.configure do |config|
81
- config.include RSpec::OpenHAB::Wait
82
- end