rubotium 0.0.7 → 0.0.17
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.
- checksums.yaml +4 -4
- data/.travis.yml +11 -0
- data/README.md +4 -0
- data/bin/rubotium +7 -1
- data/lib/rubotium/adb/commands/command.rb +35 -3
- data/lib/rubotium/adb/commands/logcat_command.rb +24 -0
- data/lib/rubotium/adb/commands/push_command.rb +24 -0
- data/lib/rubotium/adb/commands.rb +2 -0
- data/lib/rubotium/adb/parsers/test_results_parser.rb +3 -1
- data/lib/rubotium/apk/aapt.rb +9 -0
- data/lib/rubotium/apk/android_apk.rb +10 -6
- data/lib/rubotium/apk.rb +1 -0
- data/lib/rubotium/device.rb +13 -1
- data/lib/rubotium/devices.rb +15 -3
- data/lib/rubotium/formatters/html_formatter.css +97 -0
- data/lib/rubotium/formatters/html_formatter.rb +181 -0
- data/lib/rubotium/screencast/recorder.rb +43 -0
- data/lib/rubotium/screencast.rb +1 -0
- data/lib/rubotium/test_result.rb +4 -0
- data/lib/rubotium/test_results.rb +107 -0
- data/lib/rubotium/test_runners/instrumentation_test_runner.rb +12 -2
- data/lib/rubotium/tests_runner.rb +13 -5
- data/lib/rubotium/version.rb +1 -1
- data/lib/rubotium.rb +24 -7
- data/rubotium.gemspec +2 -3
- data/spec/lib/rubotium/adb/adb_result_parser_spec.rb +4 -4
- data/spec/lib/rubotium/adb/commands/command_spec.rb +40 -0
- data/spec/lib/rubotium/adb/parsers/test_results_parser_spec.rb +5 -5
- data/spec/lib/rubotium/apk/android_apk_spec.rb +25 -4
- data/spec/lib/rubotium/device_spec.rb +22 -7
- data/spec/lib/rubotium/tests_runner_spec.rb +1 -0
- data/test.rb +39 -8
- metadata +22 -40
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: d1b0f0bd231baeeb352f147b7eed2517a2bbf3c0
|
4
|
+
data.tar.gz: c7cfcc8c601126f54bc4dfea8c6a11575cfb7f40
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: e22610e5725858e4c8a5af4e9868e1fc5ed7dc03c229a393239e153c76bf62b122bbaa3273435530892d38b01ed6e6feeed82e9275f7b24f4f2f993ad027be8f
|
7
|
+
data.tar.gz: 4a976b43dfdd6ebd0105bbcce1742897256b01bc3aaa457c7ede59db7b66325039e0c7a8e2c6d52b2a0c7a7bf7853bd26ebf8d087eae9147202e816279e38015
|
data/.travis.yml
ADDED
data/README.md
CHANGED
@@ -1,3 +1,7 @@
|
|
1
|
+
[](https://travis-ci.org/ssmiech/rubotium)
|
2
|
+
[](https://codeclimate.com/github/ssmiech/rubotium)
|
3
|
+
[](https://coveralls.io/r/ssmiech/rubotium?branch=master)
|
4
|
+
|
1
5
|
# Rubotium
|
2
6
|
|
3
7
|
This is an Android's Instrumentation test runner. It's in quite early phase but already solves couple of issues with instrumentation tests:
|
data/bin/rubotium
CHANGED
@@ -10,19 +10,25 @@ opts = Trollop::options do
|
|
10
10
|
opt :rerun, 'Retries count', :default => 0, :short => '-R'
|
11
11
|
opt :out, 'Report file', :default => 'report.xml', :short => '-o'
|
12
12
|
opt :device, 'Match devices', :type => :string, :short => '-d'
|
13
|
+
opt :sdk, 'Run on devices with sdk', :type => :string, :short => '-k'
|
13
14
|
opt :runner, 'Test runner', :type => :string, :short => '-r'
|
14
15
|
opt :annotation, 'Run annotated tests', :type => :string, :short => '-n'
|
16
|
+
opt :helper_apk_path, 'Path to the helper .apk file', :type => :string, :short => '-H'
|
17
|
+
opt :serial, 'Device serial', :type => :string, :short => '-s'
|
15
18
|
end
|
16
19
|
|
17
20
|
params = {
|
21
|
+
:serial => opts[:serial],
|
18
22
|
:tests_jar_path => opts[:test_jar_path],
|
19
23
|
:tests_apk_path => opts[:test_apk_path],
|
20
24
|
:app_apk_path => opts[:app_apk_path],
|
21
25
|
:rerun_count => opts[:rerun],
|
22
26
|
:report => opts[:out],
|
23
27
|
:device_matcher => opts[:device],
|
28
|
+
:device_sdk => opts[:sdk],
|
24
29
|
:runner => opts[:runner],
|
25
|
-
:annotation => opts[:annotation]
|
30
|
+
:annotation => opts[:annotation],
|
31
|
+
:helper_apk_path => opts[:helper_apk_path]
|
26
32
|
}
|
27
33
|
|
28
34
|
Rubotium.new(params)
|
@@ -6,6 +6,14 @@ module Rubotium
|
|
6
6
|
@device_serial = device_serial
|
7
7
|
end
|
8
8
|
|
9
|
+
def clean_logcat
|
10
|
+
execute(logcat_command(:clean => true))
|
11
|
+
end
|
12
|
+
|
13
|
+
def logcat
|
14
|
+
execute(logcat_command(:dump => true))
|
15
|
+
end
|
16
|
+
|
9
17
|
def install(apk_path)
|
10
18
|
execute(install_command(apk_path))
|
11
19
|
end
|
@@ -18,20 +26,36 @@ module Rubotium
|
|
18
26
|
execute(pull_command(files_glob))
|
19
27
|
end
|
20
28
|
|
29
|
+
def push(local_glob, remote_dest)
|
30
|
+
execute(push_command(local_glob, remote_dest))
|
31
|
+
end
|
32
|
+
|
21
33
|
def shell(command)
|
22
34
|
execute(shell_command(command))
|
23
35
|
end
|
24
36
|
|
25
37
|
def execute(command_to_run)
|
26
|
-
|
27
|
-
|
38
|
+
commands = command_to_run.executable_command
|
39
|
+
puts "EXECUTING_COMMAND: #{adb_command} #{commands}"
|
40
|
+
|
41
|
+
begin
|
42
|
+
commands.each do |command|
|
43
|
+
CMD.run_command(adb_command + ' ' + command)
|
44
|
+
end
|
45
|
+
rescue NoMethodError
|
46
|
+
CMD.run_command(adb_command + ' ' + commands)
|
47
|
+
end
|
28
48
|
end
|
29
49
|
|
30
50
|
private
|
31
51
|
attr_reader :device_serial
|
32
52
|
|
33
53
|
def adb_command
|
34
|
-
|
54
|
+
if device_serial.nil? || device_serial == ''
|
55
|
+
'adb'
|
56
|
+
else
|
57
|
+
"adb -s #{device_serial}"
|
58
|
+
end
|
35
59
|
end
|
36
60
|
|
37
61
|
def install_command(apk_path)
|
@@ -46,9 +70,17 @@ module Rubotium
|
|
46
70
|
Rubotium::Adb::Commands::PullCommand.new(files_glob)
|
47
71
|
end
|
48
72
|
|
73
|
+
def push_command(local_glob, remote_dest)
|
74
|
+
Rubotium::Adb::Commands::PushCommand.new(local_glob, remote_dest)
|
75
|
+
end
|
76
|
+
|
49
77
|
def shell_command(command)
|
50
78
|
Rubotium::Adb::Commands::ShellCommand.new(command)
|
51
79
|
end
|
80
|
+
|
81
|
+
def logcat_command(options = {})
|
82
|
+
Rubotium::Adb::Commands::LogcatCommand.new(options)
|
83
|
+
end
|
52
84
|
end
|
53
85
|
end
|
54
86
|
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module Rubotium
|
2
|
+
module Adb
|
3
|
+
module Commands
|
4
|
+
class LogcatCommand
|
5
|
+
COMMAND = 'logcat'
|
6
|
+
def initialize(options)
|
7
|
+
@clean = options.delete(:clean) ? '-c' : ''
|
8
|
+
@dump = options.delete(:dump) ? '-d' : ''
|
9
|
+
end
|
10
|
+
|
11
|
+
def executable_command
|
12
|
+
"#{COMMAND} #{options}"
|
13
|
+
end
|
14
|
+
|
15
|
+
private
|
16
|
+
attr_reader :clean, :dump
|
17
|
+
|
18
|
+
def options
|
19
|
+
[clean, dump].join(' ')
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module Rubotium
|
2
|
+
module Adb
|
3
|
+
module Commands
|
4
|
+
class PushCommand
|
5
|
+
COMMAND = 'push'
|
6
|
+
|
7
|
+
def initialize(local_glob, remote_dest)
|
8
|
+
@local_paths = Dir[local_glob]
|
9
|
+
@remote_dest = remote_dest
|
10
|
+
end
|
11
|
+
|
12
|
+
def executable_command
|
13
|
+
local_paths.map do |path|
|
14
|
+
"#{COMMAND} #{path} #{remote_dest}"
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
private
|
19
|
+
attr_reader :local_paths
|
20
|
+
attr_reader :remote_dest
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -3,5 +3,7 @@ require_relative 'commands/install_command'
|
|
3
3
|
require_relative 'commands/instrument_command'
|
4
4
|
require_relative 'commands/memory_command'
|
5
5
|
require_relative 'commands/pull_command'
|
6
|
+
require_relative 'commands/push_command'
|
6
7
|
require_relative 'commands/shell_command'
|
7
8
|
require_relative 'commands/uninstall_command'
|
9
|
+
require_relative 'commands/logcat_command'
|
@@ -5,13 +5,18 @@ module Rubotium
|
|
5
5
|
require "tmpdir"
|
6
6
|
require "pp"
|
7
7
|
class AndroidApk
|
8
|
+
attr_reader :aapt
|
8
9
|
attr_accessor :results,:label,:labels,:icon,:icons,:package_name,:version_code,:version_name,:sdk_version,:target_sdk_version,:filepath
|
9
|
-
def initialize(path_to_apk)
|
10
|
+
def initialize(aapt = Aapt.new, path_to_apk)
|
11
|
+
@aapt = aapt
|
10
12
|
@path = path_to_apk
|
11
13
|
raise(Errno::ENOENT, "File does not exist") unless File.exist?(@path)
|
12
14
|
end
|
13
15
|
|
14
16
|
def package_name
|
17
|
+
if parsed_aapt['package'].nil?
|
18
|
+
raise RuntimeError.new('ERROR: dump failed because no AndroidManifest.xml found')
|
19
|
+
end
|
15
20
|
parsed_aapt['package']['name']
|
16
21
|
end
|
17
22
|
|
@@ -20,12 +25,11 @@ module Rubotium
|
|
20
25
|
end
|
21
26
|
|
22
27
|
def results
|
23
|
-
|
24
|
-
results
|
25
|
-
|
26
|
-
raise(RuntimeError, results)
|
28
|
+
@results ||= aapt.dump(@path)
|
29
|
+
if $?.exitstatus != 0 or @results.index("ERROR: dump failed")
|
30
|
+
raise(RuntimeError, @results)
|
27
31
|
end
|
28
|
-
@results
|
32
|
+
@results
|
29
33
|
end
|
30
34
|
|
31
35
|
def parsed_aapt
|
data/lib/rubotium/apk.rb
CHANGED
data/lib/rubotium/device.rb
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
module Rubotium
|
2
2
|
class Device
|
3
|
-
attr_reader :serial
|
3
|
+
attr_reader :serial, :sdk
|
4
4
|
def initialize(serial)
|
5
5
|
@serial = serial
|
6
6
|
end
|
@@ -9,6 +9,18 @@ module Rubotium
|
|
9
9
|
@name ||= adb_command.shell('getprop ro.product.model').strip
|
10
10
|
end
|
11
11
|
|
12
|
+
def sdk
|
13
|
+
@sdk ||= adb_command.shell('getprop ro.build.version.sdk').strip
|
14
|
+
end
|
15
|
+
|
16
|
+
def clean_logcat
|
17
|
+
adb_command.clean_logcat
|
18
|
+
end
|
19
|
+
|
20
|
+
def logcat
|
21
|
+
adb_command.logcat
|
22
|
+
end
|
23
|
+
|
12
24
|
def install(apk_path)
|
13
25
|
adb_command.install(apk_path)
|
14
26
|
end
|
data/lib/rubotium/devices.rb
CHANGED
@@ -3,16 +3,22 @@ module Rubotium
|
|
3
3
|
def initialize(options = {})
|
4
4
|
@match_serial = options[:serial] || ''
|
5
5
|
@match_name = options[:name] || ''
|
6
|
+
@match_sdk = options[:sdk] || ''
|
6
7
|
end
|
7
8
|
|
8
9
|
def all
|
9
10
|
raise NoDevicesError if attached_devices.empty?
|
10
|
-
raise NoMatchedDevicesError if
|
11
|
-
|
11
|
+
raise NoMatchedDevicesError if matched_devices.nil? or matched_devices.empty?
|
12
|
+
matched_devices
|
12
13
|
end
|
13
14
|
|
15
|
+
|
14
16
|
private
|
15
|
-
attr_reader :matched, :attached_devices, :match_name, :match_serial
|
17
|
+
attr_reader :matched, :attached_devices, :match_name, :match_serial, :match_sdk
|
18
|
+
|
19
|
+
def matched_devices
|
20
|
+
@matched_devices ||=[matched_by_name, matched_by_serial, matched_by_sdk].reject( &:empty? ).reduce( :& )
|
21
|
+
end
|
16
22
|
|
17
23
|
def attached_devices
|
18
24
|
@attached_devices ||= adb_devices.attached
|
@@ -30,6 +36,12 @@ module Rubotium
|
|
30
36
|
}
|
31
37
|
end
|
32
38
|
|
39
|
+
def matched_by_sdk
|
40
|
+
attached_devices.select { |device|
|
41
|
+
device.sdk.eql? match_sdk
|
42
|
+
}
|
43
|
+
end
|
44
|
+
|
33
45
|
def adb_devices
|
34
46
|
Adb::Devices.new
|
35
47
|
end
|
@@ -0,0 +1,97 @@
|
|
1
|
+
body {
|
2
|
+
margin: 0 auto;
|
3
|
+
padding: 0;
|
4
|
+
text-align: left;
|
5
|
+
height: 100%;
|
6
|
+
font-family: myriad, arial, tahoma, verdana, sans-serif;
|
7
|
+
color: #151515;
|
8
|
+
font-size: 90%;
|
9
|
+
line-height: 1.3em;
|
10
|
+
background-color: #fff;
|
11
|
+
}
|
12
|
+
|
13
|
+
ul {
|
14
|
+
list-style: none;
|
15
|
+
}
|
16
|
+
|
17
|
+
#header {
|
18
|
+
padding: 0;
|
19
|
+
position: fixed;
|
20
|
+
width: 100%;
|
21
|
+
z-index: 10;
|
22
|
+
background-color: #c7ceda;
|
23
|
+
}
|
24
|
+
|
25
|
+
#header .time {
|
26
|
+
margin-top: 2.2em;
|
27
|
+
margin-right: 3.4em;
|
28
|
+
float: right;
|
29
|
+
}
|
30
|
+
|
31
|
+
#header h1 {
|
32
|
+
margin: 1em 3em 1em 1.7em;
|
33
|
+
color: #151515;
|
34
|
+
font-size: 180%;
|
35
|
+
line-height: 1.1em;
|
36
|
+
font-weight: bold;
|
37
|
+
}
|
38
|
+
.testsuite {
|
39
|
+
margin: 5px;
|
40
|
+
}
|
41
|
+
|
42
|
+
.testsuite > div {
|
43
|
+
padding: .5em 0 .5em 1em;
|
44
|
+
background-color: #f2f2f2;
|
45
|
+
font-size: 120%;
|
46
|
+
color: #151515;
|
47
|
+
font-weight: bold;
|
48
|
+
}
|
49
|
+
|
50
|
+
span.error {
|
51
|
+
color: #ff0000;
|
52
|
+
}
|
53
|
+
|
54
|
+
span.passed {
|
55
|
+
color: #1d9d01;
|
56
|
+
}
|
57
|
+
|
58
|
+
#content {
|
59
|
+
padding: 112px 2.5em 2em 1.7em;
|
60
|
+
}
|
61
|
+
|
62
|
+
.test_case > div{
|
63
|
+
padding: .5em 0 .5em 1em;
|
64
|
+
color: #0046b0;
|
65
|
+
font-size: 100%;
|
66
|
+
font-weight: bold;
|
67
|
+
border-bottom: solid 1px #dbdbdb;
|
68
|
+
}
|
69
|
+
|
70
|
+
.test_case .time {
|
71
|
+
margin-right: .5em;
|
72
|
+
width: 5em;
|
73
|
+
text-align: right;
|
74
|
+
font-size: 13px;
|
75
|
+
color: #151515;
|
76
|
+
font-style: normal;
|
77
|
+
font-weight: normal;
|
78
|
+
float: right;
|
79
|
+
}
|
80
|
+
|
81
|
+
#testsuites .passed {
|
82
|
+
border-left: solid 10px #93e078;
|
83
|
+
}
|
84
|
+
|
85
|
+
#testsuites .failed {
|
86
|
+
border-left: solid 10px #ff0000;
|
87
|
+
}
|
88
|
+
|
89
|
+
.stderr {
|
90
|
+
color: #8b0000 !important;
|
91
|
+
font-size: 80% !important;
|
92
|
+
font-weight: normal !important;
|
93
|
+
overflow-x: auto;
|
94
|
+
cursor: auto !important;
|
95
|
+
background: none !important;
|
96
|
+
border: none !important;
|
97
|
+
}
|
@@ -0,0 +1,181 @@
|
|
1
|
+
require 'builder'
|
2
|
+
require 'uri'
|
3
|
+
|
4
|
+
module Rubotium
|
5
|
+
module Formatters
|
6
|
+
class HtmlFormatter
|
7
|
+
attr_reader :html
|
8
|
+
def initialize(results, path_to_file)
|
9
|
+
@test_results = results
|
10
|
+
@report_file_path = path_to_file
|
11
|
+
|
12
|
+
@html = Builder::XmlMarkup.new :target => ensure_io(report_path), :indent => 2
|
13
|
+
inline_css
|
14
|
+
add_header
|
15
|
+
add_body
|
16
|
+
add_footer
|
17
|
+
end
|
18
|
+
|
19
|
+
private
|
20
|
+
attr_reader :report_file_path, :device_serial, :test_results
|
21
|
+
|
22
|
+
def inline_css
|
23
|
+
# html.link(:rel=>"stylesheet", :href=> (File.dirname(__FILE__) + '/html_formatter.css'))
|
24
|
+
html.style(:type => 'text/css') do
|
25
|
+
html << File.read(File.dirname(__FILE__) + '/html_formatter.css')
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def add_header
|
30
|
+
html.div(:id => 'header') do
|
31
|
+
display_time
|
32
|
+
display_info
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def display_info
|
37
|
+
html.h1 do
|
38
|
+
html.text('All tests:')
|
39
|
+
html.strong do
|
40
|
+
html.span("#{test_results.total} total,")
|
41
|
+
html.span("#{test_results.passed} passed,", { :class => 'passed' })
|
42
|
+
html.span("#{test_results.failed} failed", { :class => 'error' }) if test_results.failed
|
43
|
+
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def display_time
|
49
|
+
html.div(format_time(test_results.time.to_i), { :class => 'time' })
|
50
|
+
end
|
51
|
+
|
52
|
+
def add_body
|
53
|
+
html.body do
|
54
|
+
add_content
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
def add_footer
|
59
|
+
|
60
|
+
end
|
61
|
+
|
62
|
+
def add_content
|
63
|
+
html.div(:id => 'content') do
|
64
|
+
html.ul(:id => 'testsuites') do
|
65
|
+
test_results.each{|suite|
|
66
|
+
start_test_suite(suite)
|
67
|
+
}
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
def start_test_suite(suite)
|
73
|
+
html.li(:class => 'testsuite') do
|
74
|
+
html.div(:class => suite.state) do
|
75
|
+
html.span(suite.name)
|
76
|
+
end
|
77
|
+
|
78
|
+
html.ul do
|
79
|
+
suite.each_test { |test|
|
80
|
+
html << TestCaseHtml.new(test).target
|
81
|
+
}
|
82
|
+
end
|
83
|
+
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
def report_path
|
88
|
+
report_file_path
|
89
|
+
end
|
90
|
+
|
91
|
+
def ensure_io(path_to_file)
|
92
|
+
File.open(path_to_file, 'w')
|
93
|
+
end
|
94
|
+
|
95
|
+
def format_time(seconds)
|
96
|
+
return '0 s' if seconds == 0
|
97
|
+
[[60, :s], [60, :m], [24, :h]].map{ |count, name|
|
98
|
+
if seconds > 0
|
99
|
+
seconds, n = seconds.divmod(count)
|
100
|
+
"#{n.to_i} #{name}"
|
101
|
+
end
|
102
|
+
}.compact.reverse.join(' ')
|
103
|
+
end
|
104
|
+
|
105
|
+
class TestCaseHtml
|
106
|
+
def initialize(test_result)
|
107
|
+
@test_result = test_result
|
108
|
+
@builder = Builder::XmlMarkup.new(:indent => 2)
|
109
|
+
end
|
110
|
+
|
111
|
+
def target
|
112
|
+
html
|
113
|
+
end
|
114
|
+
|
115
|
+
private
|
116
|
+
attr_reader :test_result, :builder
|
117
|
+
|
118
|
+
def html
|
119
|
+
builder.li(:class => 'test_case') do
|
120
|
+
print_name
|
121
|
+
builder.ul do
|
122
|
+
unless test_result.passed?
|
123
|
+
print_stacktrace
|
124
|
+
print_logcat
|
125
|
+
print_screen_record if File.exist?(File.join(Dir.pwd, 'results', video_file))
|
126
|
+
end
|
127
|
+
end
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
def print_stacktrace
|
132
|
+
builder.li(:class => 'stderr') do
|
133
|
+
test_result.stack_trace.each_line{|line|
|
134
|
+
builder.br
|
135
|
+
builder.span(line)
|
136
|
+
}
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
def print_logcat
|
141
|
+
builder.li(:class => 'logcat') do
|
142
|
+
builder.a("full log", :href => URI.encode(logcat_file))
|
143
|
+
end
|
144
|
+
end
|
145
|
+
|
146
|
+
def print_screen_record
|
147
|
+
builder.li(:class => 'screencast') do
|
148
|
+
builder.video({ :width => "240", :height => "320", :controls => true }) do
|
149
|
+
builder.source({ :src => URI.encode(video_file), :type => "video/mp4" })
|
150
|
+
end
|
151
|
+
end
|
152
|
+
end
|
153
|
+
|
154
|
+
def print_name
|
155
|
+
builder.div(:class => test_result.passed? ? 'passed' : 'failed') do
|
156
|
+
builder.div(format_time(test_result.time), { :class => 'time' })
|
157
|
+
builder.span(test_result.test_name)
|
158
|
+
end
|
159
|
+
end
|
160
|
+
|
161
|
+
def logcat_file
|
162
|
+
"logs/#{test_result.name}.log"
|
163
|
+
end
|
164
|
+
|
165
|
+
def video_file
|
166
|
+
"screencasts/#{test_result.name}.mp4"
|
167
|
+
end
|
168
|
+
|
169
|
+
def format_time(seconds)
|
170
|
+
return '0 s' if seconds == 0
|
171
|
+
[[60, :s], [60, :m], [24, :h]].map{ |count, name|
|
172
|
+
if seconds > 0
|
173
|
+
seconds, n = seconds.divmod(count)
|
174
|
+
"#{n.to_i} #{name}"
|
175
|
+
end
|
176
|
+
}.compact.reverse.join(' ')
|
177
|
+
end
|
178
|
+
end
|
179
|
+
end
|
180
|
+
end
|
181
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
module Rubotium
|
2
|
+
class Recorder
|
3
|
+
SIGINT = 'SIGINT'
|
4
|
+
|
5
|
+
def initialize(device_serial)
|
6
|
+
@serial = device_serial
|
7
|
+
end
|
8
|
+
|
9
|
+
def start(test_name)
|
10
|
+
@pid = Process.spawn(record_command(test_name))
|
11
|
+
puts "Starting screencast, PID: #{@pid}"
|
12
|
+
end
|
13
|
+
|
14
|
+
def record_command(test_name)
|
15
|
+
"adb -s #{serial} shell screenrecord /sdcard/screencasts/#{test_name}.mp4"
|
16
|
+
end
|
17
|
+
|
18
|
+
def stop
|
19
|
+
puts "Stopping screencast, PID: #{@pid}"
|
20
|
+
kill_children
|
21
|
+
kill_self
|
22
|
+
end
|
23
|
+
|
24
|
+
private
|
25
|
+
attr_reader :serial
|
26
|
+
|
27
|
+
def kill_self
|
28
|
+
Process.kill(SIGINT, @pid)
|
29
|
+
end
|
30
|
+
|
31
|
+
def kill_children
|
32
|
+
find_child_processes.each{|pid|
|
33
|
+
if pid > 0
|
34
|
+
Process.kill(SIGINT, pid)
|
35
|
+
end
|
36
|
+
}
|
37
|
+
end
|
38
|
+
|
39
|
+
def find_child_processes
|
40
|
+
`pgrep -P #{@pid}`.each_line.map(&:strip).map(&:to_i)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1 @@
|
|
1
|
+
require_relative 'screencast/recorder'
|