xcknife 0.5.0 → 0.6.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +5 -0
- data/.gitmodules +3 -0
- data/.travis.yml +9 -0
- data/README.md +5 -1
- data/Rakefile +11 -0
- data/TestDumper/README.md +11 -0
- data/TestDumper/TestDumper.xcodeproj/project.pbxproj +525 -0
- data/TestDumper/TestDumper.xcodeproj/xcshareddata/xcschemes/TestDumper.xcscheme +109 -0
- data/TestDumper/TestDumper/Info.plist +26 -0
- data/TestDumper/TestDumper/Initialize.m +193 -0
- data/TestDumper/TestDumper/TestDumper.h +7 -0
- data/TestDumper/build.sh +6 -0
- data/bin/xcknife +0 -0
- data/bin/xcknife-min +1 -1
- data/bin/xcknife-test-dumper +4 -0
- data/example/README.md +1 -3
- data/lib/xcknife.rb +2 -1
- data/lib/xcknife/runner.rb +10 -2
- data/lib/xcknife/test_dumper.rb +196 -0
- data/lib/xcknife/xctool_cmd_helper.rb +26 -0
- data/xcknife.gemspec +2 -2
- metadata +16 -9
- data/spec/spec_helper.rb +0 -4
- data/spec/xcknife_spec.rb +0 -344
- data/spec/xctool_cmd_helper_spec.rb +0 -13
@@ -1,6 +1,18 @@
|
|
1
1
|
require 'pp'
|
2
2
|
module XCKnife
|
3
3
|
module XCToolCmdHelper
|
4
|
+
def only_arguments_for_a_partition_set(output_type, partition_set)
|
5
|
+
method = "#{output_type}_only_arguments_for_a_partition_set"
|
6
|
+
raise "Unknown output_type: #{output_type}" unless respond_to?(method)
|
7
|
+
__send__(method, partition_set)
|
8
|
+
end
|
9
|
+
|
10
|
+
def only_arguments(output_type, partition)
|
11
|
+
method = "#{output_type}_only_arguments"
|
12
|
+
raise "Unknown output_type: #{output_type}" unless respond_to?(method)
|
13
|
+
__send__(method, partition)
|
14
|
+
end
|
15
|
+
|
4
16
|
def xctool_only_arguments(single_partition)
|
5
17
|
single_partition.flat_map do |test_target, classes|
|
6
18
|
['-only', "#{test_target}:#{classes.sort.join(',')}"]
|
@@ -10,5 +22,19 @@ module XCKnife
|
|
10
22
|
def xctool_only_arguments_for_a_partition_set(partition_set)
|
11
23
|
partition_set.map { |partition| xctool_only_arguments(partition) }
|
12
24
|
end
|
25
|
+
|
26
|
+
# only-testing is available since Xcode 8
|
27
|
+
def xcodebuild_only_arguments(single_partition)
|
28
|
+
single_partition.flat_map do |test_target, classes|
|
29
|
+
classes.sort.map do |clazz|
|
30
|
+
"-only-testing:#{test_target}/#{clazz}"
|
31
|
+
end
|
32
|
+
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def xcodebuild_only_arguments_for_a_partition_set(partition_set)
|
37
|
+
partition_set.map { |partition| xctool_only_arguments(partition) }
|
38
|
+
end
|
13
39
|
end
|
14
40
|
end
|
data/xcknife.gemspec
CHANGED
@@ -20,9 +20,9 @@ Gem::Specification.new do |s|
|
|
20
20
|
# Only allow gem to be pushed to https://rubygems.org
|
21
21
|
s.metadata["allowed_push_host"] = 'https://rubygems.org'
|
22
22
|
|
23
|
-
s.files = `git ls-files`.split($INPUT_RECORD_SEPARATOR)
|
23
|
+
s.files = `git ls-files`.split($INPUT_RECORD_SEPARATOR).reject { |f| f =~ /^spec/} + ["TestDumper/TestDumper.dylib"]
|
24
24
|
s.bindir = 'bin'
|
25
|
-
s.executables = ['xcknife', 'xcknife-min']
|
25
|
+
s.executables = ['xcknife', 'xcknife-min', 'xcknife-test-dumper']
|
26
26
|
s.require_paths = ['lib']
|
27
27
|
s.test_files = s.files.grep(%r{^(test|spec|features)/})
|
28
28
|
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: xcknife
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.6.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Daniel Ribeiro
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2017-01-11 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -33,19 +33,31 @@ email:
|
|
33
33
|
executables:
|
34
34
|
- xcknife
|
35
35
|
- xcknife-min
|
36
|
+
- xcknife-test-dumper
|
36
37
|
extensions: []
|
37
38
|
extra_rdoc_files: []
|
38
39
|
files:
|
39
40
|
- ".gitignore"
|
41
|
+
- ".gitmodules"
|
40
42
|
- ".rspec"
|
41
43
|
- ".ruby-version"
|
44
|
+
- ".travis.yml"
|
42
45
|
- CONTRIBUTING.md
|
43
46
|
- Gemfile
|
44
47
|
- LICENSE
|
45
48
|
- README.md
|
46
49
|
- Rakefile
|
50
|
+
- TestDumper/README.md
|
51
|
+
- TestDumper/TestDumper.dylib
|
52
|
+
- TestDumper/TestDumper.xcodeproj/project.pbxproj
|
53
|
+
- TestDumper/TestDumper.xcodeproj/xcshareddata/xcschemes/TestDumper.xcscheme
|
54
|
+
- TestDumper/TestDumper/Info.plist
|
55
|
+
- TestDumper/TestDumper/Initialize.m
|
56
|
+
- TestDumper/TestDumper/TestDumper.h
|
57
|
+
- TestDumper/build.sh
|
47
58
|
- bin/xcknife
|
48
59
|
- bin/xcknife-min
|
60
|
+
- bin/xcknife-test-dumper
|
49
61
|
- example/README.md
|
50
62
|
- example/run_example.rb
|
51
63
|
- example/xcknife-exemplar-historical-data.json-stream
|
@@ -56,10 +68,8 @@ files:
|
|
56
68
|
- lib/xcknife/json_stream_parser_helper.rb
|
57
69
|
- lib/xcknife/runner.rb
|
58
70
|
- lib/xcknife/stream_parser.rb
|
71
|
+
- lib/xcknife/test_dumper.rb
|
59
72
|
- lib/xcknife/xctool_cmd_helper.rb
|
60
|
-
- spec/spec_helper.rb
|
61
|
-
- spec/xcknife_spec.rb
|
62
|
-
- spec/xctool_cmd_helper_spec.rb
|
63
73
|
- xcknife.gemspec
|
64
74
|
homepage: https://github.com/square/xcknife
|
65
75
|
licenses:
|
@@ -86,7 +96,4 @@ rubygems_version: 2.4.5.1
|
|
86
96
|
signing_key:
|
87
97
|
specification_version: 4
|
88
98
|
summary: Simple tool for optimizing XCTest runs across machines
|
89
|
-
test_files:
|
90
|
-
- spec/spec_helper.rb
|
91
|
-
- spec/xcknife_spec.rb
|
92
|
-
- spec/xctool_cmd_helper_spec.rb
|
99
|
+
test_files: []
|
data/spec/spec_helper.rb
DELETED
data/spec/xcknife_spec.rb
DELETED
@@ -1,344 +0,0 @@
|
|
1
|
-
require 'spec_helper'
|
2
|
-
|
3
|
-
describe XCKnife::StreamParser do
|
4
|
-
context 'test_time_for_partitions' do
|
5
|
-
subject { XCKnife::StreamParser.new(2, [["TestTarget1"], ["TestTarget2"]]) }
|
6
|
-
|
7
|
-
it 'decide how many shards each partition set needs' do
|
8
|
-
stream = [xctool_target_event("TestTarget1"),
|
9
|
-
xctool_test_event("ClassTest1", "test1"),
|
10
|
-
xctool_target_event("TestTarget2"),
|
11
|
-
xctool_test_event("ClassTest2", "test1")
|
12
|
-
]
|
13
|
-
result = subject.test_time_for_partitions(stream)
|
14
|
-
expect(result).to eq([{ "TestTarget1" => { "ClassTest1" => 1000 } },
|
15
|
-
{ "TestTarget2" => { "ClassTest2" => 1000 } }])
|
16
|
-
end
|
17
|
-
|
18
|
-
it 'aggretates the times at the class level' do
|
19
|
-
stream_parser = XCKnife::StreamParser.new(2, [["TestTarget1"]])
|
20
|
-
stream = [xctool_target_event("TestTarget1"),
|
21
|
-
xctool_test_event("ClassTest1", "test1", 1.0),
|
22
|
-
xctool_test_event("ClassTest1", "test2", 2.0)
|
23
|
-
]
|
24
|
-
result = stream_parser.test_time_for_partitions(stream)
|
25
|
-
expect(result).to eq([{ "TestTarget1" => { "ClassTest1" => 3000 } }])
|
26
|
-
end
|
27
|
-
|
28
|
-
it 'works with multiple partitions' do
|
29
|
-
stream_parser = XCKnife::StreamParser.new(2, [["TestTarget1"], ["TestTarget2"], ["TestTarget3"]])
|
30
|
-
|
31
|
-
stream = [xctool_target_event("TestTarget1"),
|
32
|
-
xctool_test_event("Class1", "test1"),
|
33
|
-
xctool_target_event("TestTarget2"),
|
34
|
-
xctool_test_event("Class2", "test1"),
|
35
|
-
xctool_target_event("TestTarget3"),
|
36
|
-
xctool_test_event("Class3", "test1"),
|
37
|
-
]
|
38
|
-
result = stream_parser.test_time_for_partitions(stream)
|
39
|
-
expect(result).to eq([{ "TestTarget1" => { "Class1" => 1000 } },
|
40
|
-
{ "TestTarget2" => { "Class2" => 1000 } },
|
41
|
-
{ "TestTarget3" => { "Class3" => 1000 } }])
|
42
|
-
end
|
43
|
-
|
44
|
-
it 'allows the same target to be listed on multiple partitions' do
|
45
|
-
stream_parser = XCKnife::StreamParser.new(2, [["TestTarget1"], ["TestTarget2", "TestTarget1"]])
|
46
|
-
stream = [xctool_target_event("TestTarget1"),
|
47
|
-
xctool_test_event("ClassTest1", "test1"),
|
48
|
-
xctool_target_event("TestTarget2"),
|
49
|
-
xctool_test_event("ClassTest2", "test1"),
|
50
|
-
]
|
51
|
-
result = stream_parser.test_time_for_partitions(stream)
|
52
|
-
expect(result).to eq([{ "TestTarget1" => { "ClassTest1" => 1000 } },
|
53
|
-
{ "TestTarget2" => { "ClassTest2" => 1000 },
|
54
|
-
"TestTarget1" => { "ClassTest1" => 1000 }
|
55
|
-
}])
|
56
|
-
end
|
57
|
-
|
58
|
-
it 'raises error when an empty partition is specified' do
|
59
|
-
stream_parser = XCKnife::StreamParser.new(1, [["TestTarget1"]])
|
60
|
-
expect { stream_parser.test_time_for_partitions([]) }.to raise_error(XCKnife::XCKnifeError, 'The following partition has no tests: ["TestTarget1"]')
|
61
|
-
end
|
62
|
-
end
|
63
|
-
|
64
|
-
context 'provided historical events' do
|
65
|
-
subject { XCKnife::StreamParser.new(2, [["TestTarget1", "TestTarget2", "TestTarget3", "NewTestTarget1"]]) }
|
66
|
-
|
67
|
-
it 'ignores test targets not present on current events' do
|
68
|
-
historical_events = [xctool_target_event("TestTarget1"),
|
69
|
-
xctool_test_event("ClassTest1", "test1"),
|
70
|
-
xctool_test_event("ClassTest1", "test2"),
|
71
|
-
xctool_target_event("TestTarget2"),
|
72
|
-
xctool_test_event("ClassTest2", "test1"),
|
73
|
-
xctool_test_event("ClassTest2", "test2")
|
74
|
-
]
|
75
|
-
current_events = [
|
76
|
-
xctool_target_event("TestTarget1"),
|
77
|
-
xctool_test_event("ClassTest1", "test1")
|
78
|
-
]
|
79
|
-
result = subject.test_time_for_partitions(historical_events, current_events)
|
80
|
-
expect(result).to eq([{ "TestTarget1" => { "ClassTest1" => 2000 } }])
|
81
|
-
expect(subject.stats.to_h).to eq({historical_total_tests: 4, current_total_tests: 1, class_extrapolations: 0, target_extrapolations: 0})
|
82
|
-
end
|
83
|
-
|
84
|
-
|
85
|
-
it 'ignores test classes not present on current events' do
|
86
|
-
historical_events = [xctool_target_event("TestTarget1"),
|
87
|
-
xctool_test_event("ClassTest1", "test1"),
|
88
|
-
xctool_test_event("ClassTest1", "test2"),
|
89
|
-
xctool_test_event("ClassTest2", "test1"),
|
90
|
-
xctool_test_event("ClassTest2", "test2")
|
91
|
-
]
|
92
|
-
current_events = [
|
93
|
-
xctool_target_event("TestTarget1"),
|
94
|
-
xctool_test_event("ClassTest1", "test1")
|
95
|
-
]
|
96
|
-
result = subject.test_time_for_partitions(historical_events, current_events)
|
97
|
-
expect(result).to eq([{ "TestTarget1" => { "ClassTest1" => 2000 } }])
|
98
|
-
expect(subject.stats.to_h).to eq({historical_total_tests: 4, current_total_tests: 1, class_extrapolations: 0, target_extrapolations: 0})
|
99
|
-
end
|
100
|
-
|
101
|
-
it 'extrapolates for new test targets' do
|
102
|
-
historical_events = [
|
103
|
-
xctool_target_event("TestTarget1"),
|
104
|
-
xctool_test_event("ClassTest1", "test1")
|
105
|
-
]
|
106
|
-
current_events = [
|
107
|
-
xctool_target_event("TestTarget1"),
|
108
|
-
xctool_test_event("ClassTest1", "test1"),
|
109
|
-
xctool_target_event("NewTestTargetButNotRelevant"),
|
110
|
-
xctool_test_event("ClassTest10", "test1")
|
111
|
-
]
|
112
|
-
result = subject.test_time_for_partitions(historical_events, current_events)
|
113
|
-
expect(result.to_set).to eq([{
|
114
|
-
"TestTarget1" => { "ClassTest1" => 1000 }
|
115
|
-
}
|
116
|
-
].to_set)
|
117
|
-
expect(subject.stats.to_h).to eq({historical_total_tests: 1, current_total_tests: 1, class_extrapolations: 0, target_extrapolations: 0})
|
118
|
-
end
|
119
|
-
|
120
|
-
it 'extrapolates for new test classes' do
|
121
|
-
historical_events = [
|
122
|
-
xctool_target_event("TestTarget1"),
|
123
|
-
xctool_test_event("ClassTest1", "test1", 1.0),
|
124
|
-
xctool_test_event("ClassTest2", "test2", 5.0),
|
125
|
-
xctool_test_event("ClassTest3", "test3", 10000.0)
|
126
|
-
]
|
127
|
-
current_events = historical_events + [
|
128
|
-
xctool_target_event("TestTarget1"),
|
129
|
-
xctool_test_event("ClassTest2", "test2"),
|
130
|
-
xctool_test_event("ClassTestNew", "test1")
|
131
|
-
]
|
132
|
-
result = subject.test_time_for_partitions(historical_events, current_events)
|
133
|
-
median = 5000
|
134
|
-
expect(result).to eq([{
|
135
|
-
"TestTarget1" =>
|
136
|
-
{
|
137
|
-
"ClassTest1" => 1000,
|
138
|
-
"ClassTest2" => 5000,
|
139
|
-
"ClassTest3" => 10000000,
|
140
|
-
"ClassTestNew" => median
|
141
|
-
},
|
142
|
-
}])
|
143
|
-
expect(subject.stats.to_h).to eq({historical_total_tests: 3, current_total_tests: 5, class_extrapolations: 1, target_extrapolations: 0})
|
144
|
-
end
|
145
|
-
|
146
|
-
it "ignores test classes that don't belong to relevant targets" do
|
147
|
-
historical_events = [
|
148
|
-
xctool_target_event("TestTarget1"),
|
149
|
-
xctool_test_event("ClassTest1", "test1", 1.0),
|
150
|
-
xctool_test_event("ClassTest2", "test2", 5.0),
|
151
|
-
xctool_test_event("ClassTest3", "test3", 10000.0)
|
152
|
-
]
|
153
|
-
current_events = historical_events + [
|
154
|
-
xctool_target_event("TestTarget1"),
|
155
|
-
xctool_test_event("ClassTest2", "test2"),
|
156
|
-
xctool_test_event("ClassTestNew", "test1")
|
157
|
-
]
|
158
|
-
result = subject.test_time_for_partitions(historical_events, current_events)
|
159
|
-
median = 5000
|
160
|
-
expect(result).to eq([{
|
161
|
-
"TestTarget1" =>
|
162
|
-
{
|
163
|
-
"ClassTest1" => 1000,
|
164
|
-
"ClassTest2" => 5000,
|
165
|
-
"ClassTest3" => 10000000,
|
166
|
-
"ClassTestNew" => median
|
167
|
-
},
|
168
|
-
}])
|
169
|
-
expect(subject.stats.to_h).to eq({historical_total_tests: 3, current_total_tests: 5, class_extrapolations: 1, target_extrapolations: 0})
|
170
|
-
end
|
171
|
-
end
|
172
|
-
|
173
|
-
context 'provided an empty set of applicable historical events' do
|
174
|
-
subject { XCKnife::StreamParser.new(2, [["TestTarget1", "TestTarget2", "TestTarget3", "NewTestTarget1"]]) }
|
175
|
-
|
176
|
-
let(:empty_historical_events) { [] }
|
177
|
-
let(:default_extrapolated_duration) { XCKnife::StreamParser::DEFAULT_EXTRAPOLATED_DURATION }
|
178
|
-
|
179
|
-
it 'extrapolates the test target duration and classes get extrapolated' do
|
180
|
-
current_events = [
|
181
|
-
xctool_target_event("TestTarget1"),
|
182
|
-
xctool_test_event("ClassTest1", "test1")
|
183
|
-
]
|
184
|
-
result = subject.test_time_for_partitions(empty_historical_events, current_events)
|
185
|
-
expect(result).to eq([{ "TestTarget1" => { "ClassTest1" => default_extrapolated_duration } }])
|
186
|
-
end
|
187
|
-
|
188
|
-
it 'extrapolates the test target to different classes' do
|
189
|
-
effectively_empty_historical_events = [
|
190
|
-
xctool_target_event("TestTarget2"),
|
191
|
-
xctool_test_event("IgnoredClass", "ignoredTest"),
|
192
|
-
]
|
193
|
-
current_events = [
|
194
|
-
xctool_target_event("TestTarget1"),
|
195
|
-
xctool_test_event("ClassTest1", "test1"),
|
196
|
-
xctool_test_event("ClassTest2", "test2")
|
197
|
-
]
|
198
|
-
result = subject.test_time_for_partitions(effectively_empty_historical_events, current_events)
|
199
|
-
duration = default_extrapolated_duration
|
200
|
-
expect(result).to eq([{ "TestTarget1" => { "ClassTest1" => duration, "ClassTest2" => duration } }])
|
201
|
-
end
|
202
|
-
|
203
|
-
it "can handle multiple test targets and test classes" do
|
204
|
-
current_events = [
|
205
|
-
xctool_target_event("TestTarget1"),
|
206
|
-
xctool_test_event("ClassTest11", "test1"),
|
207
|
-
xctool_test_event("ClassTest12", "test1"),
|
208
|
-
xctool_test_event("ClassTest13", "test1"),
|
209
|
-
xctool_target_event("TestTarget2"),
|
210
|
-
xctool_test_event("ClassTest21", "test1"),
|
211
|
-
xctool_test_event("ClassTest22", "test1"),
|
212
|
-
xctool_test_event("ClassTest23", "test1"),
|
213
|
-
xctool_target_event("TestTarget3"),
|
214
|
-
xctool_test_event("ClassTest31", "test1"),
|
215
|
-
xctool_test_event("ClassTest32", "test1"),
|
216
|
-
xctool_test_event("ClassTest33", "test1"),
|
217
|
-
]
|
218
|
-
result = subject.test_time_for_partitions(empty_historical_events, current_events)
|
219
|
-
duration = default_extrapolated_duration
|
220
|
-
expect(result).to eq(
|
221
|
-
[
|
222
|
-
{
|
223
|
-
"TestTarget1" => { "ClassTest11" => duration, "ClassTest12" => duration, "ClassTest13" => duration },
|
224
|
-
"TestTarget2" => { "ClassTest21" => duration, "ClassTest22" => duration, "ClassTest23" => duration },
|
225
|
-
"TestTarget3" => { "ClassTest31" => duration, "ClassTest32" => duration, "ClassTest33" => duration } }]
|
226
|
-
)
|
227
|
-
end
|
228
|
-
end
|
229
|
-
|
230
|
-
it "can split_machines_proportionally" do
|
231
|
-
stream_parser = XCKnife::StreamParser.new(5, [["TargetOnPartition1"], ["TargetOnPartition2"]])
|
232
|
-
result = stream_parser.split_machines_proportionally([
|
233
|
-
{ "TargetOnPartition1" => { "TestClass1" => 500, "TestClass2" => 500 } },
|
234
|
-
{ "TargetOnPartition2" => { "TestClass3" => 1000, "TestClass4" => 1000, "TestClass5" => 1000, "TestClass6" => 1000 } }])
|
235
|
-
expect(result.map(&:number_of_shards)).to eq([1, 4])
|
236
|
-
end
|
237
|
-
|
238
|
-
it "can split_machines_proportionally even when in the presence of large imbalances" do
|
239
|
-
stream_parser = XCKnife::StreamParser.new(5, [["TargetOnPartition1"], ["TargetOnPartition2"], ["TargetOnPartition3"]])
|
240
|
-
result = stream_parser.split_machines_proportionally([{ "TargetOnPartition1" => { "TestClass1" => 1 } },
|
241
|
-
{ "TargetOnPartition2" => { "TestClass2" => 1} },
|
242
|
-
{ "TargetOnPartition3" => { "TestClass3" => 1000, "TestClass4" => 1000, "TestClass5" => 1000} }])
|
243
|
-
expect(result.map(&:number_of_shards)).to eq([1, 1, 3])
|
244
|
-
end
|
245
|
-
|
246
|
-
|
247
|
-
it "should never let partition_sets have less than 1 machine alocated to them" do
|
248
|
-
stream_parser = XCKnife::StreamParser.new(3, [["TestTarget1"], ["TestTarget2"]])
|
249
|
-
result = stream_parser.split_machines_proportionally([{ "TargetOnPartition1" => { "TestClass1" => 1 } },
|
250
|
-
{ "TargetOnPartition2" => { "TestClass2" => 2000, "TestClass3" => 2000 } }])
|
251
|
-
expect(result.map(&:number_of_shards)).to eq([1, 2])
|
252
|
-
end
|
253
|
-
|
254
|
-
|
255
|
-
context 'test_time_for_partitions' do
|
256
|
-
it "partitions the test classes accross the number of machines" do
|
257
|
-
stream_parser = XCKnife::StreamParser.new(2, [["Test Target"]])
|
258
|
-
partition = { "Test Target" => { "Class1" => 1000, "Class2" => 1000, "Class3" => 2000 } }
|
259
|
-
shards = stream_parser.compute_single_shards(2, partition).map(&:test_time_map)
|
260
|
-
expect(shards.size).to eq 2
|
261
|
-
first_shard, second_shard = shards.sort_by { |map| map.values.flatten.size }
|
262
|
-
expect(first_shard.keys).to eq(["Test Target"])
|
263
|
-
expect(first_shard.values).to eq([["Class3"]])
|
264
|
-
|
265
|
-
expect(second_shard.keys).to eq(["Test Target"])
|
266
|
-
expect(second_shard.values.map(&:to_set)).to eq([["Class1", "Class2"].to_set])
|
267
|
-
end
|
268
|
-
|
269
|
-
it "partitions the test, across targets" do
|
270
|
-
stream_parser = XCKnife::StreamParser.new(2, [["Test Target1", "Test Target2", "Test Target3"]])
|
271
|
-
partition = { "Test Target1" => { "Class1" => 1000 },
|
272
|
-
"Test Target2" => { "Class2" => 1000 },
|
273
|
-
"Test Target3" => { "Class3" => 2000 } }
|
274
|
-
shards = stream_parser.compute_single_shards(2, partition).map(&:test_time_map)
|
275
|
-
expect(shards.size).to eq 2
|
276
|
-
first_shard, second_shard = shards.sort_by { |map| map.values.flatten.size }
|
277
|
-
expect(first_shard.keys).to eq(["Test Target3"])
|
278
|
-
expect(first_shard.values).to eq([["Class3"]])
|
279
|
-
|
280
|
-
expect(second_shard.keys.to_set).to eq(["Test Target1", "Test Target2"].to_set)
|
281
|
-
expect(second_shard.values.to_set).to eq([["Class1"], ["Class2"]].to_set)
|
282
|
-
end
|
283
|
-
|
284
|
-
it "raises an error if there are too many shards" do
|
285
|
-
too_many_machines = 2
|
286
|
-
stream_parser = XCKnife::StreamParser.new(too_many_machines, [["Test Target1"]])
|
287
|
-
partition = { "Test Target1" => { "Class1" => 1000 } }
|
288
|
-
expect { stream_parser.compute_single_shards(too_many_machines, partition) }.
|
289
|
-
to raise_error(XCKnife::XCKnifeError, "Too many shards")
|
290
|
-
end
|
291
|
-
end
|
292
|
-
|
293
|
-
it "can compute test for all partitions" do
|
294
|
-
stream_parser = XCKnife::StreamParser.new(3, [["TargetOnPartition1"], ["TargetOnPartition2"]])
|
295
|
-
result = stream_parser.compute_shards_for_partitions([{ "TargetOnPartition1" => { "TestClass1" => 1000 } },
|
296
|
-
{ "TargetOnPartition2" => { "TestClass2" => 4000, "TestClass3" => 4000 } }])
|
297
|
-
expect(result.test_maps).to eq([[{ "TargetOnPartition1" => ["TestClass1"] }],
|
298
|
-
[{ "TargetOnPartition2" => ["TestClass2"] },
|
299
|
-
{ "TargetOnPartition2" => ["TestClass3"] }]])
|
300
|
-
expect(result.test_times).to eq [[1000], [4000, 4000]]
|
301
|
-
expect(result.total_test_time).to eq 9000
|
302
|
-
expect(result.test_time_imbalances.to_h).to eq({
|
303
|
-
partition_set: [0.4, 1.6],
|
304
|
-
partitions: [[1.0], [1.0, 1.0]]
|
305
|
-
})
|
306
|
-
end
|
307
|
-
|
308
|
-
it "can compute for only one partition set" do
|
309
|
-
stream_parser = XCKnife::StreamParser.new(1, [["TargetOnPartition1"]])
|
310
|
-
historical_events = [xctool_target_event("TargetOnPartition1"),
|
311
|
-
xctool_test_event("ClassTest1", "test1"),
|
312
|
-
]
|
313
|
-
result = stream_parser.compute_shards_for_events(historical_events)
|
314
|
-
expect(result.test_maps).to eq([[{ "TargetOnPartition1" => ["ClassTest1"] }]])
|
315
|
-
expect(result.test_times).to eq [[1000]]
|
316
|
-
expect(result.total_test_time).to eq 1000
|
317
|
-
expect(result.stats.to_h).to eq({historical_total_tests: 1, current_total_tests: 0, class_extrapolations: 0, target_extrapolations: 0})
|
318
|
-
expect(result.test_time_imbalances.to_h).to eq({
|
319
|
-
partition_set: [1.0],
|
320
|
-
partitions: [[1.0]]
|
321
|
-
})
|
322
|
-
end
|
323
|
-
|
324
|
-
def xctool_test_event(class_name, method_name, duration = 1.0)
|
325
|
-
OpenStruct.new({ result: "success",
|
326
|
-
exceptions: [],
|
327
|
-
test: "-[#{class_name} #{method_name}]",
|
328
|
-
className: class_name,
|
329
|
-
event: "end-test",
|
330
|
-
methodName: method_name,
|
331
|
-
succeeded: true,
|
332
|
-
output: "",
|
333
|
-
totalDuration: duration,
|
334
|
-
timestamp: 0
|
335
|
-
})
|
336
|
-
end
|
337
|
-
|
338
|
-
def xctool_target_event(target_name)
|
339
|
-
OpenStruct.new({ result: "success",
|
340
|
-
event: "begin-ocunit",
|
341
|
-
targetName: target_name
|
342
|
-
})
|
343
|
-
end
|
344
|
-
end
|