rspec-sharder 0.0.3 → 0.0.4
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/Gemfile.lock +1 -1
- data/README.md +23 -10
- data/lib/rspec-sharder/command.rb +26 -13
- data/lib/rspec-sharder/version.rb +1 -1
- data/lib/rspec-sharder.rb +33 -22
- data/scripts/release.sh +2 -0
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: c3d09ff7bef003d57985622045c99bb8743834d26f0f2119bc32d9ffdd800822
|
4
|
+
data.tar.gz: 9e5f371699047b82862fe5f76288122c10b80b23117dc0ec2d82e6e1f29f312e
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: be15f6f4f0b66eef6b50af889e48f3fb3f7cadfd1650314538c3088e4828e19f8f6af77092fa359f081cec206f3957389397e1d52b4e30ff81dd20b2ecb854cd
|
7
|
+
data.tar.gz: 4c69946dc056cb146ff452d80b9e31503c12fbaa6cfb14f164268fafd84a7baf13da04b694e77f0b9d9dfea65898f8579f2b7fb81b401bde7ef80b9494f458d8
|
data/Gemfile.lock
CHANGED
data/README.md
CHANGED
@@ -5,22 +5,35 @@ Groups specs into shards, ensuring that each shard has a similar size, and runs
|
|
5
5
|
the specified shard.
|
6
6
|
|
7
7
|
Shard size is determined by summing the saved durations for each spec file in
|
8
|
-
the shard. Durations are saved in .
|
9
|
-
in .
|
10
|
-
the spec file.
|
8
|
+
the shard. Durations are saved in .rspec-sharder-durations. If a spec file is
|
9
|
+
not found in .rspec-sharder-durations, the duration is estimated based on the
|
10
|
+
number of examples in the spec file.
|
11
11
|
|
12
|
-
.
|
13
|
-
|
14
|
-
durations for all
|
15
|
-
|
12
|
+
.rspec-sharder-durations is generated after each successful run, but only for
|
13
|
+
the specs which were run as part of the specified shard. Before first use,
|
14
|
+
generate .rspec-sharder-durations for all specs by executing with the default
|
15
|
+
option of 1 total shards:
|
16
16
|
|
17
|
-
bundle exec rspec-sharder --
|
17
|
+
bundle exec rspec-sharder -- [<rspec-args...>]
|
18
18
|
|
19
|
-
|
19
|
+
Commit .rspec-sharder-durations file to your version control.
|
20
|
+
|
21
|
+
Next, configure 2 or more CI jobs to execute separate shards like:
|
22
|
+
|
23
|
+
bundle exec rspec-sharder --total-shards 4 --shard 1 -- [<rspec-args...>]
|
24
|
+
bundle exec rspec-sharder --total-shards 4 --shard 2 -- [<rspec-args...>]
|
25
|
+
bundle exec rspec-sharder --total-shards 4 --shard 3 -- [<rspec-args...>]
|
26
|
+
bundle exec rspec-sharder --total-shards 4 --shard 4 -- [<rspec-args...>]
|
27
|
+
|
28
|
+
Finally, set up some job or process to periodically pull .rspec-sharder-durations
|
29
|
+
files from CI, combine them, and commit them to source control. This will ensure
|
30
|
+
you pick up updated durations for any new or changed files.
|
31
|
+
|
32
|
+
Usage: bundle exec rspec-sharder [--total-shards <num> [--shard <num>]] [--no-persist] -- [<rspec-args...>]
|
20
33
|
|
21
34
|
Options:
|
22
35
|
-h, --help Print this message.
|
23
36
|
-t, --total-shards <num> The total number of shards. Defaults to 1.
|
24
37
|
-s, --shard <num> The shard to run. Defaults to 1.
|
25
|
-
|
38
|
+
--no-persist Don't save durations to .rspec-sharder-durations.
|
26
39
|
```
|
@@ -10,7 +10,7 @@ end
|
|
10
10
|
|
11
11
|
@total_shards = 1
|
12
12
|
@shard = 1
|
13
|
-
@persist =
|
13
|
+
@persist = true
|
14
14
|
|
15
15
|
@parser = OptionParser.new do |opts|
|
16
16
|
opts.banner = <<~EOF
|
@@ -18,18 +18,31 @@ end
|
|
18
18
|
the specified shard.
|
19
19
|
|
20
20
|
Shard size is determined by summing the saved durations for each spec file in
|
21
|
-
the shard. Durations are saved in .
|
22
|
-
in .
|
23
|
-
the spec file.
|
21
|
+
the shard. Durations are saved in .rspec-sharder-durations. If a spec file is
|
22
|
+
not found in .rspec-sharder-durations, the duration is estimated based on the
|
23
|
+
number of examples in the spec file.
|
24
24
|
|
25
|
-
.
|
26
|
-
|
27
|
-
durations for all
|
28
|
-
|
25
|
+
.rspec-sharder-durations is generated after each successful run, but only for
|
26
|
+
the specs which were run as part of the specified shard. Before first use,
|
27
|
+
generate .rspec-sharder-durations for all specs by executing with the default
|
28
|
+
option of 1 total shards:
|
29
29
|
|
30
|
-
|
30
|
+
bundle exec rspec-sharder -- [<rspec-args...>]
|
31
31
|
|
32
|
-
|
32
|
+
Commit .rspec-sharder-durations file to your version control.
|
33
|
+
|
34
|
+
Next, configure 2 or more CI jobs to execute separate shards like:
|
35
|
+
|
36
|
+
bundle exec rspec-sharder --total-shards 4 --shard 1 -- [<rspec-args...>]
|
37
|
+
bundle exec rspec-sharder --total-shards 4 --shard 2 -- [<rspec-args...>]
|
38
|
+
bundle exec rspec-sharder --total-shards 4 --shard 3 -- [<rspec-args...>]
|
39
|
+
bundle exec rspec-sharder --total-shards 4 --shard 4 -- [<rspec-args...>]
|
40
|
+
|
41
|
+
Finally, set up some job or process to periodically pull .rspec-sharder-durations
|
42
|
+
files from CI, combine them, and commit them to source control. This will ensure
|
43
|
+
you pick up updated durations for any new or changed files.
|
44
|
+
|
45
|
+
Usage: bundle exec rspec-sharder [--total-shards <num> [--shard <num>]] [--no-persist] -- [<rspec-args...>]
|
33
46
|
|
34
47
|
Options:
|
35
48
|
EOF
|
@@ -55,8 +68,8 @@ end
|
|
55
68
|
end
|
56
69
|
end
|
57
70
|
|
58
|
-
opts.on('-
|
59
|
-
@persist =
|
71
|
+
opts.on('--no-persist', "Don't save durations to .rspec-sharder-durations.") do
|
72
|
+
@persist = false
|
60
73
|
end
|
61
74
|
end
|
62
75
|
|
@@ -70,4 +83,4 @@ fail('fatal: invalid value for --total-shards') unless @total_shards > 0
|
|
70
83
|
fail('fatal: invalid value for --shard') unless @shard > 0
|
71
84
|
fail('fatal: --shard may not be greater than --total-shards') unless @shard <= @total_shards
|
72
85
|
|
73
|
-
RSpec::Sharder.run(total_shards: @total_shards, shard_num: @shard, persist: @persist, rspec_args: ARGV)
|
86
|
+
exit RSpec::Sharder.run(total_shards: @total_shards, shard_num: @shard, persist: @persist, rspec_args: ARGV)
|
data/lib/rspec-sharder.rb
CHANGED
@@ -27,7 +27,7 @@ module RSpec
|
|
27
27
|
shards = build_shards(total_shards, shard_num, all_durations)
|
28
28
|
rescue ShardError => e
|
29
29
|
::RSpec.configuration.error_stream.puts e.message
|
30
|
-
|
30
|
+
return ::RSpec.configuration.reporter.exit_early(::RSpec.configuration.failure_exit_code)
|
31
31
|
end
|
32
32
|
|
33
33
|
print_shards(shards)
|
@@ -79,7 +79,7 @@ module RSpec
|
|
79
79
|
if persist
|
80
80
|
::RSpec.configuration.output_stream.puts <<~EOF
|
81
81
|
|
82
|
-
Dry run. Not saving to .
|
82
|
+
Dry run. Not saving to .rspec-sharder-durations.
|
83
83
|
EOF
|
84
84
|
end
|
85
85
|
else
|
@@ -99,27 +99,23 @@ module RSpec
|
|
99
99
|
EOF
|
100
100
|
|
101
101
|
if persist
|
102
|
-
# Write
|
102
|
+
# Write durations to .rspec-sharder-durations.
|
103
103
|
::RSpec.configuration.output_stream.puts <<~EOF
|
104
104
|
|
105
|
-
Saving to .
|
105
|
+
Saving to .rspec-sharder-durations.
|
106
106
|
EOF
|
107
107
|
|
108
|
-
new_durations
|
109
|
-
all_durations[file_path] = duration
|
110
|
-
end
|
111
|
-
|
112
|
-
persist_durations(all_durations)
|
108
|
+
persist_durations(new_durations)
|
113
109
|
end
|
114
110
|
elsif persist
|
115
111
|
::RSpec.configuration.output_stream.puts <<~EOF
|
116
112
|
|
117
|
-
RSpec failed. Not saving to .
|
113
|
+
RSpec failed. Not saving to .rspec-sharder-durations.
|
118
114
|
EOF
|
119
115
|
end
|
120
116
|
end
|
121
117
|
|
122
|
-
|
118
|
+
exit_code
|
123
119
|
end
|
124
120
|
|
125
121
|
private
|
@@ -127,36 +123,45 @@ module RSpec
|
|
127
123
|
def self.load_recorded_durations
|
128
124
|
durations = { }
|
129
125
|
|
130
|
-
|
131
|
-
|
126
|
+
missing_files = 0
|
127
|
+
|
128
|
+
if File.exist?('.rspec-sharder-durations')
|
129
|
+
File.readlines('.rspec-sharder-durations').each_with_index do |line, index|
|
132
130
|
line = line.strip
|
133
131
|
|
134
132
|
if !line.start_with?('#') && !line.empty?
|
135
133
|
parts = line.split(',')
|
136
134
|
|
137
135
|
unless parts.length == 2
|
138
|
-
raise ShardError.new("fatal: invalid .
|
136
|
+
raise ShardError.new("fatal: invalid .rspec-sharder-durations at line #{index + 1}")
|
139
137
|
end
|
140
138
|
|
141
139
|
file_path = parts[0].strip
|
142
140
|
|
143
141
|
if file_path.empty?
|
144
|
-
raise ShardError.new("fatal: invalid file path in .
|
142
|
+
raise ShardError.new("fatal: invalid file path in .rspec-sharder-durations at line #{index + 1}")
|
145
143
|
end
|
146
144
|
|
147
145
|
unless File.exist?(file_path)
|
148
|
-
|
146
|
+
missing_files += 1
|
149
147
|
end
|
150
148
|
|
151
149
|
begin
|
152
150
|
duration = Integer(parts[1])
|
153
151
|
rescue ArgumentError => e
|
154
|
-
raise ShardError.new("fatal: invalid .
|
152
|
+
raise ShardError.new("fatal: invalid .rspec-sharder-durations at line #{index + 1}")
|
155
153
|
end
|
156
154
|
|
157
155
|
durations[file_path] = duration
|
158
156
|
end
|
159
157
|
end.compact
|
158
|
+
|
159
|
+
if missing_files > 0
|
160
|
+
::RSpec.configuration.output_stream.puts <<~EOF
|
161
|
+
warning: #{missing_files} file(s) in .rspec-sharder-durations do not exist, consider regenerating
|
162
|
+
|
163
|
+
EOF
|
164
|
+
end
|
160
165
|
end
|
161
166
|
|
162
167
|
durations
|
@@ -165,19 +170,26 @@ module RSpec
|
|
165
170
|
def self.build_shards(total_shards, shard_num, durations)
|
166
171
|
files = { }
|
167
172
|
|
173
|
+
missing_files = 0
|
168
174
|
::RSpec.world.ordered_example_groups.each do |example_group|
|
169
175
|
file_path = example_group.metadata[:file_path]
|
170
176
|
files[file_path] ||= 0
|
171
177
|
if durations[file_path]
|
172
178
|
files[file_path] = durations[file_path]
|
173
179
|
else
|
174
|
-
|
175
|
-
|
180
|
+
missing_files += 1
|
176
181
|
# Assume 1000 milliseconds per example.
|
177
182
|
files[file_path] += ::RSpec.world.example_count([example_group]) * 1000
|
178
183
|
end
|
179
184
|
end
|
180
185
|
|
186
|
+
if missing_files > 0
|
187
|
+
::RSpec.configuration.output_stream.puts <<~EOF
|
188
|
+
warning: #{missing_files} file(s) in not found in .rspec-sharder-durations, consider regenerating
|
189
|
+
|
190
|
+
EOF
|
191
|
+
end
|
192
|
+
|
181
193
|
shards = (1..total_shards).map { { duration: 0, file_paths: [] } }
|
182
194
|
|
183
195
|
# First sort by duration to ensure large files are distributed evenly.
|
@@ -222,7 +234,6 @@ module RSpec
|
|
222
234
|
end
|
223
235
|
|
224
236
|
def self.print_shards(shards)
|
225
|
-
::RSpec.configuration.output_stream.puts
|
226
237
|
shards.each_with_index do |shard, i|
|
227
238
|
::RSpec.configuration.output_stream.puts(
|
228
239
|
"Shard #{i + 1} (Files: #{shard[:file_paths].size}, Duration: #{pretty_duration(shard[:duration])}):"
|
@@ -235,9 +246,9 @@ module RSpec
|
|
235
246
|
end
|
236
247
|
|
237
248
|
def self.persist_durations(durations)
|
238
|
-
File.open(".
|
249
|
+
File.open(".rspec-sharder-durations", "w+") do |file|
|
239
250
|
file.puts <<~EOF
|
240
|
-
# Generated by rspec-sharder on #{Time.now.to_s}.
|
251
|
+
# Generated by rspec-sharder on #{Time.now.to_s}. See `bundle exec rspec-sharder -h`.
|
241
252
|
|
242
253
|
EOF
|
243
254
|
durations.sort_by { |file_path, duration| file_path }.each do |file_path, duration|
|
data/scripts/release.sh
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: rspec-sharder
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.4
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Nick Dower
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2021-08-
|
11
|
+
date: 2021-08-29 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rspec-core
|