rubotium 0.0.7 → 0.0.17

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
  SHA1:
3
- metadata.gz: 72623c02c54541abecf0431da8b66994b1404a95
4
- data.tar.gz: 25d93c5df3c122e59a6fe66f9d848c5be3744556
3
+ metadata.gz: d1b0f0bd231baeeb352f147b7eed2517a2bbf3c0
4
+ data.tar.gz: c7cfcc8c601126f54bc4dfea8c6a11575cfb7f40
5
5
  SHA512:
6
- metadata.gz: f42c5267c1aba6a11a8d6e2b5b7d8c813a1690e3479544294847b4c91474e30ae65ec8407685e7bd0fa7c679c146d6cedb5787d5bbc28fefb71b28f2b5341926
7
- data.tar.gz: edd630a22f94aa3004f3287b031a40bf3ada2de15e5db653c488016941d868cc44dd9f0e983eba2b5e1a8db96da9b1e8a512dd6e59c84c5e0c7e5d8d91bc3060
6
+ metadata.gz: e22610e5725858e4c8a5af4e9868e1fc5ed7dc03c229a393239e153c76bf62b122bbaa3273435530892d38b01ed6e6feeed82e9275f7b24f4f2f993ad027be8f
7
+ data.tar.gz: 4a976b43dfdd6ebd0105bbcce1742897256b01bc3aaa457c7ede59db7b66325039e0c7a8e2c6d52b2a0c7a7bf7853bd26ebf8d087eae9147202e816279e38015
data/.travis.yml ADDED
@@ -0,0 +1,11 @@
1
+ language: ruby
2
+ rvm:
3
+ - "1.9.2"
4
+ - "1.9.3"
5
+ - "2.1.1"
6
+ script: bundle exec rspec spec
7
+ addons:
8
+ code_climate:
9
+ repo_token:
10
+ secure: f16d367e3357b0375c13e49094a99c656921d3dc0e24a8aa6899e7ad330f857e
11
+
data/README.md CHANGED
@@ -1,3 +1,7 @@
1
+ [![Build Status](https://travis-ci.org/ssmiech/rubotium.svg?branch=master)](https://travis-ci.org/ssmiech/rubotium)
2
+ [![Code Climate](https://codeclimate.com/github/ssmiech/rubotium/badges/gpa.svg)](https://codeclimate.com/github/ssmiech/rubotium)
3
+ [![Coverage Status](https://coveralls.io/repos/ssmiech/rubotium/badge.svg?branch=master)](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
- puts "EXECUTING_COMMAND: #{adb_command} #{command_to_run.executable_command}"
27
- CMD.run_command(adb_command + " " + command_to_run.executable_command)
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
- "adb -s #{device_serial} "
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'
@@ -1,3 +1,5 @@
1
+ require 'time'
2
+
1
3
  module Rubotium
2
4
  module Adb
3
5
  module Parsers
@@ -31,7 +33,7 @@ module Rubotium
31
33
  end
32
34
 
33
35
  def time
34
- match_time.strip
36
+ match_time.strip.to_f
35
37
  end
36
38
 
37
39
  def message
@@ -0,0 +1,9 @@
1
+ module Rubotium
2
+ module Apk
3
+ class Aapt
4
+ def dump(path)
5
+ CMD.run_command("aapt dump badging \"" + path + "\" 2>&1")
6
+ end
7
+ end
8
+ end
9
+ end
@@ -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
- command = "aapt dump badging \"" + @path + "\" 2>&1"
24
- results = `#{command}`
25
- if $?.exitstatus != 0 or results.index("ERROR: dump failed")
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 ||= results
32
+ @results
29
33
  end
30
34
 
31
35
  def parsed_aapt
data/lib/rubotium/apk.rb CHANGED
@@ -1,4 +1,5 @@
1
1
  require_relative 'apk/android_apk'
2
+ require_relative 'apk/aapt'
2
3
 
3
4
  module Rubotium
4
5
  module Apk
@@ -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
@@ -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 matched_by_name.empty?
11
- (matched_by_name + matched_by_serial).uniq
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'
@@ -9,6 +9,10 @@ module Rubotium
9
9
  @test_name = runnable_test.test_name
10
10
  end
11
11
 
12
+ def name
13
+ "#{class_name}##{test_name}"
14
+ end
15
+
12
16
  def stack_trace
13
17
  if(failed_run?)
14
18
  parsed_result.message