roku_builder 4.25.5 → 4.25.6

Sign up to get free protection for your applications and to get access to all the features.
@@ -36,19 +36,21 @@ module RokuBuilder
36
36
 
37
37
  def package(options:)
38
38
  check_options(options)
39
- #sideload
40
- loader = Loader.new(config: @config)
41
- loader.sideload(options: options)
42
- loader.squash(options: options) if @config.stage[:squash]
43
- #rekey
44
- key(options: options)
45
- #package
46
- sign_package(app_name_version: "", password: @config.key[:password], stage: options[:stage])
47
- #inspect
48
- if options[:inspect_package]
49
- @config.in = @config.out
50
- options[:password] = @config.key[:password]
51
- Inspector.new(config: @config).inspect(options: options)
39
+ get_device do |device|
40
+ #sideload
41
+ loader = Loader.new(config: @config)
42
+ loader.sideload(options: options, device: device)
43
+ loader.squash(options: options, device: device) if @config.stage[:squash]
44
+ #rekey
45
+ key(options: options, device: device)
46
+ #package
47
+ sign_package(app_name_version: "", password: @config.key[:password], stage: options[:stage], device: device)
48
+ #inspect
49
+ if options[:inspect_package]
50
+ @config.in = @config.out
51
+ options[:password] = @config.key[:password]
52
+ Inspector.new(config: @config).inspect(options: options, device: device)
53
+ end
52
54
  end
53
55
  end
54
56
 
@@ -76,31 +78,37 @@ module RokuBuilder
76
78
  # @param keyed_pkg [String] Path for a package signed with the desired key
77
79
  # @param password [String] Password for the package
78
80
  # @return [Boolean] True if key changed, false otherwise
79
- def key(options:)
80
- oldId = dev_id
81
-
82
- raise ExecutionError, "Missing Key Config" unless @config.key
83
-
84
- # upload new key with password
85
- payload = {
86
- mysubmit: "Rekey",
87
- passwd: @config.key[:password],
88
- archive: Faraday::UploadIO.new(@config.key[:keyed_pkg], 'application/octet-stream')
89
- }
90
- multipart_connection.post "/plugin_inspect", payload
81
+ def key(options:, device: nil)
82
+ get_device(device: device) do |device|
83
+ oldId = dev_id(device: device)
84
+
85
+ raise ExecutionError, "Missing Key Config" unless @config.key
86
+
87
+ # upload new key with password
88
+ payload = {
89
+ mysubmit: "Rekey",
90
+ passwd: @config.key[:password],
91
+ archive: Faraday::UploadIO.new(@config.key[:keyed_pkg], 'application/octet-stream')
92
+ }
93
+ multipart_connection(device: device) do |conn|
94
+ conn.post "/plugin_inspect", payload
95
+ end
91
96
 
92
- # check key
93
- newId = dev_id
94
- @logger.info("Key did not change") unless newId != oldId
95
- @logger.debug(oldId + " -> " + newId)
97
+ # check key
98
+ newId = dev_id(device: device)
99
+ @logger.info("Key did not change") unless newId != oldId
100
+ @logger.debug(oldId + " -> " + newId)
101
+ end
96
102
  end
97
103
 
98
104
  # Get the current dev id
99
105
  # @return [String] The current dev id
100
- def dev_id
106
+ def dev_id(device: nil)
101
107
  path = "/plugin_package"
102
- conn = simple_connection
103
- response = conn.get path
108
+ response = nil
109
+ simple_connection(device: device) do |conn|
110
+ response = conn.get path
111
+ end
104
112
 
105
113
  dev_id = /Your Dev ID:\s*<font[^>]*>([^<]*)<\/font>/.match(response.body)
106
114
  dev_id ||= /Your Dev ID:[^>]*<\/label> ([^<]*)/.match(response.body)
@@ -118,79 +126,88 @@ module RokuBuilder
118
126
  end
119
127
 
120
128
  # Sign and download the currently sideloaded app
121
- def sign_package(app_name_version:, password:, stage: nil)
122
- payload = {
123
- mysubmit: "Package",
124
- app_name: app_name_version,
125
- passwd: password,
126
- pkg_time: Time.now.to_i
127
- }
128
- response = multipart_connection.post "/plugin_package", payload
129
-
130
- # Check for error
131
- failed = /(Failed: [^\.]*\.)/.match(response.body)
132
- raise ExecutionError, failed[1] if failed
133
-
134
- # Download signed package
135
- pkg = /<a href="pkgs[^>]*>([^<]*)</.match(response.body)[1]
136
- path = "/pkgs/#{pkg}"
137
- conn = Faraday.new(url: @url) do |f|
138
- f.request :digest, @dev_username, @dev_password
139
- f.adapter Faraday.default_adapter
140
- end
141
- response = conn.get path
142
- raise ExecutionError, "Failed to download signed package" if response.status != 200
143
- out_file = nil
144
- unless @config.out[:file]
145
- out = @config.out
146
- build_version = Manifest.new(config: @config).build_version
147
- if stage
148
- out[:file] = "#{@config.project[:app_name]}_#{stage}_#{build_version}"
129
+ def sign_package(app_name_version:, password:, stage: nil, device: nil)
130
+ get_device(device: device) do |device|
131
+ payload = {
132
+ mysubmit: make_param("Package"),
133
+ app_name: make_param(app_name_version),
134
+ passwd: make_param(password),
135
+ pkg_time: make_param(Time.now.to_i)
136
+ }
137
+ response = nil
138
+ multipart_connection(device: device) do |conn|
139
+ response = conn.post "/plugin_package", payload
140
+ end
141
+
142
+ # Check for error
143
+ failed = /(Failed: [^\.]*\.)/.match(response.body)
144
+ raise ExecutionError, failed[1] if failed
145
+
146
+ # Download signed package
147
+ pkg = /<a href="pkgs[^>]*>([^<]*)</.match(response.body)[1]
148
+ path = "/pkgs/#{pkg}"
149
+ conn = Faraday.new(url: "http://#{device.ip}") do |f|
150
+ f.request :digest, device.user, device.password
151
+ f.adapter Faraday.default_adapter
152
+ end
153
+ response = conn.get path
154
+ raise ExecutionError, "Failed to download signed package" if response.status != 200
155
+ out_file = nil
156
+ unless @config.out[:file]
157
+ out = @config.out
158
+ build_version = Manifest.new(config: @config).build_version
159
+ if stage
160
+ out[:file] = "#{@config.project[:app_name]}_#{stage}_#{build_version}"
161
+ else
162
+ out[:file] = "#{@config.project[:app_name]}_working_#{build_version}"
163
+ end
164
+ @config.out = out
165
+ end
166
+ out_file = File.join(@config.out[:folder], @config.out[:file])
167
+ out_file = out_file+".pkg" unless out_file.end_with?(".pkg")
168
+ File.open(out_file, 'w+b') {|fp| fp.write(response.body)}
169
+ if File.exist?(out_file)
170
+ pkg_size = File.size(out_file).to_f / 2**20
171
+ raise ExecutionError, "PKG file size is too large (#{pkg_size.round(2)} MB): #{out_file}" if pkg_size > 4.0
172
+ @logger.info("Outfile: #{out_file}")
149
173
  else
150
- out[:file] = "#{@config.project[:app_name]}_working_#{build_version}"
174
+ @logger.warn("Outfile Missing: #{out_file}")
151
175
  end
152
- @config.out = out
153
- end
154
- out_file = File.join(@config.out[:folder], @config.out[:file])
155
- out_file = out_file+".pkg" unless out_file.end_with?(".pkg")
156
- File.open(out_file, 'w+b') {|fp| fp.write(response.body)}
157
- if File.exist?(out_file)
158
- pkg_size = File.size(out_file).to_f / 2**20
159
- raise ExecutionError, "PKG file size is too large (#{pkg_size.round(2)} MB): #{out_file}" if pkg_size > 4.0
160
- @logger.info("Outfile: #{out_file}")
161
- else
162
- @logger.warn("Outfile Missing: #{out_file}")
163
176
  end
164
177
  end
165
178
 
166
179
  # Uses the device to generate a new signing key
167
180
  # @return [Array<String>] Password and dev_id for the new key
168
- def generate_new_key()
169
- telnet_config = {
170
- 'Host' => @roku_ip_address,
171
- 'Port' => 8080
172
- }
173
- connection = Net::Telnet.new(telnet_config)
174
- connection.puts("genkey")
175
- waitfor_config = {
176
- 'Match' => /./,
177
- 'Timeout' => false
178
- }
181
+ def generate_new_key(device: nil)
179
182
  password = nil
180
183
  dev_id = nil
181
- while password.nil? or dev_id.nil?
182
- connection.waitfor(waitfor_config) do |txt|
183
- while line = txt.slice!(/^.*\n/) do
184
- words = line.split
185
- if words[0] == "Password:"
186
- password = words[1]
187
- elsif words[0] == "DevID:"
188
- dev_id = words[1]
184
+ get_device(device: device) do |device|
185
+ telnet_config = {
186
+ 'Host' => device.ip,
187
+ 'Port' => 8080
188
+ }
189
+ connection = Net::Telnet.new(telnet_config)
190
+ connection.puts("genkey")
191
+ waitfor_config = {
192
+ 'Match' => /./,
193
+ 'Timeout' => false
194
+ }
195
+ password = nil
196
+ dev_id = nil
197
+ while password.nil? or dev_id.nil?
198
+ connection.waitfor(waitfor_config) do |txt|
199
+ while line = txt.slice!(/^.*\n/) do
200
+ words = line.split
201
+ if words[0] == "Password:"
202
+ password = words[1]
203
+ elsif words[0] == "DevID:"
204
+ dev_id = words[1]
205
+ end
189
206
  end
190
207
  end
191
208
  end
209
+ connection.close
192
210
  end
193
- connection.close
194
211
  return password, dev_id
195
212
  end
196
213
  end
@@ -62,10 +62,13 @@ module RokuBuilder
62
62
  end
63
63
 
64
64
  def sgperf(options:)
65
- telnet_config ={
66
- 'Host' => @roku_ip_address,
67
- 'Port' => 8080
68
- }
65
+ telnet_config = nil
66
+ get_device(no_lock: true) do |device|
67
+ telnet_config ={
68
+ 'Host' => device.ip,
69
+ 'Port' => 8080
70
+ }
71
+ end
69
72
  @connection = Net::Telnet.new(telnet_config)
70
73
  @connection.puts("sgperf clear\n")
71
74
  @connection.puts("sgperf start\n")
@@ -97,10 +100,13 @@ module RokuBuilder
97
100
  end
98
101
 
99
102
  def devlog(options:)
100
- telnet_config ={
101
- 'Host' => @roku_ip_address,
102
- 'Port' => 8080
103
- }
103
+ telnet_config = nil
104
+ get_device(no_lock: true) do |device|
105
+ telnet_config ={
106
+ 'Host' => device.ip,
107
+ 'Port' => 8080
108
+ }
109
+ end
104
110
  connection = Net::Telnet.new(telnet_config)
105
111
  connection.puts("enhanced_dev_log #{options[:devlog]} #{options[:devlog_function]}\n")
106
112
  end
@@ -172,7 +178,7 @@ module RokuBuilder
172
178
  def handle_node(stats:, node:)
173
179
  if node
174
180
  node.element_children.each do |element|
175
- attributes = element.attributes.map{|attr| {"#{attr.name}": attr.value}}.reduce({}, :merge)
181
+ attributes = element.attributes.map{|key,attr| {"#{key}": attr.value}}.reduce({}, :merge)
176
182
  stats[element.name] ||= {count: 0, ids: []}
177
183
  stats[element.name][:count] += 1
178
184
  stats[element.name][:ids].push(attributes[:name]) if attributes[:name]
@@ -217,8 +223,8 @@ module RokuBuilder
217
223
  lines.each {|line| print line}
218
224
  end
219
225
  def print_nodes_by_id(id)
220
- start_reg = /<#{id}>/
221
- end_reg = /<\/#{id}>/
226
+ start_reg = /#{id}/
227
+ end_reg = /#{id}/
222
228
  lines = get_command_response(command: "sgnodes #{id}", start_reg: start_reg, end_reg: end_reg)
223
229
  lines.each {|line| print line}
224
230
  end
@@ -259,10 +265,13 @@ module RokuBuilder
259
265
  'Timeout' => 1
260
266
  }
261
267
  unless @connection
262
- telnet_config ={
263
- 'Host' => @roku_ip_address,
264
- 'Port' => 8080
265
- }
268
+ telnet_config = nil
269
+ get_device(no_lock: true) do |device|
270
+ telnet_config ={
271
+ 'Host' => device.ip,
272
+ 'Port' => 8080
273
+ }
274
+ end
266
275
  @connection = Net::Telnet.new(telnet_config)
267
276
  end
268
277
 
@@ -38,21 +38,23 @@ module RokuBuilder
38
38
  # Run tests and report results
39
39
  # @param sideload_config [Hash] The config for sideloading the app
40
40
  def test(options:)
41
- loader = Loader.new(config: @config)
42
- loader.sideload(options: options)
43
- linker = Linker.new(config: @config)
44
- linker.deeplink(options: Options.new(options: {deeplink: "RunTests:true"}))
41
+ get_device do |device|
42
+ loader = Loader.new(config: @config)
43
+ loader.sideload(options: options, device: device)
44
+ linker = Linker.new(config: @config)
45
+ linker.deeplink(options: Options.new(options: {deeplink: "RunTests:true"}), device: device)
45
46
 
46
- telnet_config ={
47
- 'Host' => @roku_ip_address,
48
- 'Port' => 8085
49
- }
50
- connection = Net::Telnet.new(telnet_config)
51
- connection.waitfor(@end_reg) do |txt|
52
- handle_text(txt: txt)
47
+ telnet_config ={
48
+ 'Host' => device.ip,
49
+ 'Port' => 8085
50
+ }
51
+ connection = Net::Telnet.new(telnet_config)
52
+ connection.waitfor(@end_reg) do |txt|
53
+ handle_text(txt: txt)
54
+ end
55
+ print_logs
56
+ connection.puts("cont\n")
53
57
  end
54
- print_logs
55
- connection.puts("cont\n")
56
58
  end
57
59
 
58
60
  private
@@ -11,10 +11,6 @@ module RokuBuilder
11
11
  def initialize(config: )
12
12
  @logger = Logger.instance
13
13
  @config = config
14
- @roku_ip_address = @config.device_config[:ip]
15
- @dev_username = @config.device_config[:user]
16
- @dev_password = @config.device_config[:password]
17
- @url = "http://#{@roku_ip_address}"
18
14
  init
19
15
  end
20
16
 
@@ -27,25 +23,51 @@ module RokuBuilder
27
23
 
28
24
  # Generates a simpe Faraday connection with digest credentials
29
25
  # @return [Faraday] The faraday connection
30
- def simple_connection
31
- Faraday.new(url: @url) do |f|
32
- f.request :digest, @dev_username, @dev_password
33
- f.adapter Faraday.default_adapter
26
+ def simple_connection(device: nil, no_lock: false, &block)
27
+ raise ImplementationError, "No block given to simple_connection" unless block_given?
28
+ get_device(device: device, no_lock: no_lock) do |device|
29
+ url = "http://#{device.ip}"
30
+ connection = Faraday.new(url: url) do |f|
31
+ f.request :digest, device.user, device.password
32
+ f.adapter Faraday.default_adapter
33
+ end
34
+ block.call(connection)
34
35
  end
35
36
  end
36
37
 
37
38
  # Generates a multipart Faraday connection with digest credentials
38
39
  # @param port [Integer] optional port to connect to
39
40
  # @return [Faraday] The faraday connection
40
- def multipart_connection(port: nil)
41
- url = @url
42
- url = "#{url}:#{port}" if port
43
- Faraday.new(url: url) do |f|
44
- f.headers['Content-Type'] = Faraday::Request::Multipart.mime_type
45
- f.request :digest, @dev_username, @dev_password
46
- f.request :multipart
47
- f.request :url_encoded
48
- f.adapter Faraday.default_adapter
41
+ def multipart_connection(port: nil, device: nil, no_lock: false, &block)
42
+ raise ImplementationError, "No block given to multipart_connection" unless block_given?
43
+ get_device(device: device, no_lock: no_lock) do |device|
44
+ url = "http://#{device.ip}"
45
+ url += ":#{port}" if port
46
+ connection = Faraday.new(url: url) do |f|
47
+ f.request :digest, device.user, device.password
48
+ f.request :multipart
49
+ f.request :url_encoded
50
+ f.adapter Faraday.default_adapter
51
+ end
52
+ block.call(connection)
53
+ end
54
+ end
55
+
56
+ def make_param(value, contentType = nil)
57
+ Faraday::Multipart::ParamPart.new(value, contentType)
58
+ end
59
+
60
+ def get_device(device: nil, no_lock: false, &block)
61
+ raise ImplementationError, "No block given to get_device" unless block_given?
62
+ device_given = true
63
+ unless device
64
+ device_given = false
65
+ device = RokuBuilder.device_manager.reserve_device(no_lock: no_lock)
66
+ end
67
+ begin
68
+ block.call(device)
69
+ ensure
70
+ RokuBuilder.device_manager.release_device(device) unless device_given
49
71
  end
50
72
  end
51
73
  end
@@ -2,5 +2,5 @@
2
2
 
3
3
  module RokuBuilder
4
4
  # Version of the RokuBuilder Gem
5
- VERSION = "4.25.5"
5
+ VERSION = "4.25.6"
6
6
  end
data/lib/roku_builder.rb CHANGED
@@ -2,6 +2,7 @@
2
2
 
3
3
  require "logger"
4
4
  require "faraday"
5
+ require "faraday/multipart"
5
6
  require "faraday/digestauth"
6
7
  require "pathname"
7
8
  require "optparse"
@@ -68,7 +69,6 @@ module RokuBuilder
68
69
 
69
70
  def self.execute
70
71
  load_config
71
- check_devices
72
72
  execute_command
73
73
  end
74
74
 
@@ -171,24 +171,8 @@ module RokuBuilder
171
171
  end
172
172
  end
173
173
 
174
- def self.check_devices
175
- if @@options.device_command?
176
- ping = Net::Ping::External.new
177
- host = @@config.parsed[:device_config][:ip]
178
- return if ping.ping? host, 1, 0.2, 1
179
- raise DeviceError, "Device not online" if @@options[:device_given]
180
- @@config.raw[:devices].each_pair {|key, value|
181
- unless key == :default
182
- host = value[:ip]
183
- if ping.ping? host, 1, 0.2, 1
184
- @@config.parsed[:device_config] = value
185
- Logger.instance.warn("Default device offline, choosing Alternate")
186
- return
187
- end
188
- end
189
- }
190
- raise DeviceError, "No devices found"
191
- end
174
+ def self.device_manager
175
+ @@device_manager ||= DeviceManager.new(config: @@config, options: @@options)
192
176
  end
193
177
 
194
178
  def self.execute_command
data/roku_builder.gemspec CHANGED
@@ -21,7 +21,8 @@ Gem::Specification.new do |spec|
21
21
  spec.required_ruby_version = "~> 3.0"
22
22
 
23
23
  spec.add_dependency "rubyzip", "~> 1.2"
24
- spec.add_dependency "faraday", "~> 0.13"
24
+ spec.add_dependency "faraday", "~> 2.3"
25
+ spec.add_dependency "faraday-multipart", "~> 1.0"
25
26
  spec.add_dependency "faraday-digestauth", "~> 0.2"
26
27
  spec.add_dependency "git", "~> 1.3"
27
28
  spec.add_dependency "net-ping", "~> 2.0"
@@ -33,7 +34,7 @@ Gem::Specification.new do |spec|
33
34
  spec.add_development_dependency "bundler", "~> 2.0"
34
35
  spec.add_development_dependency "rake", "~> 12.0"
35
36
  spec.add_development_dependency "byebug", "~> 10.0"
36
- spec.add_development_dependency "minitest", "~> 5.10"
37
+ spec.add_development_dependency "minitest", "~> 5.16"
37
38
  spec.add_development_dependency "minitest-autotest", "~> 1.0"
38
39
  spec.add_development_dependency "minitest-server", "~> 1.0"
39
40
  spec.add_development_dependency "minitest-utils", "~> 0.3"
@@ -18,7 +18,7 @@ module RokuBuilder
18
18
  "--do-unstage", "--edit", "params", "--config", "config-path",
19
19
  "--ref", "ref", "--working", "--current", "--stage", "stage", "--project",
20
20
  "project", "--out", "out", "--in", "in", "--device", "device",
21
- "--verbose", "--debug"]
21
+ "--device-blocking", "--verbose", "--debug"]
22
22
  parser.parse! argv
23
23
  assert options[:configure]
24
24
  assert options[:validate]
@@ -35,7 +35,7 @@ module RokuBuilder
35
35
  assert_equal "out", options[:out]
36
36
  assert_equal "in", options[:in]
37
37
  assert_equal "device", options[:device]
38
- assert options[:device_given]
38
+ assert options[:device_blocking]
39
39
  assert options[:verbose]
40
40
  assert options[:debug]
41
41
  end
@@ -56,7 +56,6 @@ module RokuBuilder
56
56
  assert_equal "out", options[:out]
57
57
  assert_equal "in", options[:in]
58
58
  assert_equal "device", options[:device]
59
- assert options[:device_given]
60
59
  assert options[:verbose]
61
60
  end
62
61
  def test_core_configure
@@ -32,6 +32,7 @@ module RokuBuilder
32
32
  assert options[:screencapture]
33
33
  end
34
34
  def test_inspector_inspect
35
+
35
36
  logger = Minitest::Mock.new()
36
37
 
37
38
  logger.expect(:formatter=, nil, [Proc])
@@ -77,9 +78,19 @@ module RokuBuilder
77
78
  options = {inspect: true, in: File.join(test_files_path(InspectorTest), "test.pkg"), password: "password"}
78
79
  config, options = build_config_options_objects(InspectorTest, options, false)
79
80
  inspector = Inspector.new(config: config)
80
- ::Logger.stub(:new, logger) do
81
- inspector.inspect(options: options)
81
+
82
+ device_manager = Minitest::Mock.new
83
+ device = RokuBuilder::Device.new("roku", config.raw[:devices][:roku])
84
+ device_manager.expect(:reserve_device, device, no_lock: false)
85
+ device_manager.expect(:release_device, nil, [device])
86
+
87
+ RokuBuilder.stub(:device_manager, device_manager) do
88
+ ::Logger.stub(:new, logger) do
89
+ inspector.inspect(options: options)
90
+ end
82
91
  end
92
+ logger.verify
93
+ device_manager.verify
83
94
  end
84
95
  def test_inspector_inspect_old_interface
85
96
  logger = Minitest::Mock.new()
@@ -119,9 +130,20 @@ module RokuBuilder
119
130
  options = {inspect: true, in: File.join(test_files_path(InspectorTest), "test.pkg"), password: "password"}
120
131
  config, options = build_config_options_objects(InspectorTest, options, false)
121
132
  inspector = Inspector.new(config: config)
122
- ::Logger.stub(:new, logger) do
123
- inspector.inspect(options: options)
133
+
134
+ device_manager = Minitest::Mock.new
135
+ device = RokuBuilder::Device.new("roku", config.raw[:devices][:roku])
136
+ device_manager.expect(:reserve_device, device, no_lock: false)
137
+ device_manager.expect(:release_device, nil, [device])
138
+
139
+ RokuBuilder.stub(:device_manager, device_manager) do
140
+ ::Logger.stub(:new, logger) do
141
+ inspector.inspect(options: options)
142
+ end
124
143
  end
144
+
145
+ logger.verify
146
+ device_manager.verify
125
147
  end
126
148
 
127
149
  def test_screencapture
@@ -139,9 +161,19 @@ module RokuBuilder
139
161
  options = {screencapture: true }
140
162
  config, options = build_config_options_objects(InspectorTest, options, false)
141
163
  inspector = Inspector.new(config: config)
142
- File.stub(:open, nil, io) do
143
- inspector.screencapture(options: options)
164
+
165
+ device_manager = Minitest::Mock.new
166
+ device = RokuBuilder::Device.new("roku", config.raw[:devices][:roku])
167
+ device_manager.expect(:reserve_device, device, no_lock: false)
168
+ device_manager.expect(:release_device, nil, [device])
169
+
170
+ RokuBuilder.stub(:device_manager, device_manager) do
171
+ File.stub(:open, nil, io) do
172
+ inspector.screencapture(options: options)
173
+ end
144
174
  end
175
+ io.verify
176
+ device_manager.verify
145
177
  end
146
178
  def test_screencapture_png
147
179
  body = "<hr /><img src=\"pkgs/dev.png?time=1455629573\">"
@@ -158,8 +190,16 @@ module RokuBuilder
158
190
  options = {screencapture: true }
159
191
  config, options = build_config_options_objects(InspectorTest, options, false)
160
192
  inspector = Inspector.new(config: config)
161
- File.stub(:open, nil, io) do
162
- inspector.screencapture(options: options)
193
+
194
+ device_manager = Minitest::Mock.new
195
+ device = RokuBuilder::Device.new("roku", config.raw[:devices][:roku])
196
+ device_manager.expect(:reserve_device, device, no_lock: false)
197
+ device_manager.expect(:release_device, nil, [device])
198
+
199
+ RokuBuilder.stub(:device_manager, device_manager) do
200
+ File.stub(:open, nil, io) do
201
+ inspector.screencapture(options: options)
202
+ end
163
203
  end
164
204
  end
165
205
  end