test_suite_splitter 0.0.2 → 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: bf8e75ec7693f017eb21ec9ed18216b219800e85498f9e1445041dec1410b3dc
4
- data.tar.gz: 14314af600cfa9a1afa087b912306ca11659867fa8f9bc62e4292ac6ac2a85cb
3
+ metadata.gz: 2bf5b723c5f3b7bc866531725c9d7127fdca1277d5c53079e9bb2751817342fc
4
+ data.tar.gz: 35b104aca369d17a0b256a732bb7aaa4c73eb60a7dfa3f028eec5c1d497ada40
5
5
  SHA512:
6
- metadata.gz: 8f543e59a5bdc560c1dfbb9b904dca9be82682b142df14ad9a86b4c594c38169d0d27165e14102f4d36e1ee5e8e4d84aa5e131ac400e9657261742dbe9828167
7
- data.tar.gz: 17be6115b41e1440e67b36441cc997eb3bb9468372644a836acbe7174239f8a1de056648ccad42fe992f6a0990e7530b38e7b9c02f58b208a77a0d4256692b01
6
+ metadata.gz: '08ae2c9be973a1b14b893ecc9517794ecf297d02aac5a8825346511803274edffd6866c01778fa460ca4c6d6d175c8735dbe19190a7b55a6654ccd5c3da3ec89'
7
+ data.tar.gz: 9138c299cd2fe61f270a57d6982e24a5ef9e2a66b35aa99b041aa3830925b6473efeaf45d7b46d21a3f42a15d3a04b2de819870dcf8ea8abb2606bb3c73ce4e7
data/README.md CHANGED
@@ -12,11 +12,40 @@ Change your CI configuration file to execute something like this:
12
12
  bundle exec rspec `bundle exec test_suite_splitter --groups=6 --group-number=3`
13
13
  ```
14
14
 
15
- Or Semaphore that could be done dynamically like this:
15
+ Or let `test_suite_splitter` run RSpec directly:
16
+ ```bash
17
+ bundle exec test_suite_splitter_rspec --groups=6 --group-number=3 -- --format documentation
18
+ ```
19
+
20
+ On Semaphore that could be done dynamically like this:
16
21
  ```bash
17
22
  bundle exec rspec `bundle exec test_suite_splitter --groups=${SEMAPHORE_JOB_COUNT} --group-number=${SEMAPHORE_JOB_INDEX}`
18
23
  ```
19
24
 
25
+ Run only a certain type of specs:
26
+ ```bash
27
+ bundle exec rspec `bundle exec test_suite_splitter --groups=6 --group-number=3 --only-types=system,model`
28
+ ```
29
+
30
+ Exclude a certain type of specs:
31
+ ```bash
32
+ bundle exec rspec `bundle exec test_suite_splitter --groups=6 --group-number=3 --exclude-types=system,feature`
33
+ ```
34
+
35
+ Exclude file paths by prefix:
36
+ ```bash
37
+ bundle exec test_suite_splitter_rspec --groups=6 --group-number=3 --exclude-path-prefixes=spec/system/projects/project_environments_terminal_e2e_spec/ -- --format documentation
38
+ ```
39
+
40
+ When the dry run fails, `test_suite_splitter` writes the failure output to `log/test_suite_splitter.log` by default. Override that path with `--log-file=path/to/file.log`.
41
+
42
+ Release a new gem version:
43
+ ```bash
44
+ bundle exec rake release:patch
45
+ ```
46
+
47
+ `release:path` is available as an alias for the same flow.
48
+
20
49
  ## Contributing to test_suite_splitter
21
50
 
22
51
  * Check out the latest master to make sure the feature hasn't been implemented or the bug hasn't been fixed yet.
data/Rakefile CHANGED
@@ -8,6 +8,7 @@ rescue Bundler::BundlerError => e
8
8
  exit e.status_code
9
9
  end
10
10
  require "rake"
11
+ require_relative "lib/test_suite_splitter"
11
12
  task default: :test
12
13
 
13
14
  require "rdoc/task"
@@ -19,3 +20,12 @@ Rake::RDocTask.new do |rdoc|
19
20
  rdoc.rdoc_files.include("README*")
20
21
  rdoc.rdoc_files.include("lib/**/*.rb")
21
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
@@ -4,26 +4,4 @@
4
4
 
5
5
  require "#{__dir__}/../lib/test_suite_splitter"
6
6
 
7
- args = {}
8
- ARGV.each do |arg|
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}"
22
- end
23
-
24
- args[hash_key] = value
25
- end
26
-
27
- rspec_helper = ::TestSuiteSplitter::RspecHelper.new(**args)
28
-
29
- print rspec_helper.group_files.map { |group_file| group_file.fetch(:path) }.join(" ")
7
+ exit(TestSuiteSplitter::Cli.new(argv: ARGV).execute_splitter_command)
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "#{__dir__}/../lib/test_suite_splitter"
4
+
5
+ exit(TestSuiteSplitter::Cli.new(argv: ARGV).execute_rspec_command)
@@ -0,0 +1,124 @@
1
+ class TestSuiteSplitter::Cli
2
+ DEFAULT_LOG_FILE_PATH = "log/test_suite_splitter.log".freeze
3
+
4
+ attr_reader :argv, :stderr, :stdout
5
+
6
+ def initialize(argv:, stderr: $stderr, stdout: $stdout)
7
+ @argv = argv
8
+ @stderr = stderr
9
+ @stdout = stdout
10
+ end
11
+
12
+ def execute_splitter_command
13
+ print_output(group_file_paths.join(" "))
14
+ 0
15
+ rescue StandardError => e
16
+ handle_error(e)
17
+ end
18
+
19
+ def execute_rspec_command
20
+ files = group_file_paths
21
+
22
+ if files.empty?
23
+ print_output("No files from test_suite_splitter - skipping shard")
24
+ return 0
25
+ end
26
+
27
+ require "rspec/core"
28
+
29
+ RSpec::Core::Runner.run(rspec_arguments + files, stderr, stdout)
30
+ rescue StandardError => e
31
+ handle_error(e)
32
+ end
33
+
34
+ def group_file_paths
35
+ rspec_helper.group_files.map { |group_file| group_file.fetch(:path) }
36
+ end
37
+
38
+ private
39
+
40
+ def handle_error(error)
41
+ message = error.message
42
+
43
+ write_log(message)
44
+ print_output("#{message}\n\nLogged to #{log_file_path}")
45
+
46
+ 1
47
+ end
48
+
49
+ def write_log(message)
50
+ require "fileutils"
51
+
52
+ FileUtils.mkdir_p(File.dirname(log_file_path))
53
+ File.write(log_file_path, "#{message}\n")
54
+ end
55
+
56
+ def print_output(message)
57
+ stdout.print(message)
58
+ end
59
+
60
+ def log_file_path
61
+ @parsed_arguments&.fetch(:log_file) || DEFAULT_LOG_FILE_PATH
62
+ end
63
+
64
+ def rspec_helper
65
+ @rspec_helper ||= TestSuiteSplitter::RspecHelper.new(**splitter_arguments)
66
+ end
67
+
68
+ def splitter_arguments
69
+ parsed_arguments.slice(:exclude_path_prefixes, :exclude_types, :group_number, :groups, :only_types, :tags)
70
+ end
71
+
72
+ def rspec_arguments
73
+ parsed_arguments.fetch(:rspec_arguments)
74
+ end
75
+
76
+ def parsed_arguments
77
+ @parsed_arguments ||= begin
78
+ result = {
79
+ exclude_path_prefixes: nil,
80
+ exclude_types: nil,
81
+ log_file: DEFAULT_LOG_FILE_PATH,
82
+ only_types: nil,
83
+ rspec_arguments: [],
84
+ tags: nil
85
+ }
86
+ parsing_splitter_arguments = true
87
+
88
+ argv.each do |arg|
89
+ if parsing_splitter_arguments && arg == "--"
90
+ parsing_splitter_arguments = false
91
+ next
92
+ end
93
+
94
+ if parsing_splitter_arguments
95
+ key, value = parse_argument(arg)
96
+ result[key] = value
97
+ else
98
+ result[:rspec_arguments] << arg
99
+ end
100
+ end
101
+
102
+ result
103
+ end
104
+ end
105
+
106
+ def parse_argument(argument)
107
+ match = argument.match(/\A--(.+?)=(.+)\Z/)
108
+ raise "Couldn't match argument: #{argument}" unless match
109
+
110
+ key = match[1]
111
+ value = match[2]
112
+
113
+ case key
114
+ when "exclude-path-prefixes", "exclude-types", "only-types", "tags"
115
+ [key.tr("-", "_").to_sym, value.split(",")]
116
+ when "group-number", "groups"
117
+ [key.tr("-", "_").to_sym, value.to_i]
118
+ when "log-file"
119
+ [:log_file, value]
120
+ else
121
+ raise "Unknown argument: #{key}"
122
+ end
123
+ end
124
+ 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,13 +1,14 @@
1
1
  class TestSuiteSplitter::RspecHelper
2
- attr_reader :exclude_types, :only_types, :tags
2
+ attr_reader :exclude_path_prefixes, :exclude_types, :only_types, :tags
3
3
 
4
- def initialize(groups:, group_number:, exclude_types: nil, only_types: nil, tags: nil)
5
- @exclude_types = exclude_types
4
+ def initialize(groups:, group_number:, **options)
5
+ @exclude_path_prefixes = options[:exclude_path_prefixes]
6
+ @exclude_types = options[:exclude_types]
6
7
  @groups = groups
7
8
  @group_number = group_number
8
9
  @example_data_exists = File.exist?("spec/examples.txt")
9
- @only_types = only_types
10
- @tags = tags
10
+ @only_types = options[:only_types]
11
+ @tags = options[:tags]
11
12
  end
12
13
 
13
14
  def example_data_exists?
@@ -143,11 +144,11 @@ private
143
144
  require "stringio"
144
145
 
145
146
  output_capture = StringIO.new
146
- RSpec::Core::Runner.run(rspec_options, $stderr, output_capture)
147
+ exit_code = RSpec::Core::Runner.run(rspec_options, $stderr, output_capture)
147
148
 
148
149
  result = ::JSON.parse(output_capture.string)
149
150
 
150
- raise "No examples were found" if result.fetch("examples").empty?
151
+ raise dry_run_error_message(result: result, exit_code: exit_code) if result.fetch("examples").empty?
151
152
 
152
153
  result
153
154
  end
@@ -161,11 +162,14 @@ private
161
162
  @files ||= begin
162
163
  result = {}
163
164
  dry_result.fetch("examples").each do |example|
164
- file_path = example.fetch("file_path")
165
+ file_path_id = example.fetch("id")
166
+ file_path = file_path_id.gsub(/\[([\d:]+)\]$/, "") # Same as ID but remove any [1:2:3] at the end
167
+
165
168
  file_path = file_path[2, file_path.length]
166
- type = type_from_path(file_path)
169
+ type = type_from_path(file_path_id)
167
170
  points = points_from_type(type)
168
171
 
172
+ next if ignore_path?(file_path)
169
173
  next if ignore_type?(type)
170
174
 
171
175
  result[file_path] = {examples: 0, path: file_path, points: 0, type: type} unless result.key?(file_path)
@@ -184,8 +188,16 @@ private
184
188
  false
185
189
  end
186
190
 
191
+ def ignore_path?(file_path)
192
+ exclude_path_prefixes&.any? do |exclude_path_prefix|
193
+ file_path.start_with?(exclude_path_prefix)
194
+ end
195
+ end
196
+
187
197
  def points_from_type(type)
188
- if type == "feature" || type == "system"
198
+ if type == "system"
199
+ 20
200
+ elsif type == "feature"
189
201
  10
190
202
  elsif type == "controllers"
191
203
  3
@@ -207,8 +219,21 @@ private
207
219
  rspec_options
208
220
  end
209
221
 
222
+ def dry_run_error_message(result:, exit_code:)
223
+ error_summary = []
224
+ errors_outside_of_examples_count = result.dig("summary", "errors_outside_of_examples_count")
225
+
226
+ error_summary << "exit_code=#{exit_code}"
227
+ error_summary << "errors_outside_of_examples_count=#{errors_outside_of_examples_count}" if errors_outside_of_examples_count.to_i.positive?
228
+
229
+ first_message = result.fetch("messages", []).find { |message| message.strip != "" }
230
+ details = first_message&.strip || "No examples were found"
231
+
232
+ "RSpec dry-run failed (#{error_summary.join(', ')})\n\n#{details}"
233
+ end
234
+
210
235
  def type_from_path(file_path)
211
- match = file_path.match(/^spec\/(.+?)\//)
236
+ match = file_path.match(/^\.\/spec\/(.+?)\//)
212
237
  match[1] if match
213
238
  end
214
239
  end
@@ -1,5 +1,7 @@
1
1
  module TestSuiteSplitter
2
2
  path = "#{__dir__}/test_suite_splitter"
3
3
 
4
+ autoload :Cli, "#{path}/cli"
5
+ autoload :Release, "#{path}/release"
4
6
  autoload :RspecHelper, "#{path}/rspec_helper"
5
7
  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.2
4
+ version: 0.0.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - kaspernj
8
- autorequire:
9
8
  bindir: bin
10
9
  cert_chain: []
11
- date: 2022-04-14 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
@@ -24,6 +37,34 @@ dependencies:
24
37
  - - ">="
25
38
  - !ruby/object:Gem::Version
26
39
  version: '0'
40
+ - !ruby/object:Gem::Dependency
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
56
+ requirement: !ruby/object:Gem::Requirement
57
+ requirements:
58
+ - - ">="
59
+ - !ruby/object:Gem::Version
60
+ version: '0'
61
+ type: :development
62
+ prerelease: false
63
+ version_requirements: !ruby/object:Gem::Requirement
64
+ requirements:
65
+ - - ">="
66
+ - !ruby/object:Gem::Version
67
+ version: '0'
27
68
  - !ruby/object:Gem::Dependency
28
69
  name: rspec
29
70
  requirement: !ruby/object:Gem::Requirement
@@ -84,6 +125,7 @@ description: Split your RSpec test suite up into several groups and run them in
84
125
  email: k@spernj.org
85
126
  executables:
86
127
  - test_suite_splitter
128
+ - test_suite_splitter_rspec
87
129
  extensions: []
88
130
  extra_rdoc_files:
89
131
  - LICENSE.txt
@@ -93,14 +135,16 @@ files:
93
135
  - README.md
94
136
  - Rakefile
95
137
  - bin/test_suite_splitter
138
+ - bin/test_suite_splitter_rspec
96
139
  - lib/test_suite_splitter.rb
140
+ - lib/test_suite_splitter/cli.rb
141
+ - lib/test_suite_splitter/release.rb
97
142
  - lib/test_suite_splitter/rspec_helper.rb
98
143
  homepage: http://github.com/kaspernj/test_suite_splitter
99
144
  licenses:
100
145
  - MIT
101
146
  metadata:
102
147
  rubygems_mfa_required: 'true'
103
- post_install_message:
104
148
  rdoc_options: []
105
149
  require_paths:
106
150
  - lib
@@ -115,8 +159,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
115
159
  - !ruby/object:Gem::Version
116
160
  version: '0'
117
161
  requirements: []
118
- rubygems_version: 3.2.32
119
- signing_key:
162
+ rubygems_version: 3.6.9
120
163
  specification_version: 4
121
164
  summary: Split your RSpec test suite up into several groups and run them in parallel.
122
165
  test_files: []