flaky 0.0.5

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: e23714d41d018b276cb0ec58deaa1b45a45a8d1f
4
+ data.tar.gz: d94c47c3d8e0d0a1e6536a33e8b021f799f9540e
5
+ SHA512:
6
+ metadata.gz: d52098355aedf5d4d102ba48f18840e7fcef611dc9186c1cbcec06c5887ab393c7e99f37baf01ffdb1f12a916de88ef6da7673199897c20e33d6071fbdbaaaf3
7
+ data.tar.gz: 55ad8c6da8aa5565b37cef446a53508e67dd1ce16423b9d96babe4ba3e2a6f1b35ba9fe71a965713241db8771ef5afe86a1ef42db817dd3e81aff393d7838624
@@ -0,0 +1,202 @@
1
+
2
+ Apache License
3
+ Version 2.0, January 2004
4
+ http://www.apache.org/licenses/
5
+
6
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
7
+
8
+ 1. Definitions.
9
+
10
+ "License" shall mean the terms and conditions for use, reproduction,
11
+ and distribution as defined by Sections 1 through 9 of this document.
12
+
13
+ "Licensor" shall mean the copyright owner or entity authorized by
14
+ the copyright owner that is granting the License.
15
+
16
+ "Legal Entity" shall mean the union of the acting entity and all
17
+ other entities that control, are controlled by, or are under common
18
+ control with that entity. For the purposes of this definition,
19
+ "control" means (i) the power, direct or indirect, to cause the
20
+ direction or management of such entity, whether by contract or
21
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
22
+ outstanding shares, or (iii) beneficial ownership of such entity.
23
+
24
+ "You" (or "Your") shall mean an individual or Legal Entity
25
+ exercising permissions granted by this License.
26
+
27
+ "Source" form shall mean the preferred form for making modifications,
28
+ including but not limited to software source code, documentation
29
+ source, and configuration files.
30
+
31
+ "Object" form shall mean any form resulting from mechanical
32
+ transformation or translation of a Source form, including but
33
+ not limited to compiled object code, generated documentation,
34
+ and conversions to other media types.
35
+
36
+ "Work" shall mean the work of authorship, whether in Source or
37
+ Object form, made available under the License, as indicated by a
38
+ copyright notice that is included in or attached to the work
39
+ (an example is provided in the Appendix below).
40
+
41
+ "Derivative Works" shall mean any work, whether in Source or Object
42
+ form, that is based on (or derived from) the Work and for which the
43
+ editorial revisions, annotations, elaborations, or other modifications
44
+ represent, as a whole, an original work of authorship. For the purposes
45
+ of this License, Derivative Works shall not include works that remain
46
+ separable from, or merely link (or bind by name) to the interfaces of,
47
+ the Work and Derivative Works thereof.
48
+
49
+ "Contribution" shall mean any work of authorship, including
50
+ the original version of the Work and any modifications or additions
51
+ to that Work or Derivative Works thereof, that is intentionally
52
+ submitted to Licensor for inclusion in the Work by the copyright owner
53
+ or by an individual or Legal Entity authorized to submit on behalf of
54
+ the copyright owner. For the purposes of this definition, "submitted"
55
+ means any form of electronic, verbal, or written communication sent
56
+ to the Licensor or its representatives, including but not limited to
57
+ communication on electronic mailing lists, source code control systems,
58
+ and issue tracking systems that are managed by, or on behalf of, the
59
+ Licensor for the purpose of discussing and improving the Work, but
60
+ excluding communication that is conspicuously marked or otherwise
61
+ designated in writing by the copyright owner as "Not a Contribution."
62
+
63
+ "Contributor" shall mean Licensor and any individual or Legal Entity
64
+ on behalf of whom a Contribution has been received by Licensor and
65
+ subsequently incorporated within the Work.
66
+
67
+ 2. Grant of Copyright License. Subject to the terms and conditions of
68
+ this License, each Contributor hereby grants to You a perpetual,
69
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
70
+ copyright license to reproduce, prepare Derivative Works of,
71
+ publicly display, publicly perform, sublicense, and distribute the
72
+ Work and such Derivative Works in Source or Object form.
73
+
74
+ 3. Grant of Patent License. Subject to the terms and conditions of
75
+ this License, each Contributor hereby grants to You a perpetual,
76
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
77
+ (except as stated in this section) patent license to make, have made,
78
+ use, offer to sell, sell, import, and otherwise transfer the Work,
79
+ where such license applies only to those patent claims licensable
80
+ by such Contributor that are necessarily infringed by their
81
+ Contribution(s) alone or by combination of their Contribution(s)
82
+ with the Work to which such Contribution(s) was submitted. If You
83
+ institute patent litigation against any entity (including a
84
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
85
+ or a Contribution incorporated within the Work constitutes direct
86
+ or contributory patent infringement, then any patent licenses
87
+ granted to You under this License for that Work shall terminate
88
+ as of the date such litigation is filed.
89
+
90
+ 4. Redistribution. You may reproduce and distribute copies of the
91
+ Work or Derivative Works thereof in any medium, with or without
92
+ modifications, and in Source or Object form, provided that You
93
+ meet the following conditions:
94
+
95
+ (a) You must give any other recipients of the Work or
96
+ Derivative Works a copy of this License; and
97
+
98
+ (b) You must cause any modified files to carry prominent notices
99
+ stating that You changed the files; and
100
+
101
+ (c) You must retain, in the Source form of any Derivative Works
102
+ that You distribute, all copyright, patent, trademark, and
103
+ attribution notices from the Source form of the Work,
104
+ excluding those notices that do not pertain to any part of
105
+ the Derivative Works; and
106
+
107
+ (d) If the Work includes a "NOTICE" text file as part of its
108
+ distribution, then any Derivative Works that You distribute must
109
+ include a readable copy of the attribution notices contained
110
+ within such NOTICE file, excluding those notices that do not
111
+ pertain to any part of the Derivative Works, in at least one
112
+ of the following places: within a NOTICE text file distributed
113
+ as part of the Derivative Works; within the Source form or
114
+ documentation, if provided along with the Derivative Works; or,
115
+ within a display generated by the Derivative Works, if and
116
+ wherever such third-party notices normally appear. The contents
117
+ of the NOTICE file are for informational purposes only and
118
+ do not modify the License. You may add Your own attribution
119
+ notices within Derivative Works that You distribute, alongside
120
+ or as an addendum to the NOTICE text from the Work, provided
121
+ that such additional attribution notices cannot be construed
122
+ as modifying the License.
123
+
124
+ You may add Your own copyright statement to Your modifications and
125
+ may provide additional or different license terms and conditions
126
+ for use, reproduction, or distribution of Your modifications, or
127
+ for any such Derivative Works as a whole, provided Your use,
128
+ reproduction, and distribution of the Work otherwise complies with
129
+ the conditions stated in this License.
130
+
131
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
132
+ any Contribution intentionally submitted for inclusion in the Work
133
+ by You to the Licensor shall be under the terms and conditions of
134
+ this License, without any additional terms or conditions.
135
+ Notwithstanding the above, nothing herein shall supersede or modify
136
+ the terms of any separate license agreement you may have executed
137
+ with Licensor regarding such Contributions.
138
+
139
+ 6. Trademarks. This License does not grant permission to use the trade
140
+ names, trademarks, service marks, or product names of the Licensor,
141
+ except as required for reasonable and customary use in describing the
142
+ origin of the Work and reproducing the content of the NOTICE file.
143
+
144
+ 7. Disclaimer of Warranty. Unless required by applicable law or
145
+ agreed to in writing, Licensor provides the Work (and each
146
+ Contributor provides its Contributions) on an "AS IS" BASIS,
147
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
148
+ implied, including, without limitation, any warranties or conditions
149
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
150
+ PARTICULAR PURPOSE. You are solely responsible for determining the
151
+ appropriateness of using or redistributing the Work and assume any
152
+ risks associated with Your exercise of permissions under this License.
153
+
154
+ 8. Limitation of Liability. In no event and under no legal theory,
155
+ whether in tort (including negligence), contract, or otherwise,
156
+ unless required by applicable law (such as deliberate and grossly
157
+ negligent acts) or agreed to in writing, shall any Contributor be
158
+ liable to You for damages, including any direct, indirect, special,
159
+ incidental, or consequential damages of any character arising as a
160
+ result of this License or out of the use or inability to use the
161
+ Work (including but not limited to damages for loss of goodwill,
162
+ work stoppage, computer failure or malfunction, or any and all
163
+ other commercial damages or losses), even if such Contributor
164
+ has been advised of the possibility of such damages.
165
+
166
+ 9. Accepting Warranty or Additional Liability. While redistributing
167
+ the Work or Derivative Works thereof, You may choose to offer,
168
+ and charge a fee for, acceptance of support, warranty, indemnity,
169
+ or other liability obligations and/or rights consistent with this
170
+ License. However, in accepting such obligations, You may act only
171
+ on Your own behalf and on Your sole responsibility, not on behalf
172
+ of any other Contributor, and only if You agree to indemnify,
173
+ defend, and hold each Contributor harmless for any liability
174
+ incurred by, or claims asserted against, such Contributor by reason
175
+ of your accepting any such warranty or additional liability.
176
+
177
+ END OF TERMS AND CONDITIONS
178
+
179
+ APPENDIX: How to apply the Apache License to your work.
180
+
181
+ To apply the Apache License to your work, attach the following
182
+ boilerplate notice, with the fields enclosed by brackets "[]"
183
+ replaced with your own identifying information. (Don't include
184
+ the brackets!) The text should be enclosed in the appropriate
185
+ comment syntax for the file format. We also recommend that a
186
+ file or class name and description of purpose be included on the
187
+ same "printed page" as the copyright notice for easier
188
+ identification within third-party archives.
189
+
190
+ Copyright [yyyy] [name of copyright owner]
191
+
192
+ Licensed under the Apache License, Version 2.0 (the "License");
193
+ you may not use this file except in compliance with the License.
194
+ You may obtain a copy of the License at
195
+
196
+ http://www.apache.org/licenses/LICENSE-2.0
197
+
198
+ Unless required by applicable law or agreed to in writing, software
199
+ distributed under the License is distributed on an "AS IS" BASIS,
200
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
201
+ See the License for the specific language governing permissions and
202
+ limitations under the License.
@@ -0,0 +1,155 @@
1
+ # encoding: utf-8
2
+ require 'rubygems'
3
+ require 'rake'
4
+ require 'date'
5
+
6
+ # Defines gem name.
7
+ def repo_name; 'flaky' end # ruby_lib published as appium_lib
8
+ def gh_name; 'flaky' end # the name as used on github.com
9
+ def version_file; 'lib/flaky.rb' end
10
+ def version_rgx; /VERSION = '([^']+)'/m end
11
+
12
+ def version
13
+ @version = @version || File.read(version_file).match(version_rgx)[1]
14
+ end
15
+
16
+ def bump major=false
17
+ data = File.read version_file
18
+
19
+ v_line = data.match version_rgx
20
+ d_line = data.match /DATE = '([^']+)'/m
21
+
22
+ old_v = v_line[0]
23
+ old_d = d_line[0]
24
+
25
+ old_num = v_line[1]
26
+ new_num = old_num.split('.')
27
+ new_num[-1] = new_num[-1].to_i + 1
28
+
29
+ if major
30
+ new_num[-1] = 0 # x.y.Z -> x.y.0
31
+ new_num[-2] = new_num[-2].to_i + 1 # x.Y -> x.Y+1
32
+ end
33
+
34
+ new_num = new_num.join '.'
35
+
36
+ new_v = old_v.sub old_num, new_num
37
+ puts "#{old_num} -> #{new_num}"
38
+
39
+ old_date = d_line[1]
40
+ new_date = Date.today.to_s
41
+ new_d = old_d.sub old_date, new_date
42
+ puts "#{old_date} -> #{new_date}" unless old_date == new_date
43
+
44
+ data.sub! old_v, new_v
45
+ data.sub! old_d, new_d
46
+
47
+ File.write version_file, data
48
+ end
49
+
50
+ desc 'Bump the version number and update the date.'
51
+ task :bump do
52
+ bump
53
+ end
54
+
55
+ desc 'Bump the y version number, set z to zero, update the date.'
56
+ task :bumpy do
57
+ bump true
58
+ end
59
+
60
+ # Inspired by Gollum's Rakefile
61
+ desc 'Build and release a new gem to rubygems.org'
62
+ task :release => :gem do
63
+ unless `git branch`.include? '* master'
64
+ puts 'Master branch required to release.'
65
+ exit!
66
+ end
67
+
68
+ # Commit then pull before pushing.
69
+ sh "git commit --allow-empty -am 'Release #{version}'"
70
+ sh 'git pull'
71
+ sh "git tag v#{version}"
72
+ # update notes and docs now that there's a new tag
73
+ Rake::Task['notes'].execute
74
+ sh "git commit --allow-empty -am 'Update release notes'"
75
+ sh 'git push origin master'
76
+ sh "git push origin v#{version}"
77
+ sh "gem push #{repo_name}-#{version}.gem"
78
+ end
79
+
80
+ desc 'Build and release a new gem to rubygems.org (same as release)'
81
+ task :publish => :release do
82
+ end
83
+
84
+ desc 'Build a new gem'
85
+ task :gem do
86
+ `chmod 0600 ~/.gem/credentials`
87
+ sh "gem build #{repo_name}.gemspec"
88
+ end
89
+
90
+ desc 'Build a new gem (same as gem task)'
91
+ task :build => :gem do
92
+ end
93
+
94
+ desc 'Uninstall gem'
95
+ task :uninstall do
96
+ cmd = "gem uninstall -aIx #{repo_name}"
97
+ # rescue on gem not installed error.
98
+ begin; sh "#{cmd}"; rescue; end
99
+ end
100
+
101
+ desc 'Install gem'
102
+ task :install => [ :gem, :uninstall ] do
103
+ sh "gem install --no-rdoc --no-ri --local #{repo_name}-#{version}.gem"
104
+ end
105
+
106
+ desc 'Update release notes'
107
+ task :notes do
108
+ tag_sort = ->(tag1,tag2) do
109
+ tag1_numbers = tag1.match(/\.?v(\d+\.\d+\.\d+)$/)[1].split('.').map! { |n| n.to_i }
110
+ tag2_numbers = tag2.match(/\.?v(\d+\.\d+\.\d+)$/)[1].split('.').map! { |n| n.to_i }
111
+ tag1_numbers <=> tag2_numbers
112
+ end
113
+
114
+ tags = `git tag`.split "\n"
115
+ tags.sort! &tag_sort
116
+ pairs = []
117
+ tags.each_index { |a| pairs.push tags[a] + '...' + tags[a+1] unless tags[a+1].nil? }
118
+
119
+ notes = ''
120
+
121
+ dates = `git log --tags --simplify-by-decoration --pretty="format:%d %ad" --date=short`.split "\n"
122
+
123
+ pairs.sort! &tag_sort
124
+ pairs.reverse! # pairs are in reverse order.
125
+
126
+ tag_date = []
127
+ pairs.each do |pair|
128
+ tag = pair.split('...').last
129
+ dates.each do |line|
130
+ # regular tag, or tag on master.
131
+ if line.include?(tag + ')') || line.include?(tag + ',')
132
+ tag_date.push tag + ' ' + line.match(/\d{4}-\d{2}-\d{2}/)[0]
133
+ break
134
+ end
135
+ end
136
+ end
137
+
138
+ pairs.each_index do |a|
139
+ data =`git log --pretty=oneline #{pairs[a]}`
140
+ new_data = ''
141
+ data.split("\n").each do |line|
142
+ hex = line.match(/[a-zA-Z0-9]+/)[0]
143
+ # use first 7 chars to match GitHub
144
+ comment = line.gsub(hex, '').strip
145
+ next if comment == 'Update release notes'
146
+ new_data += "- [#{hex[0...7]}](https://github.com/appium/#{gh_name}/commit/#{hex}) #{comment}\n"
147
+ end
148
+ data = new_data + "\n"
149
+
150
+ # last pair is the released version.
151
+ notes += "#### #{tag_date[a]}\n\n" + data + "\n"
152
+ end
153
+
154
+ File.open('release_notes.md', 'w') { |f| f.write notes.to_s.strip }
155
+ end
@@ -0,0 +1,16 @@
1
+ #!/usr/bin/env ruby
2
+ # encoding: utf-8
3
+
4
+ usage_string = 'flaky [count] ios[test_name]'
5
+ raise usage_string unless ARGV && ARGV.length === 2
6
+
7
+ require File.expand_path '../../lib/flaky', __FILE__
8
+
9
+ # flaky 1 ios[test_name]
10
+
11
+ count = ARGV.first
12
+ match = ARGV.last.match(/(.+)\[(.*)\]$/)
13
+ raise usage_string unless match && match.length === 3
14
+ full, os, test_name = match.to_a # rake ios[Ok] => ["ios[Ok]", "ios", "Ok"]
15
+
16
+ Flaky.run_one_test count: count, os: os, name: test_name
@@ -0,0 +1,35 @@
1
+ # encoding: utf-8
2
+
3
+ def self.add_to_path path
4
+ path = File.expand_path "../#{path}/", __FILE__
5
+
6
+ $:.unshift path unless $:.include? path
7
+ end
8
+
9
+ add_to_path 'lib'
10
+
11
+ require 'flaky'
12
+
13
+ Gem::Specification.new do |s|
14
+ # 1.8.x is not supported
15
+ s.required_ruby_version = '>= 1.9.3'
16
+
17
+ s.name = 'flaky'
18
+ s.version = Flaky::VERSION
19
+ s.date = Flaky::DATE
20
+ s.license = 'http://www.apache.org/licenses/LICENSE-2.0.txt'
21
+ s.description = s.summary = 'Measure flaky Ruby Appium tests'
22
+ s.description += '.' # avoid identical warning
23
+ s.authors = s.email = %w(code@bootstraponline.com)
24
+ s.homepage = 'https://github.com/bootstraponline/flaky'
25
+ s.require_paths = %w(lib)
26
+
27
+ s.add_runtime_dependency 'chronic_duration', '~> 0.10.2'
28
+ s.add_runtime_dependency 'escape_utils', '~> 0.3.2'
29
+ s.add_runtime_dependency 'posix-spawn', '~> 0.3.6'
30
+
31
+ s.add_development_dependency 'rake', '~> 10.1.0'
32
+
33
+ s.executables = %w(flake)
34
+ s.files = `git ls-files`.split "\n"
35
+ end
@@ -0,0 +1,31 @@
1
+ # encoding: utf-8
2
+ require 'fileutils' # system requires
3
+ require 'open3'
4
+
5
+ require 'rubygems' # gem requires
6
+ require 'chronic_duration'
7
+ require 'escape_utils'
8
+ require 'posix/spawn' # http://rubygems.org/gems/posix-spawn
9
+
10
+ module Flaky
11
+ VERSION = '0.0.5' unless defined? ::Flaky::VERSION
12
+ DATE = '2013-09-30' unless defined? ::Flaky::DATE
13
+
14
+ # https://github.com/appium/ruby_lib/blob/0e203d76610abd519ba9d2fe9c14b50c94df5bbd/lib/appium_lib.rb#L24
15
+ def self.add_to_path file, path=false
16
+ path = path ? "../#{path}/" : '..'
17
+ path = File.expand_path path, file
18
+
19
+ $:.unshift path unless $:.include? path
20
+ end
21
+
22
+ add_to_path __FILE__ # add this dir to path
23
+
24
+ # require internal files
25
+ require 'flaky/appium'
26
+ require 'flaky/log'
27
+ require 'flaky/run'
28
+
29
+ require 'flaky/run/glob_of_tests'
30
+ require 'flaky/run/one_test'
31
+ end
@@ -0,0 +1,113 @@
1
+ # encoding: utf-8
2
+ module Flaky
3
+ #noinspection RubyResolve
4
+ class Appium
5
+ include POSIX::Spawn
6
+ attr_reader :ready, :pid, :in, :out, :err, :log
7
+ @@thread = nil
8
+
9
+ def self.remove_ios_apps
10
+ user = ENV['USER']
11
+ raise 'User must be defined' unless user
12
+
13
+ # Must kill iPhone simulator or strange install errors will occur.
14
+ self.kill_all 'iPhone Simulator'
15
+
16
+ app_glob = "/Users/#{user}/Library/Application Support/iPhone Simulator/6.1/Applications/*"
17
+ Dir.glob(app_glob) do |ios_app_folder|
18
+ FileUtils.rm_rf ios_app_folder
19
+ end
20
+ end
21
+
22
+ def self.kill_all process_name
23
+ _pid, _in, _out, _err = POSIX::Spawn::popen4('killall', '-9', process_name)
24
+ raise "Unable to kill #{process_name}" unless _pid
25
+ _in.close
26
+ _out.read
27
+ _err.read
28
+ ensure
29
+ [_in, _out, _err].each { |io| io.close unless io.nil? || io.closed? }
30
+ Process::waitpid(_pid) if _pid
31
+ end
32
+
33
+ def initialize
34
+ @ready = false
35
+ @pid, @in, @out, @err = nil
36
+ @log = ''
37
+ end
38
+
39
+ def go
40
+ @log = ''
41
+ self.stop # stop existing process
42
+ self.class.remove_ios_apps
43
+
44
+ @@thread.exit if @@thread
45
+ @@thread = Thread.new do
46
+ Thread.current.abort_on_exception = true
47
+ self.start.wait
48
+ end
49
+
50
+ while !self.ready
51
+ sleep 0.5
52
+ end
53
+ end
54
+
55
+ ##
56
+ # Internal methods
57
+
58
+ def wait
59
+ out_err = [@out, @err]
60
+
61
+ # https://github.com/rtomayko/posix-spawn/blob/1d498232660763ff0db6a2f0ab5c1c47fe593896/lib/posix/spawn/child.rb
62
+ while out_err.any?
63
+ io_array = IO.select out_err, [], out_err
64
+ raise 'Appium never spawned' if io_array.nil?
65
+
66
+ ready_for_reading = io_array[0]
67
+
68
+ ready_for_reading.each do |stream|
69
+ begin
70
+ capture = stream.readpartial 999_999
71
+ @log += capture if capture
72
+ @ready = true if !@ready && capture.include?('Appium REST http interface listener started')
73
+ rescue EOFError
74
+ out_err.delete stream
75
+ stream.close
76
+ end
77
+ end
78
+ end
79
+ end
80
+
81
+ # https://github.com/rtomayko/posix-spawn#posixspawn-as-a-mixin
82
+ def end_all_nodes
83
+ self.class.kill_all 'node'
84
+ end
85
+
86
+ # Invoked inside a thread by `self.go`
87
+ def start
88
+ @log = ''
89
+ self.end_all_nodes
90
+ @ready = false
91
+ appium_home = ENV['APPIUM_HOME']
92
+ raise "ENV['APPIUM_HOME'] must be set!" if appium_home.nil? || appium_home.empty?
93
+ contains_appium = File.exists?(File.join(ENV['APPIUM_HOME'], 'server.js'))
94
+ raise "Appium home `#{appium_home}` doesn't contain server.js!" unless contains_appium
95
+ cmd = %Q(cd "#{appium_home}"; node server.js)
96
+ @pid, @in, @out, @err = popen4 cmd
97
+ @in.close
98
+ self # used to chain `start.wait`
99
+ end
100
+
101
+ def stop
102
+ @log = ''
103
+ # https://github.com/tmm1/pygments.rb/blob/master/lib/pygments/popen.rb
104
+ begin
105
+ Process.kill 'KILL', @pid
106
+ Process.waitpid @pid
107
+ rescue
108
+ end unless @pid.nil?
109
+ @pid = nil
110
+ self.end_all_nodes
111
+ end
112
+ end # class Appium
113
+ end # module Flaky
@@ -0,0 +1,105 @@
1
+ # encoding: utf-8
2
+ module Flaky
3
+ class << self
4
+ # Monaco 18 pt
5
+ # ANSI colors
6
+ #
7
+ # [36m = 00eee9 = cyan
8
+ # [32m = 00e800 = green
9
+ # [0m = f4f4f4 = reset
10
+ def html_before
11
+ <<-'HTML'
12
+ <html>
13
+ <head>
14
+ <style>
15
+ * {
16
+ padding: 0;
17
+ margin: 0;
18
+ border: 0;
19
+ width: 0;
20
+ height: 0;
21
+ }
22
+ div { display: inline; }
23
+ body { background-color: #262626; }
24
+ #terminal {
25
+ display: inherit;
26
+ white-space: pre;
27
+ font-family: "Monaco";
28
+ font-size: 14px;
29
+ color: #f4f4f4;
30
+ padding-left: 18px;
31
+ }
32
+ div.cyan { color: #00eee9; }
33
+ div.green { color: #00e800; }
34
+ div.grey { color: #666666; }
35
+ </style>
36
+ </head>
37
+ <body>
38
+ <div id="terminal">
39
+ HTML
40
+ end
41
+
42
+ def html_after
43
+ <<-'HTML'
44
+ </div>
45
+ </body>
46
+ </html>
47
+ HTML
48
+ end
49
+
50
+ def write log_file, log
51
+ # directory must exist
52
+ FileUtils.mkdir_p File.dirname log_file
53
+ # Pry & Awesome Print use the ruby objects to insert term colors.
54
+ # this can't be done with the raw text output.
55
+
56
+ # must escape for rendering HTML in the browser
57
+ log = EscapeUtils.escape_html log
58
+
59
+ # POST /wd/hub/session 303 6877ms - 9
60
+
61
+ scan = StringScanner.new log
62
+
63
+ new_log = '<div>'
64
+
65
+ color_rgx = /\[(\d+)m/
66
+ while !scan.eos?
67
+ match = scan.scan_until color_rgx
68
+ match_size = scan.matched_size
69
+
70
+ # no more color codes
71
+ if match_size.nil?
72
+ new_log += scan.rest
73
+ new_log += '</div>'
74
+ break
75
+ end
76
+
77
+ # save before the color code, excluding the color code
78
+ new_log += match[0..-1 - match_size]
79
+ new_log += '</div>'
80
+
81
+ found_number = match.match(color_rgx).to_a.last.gsub(/[^\d]/,'').to_i
82
+
83
+ # now make a new colored div
84
+ color = case(found_number)
85
+ when 39, 0 # white text
86
+ '<div>'
87
+ when 90 # grey
88
+ '<div class="grey">'
89
+ when 36
90
+ '<div class="cyan">'
91
+ when 32
92
+ '<div class="green">'
93
+ else
94
+ '<div>' # Unknown color code
95
+ end
96
+
97
+ new_log += color
98
+ end
99
+
100
+ File.open(log_file, 'w') do |f|
101
+ f.write html_before + new_log + html_after
102
+ end
103
+ end
104
+ end # class << self
105
+ end # module Flaky
@@ -0,0 +1,143 @@
1
+ # encoding: utf-8
2
+ module Flaky
3
+ module Color
4
+ def cyan str
5
+ "\e[36m#{str}\e[0m"
6
+ end
7
+
8
+ def red str
9
+ "\e[31m#{str}\e[0m"
10
+ end
11
+
12
+ def green str
13
+ "\e[32m#{str}\e[0m"
14
+ end
15
+ end
16
+
17
+ class Run
18
+ include Flaky::Color
19
+ attr_reader :tests, :result_dir, :result_file
20
+
21
+ def initialize
22
+ @tests = {}
23
+ @start_time = Time.now
24
+
25
+ result_dir = '/tmp/flaky/'
26
+ # rm -rf result_dir
27
+ FileUtils.rm_rf result_dir
28
+ FileUtils.mkdir_p result_dir
29
+
30
+ @result_dir = result_dir
31
+ @result_file = File.join result_dir, 'result.txt'
32
+ end
33
+
34
+ def report opts={}
35
+ save_file = opts.fetch :save_file, true
36
+ puts "\n" * 2
37
+ success = ''
38
+ failure = ''
39
+ @tests.each do |name, stats|
40
+ runs = stats[:runs]
41
+ pass = stats[:pass]
42
+ fail = stats[:fail]
43
+ line = "#{name}, runs: #{runs}, pass: #{pass}," +
44
+ " fail: #{fail}\n"
45
+ if fail > 0
46
+ failure += line
47
+ else
48
+ success += line
49
+ end
50
+ end
51
+
52
+ out = ''
53
+ out += "Failure:\n#{failure}\n" unless failure.empty?
54
+ out += "Success:\n#{success}" unless success.empty?
55
+
56
+ duration = Time.now - @start_time
57
+ duration = ChronicDuration.output(duration.round) || '0s'
58
+ out += "\nFinished in #{duration}"
59
+
60
+ # overwrite file
61
+ File.open(@result_file, 'w') do |f|
62
+ f.puts out
63
+ end if save_file
64
+
65
+ puts out
66
+ end
67
+
68
+ def _execute run_cmd, test_name, runs, appium
69
+ # must capture exit code or log is an array.
70
+ log, exit_code = Open3.capture2e run_cmd
71
+
72
+ result = /\d+ runs, \d+ assertions, \d+ failures, \d+ errors, \d+ skips/
73
+ success = /0 failures, 0 errors, 0 skips/
74
+ passed = true
75
+
76
+ found_results = log.scan result
77
+ # all result instances must match success
78
+ found_results.each do |result|
79
+ # runs must be >= 1. 0 runs mean no tests were run.
80
+ r_count = result.match /(\d+) runs/
81
+ runs_not_zero = r_count && r_count[1] && r_count[1].to_i > 0 ? true : false
82
+
83
+ unless result.match(success) && runs_not_zero
84
+ passed = false
85
+ break
86
+ end
87
+ end
88
+
89
+ # no results found.
90
+ passed = false if found_results.length <= 0
91
+ pass_str = passed ? 'pass' : 'fail'
92
+ test = @tests[test_name]
93
+ # save log
94
+ if passed
95
+ pass = test[:pass] += 1
96
+ postfix = "pass_#{pass}"
97
+ else
98
+ fail = test[:fail] += 1
99
+ postfix = "fail_#{fail}"
100
+ end
101
+
102
+ postfix = "#{runs}_#{test_name}_" + postfix
103
+ postfix = '0' + postfix if runs <= 9
104
+
105
+ log_name = "#{postfix}.html"
106
+ log_name = File.join result_dir, pass_str, log_name
107
+ Flaky.write log_name, log
108
+
109
+ appium_log_name = File.join result_dir, pass_str, "#{postfix}.appium.html"
110
+ Flaky.write appium_log_name, appium.log
111
+
112
+ # save uncolored version
113
+ # File.open(appium_log_name + '.nocolor.txt', 'w') do |f|
114
+ # f.write appium.log
115
+ # end
116
+
117
+ passed
118
+ end
119
+
120
+ def execute opts={}
121
+ run_cmd = opts[:run_cmd]
122
+ test_name = opts[:test_name]
123
+ appium = opts[:appium]
124
+
125
+ raise 'must pass :run_cmd' unless run_cmd
126
+ raise 'must pass :test_name' unless test_name
127
+ raise 'must pass :appium' unless appium
128
+
129
+ test = @tests[test_name] ||= {runs: 0, pass: 0, fail: 0}
130
+ runs = test[:runs] += 1
131
+
132
+ passed = _execute run_cmd, test_name, runs, appium
133
+
134
+ print cyan("\n #{test_name} ") if @last_test.nil? ||
135
+ @last_test != test_name
136
+
137
+ print passed ? green(' ✓') : red(' ✖')
138
+
139
+ @last_test = test_name
140
+ passed
141
+ end
142
+ end # class Run
143
+ end # module Flaky
@@ -0,0 +1,28 @@
1
+ # encoding: utf-8
2
+ module Flaky
3
+ # TODO: Rewrite to run without system specific dependencies.
4
+ def self.run_glob_of_tests
5
+ flaky = Flaky::Run.new
6
+
7
+ txt = File.join File.expand_path('../..', __FILE__), 'automation.txt'
8
+ dir = File.readlines(txt)[0].strip
9
+ spec_dir = File.join dir, 'appium', 'ios', 'specs'
10
+ ios = File.join spec_dir, '**', '*.rb'
11
+
12
+ appium = Appium.new
13
+
14
+ Dir.glob(ios) do |file|
15
+ next unless file.include?('view_album')
16
+ next unless File.extname(file).downcase == '.rb'
17
+
18
+ appium.go
19
+
20
+ rake_file = File.basename file, '.*'
21
+ run_cmd = "cd #{dir}; rake ios['#{rake_file}']"
22
+ flaky.execute run_cmd: run_cmd, test_name: file.sub(dir, '').gsub('/', '_')
23
+ end
24
+
25
+ appium.stop
26
+ flaky.report
27
+ end
28
+ end # module Flaky
@@ -0,0 +1,41 @@
1
+ # encoding: utf-8
2
+ module Flaky
3
+ def self.run_one_test opts={}
4
+ raise 'Must pass :count and :name' unless opts && opts[:count] && opts[:os] && opts[:name]
5
+
6
+ count = opts[:count].to_i
7
+ os = opts[:os]
8
+ name = opts[:name]
9
+
10
+ raise ':count must be an int' unless count.kind_of?(Integer)
11
+ raise ':os must be a string' unless os.kind_of?(String)
12
+ raise ':name must be a string' unless name.kind_of?(String)
13
+
14
+ # ensure file name does not contain an extension
15
+ name = File.basename name, '.*'
16
+
17
+ flaky = Flaky::Run.new
18
+ appium = Appium.new
19
+
20
+ current_dir = Dir.pwd
21
+
22
+ raise "Rakefile doesn't exist in #{current_dir}" unless File.exists?(File.join(current_dir, 'Rakefile'))
23
+
24
+ file = ''
25
+ Dir.glob(File.join current_dir, 'appium', os, 'specs', "**/#{name}.rb") do |test_file|
26
+ file = test_file
27
+ end
28
+
29
+ raise "#{test_file} does not exist." if file.empty?
30
+ test_name = file.sub(current_dir + '/appium/', '').gsub('/', '_')
31
+
32
+ puts "Running #{name} #{count}x"
33
+ count.times do
34
+ appium.go # start appium
35
+ run_cmd = "cd #{current_dir}; rake ios['#{name}']"
36
+ flaky.execute run_cmd: run_cmd, test_name: test_name, appium: appium
37
+ end
38
+
39
+ flaky.report
40
+ end
41
+ end # module Flaky
@@ -0,0 +1,15 @@
1
+ #### flaky [![Gem Version](https://badge.fury.io/rb/flaky.png)](http://rubygems.org/gems/flaky) [![Dependency Status](https://gemnasium.com/bootstraponline/flaky.png)](https://gemnasium.com/bootstraponline/flaky)
2
+
3
+ Run Appium iOS tests to measure flakiness.
4
+
5
+ - `gem install flaky`
6
+ - `flake 3 ios[nop]` - Run the iOS test named nop 3 times.
7
+
8
+ Results are stored in `/tmp/flaky`
9
+
10
+ Must set `ENV['APPIUM_HOME']` to point to the appium folder containing `server.js`.
11
+
12
+ This only works with:
13
+
14
+ - [Ruby / appium_lib iOS](https://github.com/appium/ruby_lib_ios)
15
+ - iOS iPhone Simulator 6.1
@@ -0,0 +1,5 @@
1
+ #### v0.0.4 2013-09-27
2
+
3
+ - [7e9918f](https://github.com/appium/flaky/commit/7e9918f5a5dbf7027e448e177780be68857d11fa) Release 0.0.4
4
+ - [6f3b278](https://github.com/appium/flaky/commit/6f3b27864a7a82554ef228806bfd3e3b1c69b9d0) Add readme
5
+ - [3ee2e43](https://github.com/appium/flaky/commit/3ee2e43723a390d220656e128503f4e0ddd9c738) Fix bin/flaky
@@ -0,0 +1,16 @@
1
+ # encoding: utf-8
2
+ require File.expand_path '../lib/flaky/log', __FILE__
3
+ require File.expand_path '../lib/flaky/run', __FILE__
4
+ require File.expand_path '../mock_execute', __FILE__
5
+
6
+ flaky = Flaky::Run.new
7
+
8
+ dir = File.readlines('automation.txt')[0].strip
9
+ ios = File.join dir, 'appium', 'ios', 'specs', '**', '*.rb'
10
+
11
+ Dir.glob(ios) do |file|
12
+ run_cmd = "cd #{dir}; rake ios['#{file}']"
13
+ flaky.execute run_cmd: run_cmd, test_name: file.sub(dir, '')
14
+ end
15
+
16
+ flaky.report
@@ -0,0 +1,15 @@
1
+ module Flaky
2
+ class Run
3
+ def _execute run_cmd, test_name, count
4
+ passed = rand(0..1) == 0 ? false : true
5
+ test = @tests[test_name]
6
+ if passed
7
+ test[:pass] += 1
8
+ else
9
+ test[:fail] += 1
10
+ end
11
+
12
+ passed
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,13 @@
1
+ # encoding: utf-8
2
+ require File.expand_path '../lib/flaky/run', __FILE__
3
+ require File.expand_path '../mock_execute', __FILE__
4
+
5
+ require 'rubygems'
6
+ require 'awesome_print'
7
+
8
+ flaky = Flaky::Run.new
9
+ 4.times do |count|
10
+ (rand(1..2)).times { flaky.execute run_cmd: '', test_name: "test_#{count}" }
11
+ end
12
+
13
+ flaky.report save_file: false
data/todo.md ADDED
@@ -0,0 +1,6 @@
1
+ #### To Do
2
+
3
+ - Save appium server log after each test.
4
+ - Save execution time for each test. (this may already be in the minitest logs)
5
+ - Save overall execution time.
6
+ - Fix test numbering. 01, 02, etc.
metadata ADDED
@@ -0,0 +1,118 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: flaky
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.5
5
+ platform: ruby
6
+ authors:
7
+ - code@bootstraponline.com
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2013-09-30 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: chronic_duration
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ~>
18
+ - !ruby/object:Gem::Version
19
+ version: 0.10.2
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ~>
25
+ - !ruby/object:Gem::Version
26
+ version: 0.10.2
27
+ - !ruby/object:Gem::Dependency
28
+ name: escape_utils
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ~>
32
+ - !ruby/object:Gem::Version
33
+ version: 0.3.2
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ~>
39
+ - !ruby/object:Gem::Version
40
+ version: 0.3.2
41
+ - !ruby/object:Gem::Dependency
42
+ name: posix-spawn
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ~>
46
+ - !ruby/object:Gem::Version
47
+ version: 0.3.6
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ~>
53
+ - !ruby/object:Gem::Version
54
+ version: 0.3.6
55
+ - !ruby/object:Gem::Dependency
56
+ name: rake
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ~>
60
+ - !ruby/object:Gem::Version
61
+ version: 10.1.0
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ~>
67
+ - !ruby/object:Gem::Version
68
+ version: 10.1.0
69
+ description: Measure flaky Ruby Appium tests.
70
+ email:
71
+ - code@bootstraponline.com
72
+ executables:
73
+ - flake
74
+ extensions: []
75
+ extra_rdoc_files: []
76
+ files:
77
+ - LICENSE-2.0.txt
78
+ - Rakefile
79
+ - bin/flake
80
+ - flaky.gemspec
81
+ - lib/flaky.rb
82
+ - lib/flaky/appium.rb
83
+ - lib/flaky/log.rb
84
+ - lib/flaky/run.rb
85
+ - lib/flaky/run/glob_of_tests.rb
86
+ - lib/flaky/run/one_test.rb
87
+ - readme.md
88
+ - release_notes.md
89
+ - test/all_tests.rb
90
+ - test/mock_execute.rb
91
+ - test/test_run.rb
92
+ - todo.md
93
+ homepage: https://github.com/bootstraponline/flaky
94
+ licenses:
95
+ - http://www.apache.org/licenses/LICENSE-2.0.txt
96
+ metadata: {}
97
+ post_install_message:
98
+ rdoc_options: []
99
+ require_paths:
100
+ - lib
101
+ required_ruby_version: !ruby/object:Gem::Requirement
102
+ requirements:
103
+ - - '>='
104
+ - !ruby/object:Gem::Version
105
+ version: 1.9.3
106
+ required_rubygems_version: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - '>='
109
+ - !ruby/object:Gem::Version
110
+ version: '0'
111
+ requirements: []
112
+ rubyforge_project:
113
+ rubygems_version: 2.0.6
114
+ signing_key:
115
+ specification_version: 4
116
+ summary: Measure flaky Ruby Appium tests
117
+ test_files: []
118
+ has_rdoc: