test_suite_splitter 0.0.1 → 0.0.3

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: 1e8eca9c33b9e9db919f0e2a8a7aa816217e054f17785b22411c0196315591e8
4
- data.tar.gz: 8618f272307e0b5967c89822f18b3e4f0d6c425cf3011e294b2c4ce634e83ef0
3
+ metadata.gz: ac2544ca288e5c7cb4c154043483963b1dbc9f7666c058caca6b89da805a8e2a
4
+ data.tar.gz: 6dc90fe1986b9f55b7aa82dda12c1545e566ed3830725d72bda19fbd863cedbd
5
5
  SHA512:
6
- metadata.gz: 188817535dd7829b1c2513071165287c76b405100365464744636639af514004f511aa8859da73a302d7b65127784e2a57d14fdc2ece24eeb546e076252b1ee1
7
- data.tar.gz: 69f7f346e5dc3c6708b58e36ec55ab1d9990745fa5382479611a136318bd5f08008a478a2e47f7033f9545461b7ed61576d9178998f3d6b4779d5cb0dfaf3c18
6
+ metadata.gz: 6ee0867d88f4e7ffcc864ac2fad277071d8cf15e85bea3bdbf107ac8254ac276746d6a6e218ce31667a58779162c51871c61cb55e60319638d25f907130658ab
7
+ data.tar.gz: c4d31874181ec420bbc0e28e6e2b494bbb540513c91fa0e75112eabed4f6de1d6ae9090815a2aade27ee6eb1ef42d927ffb665052f53c0bb23b0d4f01f14223c
data/README.md ADDED
@@ -0,0 +1,50 @@
1
+ # test_suite_splitter
2
+
3
+ Add it to your Gemfile:
4
+ ```ruby
5
+ group :test do
6
+ gem "test_suite_splitter", require: false
7
+ end
8
+ ```
9
+
10
+ Change your CI configuration file to execute something like this:
11
+ ```bash
12
+ bundle exec rspec `bundle exec test_suite_splitter --groups=6 --group-number=3`
13
+ ```
14
+
15
+ On Semaphore that could be done dynamically like this:
16
+ ```bash
17
+ bundle exec rspec `bundle exec test_suite_splitter --groups=${SEMAPHORE_JOB_COUNT} --group-number=${SEMAPHORE_JOB_INDEX}`
18
+ ```
19
+
20
+ Run only a certain type of specs:
21
+ ```bash
22
+ bundle exec rspec `bundle exec test_suite_splitter --groups=6 --group-number=3 --only-types=system,model`
23
+ ```
24
+
25
+ Exclude a certain type of specs:
26
+ ```bash
27
+ bundle exec rspec `bundle exec test_suite_splitter --groups=6 --group-number=3 --exclude-types=system,feature`
28
+ ```
29
+
30
+ Release a new gem version:
31
+ ```bash
32
+ bundle exec rake release:patch
33
+ ```
34
+
35
+ `release:path` is available as an alias for the same flow.
36
+
37
+ ## Contributing to test_suite_splitter
38
+
39
+ * Check out the latest master to make sure the feature hasn't been implemented or the bug hasn't been fixed yet.
40
+ * Check out the issue tracker to make sure someone already hasn't requested it and/or contributed it.
41
+ * Fork the project.
42
+ * Start a feature/bugfix branch.
43
+ * Commit and push until you are happy with your contribution.
44
+ * Make sure to add tests for it. This is important so I don't break it in a future version unintentionally.
45
+ * Please try not to mess with the Rakefile, version, or history. If you want to have your own version, or is otherwise necessary, that is fine, but please isolate to its own commit so I can cherry-pick around it.
46
+
47
+ ## Copyright
48
+
49
+ Copyright (c) 2021 kaspernj. See LICENSE.txt for
50
+ further details.
data/Rakefile CHANGED
@@ -8,33 +8,7 @@ rescue Bundler::BundlerError => e
8
8
  exit e.status_code
9
9
  end
10
10
  require "rake"
11
- require "juwelier"
12
- Juwelier::Tasks.new do |gem|
13
- # gem is a Gem::Specification... see http://guides.rubygems.org/specification-reference/ for more options
14
- gem.name = "test_suite_splitter"
15
- gem.homepage = "http://github.com/kaspernj/test_suite_splitter"
16
- gem.license = "MIT"
17
- gem.summary = "Split your RSpec test suite up into several groups and run them in parallel."
18
- gem.description = "Split your RSpec test suite up into several groups and run them in parallel."
19
- gem.email = "k@spernj.org"
20
- gem.authors = ["kaspernj"]
21
-
22
- # dependencies defined in Gemfile
23
- end
24
- Juwelier::RubygemsDotOrgTasks.new
25
- require "rake/testtask"
26
- Rake::TestTask.new(:test) do |test|
27
- test.libs << "lib" << "test"
28
- test.pattern = "test/**/test_*.rb"
29
- test.verbose = true
30
- end
31
-
32
- desc "Code coverage detail"
33
- task :simplecov do
34
- ENV["COVERAGE"] = "true"
35
- Rake::Task["test"].execute
36
- end
37
-
11
+ require_relative "lib/test_suite_splitter"
38
12
  task default: :test
39
13
 
40
14
  require "rdoc/task"
@@ -46,3 +20,12 @@ Rake::RDocTask.new do |rdoc|
46
20
  rdoc.rdoc_files.include("README*")
47
21
  rdoc.rdoc_files.include("lib/**/*.rb")
48
22
  end
23
+
24
+ namespace :release do
25
+ desc "Bump the patch version, commit it, push master, build the gem, and push it"
26
+ task :patch do
27
+ TestSuiteSplitter::Release.call(part: :patch)
28
+ end
29
+
30
+ task path: :patch
31
+ end
@@ -6,16 +6,29 @@ require "#{__dir__}/../lib/test_suite_splitter"
6
6
 
7
7
  args = {}
8
8
  ARGV.each do |arg|
9
- if (match = arg.match(/\A--(.+?)=(.+)\Z/))
10
- args[match[1]] = match[2]
9
+ match = arg.match(/\A--(.+?)=(.+)\Z/)
10
+ raise "Couldn't match argument: #{arg}" unless match
11
+
12
+ key = match[1]
13
+ hash_key = key.tr("-", "_").to_sym
14
+ value = match[2]
15
+
16
+ if key == "exclude-types" || key == "only-types" || key == "tags"
17
+ value = value.split(",")
18
+ elsif key == "group-number" || key == "groups"
19
+ value = value.to_i
20
+ else
21
+ raise "Unknown argument: #{key}"
11
22
  end
23
+
24
+ args[hash_key] = value
12
25
  end
13
26
 
14
- rspec_helper = ::TestSuiteSplitter::RspecHelper.new(
15
- groups: args.fetch("groups").to_i,
16
- group_number: args.fetch("group-number").to_i,
17
- only_types: args["only-types"]&.split(","),
18
- tags: args["tags"]&.split(",")
19
- )
27
+ begin
28
+ rspec_helper = ::TestSuiteSplitter::RspecHelper.new(**args)
20
29
 
21
- print rspec_helper.group_files.map { |group_file| group_file.fetch(:path) }.join(" ")
30
+ print rspec_helper.group_files.map { |group_file| group_file.fetch(:path) }.join(" ")
31
+ rescue StandardError => e
32
+ puts e.message
33
+ exit 1
34
+ end
@@ -0,0 +1,69 @@
1
+ module TestSuiteSplitter
2
+ end
3
+
4
+ class TestSuiteSplitter::Release
5
+ VERSION_FILE = File.expand_path("../../VERSION", __dir__)
6
+ GEMSPEC_FILE = "test_suite_splitter.gemspec".freeze
7
+ MASTER_BRANCH = "master".freeze
8
+
9
+ def self.call(part: :patch)
10
+ new(part: part).call
11
+ end
12
+
13
+ def initialize(part:)
14
+ @part = part.to_sym
15
+ end
16
+
17
+ def call
18
+ next_version = bumped_version
19
+ write_version(next_version)
20
+ run("git", "commit", "VERSION", GEMSPEC_FILE, "-m", "Release #{next_version}")
21
+ run("git", "push", "origin", MASTER_BRANCH)
22
+ run("gem", "build", GEMSPEC_FILE)
23
+ run("gem", "push", gem_file_name(next_version))
24
+ next_version
25
+ end
26
+
27
+ private
28
+
29
+ attr_reader :part
30
+
31
+ def bumped_version
32
+ case part
33
+ when :patch, :path
34
+ major, minor, patch = current_version_segments
35
+ [major, minor, patch + 1].join(".")
36
+ else
37
+ raise ArgumentError, "Unsupported release part: #{part}"
38
+ end
39
+ end
40
+
41
+ def current_version
42
+ File.read(VERSION_FILE).strip
43
+ end
44
+
45
+ def current_version_segments
46
+ segments = current_version.split(".").map do |segment|
47
+ Integer(segment, 10)
48
+ rescue ArgumentError
49
+ raise ArgumentError, "Invalid version: #{current_version}"
50
+ end
51
+
52
+ raise ArgumentError, "Invalid version: #{current_version}" unless segments.length == 3
53
+
54
+ segments
55
+ end
56
+
57
+ def gem_file_name(version)
58
+ "test_suite_splitter-#{version}.gem"
59
+ end
60
+
61
+ def run(*command)
62
+ success = system(*command)
63
+ raise "Command failed: #{command.join(' ')}" unless success
64
+ end
65
+
66
+ def write_version(version)
67
+ File.write(VERSION_FILE, "#{version}\n")
68
+ end
69
+ end
@@ -1,7 +1,8 @@
1
1
  class TestSuiteSplitter::RspecHelper
2
- attr_reader :only_types, :tags
2
+ attr_reader :exclude_types, :only_types, :tags
3
3
 
4
- def initialize(groups:, group_number:, only_types: nil, tags: nil)
4
+ def initialize(groups:, group_number:, exclude_types: nil, only_types: nil, tags: nil)
5
+ @exclude_types = exclude_types
5
6
  @groups = groups
6
7
  @group_number = group_number
7
8
  @example_data_exists = File.exist?("spec/examples.txt")
@@ -142,11 +143,11 @@ private
142
143
  require "stringio"
143
144
 
144
145
  output_capture = StringIO.new
145
- RSpec::Core::Runner.run(rspec_options, $stderr, output_capture)
146
+ exit_code = RSpec::Core::Runner.run(rspec_options, $stderr, output_capture)
146
147
 
147
148
  result = ::JSON.parse(output_capture.string)
148
149
 
149
- raise "No examples were found" if result.fetch("examples").empty?
150
+ raise dry_run_error_message(result: result, exit_code: exit_code) if result.fetch("examples").empty?
150
151
 
151
152
  result
152
153
  end
@@ -160,9 +161,11 @@ private
160
161
  @files ||= begin
161
162
  result = {}
162
163
  dry_result.fetch("examples").each do |example|
163
- file_path = example.fetch("file_path")
164
+ file_path_id = example.fetch("id")
165
+ file_path = file_path_id.gsub(/\[([\d:]+)\]$/, "") # Same as ID but remove any [1:2:3] at the end
166
+
164
167
  file_path = file_path[2, file_path.length]
165
- type = type_from_path(file_path)
168
+ type = type_from_path(file_path_id)
166
169
  points = points_from_type(type)
167
170
 
168
171
  next if ignore_type?(type)
@@ -177,11 +180,16 @@ private
177
180
  end
178
181
 
179
182
  def ignore_type?(type)
180
- only_types && !only_types.include?(type) # rubocop:disable: Style/SafeNavigation
183
+ return true if only_types && !only_types.include?(type)
184
+ return true if exclude_types&.include?(type)
185
+
186
+ false
181
187
  end
182
188
 
183
189
  def points_from_type(type)
184
- if type == "feature" || type == "system"
190
+ if type == "system"
191
+ 20
192
+ elsif type == "feature"
185
193
  10
186
194
  elsif type == "controllers"
187
195
  3
@@ -203,8 +211,21 @@ private
203
211
  rspec_options
204
212
  end
205
213
 
214
+ def dry_run_error_message(result:, exit_code:)
215
+ error_summary = []
216
+ errors_outside_of_examples_count = result.dig("summary", "errors_outside_of_examples_count")
217
+
218
+ error_summary << "exit_code=#{exit_code}"
219
+ error_summary << "errors_outside_of_examples_count=#{errors_outside_of_examples_count}" if errors_outside_of_examples_count.to_i.positive?
220
+
221
+ first_message = result.fetch("messages", []).find { |message| message.strip != "" }
222
+ details = first_message&.strip || "No examples were found"
223
+
224
+ "RSpec dry-run failed (#{error_summary.join(', ')})\n\n#{details}"
225
+ end
226
+
206
227
  def type_from_path(file_path)
207
- match = file_path.match(/^spec\/(.+?)\//)
228
+ match = file_path.match(/^\.\/spec\/(.+?)\//)
208
229
  match[1] if match
209
230
  end
210
231
  end
@@ -1,5 +1,6 @@
1
1
  module TestSuiteSplitter
2
2
  path = "#{__dir__}/test_suite_splitter"
3
3
 
4
+ autoload :Release, "#{path}/release"
4
5
  autoload :RspecHelper, "#{path}/rspec_helper"
5
6
  end
metadata CHANGED
@@ -1,15 +1,28 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: test_suite_splitter
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.1
4
+ version: 0.0.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - kaspernj
8
- autorequire:
9
8
  bindir: bin
10
9
  cert_chain: []
11
- date: 2021-11-17 00:00:00.000000000 Z
10
+ date: 1980-01-02 00:00:00.000000000 Z
12
11
  dependencies:
12
+ - !ruby/object:Gem::Dependency
13
+ name: base64
14
+ requirement: !ruby/object:Gem::Requirement
15
+ requirements:
16
+ - - ">="
17
+ - !ruby/object:Gem::Version
18
+ version: '0'
19
+ type: :development
20
+ prerelease: false
21
+ version_requirements: !ruby/object:Gem::Requirement
22
+ requirements:
23
+ - - ">="
24
+ - !ruby/object:Gem::Version
25
+ version: '0'
13
26
  - !ruby/object:Gem::Dependency
14
27
  name: bundler
15
28
  requirement: !ruby/object:Gem::Requirement
@@ -25,7 +38,21 @@ dependencies:
25
38
  - !ruby/object:Gem::Version
26
39
  version: '0'
27
40
  - !ruby/object:Gem::Dependency
28
- name: juwelier
41
+ name: racc
42
+ requirement: !ruby/object:Gem::Requirement
43
+ requirements:
44
+ - - ">="
45
+ - !ruby/object:Gem::Version
46
+ version: '0'
47
+ type: :development
48
+ prerelease: false
49
+ version_requirements: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - ">="
52
+ - !ruby/object:Gem::Version
53
+ version: '0'
54
+ - !ruby/object:Gem::Dependency
55
+ name: rake
29
56
  requirement: !ruby/object:Gem::Requirement
30
57
  requirements:
31
58
  - - ">="
@@ -101,29 +128,20 @@ executables:
101
128
  extensions: []
102
129
  extra_rdoc_files:
103
130
  - LICENSE.txt
104
- - README.rdoc
131
+ - README.md
105
132
  files:
106
- - ".document"
107
- - ".rubocop.yml"
108
- - Gemfile
109
- - Gemfile.lock
110
133
  - LICENSE.txt
111
- - README.rdoc
134
+ - README.md
112
135
  - Rakefile
113
- - VERSION
114
136
  - bin/test_suite_splitter
115
137
  - lib/test_suite_splitter.rb
138
+ - lib/test_suite_splitter/release.rb
116
139
  - lib/test_suite_splitter/rspec_helper.rb
117
- - peak_flow.yml
118
- - spec/spec_helper.rb
119
- - spec/test_suite_splitter/rspec_helper/nemoa_rspec_output.json
120
- - spec/test_suite_splitter/rspec_helper_spec.rb
121
- - test_suite_splitter.gemspec
122
140
  homepage: http://github.com/kaspernj/test_suite_splitter
123
141
  licenses:
124
142
  - MIT
125
- metadata: {}
126
- post_install_message:
143
+ metadata:
144
+ rubygems_mfa_required: 'true'
127
145
  rdoc_options: []
128
146
  require_paths:
129
147
  - lib
@@ -131,15 +149,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
131
149
  requirements:
132
150
  - - ">="
133
151
  - !ruby/object:Gem::Version
134
- version: '0'
152
+ version: 2.5.0
135
153
  required_rubygems_version: !ruby/object:Gem::Requirement
136
154
  requirements:
137
155
  - - ">="
138
156
  - !ruby/object:Gem::Version
139
157
  version: '0'
140
158
  requirements: []
141
- rubygems_version: 3.1.6
142
- signing_key:
159
+ rubygems_version: 3.6.9
143
160
  specification_version: 4
144
161
  summary: Split your RSpec test suite up into several groups and run them in parallel.
145
162
  test_files: []
data/.document DELETED
@@ -1,5 +0,0 @@
1
- lib/**/*.rb
2
- bin/*
3
- -
4
- features/**/*.feature
5
- LICENSE.txt
data/.rubocop.yml DELETED
@@ -1,195 +0,0 @@
1
- AllCops:
2
- DisplayCopNames: true
3
- DisplayStyleGuide: true
4
- NewCops: enable
5
- TargetRubyVersion: 2.7
6
-
7
- require:
8
- - rubocop-performance
9
- - rubocop-rspec
10
-
11
- Layout/AccessModifierIndentation:
12
- EnforcedStyle: outdent
13
-
14
- Layout/ArgumentAlignment:
15
- EnforcedStyle: with_fixed_indentation
16
-
17
- Layout/CaseIndentation:
18
- EnforcedStyle: end
19
-
20
- Layout/EmptyLines:
21
- Enabled: false
22
-
23
- Layout/EmptyLinesAroundArguments:
24
- Enabled: false
25
-
26
- Layout/EmptyLinesAroundAttributeAccessor:
27
- Enabled: true
28
-
29
- Layout/EndAlignment:
30
- EnforcedStyleAlignWith: variable
31
-
32
- Layout/LineLength:
33
- Max: 160
34
-
35
- Layout/MultilineMethodCallIndentation:
36
- EnforcedStyle: indented
37
-
38
- Layout/MultilineOperationIndentation:
39
- EnforcedStyle: indented
40
-
41
- Layout/ParameterAlignment:
42
- EnforcedStyle: with_fixed_indentation
43
-
44
- Layout/RescueEnsureAlignment:
45
- Enabled: false
46
-
47
- Layout/SpaceAroundMethodCallOperator:
48
- Enabled: true
49
-
50
- Layout/SpaceInsideHashLiteralBraces:
51
- EnforcedStyle: no_space
52
-
53
- Lint/DeprecatedOpenSSLConstant:
54
- Enabled: true
55
-
56
- Lint/FloatComparison:
57
- Enabled: false
58
-
59
- Lint/RaiseException:
60
- Enabled: true
61
-
62
- Lint/StructNewOverride:
63
- Enabled: true
64
-
65
- # Metrics/AbcSize:
66
- # Max: 25
67
-
68
- Metrics/BlockLength:
69
- Enabled: false
70
-
71
- Metrics/ClassLength:
72
- Max: 250
73
-
74
- Metrics/AbcSize:
75
- Max: 29
76
-
77
- Metrics/CyclomaticComplexity:
78
- Max: 13
79
-
80
- Metrics/MethodLength:
81
- Max: 50
82
-
83
- Metrics/PerceivedComplexity:
84
- Max: 15
85
-
86
- RSpec/AnyInstance:
87
- Enabled: false
88
-
89
- RSpec/ContextWording:
90
- Enabled: false
91
-
92
- RSpec/DescribeClass:
93
- Enabled: false
94
-
95
- RSpec/DescribedClass:
96
- Enabled: false
97
-
98
- RSpec/ExampleLength:
99
- Enabled: false
100
-
101
- Style/FormatStringToken:
102
- Enabled: false
103
-
104
- RSpec/LetSetup:
105
- Enabled: false
106
-
107
- RSpec/MessageSpies:
108
- Enabled: false
109
-
110
- RSpec/MultipleExpectations:
111
- Enabled: false
112
-
113
- RSpec/NamedSubject:
114
- Enabled: false
115
-
116
- RSpec/NestedGroups:
117
- Enabled: false
118
-
119
- # This can make expectaions look weird: Prefer using be_rvm_in_build_config matcher over rvm_in_build_config?
120
- RSpec/PredicateMatcher:
121
- Enabled: false
122
-
123
- RSpec/StubbedMock:
124
- Enabled: false
125
-
126
- Style/CaseLikeIf:
127
- Enabled: false
128
-
129
- Style/ClassAndModuleChildren:
130
- EnforcedStyle: compact
131
-
132
- Style/ConditionalAssignment:
133
- Enabled: false
134
-
135
- Style/Documentation:
136
- Enabled: false
137
-
138
- Style/ExplicitBlockArgument:
139
- Enabled: false
140
-
141
- Style/ExponentialNotation:
142
- Enabled: true
143
-
144
- Style/FrozenStringLiteralComment:
145
- Enabled: false
146
-
147
- # Will report offences for many places that are much more readable without using a guard clause
148
- Style/GuardClause:
149
- Enabled: false
150
-
151
- Style/HashEachMethods:
152
- Enabled: true
153
-
154
- Style/HashTransformKeys:
155
- Enabled: true
156
-
157
- Style/HashTransformValues:
158
- Enabled: true
159
-
160
- Style/Lambda:
161
- Enabled: false
162
-
163
- Style/LambdaCall:
164
- Enabled: false
165
-
166
- Style/MultipleComparison:
167
- Enabled: false
168
-
169
- Style/RegexpLiteral:
170
- Enabled: false
171
-
172
- Style/StringLiterals:
173
- EnforcedStyle: double_quotes
174
-
175
- Style/StringLiteralsInInterpolation:
176
- Enabled: false
177
-
178
- Style/NilComparison:
179
- Enabled: false
180
-
181
- Style/SignalException:
182
- EnforcedStyle: only_raise
183
-
184
- Style/SlicingWithRange:
185
- Enabled: true
186
-
187
- Style/SymbolArray:
188
- Enabled: false
189
-
190
- Style/TrivialAccessors:
191
- ExactNameMatch: true
192
- Enabled: true
193
-
194
- Style/WordArray:
195
- Enabled: false
data/Gemfile DELETED
@@ -1,18 +0,0 @@
1
- source "https://rubygems.org"
2
- # Add dependencies required to use your gem here.
3
- # Example:
4
- # gem "activesupport", ">= 2.3.5"
5
-
6
- # Add dependencies to develop your gem here.
7
- # Include everything needed to run rake, tests, features, etc.
8
- group :development do
9
- gem "bundler"
10
- gem "juwelier"
11
- end
12
-
13
- group :development, :test do
14
- gem "rspec"
15
- gem "rubocop"
16
- gem "rubocop-performance"
17
- gem "rubocop-rspec"
18
- end