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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 7abffffb75542211fc36495edeca3b96f32f59b6975686568b3532d2df6ff9c6
4
- data.tar.gz: 64bccf923c9897fa23205ade6af164ca10eb60dfc3c89b760b9a73be35d382bb
3
+ metadata.gz: c3d09ff7bef003d57985622045c99bb8743834d26f0f2119bc32d9ffdd800822
4
+ data.tar.gz: 9e5f371699047b82862fe5f76288122c10b80b23117dc0ec2d82e6e1f29f312e
5
5
  SHA512:
6
- metadata.gz: 3cbba17bc9116dbe0a8e3f6aef9d7865f57e40c6393d2e3d3243afacfc5dee658d33fe2c729734fb43ad291280a67f124913807f0b5d69eb28f13671d194309f
7
- data.tar.gz: 809210eec8b32c39a87c6f64dbe6e9f699c1f1242b8edb1c0a9ea8120c7945801461744ad17065913f981a81bdfe1b5e1a031a2ce68a746871db995504e39ff1
6
+ metadata.gz: be15f6f4f0b66eef6b50af889e48f3fb3f7cadfd1650314538c3088e4828e19f8f6af77092fa359f081cec206f3957389397e1d52b4e30ff81dd20b2ecb854cd
7
+ data.tar.gz: 4c69946dc056cb146ff452d80b9e31503c12fbaa6cfb14f164268fafd84a7baf13da04b694e77f0b9d9dfea65898f8579f2b7fb81b401bde7ef80b9494f458d8
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- rspec-sharder (0.0.2)
4
+ rspec-sharder (0.0.3)
5
5
  rspec-core
6
6
 
7
7
  GEM
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 .spec_durations. If a spec file is not found
9
- in .spec_durations, the duration is estimated based on the number of examples 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
- .spec_durations is generate/updated after a successful run when --persist is
13
- specified, but only for the shard which was actually executed. To generate
14
- durations for all shards simultaneously, run with the default options of 1 total
15
- shards and --persist:
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 --persist -- [<rspec-args...>]
17
+ bundle exec rspec-sharder -- [<rspec-args...>]
18
18
 
19
- Usage: bundle exec rspec-sharder [--total-shards <num> [--shard <num>]] [--persist] -- [<rspec-args...>]
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
- -p, --persist Save durations to .spec_durations.
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 = false
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 .spec_durations. If a spec file is not found
22
- in .spec_durations, the duration is estimated based on the number of examples 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
- .spec_durations is generate/updated after a successful run when --persist is
26
- specified, but only for the shard which was actually executed. To generate
27
- durations for all shards simultaneously, run with the default options of 1 total
28
- shards and --persist:
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
- bundle exec rspec-sharder --persist -- [<rspec-args...>]
30
+ bundle exec rspec-sharder -- [<rspec-args...>]
31
31
 
32
- Usage: bundle exec rspec-sharder [--total-shards <num> [--shard <num>]] [--persist] -- [<rspec-args...>]
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('-p', '--persist', 'Save durations to .spec_durations.') do
59
- @persist = true
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)
@@ -2,6 +2,6 @@
2
2
 
3
3
  module RSpec
4
4
  module Sharder
5
- VERSION = "0.0.3"
5
+ VERSION = "0.0.4"
6
6
  end
7
7
  end
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
- exit ::RSpec.configuration.failure_exit_cod
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 .spec_durations.
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 all durations with updates to .spec_durations.
102
+ # Write durations to .rspec-sharder-durations.
103
103
  ::RSpec.configuration.output_stream.puts <<~EOF
104
104
 
105
- Saving to .spec_durations.
105
+ Saving to .rspec-sharder-durations.
106
106
  EOF
107
107
 
108
- new_durations.each do |file_path, duration|
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 .spec_durations.
113
+ RSpec failed. Not saving to .rspec-sharder-durations.
118
114
  EOF
119
115
  end
120
116
  end
121
117
 
122
- exit exit_code
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
- if File.exist?('.spec_durations')
131
- File.readlines('.spec_durations').each_with_index do |line, index|
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 .spec_durations at line #{index + 1}")
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 .spec_durations at line #{index + 1}")
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
- raise ShardError.new("fatal: file in .spec_durations not found at line #{index + 1}")
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 .spec_durations at line #{index + 1}")
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
- ::RSpec.configuration.output_stream.puts "warning: recorded duration not found for #{file_path}"
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(".spec_durations", "w+") do |file|
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
@@ -15,6 +15,8 @@ fi
15
15
  NEW_VERSION=$1
16
16
  CURRENT_VERSION=$(grep VERSION lib/rspec-sharder/version.rb | cut -d'"' -f 2)
17
17
 
18
+ bundle exec rspec
19
+
18
20
  echo "Updating from v$CURRENT_VERSION to v$NEW_VERSION. Press enter to continue."
19
21
  read
20
22
 
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.3
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-28 00:00:00.000000000 Z
11
+ date: 2021-08-29 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rspec-core