xcknife 0.1.0 → 0.5.0
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/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
|
+
[](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
|