xcknife 0.1.0 → 0.5.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CONTRIBUTING.md +15 -0
- data/README.md +218 -1
- data/Rakefile +6 -0
- data/bin/xcknife +4 -0
- data/bin/xcknife-min +38 -0
- data/example/README.md +15 -0
- data/example/run_example.rb +34 -0
- data/example/xcknife-exemplar-historical-data.json-stream +14 -0
- data/example/xcknife-exemplar.json-stream +15 -0
- data/lib/xcknife.rb +9 -0
- data/lib/xcknife/events_analyzer.rb +55 -0
- data/lib/xcknife/exceptions.rb +6 -0
- data/lib/xcknife/json_stream_parser_helper.rb +24 -0
- data/lib/xcknife/runner.rb +138 -0
- data/lib/xcknife/stream_parser.rb +222 -0
- data/lib/xcknife/xctool_cmd_helper.rb +14 -0
- data/spec/spec_helper.rb +4 -0
- data/spec/xcknife_spec.rb +344 -0
- data/spec/xctool_cmd_helper_spec.rb +13 -0
- data/xcknife.gemspec +5 -3
- metadata +30 -6
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 0b6205c8d36ae8e54613311d15ca1819c311e091
|
4
|
+
data.tar.gz: fee83a2de8da43399901a1b1dd90185b6dcbf8e1
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 938970059eaeb4463ee976b0b22a05b0912098817f007dc22e2ac1401fb7dac8fc148ed8c4c1da8bbb99dfd82951fcf51d19873d8d14c116f38b695d3f554667
|
7
|
+
data.tar.gz: 9bf514e7e67fdf21ddbf358bdf9fc58569a0eef1e25a0c4715aa6524b0cfdae0fa14214db30eee8c50e880b366905686316d53f6acf3ae9a85d1eb27648d6de9
|
data/CONTRIBUTING.md
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
### Sign the CLA
|
2
|
+
|
3
|
+
Any contributors to the master *xcknife* repository must sign the
|
4
|
+
[Individual Contributor License Agreement (CLA)]. It's a short form that covers
|
5
|
+
our bases and makes sure you're eligible to contribute.
|
6
|
+
|
7
|
+
### Submitting a Pull Request
|
8
|
+
|
9
|
+
When you have a change you'd like to see in the master repository, send a
|
10
|
+
[pull request]. Before we merge your request, we'll make sure you're in the list
|
11
|
+
of people who have signed a CLA.
|
12
|
+
|
13
|
+
[Individual Contributor License Agreement (CLA)]: https://spreadsheets.google.com/spreadsheet/viewform?formkey=dDViT2xzUHAwRkI3X3k5Z0lQM091OGc6MQ&ndplr=1
|
14
|
+
[pull request]: https://github.com/square/xcknife/pulls
|
15
|
+
|
data/README.md
CHANGED
@@ -1 +1,218 @@
|
|
1
|
-
|
1
|
+
# XCKnife
|
2
|
+
[![Apache 2 licensed](https://img.shields.io/badge/license-Apache2-blue.svg)](https://github.com/square/xcknife/blob/master/LICENSE)
|
3
|
+
|
4
|
+
XCKnife is a tool that partitions XCTestCase tests in a way that minimizes total execution time (particularly useful for distributed test executions).
|
5
|
+
|
6
|
+
It works by leveraging [xctool's](https://github.com/facebook/xctool) [json-stream](https://github.com/facebook/xctool#included-reporters) reporter output.
|
7
|
+
|
8
|
+
XCKnife generates a list of only arguments meant to be pass to Xctool's [*-only* test arguments](https://github.com/facebook/xctool#testing), but alternatively could used to generate multiple xcschemes with the proper test partitions.
|
9
|
+
|
10
|
+
## Install
|
11
|
+
|
12
|
+
`$ gem install xcknife`
|
13
|
+
|
14
|
+
## Using as command line tool
|
15
|
+
|
16
|
+
```
|
17
|
+
$ xcknife -h
|
18
|
+
Usage: xcknife [options] worker-count historical-timings-json-stream-file [current-tests-json-stream-file]
|
19
|
+
-p, --partition TARGETS Comma separated list of targets. Can be used multiple times
|
20
|
+
-o, --output FILENAME Output file. Defaults to STDOUT
|
21
|
+
-a, --abbrev Results are abbreviated
|
22
|
+
-h, --help Show this message
|
23
|
+
```
|
24
|
+
|
25
|
+
## Example
|
26
|
+
|
27
|
+
The data provided on the [example](https://github.com/square/xcknife/tree/master/example) folder:
|
28
|
+
|
29
|
+
`$ xcknife -p iPhoneTestTarget 3 example/xcknife-exemplar-historical-data.json-stream example/xcknife-exemplar.json-stream`
|
30
|
+
|
31
|
+
This will balance the tests onthe `iPhoneTestTarget` into 3 machines. The output is:
|
32
|
+
|
33
|
+
```json
|
34
|
+
{
|
35
|
+
"metadata": {
|
36
|
+
"worker_count": 3,
|
37
|
+
"partition_set_count": 1,
|
38
|
+
"total_time_in_ms": 910,
|
39
|
+
"historical_total_tests": 5,
|
40
|
+
"current_total_tests": 5,
|
41
|
+
"class_extrapolations": 0,
|
42
|
+
"target_extrapolations": 0
|
43
|
+
},
|
44
|
+
"partition_set_data": [
|
45
|
+
{
|
46
|
+
"partition_set": "iPhoneTestTarget",
|
47
|
+
"size": 3,
|
48
|
+
"imbalance_ratio": 1.0,
|
49
|
+
"partitions": [
|
50
|
+
{
|
51
|
+
"shard_number": 1,
|
52
|
+
"cli_arguments": [ "-only", "iPhoneTestTarget:iPhoneTestClassGama" ],
|
53
|
+
"partition_imbalance_ratio": 0.9923076923076923
|
54
|
+
},
|
55
|
+
{
|
56
|
+
"shard_number": 2,
|
57
|
+
"cli_arguments": [ "-only", "iPhoneTestTarget:iPhoneTestClassAlpha,iPhoneTestClassDelta" ],
|
58
|
+
"partition_imbalance_ratio": 1.0054945054945055
|
59
|
+
},
|
60
|
+
{
|
61
|
+
"shard_number": 3,
|
62
|
+
"cli_arguments": [ "-only", "iPhoneTestTarget:iPhoneTestClassBeta,iPhoneTestClassOmega" ],
|
63
|
+
"partition_imbalance_ratio": 1.0021978021978022
|
64
|
+
}]}]}
|
65
|
+
```
|
66
|
+
|
67
|
+
This provides a lot of data about the partitions and their imabalances (both internal to the partition sets, and amongst them).
|
68
|
+
|
69
|
+
If you only want the *-only* arguments, run with the `-a` flag:
|
70
|
+
|
71
|
+
`$ xcknife -p iPhoneTestTarget 3 example/xcknife-exemplar-historical-data.json-stream example/xcknife-exemplar.json-stream -a`
|
72
|
+
|
73
|
+
outputing:
|
74
|
+
|
75
|
+
```json
|
76
|
+
[
|
77
|
+
[
|
78
|
+
[
|
79
|
+
"-only",
|
80
|
+
"iPhoneTestTarget:iPhoneTestClassGama"
|
81
|
+
],
|
82
|
+
[
|
83
|
+
"-only",
|
84
|
+
"iPhoneTestTarget:iPhoneTestClassAlpha,iPhoneTestClassDelta"
|
85
|
+
],
|
86
|
+
[
|
87
|
+
"-only",
|
88
|
+
"iPhoneTestTarget:iPhoneTestClassBeta,iPhoneTestClassOmega"
|
89
|
+
]
|
90
|
+
]
|
91
|
+
]
|
92
|
+
```
|
93
|
+
|
94
|
+
## Example: Multiple partition Sets
|
95
|
+
|
96
|
+
You can pass the partition flag mutliple times, so that XCKnife will do two level partiotioning: inside each partition, and then for all partitions.
|
97
|
+
|
98
|
+
This is useful if each partition is tested against multiple devices, simulator versions or configurations. On the following example picture `CommonTestTarget` being tested against iPhones only, while `CommonTestTarget,iPadTestTarget` is tested against iPads.
|
99
|
+
|
100
|
+
`$ xcknife -p CommonTestTarget -p CommonTestTarget,iPadTestTarget 6 example/xcknife-exemplar-historical-data.json-stream example/xcknife-exemplar.json-stream`
|
101
|
+
|
102
|
+
This will balance two partition sets: `CommonTestTarget` and `CommonTestTarget,iPadTestTarget` into 6 machines. The output is:
|
103
|
+
|
104
|
+
```json
|
105
|
+
{
|
106
|
+
"metadata": {
|
107
|
+
"worker_count": 6,
|
108
|
+
"partition_set_count": 2,
|
109
|
+
"total_time_in_ms": 8733,
|
110
|
+
"historical_total_tests": 6,
|
111
|
+
"current_total_tests": 7,
|
112
|
+
"class_extrapolations": 1,
|
113
|
+
"target_extrapolations": 0
|
114
|
+
},
|
115
|
+
"partition_set_data": [
|
116
|
+
{
|
117
|
+
"partition_set": "CommonTestTarget",
|
118
|
+
"size": 1,
|
119
|
+
"imbalance_ratio": 0.5480554313813143,
|
120
|
+
"partitions": [
|
121
|
+
{
|
122
|
+
"shard_number": 1,
|
123
|
+
"cli_arguments": [
|
124
|
+
"-only",
|
125
|
+
"CommonTestTarget:CommonTestClass"
|
126
|
+
],
|
127
|
+
"partition_imbalance_ratio": 1.0
|
128
|
+
}
|
129
|
+
]
|
130
|
+
},
|
131
|
+
{
|
132
|
+
"partition_set": "CommonTestTarget,iPadTestTarget",
|
133
|
+
"size": 5,
|
134
|
+
"imbalance_ratio": 1.4519445686186858,
|
135
|
+
"partitions": [
|
136
|
+
{
|
137
|
+
"shard_number": 2,
|
138
|
+
"cli_arguments": [
|
139
|
+
"-only",
|
140
|
+
"iPadTestTarget:iPadTestClassTwo"
|
141
|
+
],
|
142
|
+
"partition_imbalance_ratio": 3.0800492610837438
|
143
|
+
},
|
144
|
+
{
|
145
|
+
"shard_number": 3,
|
146
|
+
"cli_arguments": [
|
147
|
+
"-only",
|
148
|
+
"iPadTestTarget:iPadTestClassOne"
|
149
|
+
],
|
150
|
+
"partition_imbalance_ratio": 0.6169950738916257
|
151
|
+
},
|
152
|
+
{
|
153
|
+
"shard_number": 4,
|
154
|
+
"cli_arguments": [
|
155
|
+
"-only",
|
156
|
+
"iPadTestTarget:iPadTestClassFour"
|
157
|
+
],
|
158
|
+
"partition_imbalance_ratio": 0.6169950738916257
|
159
|
+
},
|
160
|
+
{
|
161
|
+
"shard_number": 5,
|
162
|
+
"cli_arguments": [
|
163
|
+
"-only",
|
164
|
+
"CommonTestTarget:CommonTestClass"
|
165
|
+
],
|
166
|
+
"partition_imbalance_ratio": 0.3774630541871921
|
167
|
+
},
|
168
|
+
{
|
169
|
+
"shard_number": 6,
|
170
|
+
"cli_arguments": [
|
171
|
+
"-only",
|
172
|
+
"iPadTestTarget:iPadTestClassThree"
|
173
|
+
],
|
174
|
+
"partition_imbalance_ratio": 0.30849753694581283
|
175
|
+
}
|
176
|
+
]
|
177
|
+
}
|
178
|
+
]
|
179
|
+
}
|
180
|
+
```
|
181
|
+
|
182
|
+
## Using as Ruby gem
|
183
|
+
|
184
|
+
Described [here](https://github.com/square/xcknife/tree/master/example).
|
185
|
+
|
186
|
+
## Minimizing json-stream files
|
187
|
+
|
188
|
+
XCKnife uses only a few attributes of a json-stream file. If you are storing the files in repository, you may want to remove unecessary data with `xcknife-min`. For example:
|
189
|
+
|
190
|
+
`$ xcknife-min example/xcknife-exemplar-historical-data.json-stream minified.json-stream`
|
191
|
+
|
192
|
+
## Contributing
|
193
|
+
|
194
|
+
Any contributors to the master *xcknife* repository must sign the
|
195
|
+
[Individual Contributor License Agreement (CLA)]. It's a short form that covers
|
196
|
+
our bases and makes sure you're eligible to contribute.
|
197
|
+
|
198
|
+
When you have a change you'd like to see in the master repository, send a
|
199
|
+
[pull request]. Before we merge your request, we'll make sure you're in the list
|
200
|
+
of people who have signed a CLA.
|
201
|
+
|
202
|
+
[Individual Contributor License Agreement (CLA)]: https://spreadsheets.google.com/spreadsheet/viewform?formkey=dDViT2xzUHAwRkI3X3k5Z0lQM091OGc6MQ&ndplr=1
|
203
|
+
[pull request]: https://github.com/square/xcknife/pulls
|
204
|
+
|
205
|
+
|
206
|
+
## License
|
207
|
+
|
208
|
+
Copyright 2016 Square Inc.
|
209
|
+
|
210
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
211
|
+
you may not use this file except in compliance with the License.
|
212
|
+
You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
|
213
|
+
|
214
|
+
Unless required by applicable law or agreed to in writing, software
|
215
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
216
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
217
|
+
See the License for the specific language governing permissions and
|
218
|
+
limitations under the License.
|
data/Rakefile
ADDED
data/bin/xcknife
ADDED
data/bin/xcknife-min
ADDED
@@ -0,0 +1,38 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# encoding: utf-8
|
3
|
+
|
4
|
+
# Script for cleaning up historical events, so that we only store the ones we need
|
5
|
+
require 'set'
|
6
|
+
require 'json' unless defined?(::JSON)
|
7
|
+
|
8
|
+
INTERESTING_EVENTS = %w[begin-ocunit end-test].to_set
|
9
|
+
def cleanup(input_file_name, output_file_name)
|
10
|
+
if input_file_name.nil? or output_file_name.nil?
|
11
|
+
return puts "Usage: xcknife_clean [input] [output]"
|
12
|
+
end
|
13
|
+
lines = IO.readlines(input_file_name)
|
14
|
+
lines_written = 0
|
15
|
+
total = lines.size
|
16
|
+
File.open(output_file_name, "w") do |f|
|
17
|
+
lines.each do |line|
|
18
|
+
data = JSON.load(line)
|
19
|
+
if INTERESTING_EVENTS.include?(data["event"])
|
20
|
+
lines_written += 1
|
21
|
+
%w[output sdk timestamp exceptions result succeeded methodName].each do |k|
|
22
|
+
data.delete(k)
|
23
|
+
end
|
24
|
+
if data["test"]
|
25
|
+
data["test"] = "1"
|
26
|
+
end
|
27
|
+
f.puts(data.to_json)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
lines_removed = total - lines_written
|
32
|
+
percent = (100.0 * lines_removed / total).round(2)
|
33
|
+
puts "Done. Removed #{lines_removed} lines (#{percent}%) out of #{total}"
|
34
|
+
puts "Written new json-stream file to: #{output_file_name}"
|
35
|
+
end
|
36
|
+
|
37
|
+
input_file_name, output_file_name = ARGV
|
38
|
+
cleanup input_file_name, output_file_name
|
data/example/README.md
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
# Example
|
2
|
+
|
3
|
+
Data gathered from the [xcknife-exemplar sample project](https://github.com/danielribeiro/xcknife-exemplar).
|
4
|
+
|
5
|
+
To run the example run:
|
6
|
+
|
7
|
+
```
|
8
|
+
$ ruby run_example.rb
|
9
|
+
```
|
10
|
+
|
11
|
+
This is equivalent to the example of:
|
12
|
+
|
13
|
+
```
|
14
|
+
$ xcknife -p CommonTestTarget -p CommonTestTarget,iPadTestTarget 6 example/xcknife-exemplar-historical-data.json-stream example/xcknife-exemplar.json-stream
|
15
|
+
```
|
@@ -0,0 +1,34 @@
|
|
1
|
+
require_relative '../lib/xcknife'
|
2
|
+
require 'pp'
|
3
|
+
|
4
|
+
# Gem usage of xcknife. Functionaly equivalent to
|
5
|
+
# $ xcknife -p CommonTestTarget -p CommonTestTarget,iPadTestTarget 6 example/xcknife-exemplar-historical-data.json-stream example/xcknife-exemplar.json-stream
|
6
|
+
include XCKnife::XCToolCmdHelper
|
7
|
+
TARGET_PARTITIONS = {
|
8
|
+
"AllTests" => ["CommonTestTarget", "iPadTestTarget"],
|
9
|
+
"OnlyCommon" => ["CommonTestTarget"]
|
10
|
+
}
|
11
|
+
|
12
|
+
def run(historical_file, current_file)
|
13
|
+
test_target_names = TARGET_PARTITIONS.keys
|
14
|
+
knife = XCKnife::StreamParser.new(6, TARGET_PARTITIONS.values)
|
15
|
+
result = knife.compute_shards_for_file(historical_file, current_file)
|
16
|
+
partition_sets = result.test_maps
|
17
|
+
puts "total = #{result.total_test_time}"
|
18
|
+
puts "test times = #{result.test_times.inspect}"
|
19
|
+
puts "stats = #{result.stats.to_h.pretty_inspect}"
|
20
|
+
puts "imbalances = #{result.test_time_imbalances.to_h.inspect}"
|
21
|
+
shard_number = 0
|
22
|
+
puts "size = #{partition_sets.size}"
|
23
|
+
puts "sizes = #{partition_sets.map(&:size).join(", ")}"
|
24
|
+
partition_sets.each_with_index do |partition_set, i|
|
25
|
+
target_name = test_target_names[i]
|
26
|
+
partition_set.each do |partition|
|
27
|
+
puts "target name for worker #{shard_number} = #{target_name}"
|
28
|
+
puts "only is: #{xctool_only_arguments(partition).inspect}"
|
29
|
+
shard_number += 1
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
run("xcknife-exemplar-historical-data.json-stream", "xcknife-exemplar.json-stream")
|
@@ -0,0 +1,14 @@
|
|
1
|
+
{"bundleName":"CommonTestTarget.xctest","deviceName":"iPad Air","event":"begin-ocunit","targetName":"CommonTestTarget","testType":"logic-test","sdkName":"iphonesimulator9.2"}
|
2
|
+
{"test":"1","className":"CommonTestClass","event":"end-test","totalDuration":0.1075069904327393}
|
3
|
+
{"test":"1","className":"CommonTestClass","event":"end-test","totalDuration":0.303464949131012}
|
4
|
+
{"test":"1","className":"CommonTestClass","event":"end-test","totalDuration":0.2003870010375977}
|
5
|
+
{"bundleName":"iPadTestTarget.xctest","deviceName":"iPad Air","event":"begin-ocunit","targetName":"iPadTestTarget","testType":"application-test","sdkName":"iphonesimulator9.2"}
|
6
|
+
{"test":"1","className":"iPadTestClassOne","event":"end-test","totalDuration":1.001249969005585}
|
7
|
+
{"test":"1","className":"iPadTestClassThree","event":"end-test","totalDuration":0.5002140402793884}
|
8
|
+
{"test":"1","className":"iPadTestClassTwo","event":"end-test","totalDuration":5.001157999038696}
|
9
|
+
{"bundleName":"iPhoneTestTarget.xctest","deviceName":"iPad Air","event":"begin-ocunit","targetName":"iPhoneTestTarget","testType":"application-test","sdkName":"iphonesimulator9.2"}
|
10
|
+
{"test":"1","className":"iPhoneTestClassAlpha","event":"end-test","totalDuration":0.2037490010261536}
|
11
|
+
{"test":"1","className":"iPhoneTestClassBeta","event":"end-test","totalDuration":0.2012439966201782}
|
12
|
+
{"test":"1","className":"iPhoneTestClassDelta","event":"end-test","totalDuration":0.1004489660263062}
|
13
|
+
{"test":"1","className":"iPhoneTestClassGama","event":"end-test","totalDuration":0.3001730442047119}
|
14
|
+
{"test":"1","className":"iPhoneTestClassOmega","event":"end-test","totalDuration":0.101157009601593}
|
@@ -0,0 +1,15 @@
|
|
1
|
+
{"bundleName":"iPadTestTarget.xctest","deviceName":"iPad Air","event":"begin-ocunit","targetName":"iPadTestTarget","testType":"application-test","sdkName":"iphonesimulator9.2"}
|
2
|
+
{"test":"1","className":"iPadTestClassFour","event":"end-test","totalDuration":"0"}
|
3
|
+
{"test":"1","className":"iPadTestClassOne","event":"end-test","totalDuration":"0"}
|
4
|
+
{"test":"1","className":"iPadTestClassThree","event":"end-test","totalDuration":"0"}
|
5
|
+
{"test":"1","className":"iPadTestClassTwo","event":"end-test","totalDuration":"0"}
|
6
|
+
{"bundleName":"iPhoneTestTarget.xctest","deviceName":"iPad Air","event":"begin-ocunit","targetName":"iPhoneTestTarget","testType":"application-test","sdkName":"iphonesimulator9.2"}
|
7
|
+
{"test":"1","className":"iPhoneTestClassAlpha","event":"end-test","totalDuration":"0"}
|
8
|
+
{"test":"1","className":"iPhoneTestClassBeta","event":"end-test","totalDuration":"0"}
|
9
|
+
{"test":"1","className":"iPhoneTestClassDelta","event":"end-test","totalDuration":"0"}
|
10
|
+
{"test":"1","className":"iPhoneTestClassGama","event":"end-test","totalDuration":"0"}
|
11
|
+
{"test":"1","className":"iPhoneTestClassOmega","event":"end-test","totalDuration":"0"}
|
12
|
+
{"bundleName":"CommonTestTarget.xctest","deviceName":"iPad Air","event":"begin-ocunit","targetName":"CommonTestTarget","testType":"logic-test","sdkName":"iphonesimulator9.2"}
|
13
|
+
{"test":"1","className":"CommonTestClass","event":"end-test","totalDuration":"0"}
|
14
|
+
{"test":"1","className":"CommonTestClass","event":"end-test","totalDuration":"0"}
|
15
|
+
{"test":"1","className":"CommonTestClass","event":"end-test","totalDuration":"0"}
|
data/lib/xcknife.rb
ADDED
@@ -0,0 +1,55 @@
|
|
1
|
+
require 'xcknife/json_stream_parser_helper'
|
2
|
+
require 'set'
|
3
|
+
|
4
|
+
module XCKnife
|
5
|
+
class EventsAnalyzer
|
6
|
+
include JsonStreamParserHelper
|
7
|
+
attr_reader :target_class_map, :total_tests
|
8
|
+
|
9
|
+
def self.for(events, relevant_partitions)
|
10
|
+
return NullEventsAnalyzer.new if events.nil?
|
11
|
+
new(events, relevant_partitions)
|
12
|
+
end
|
13
|
+
|
14
|
+
def initialize(events, relevant_partitions)
|
15
|
+
@total_tests = 0
|
16
|
+
@relevant_partitions = relevant_partitions
|
17
|
+
@target_class_map = analyze_events(events)
|
18
|
+
end
|
19
|
+
|
20
|
+
def has_test_target?(target)
|
21
|
+
target_class_map.has_key?(target)
|
22
|
+
end
|
23
|
+
|
24
|
+
def has_test_class?(target, clazz)
|
25
|
+
has_test_target?(target) and target_class_map[target].include?(clazz)
|
26
|
+
end
|
27
|
+
|
28
|
+
private
|
29
|
+
def analyze_events(events)
|
30
|
+
ret = Hash.new { |h, key| h[key] = Set.new }
|
31
|
+
each_test_event(events) do |target_name, result|
|
32
|
+
next unless @relevant_partitions.include?(target_name)
|
33
|
+
@total_tests += 1
|
34
|
+
ret[target_name] << result.className
|
35
|
+
end
|
36
|
+
ret
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
# Null object for EventsAnalyzer
|
41
|
+
# @ref https://en.wikipedia.org/wiki/Null_Object_pattern
|
42
|
+
class NullEventsAnalyzer
|
43
|
+
def has_test_target?(target)
|
44
|
+
true
|
45
|
+
end
|
46
|
+
|
47
|
+
def has_test_class?(target, clazz)
|
48
|
+
true
|
49
|
+
end
|
50
|
+
|
51
|
+
def total_tests
|
52
|
+
0
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|