Class: Scarpe::ControlInterface
- Inherits:
-
Object
- Object
- Scarpe::ControlInterface
- Includes:
- Test::Helpers, Shoes::Log
- Defined in:
- lib/scarpe/wasm/control_interface.rb,
lib/scarpe/wasm/control_interface_test.rb
Constant Summary collapse
- SUBSCRIBE_EVENTS =
[:init, :shutdown, :next_redraw, :every_redraw, :next_heartbeat, :every_heartbeat]
- DISPATCH_EVENTS =
[:init, :shutdown, :redraw, :heartbeat]
Instance Attribute Summary collapse
-
#do_shutdown ⇒ Object
readonly
Returns the value of attribute do_shutdown.
- #doc_root ⇒ Object
Instance Method Summary collapse
-
#all_wv_widgets ⇒ Object
Need to be able to query widgets in test code.
- #app ⇒ Object
- #assert(value, msg = nil) ⇒ Object
- #assert_equal(val1, val2, msg = nil) ⇒ Object
-
#assert_js(js_code, wait_for: [], timeout: DEFAULT_ASSERTION_TIMEOUT) ⇒ Object
Create a promise to do a JS assertion, normally after other ops have finished.
- #assertion_data_as_a_struct ⇒ Object
- #assertions_may_exist ⇒ Object
- #assertions_pending? ⇒ Boolean
- #die_after(time) ⇒ Object
-
#dispatch_event(event, *args, **keywords) ⇒ Object
Send out the specified event.
- #fail_assertion(id, fail_message) ⇒ Object
-
#find_wv_widgets(*specifiers) ⇒ Object
Shoes doesn’t name widgets.
- #fully_updated(wait_for: []) ⇒ Object
-
#initialize ⇒ ControlInterface
constructor
The control interface needs to see major system components to hook into their events.
- #inspect ⇒ Object
-
#on_event(event, &block) ⇒ Object
On recognised events, this sets a handler for that event.
- #pass_assertion(id) ⇒ Object
-
#return_when_assertions_done ⇒ Object
Note that we do not extract this assertions library to use elsewhere because it’s very focused on evented assertions that start and stop over a period of time.
-
#set_system_components(app:, doc_root:, wrangler:) ⇒ Object
This should get called once, from Shoes::App.
- #start_assertion(code) ⇒ Object
-
#test_metadata ⇒ Object
This is returned alongside the actual results automatically.
- #timed_out? ⇒ Boolean
- #with_js_dom_html(wait_for: [], timeout: DEFAULT_ASSERTION_TIMEOUT, &block) ⇒ Object
-
#with_js_value(js_code, wait_for: [], timeout: DEFAULT_ASSERTION_TIMEOUT, &block) ⇒ Object
How do we signal an error?.
- #wrangler ⇒ Object
Constructor Details
#initialize ⇒ ControlInterface
The control interface needs to see major system components to hook into their events
24 25 26 27 28 29 30 |
# File 'lib/scarpe/wasm/control_interface.rb', line 24 def initialize log_init("WV::ControlInterface") @do_shutdown = false @event_handlers = {} (SUBSCRIBE_EVENTS | DISPATCH_EVENTS).each { |e| @event_handlers[e] = [] } end |
Instance Attribute Details
#do_shutdown ⇒ Object (readonly)
Returns the value of attribute do_shutdown.
21 22 23 |
# File 'lib/scarpe/wasm/control_interface.rb', line 21 def do_shutdown @do_shutdown end |
#doc_root ⇒ Object
61 62 63 64 65 66 67 68 |
# File 'lib/scarpe/wasm/control_interface.rb', line 61 def doc_root unless @doc_root raise "ControlInterface code needs to be wrapped in handlers like on_event(:init) " + "to make sure they have access to app, doc_root, wrangler, etc!" end @doc_root end |
Instance Method Details
#all_wv_widgets ⇒ Object
Need to be able to query widgets in test code
51 52 53 54 55 56 57 58 59 60 61 62 63 |
# File 'lib/scarpe/wasm/control_interface_test.rb', line 51 def known = [doc_root] to_check = [doc_root] until to_check.empty? next_layer = to_check.flat_map(&:children) known += next_layer to_check = next_layer end # I don't *think* we'll ever have widget trees that merge back together, but just in case we'll de-dup known.uniq end |
#app ⇒ Object
52 53 54 55 56 57 58 59 |
# File 'lib/scarpe/wasm/control_interface.rb', line 52 def app unless @app raise "ControlInterface code needs to be wrapped in handlers like on_event(:init) " + "to make sure they have access to app, doc_root, wrangler, etc!" end @app end |
#assert(value, msg = nil) ⇒ Object
169 170 171 172 173 174 175 176 177 |
# File 'lib/scarpe/wasm/control_interface_test.rb', line 169 def assert(value, msg = nil) id = start_assertion("#{caller[0]}: #{msg || "Value should be true!"}") if value pass_assertion(id) else fail_assertion(id, "Expected true Ruby value: #{value.inspect}") end end |
#assert_equal(val1, val2, msg = nil) ⇒ Object
179 180 181 |
# File 'lib/scarpe/wasm/control_interface_test.rb', line 179 def assert_equal(val1, val2, msg = nil) assert val1 == val2, (msg || "Expected #{val2.inspect} to equal #{val1.inspect}!") end |
#assert_js(js_code, wait_for: [], timeout: DEFAULT_ASSERTION_TIMEOUT) ⇒ Object
Create a promise to do a JS assertion, normally after other ops have finished.
148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 |
# File 'lib/scarpe/wasm/control_interface_test.rb', line 148 def assert_js(js_code, wait_for: [], timeout: DEFAULT_ASSERTION_TIMEOUT) id = start_assertion(js_code) # this isn't a TestPromise, so it doesn't have the additional DSL entries promise = wrangler.eval_js_async(js_code, wait_for: wait_for, timeout: timeout) promise.on_rejected do fail_assertion(id, "JS Eval failed: #{promise.reason.inspect}") end promise.on_fulfilled do ret_val = promise.returned_value if ret_val pass_assertion(id) else fail_assertion(id, "Expected true JS value: #{ret_val.inspect}") end end # So we wrap it in a no-op TestPromise, to get the DSL entries. TestPromise.new(iface: self, wait_for: [promise]).to_execute {} end |
#assertion_data_as_a_struct ⇒ Object
138 139 140 141 142 143 144 145 |
# File 'lib/scarpe/wasm/control_interface_test.rb', line 138 def assertion_data_as_a_struct { still_pending: @assertions_pending.size, succeeded: @assertions_passed, failed: @assertions_failed.size, failures: @assertions_failed.values.map { |item| [item[:code], item[:failure_reason]] }, } end |
#assertions_may_exist ⇒ Object
102 103 104 105 106 107 |
# File 'lib/scarpe/wasm/control_interface_test.rb', line 102 def assertions_may_exist @assertions_pending ||= {} @assertions_failed ||= {} @assertions_passed ||= 0 @assertion_counter ||= 0 end |
#assertions_pending? ⇒ Boolean
134 135 136 |
# File 'lib/scarpe/wasm/control_interface_test.rb', line 134 def assertions_pending? !@assertions_pending.empty? end |
#die_after(time) ⇒ Object
20 21 22 23 24 25 26 27 28 29 30 31 32 33 |
# File 'lib/scarpe/wasm/control_interface_test.rb', line 20 def die_after(time) t_start = Time.now @die_after = [t_start, time] wrangler.periodic_code("scarpeTestTimeout") do |*_args| t_delta = (Time.now - t_start).to_f if t_delta > time @did_time_out = true @log.warn("die_after - timed out after #{t_delta.inspect} (threshold: #{time.inspect})") return_results(false, "Timed out!") app.destroy end end end |
#dispatch_event(event, *args, **keywords) ⇒ Object
Send out the specified event
96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 |
# File 'lib/scarpe/wasm/control_interface.rb', line 96 def dispatch_event(event, *args, **keywords) @log.debug("CTL event #{event.inspect} #{args.inspect} #{keywords.inspect}") unless DISPATCH_EVENTS.include?(event) raise "Illegal dispatch of event #{event.inspect}! Valid values are: #{DISPATCH_EVENTS.inspect}" end if @do_shutdown @log.debug("CTL: Shutting down - not dispatching #{event}!") return end if event == :redraw dumb_dispatch_event(:every_redraw, *args, **keywords) # Next redraw is interesting. We can add new handlers # when dispatching a next_redraw handler. But we want # each handler to run only once. handlers = @event_handlers[:next_redraw] dumb_dispatch_event(:next_redraw, *args, **keywords) @event_handlers[:next_redraw] -= handlers return end if event == :heartbeat dumb_dispatch_event(:every_heartbeat, *args, **keywords) # Next heartbeat is interesting. We can add new handlers # when dispatching a next_heartbeat handler. But we want # each handler to run only once. handlers = @event_handlers[:next_heartbeat] dumb_dispatch_event(:next_heartbeat, *args, **keywords) @event_handlers[:next_heartbeat] -= handlers return end if event == :shutdown @do_shutdown = true end dumb_dispatch_event(event, *args, **keywords) end |
#fail_assertion(id, fail_message) ⇒ Object
128 129 130 131 132 |
# File 'lib/scarpe/wasm/control_interface_test.rb', line 128 def fail_assertion(id, ) item = @assertions_pending.delete(id) item[:fail_message] = @assertions_failed[id] = item end |
#find_wv_widgets(*specifiers) ⇒ Object
Shoes doesn’t name widgets. We aren’t guaranteed that the Shoes widgets are even in the same process, since we have the Relay display service for Webview. So mostly we can look by display service class.
68 69 70 71 72 73 74 75 76 77 78 79 80 |
# File 'lib/scarpe/wasm/control_interface_test.rb', line 68 def (*specifiers) = specifiers.each do |spec| if spec.is_a?(Class) .select! { |w| spec === w } else raise "I don't know how to search for widgets by #{spec.inspect}!" end end end |
#fully_updated(wait_for: []) ⇒ Object
197 198 199 |
# File 'lib/scarpe/wasm/control_interface_test.rb', line 197 def fully_updated(wait_for: []) wrangler.promise_dom_fully_updated end |
#inspect ⇒ Object
32 33 34 |
# File 'lib/scarpe/wasm/control_interface.rb', line 32 def inspect "<#ControlInterface>" end |
#on_event(event, &block) ⇒ Object
On recognised events, this sets a handler for that event
83 84 85 86 87 88 89 90 91 92 93 |
# File 'lib/scarpe/wasm/control_interface.rb', line 83 def on_event(event, &block) unless SUBSCRIBE_EVENTS.include?(event) raise "Illegal subscribe to event #{event.inspect}! Valid values are: #{SUBSCRIBE_EVENTS.inspect}" end @unsub_id ||= 0 @unsub_id += 1 @event_handlers[event] << { handler: block, unsub: @unsub_id } @unsub_id end |
#pass_assertion(id) ⇒ Object
123 124 125 126 |
# File 'lib/scarpe/wasm/control_interface_test.rb', line 123 def pass_assertion(id) @assertions_pending.delete(id) @assertions_passed += 1 end |
#return_when_assertions_done ⇒ Object
Note that we do not extract this assertions library to use elsewhere because it’s very focused on evented assertions that start and stop over a period of time. Instantaneous procedural asserts don’t want to use this API.
90 91 92 93 94 95 96 97 98 99 100 |
# File 'lib/scarpe/wasm/control_interface_test.rb', line 90 def return_when_assertions_done assertions_may_exist wrangler.periodic_code("scarpeReturnWhenAssertionsDone") do |*_args| if @assertions_pending.empty? success = @assertions_failed.empty? return_results success, "Assertions #{success ? "succeeded" : "failed"}", assertion_data_as_a_struct app.destroy end end end |
#set_system_components(app:, doc_root:, wrangler:) ⇒ Object
This should get called once, from Shoes::App
37 38 39 40 41 42 43 44 45 46 47 48 49 50 |
# File 'lib/scarpe/wasm/control_interface.rb', line 37 def set_system_components(app:, doc_root:, wrangler:) unless app && wrangler @log.error("False app passed to set_system_components!") unless app @log.error("False wrangler passed to set_system_components!") unless wrangler raise "Must pass non-nil app and wrangler to ControlInterface#set_system_components!" end @app = app @doc_root = doc_root # May be nil at this point @wrangler = wrangler @wrangler.control_interface = self @wrangler.on_every_redraw { self.dispatch_event(:redraw) } end |
#start_assertion(code) ⇒ Object
109 110 111 112 113 114 115 116 117 118 119 120 121 |
# File 'lib/scarpe/wasm/control_interface_test.rb', line 109 def start_assertion(code) assertions_may_exist this_assertion = @assertion_counter @assertion_counter += 1 @assertions_pending[this_assertion] = { id: this_assertion, code: code, } this_assertion end |
#test_metadata ⇒ Object
This is returned alongside the actual results automatically
36 37 38 39 40 41 42 43 44 45 46 47 |
# File 'lib/scarpe/wasm/control_interface_test.rb', line 36 def data = {} if @die_after t_delta = (Time.now - @die_after[0]).to_f data["die_after"] = { t_start: @die_after[0].to_s, threshold: @die_after[1], passed: t_delta, } end data end |
#timed_out? ⇒ Boolean
16 17 18 |
# File 'lib/scarpe/wasm/control_interface_test.rb', line 16 def timed_out? @did_time_out end |
#with_js_dom_html(wait_for: [], timeout: DEFAULT_ASSERTION_TIMEOUT, &block) ⇒ Object
193 194 195 |
# File 'lib/scarpe/wasm/control_interface_test.rb', line 193 def with_js_dom_html(wait_for: [], timeout: DEFAULT_ASSERTION_TIMEOUT, &block) with_js_value("document.getElementById('wrapper-wvroot').innerHTML", wait_for: wait_for, timeout: timeout, &block) end |
#with_js_value(js_code, wait_for: [], timeout: DEFAULT_ASSERTION_TIMEOUT, &block) ⇒ Object
How do we signal an error?
184 185 186 187 188 189 190 191 |
# File 'lib/scarpe/wasm/control_interface_test.rb', line 184 def with_js_value(js_code, wait_for: [], timeout: DEFAULT_ASSERTION_TIMEOUT, &block) raise "Must give a block to with_js_value!" unless block js_promise = wrangler.eval_js_async(js_code, wait_for: wait_for, timeout: timeout) ruby_promise = TestPromise.new(iface: self, wait_for: [js_promise]) ruby_promise.to_execute(&block) ruby_promise end |
#wrangler ⇒ Object
70 71 72 73 74 75 76 77 |
# File 'lib/scarpe/wasm/control_interface.rb', line 70 def wrangler unless @wrangler raise "ControlInterface code needs to be wrapped in handlers like on_event(:init) " + "to make sure they have access to app, doc_root, wrangler, etc!" end @wrangler end |