test_suite_splitter 0.0.3 → 0.0.5

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: ac2544ca288e5c7cb4c154043483963b1dbc9f7666c058caca6b89da805a8e2a
4
- data.tar.gz: 6dc90fe1986b9f55b7aa82dda12c1545e566ed3830725d72bda19fbd863cedbd
3
+ metadata.gz: 87ca0053702cb8904a2856c58df606e3bd8f12a48cf98e0edc804c7959d9655a
4
+ data.tar.gz: 9a3b00d07bf4ad69343b1ffc3229914b05b7aa4d138f4446331ad056af23c5f9
5
5
  SHA512:
6
- metadata.gz: 6ee0867d88f4e7ffcc864ac2fad277071d8cf15e85bea3bdbf107ac8254ac276746d6a6e218ce31667a58779162c51871c61cb55e60319638d25f907130658ab
7
- data.tar.gz: c4d31874181ec420bbc0e28e6e2b494bbb540513c91fa0e75112eabed4f6de1d6ae9090815a2aade27ee6eb1ef42d927ffb665052f53c0bb23b0d4f01f14223c
6
+ metadata.gz: d53b1810eb33ea334e3ce519794112e7c9ec44d9f458e11488058bd5c999a20405d0cba1c55e0d70087ef1f1b871c2632ae6d1c53acf4b8f1435a620fdf23e82
7
+ data.tar.gz: 722e776eadb0266991e96da7f1a64b40b77ca3bfd5c335fe4cdeea5bd2d338260dba602f0127c252d8048e9ad7c232c62abe06db17b4d0d540bf124fe076d4d8
data/README.md CHANGED
@@ -12,6 +12,11 @@ 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 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
+
15
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}`
@@ -27,6 +32,13 @@ Exclude a certain type of specs:
27
32
  bundle exec rspec `bundle exec test_suite_splitter --groups=6 --group-number=3 --exclude-types=system,feature`
28
33
  ```
29
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
+
30
42
  Release a new gem version:
31
43
  ```bash
32
44
  bundle exec rake release:patch
@@ -4,31 +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
- begin
28
- rspec_helper = ::TestSuiteSplitter::RspecHelper.new(**args)
29
-
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
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
@@ -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?
@@ -75,6 +76,7 @@ class TestSuiteSplitter::RspecHelper
75
76
  group[:examples] += examples
76
77
  group[:files] << file
77
78
  group[:seconds] += seconds if seconds
79
+ group[:weight] += file_weight(file, file_data)
78
80
  end
79
81
 
80
82
  @group_files = group_orders[@group_number - 1].fetch(:files)
@@ -87,7 +89,8 @@ class TestSuiteSplitter::RspecHelper
87
89
  group_orders << {
88
90
  examples: 0,
89
91
  files: [],
90
- seconds: 0.0
92
+ seconds: 0.0,
93
+ weight: 0.0
91
94
  }
92
95
  end
93
96
  group_orders
@@ -95,13 +98,7 @@ class TestSuiteSplitter::RspecHelper
95
98
  end
96
99
 
97
100
  def group_with_least
98
- group_orders.min do |group1, group2|
99
- if example_data_exists? && group1.fetch(:seconds) != 0.0 && group2.fetch(:seconds) != 0.0
100
- group1.fetch(:seconds) <=> group2.fetch(:seconds)
101
- else
102
- group1.fetch(:examples) <=> group2.fetch(:examples)
103
- end
104
- end
101
+ group_orders.min_by { |group| group.fetch(:weight) }
105
102
  end
106
103
 
107
104
  # Sort them so that they are sorted by file path in three groups so each group have an equal amount of controller specs, features specs and so on
@@ -113,17 +110,8 @@ class TestSuiteSplitter::RspecHelper
113
110
  file1_data = example_file(file1_path) if example_data_exists?
114
111
  file2_data = example_file(file2_path) if example_data_exists?
115
112
 
116
- if file1_data && file2_data && file1_data.fetch(:seconds) != 0.0 && file2_data.fetch(:seconds) != 0.0
117
- value1 = file1_data[:seconds]
118
- else
119
- value1 = file1.fetch(:points)
120
- end
121
-
122
- if file2_data && file1_data && file2_data.fetch(:seconds) != 0.0 && file2_data.fetch(:seconds) != 0.0
123
- value2 = file2_data[:seconds]
124
- else
125
- value2 = file2.fetch(:points)
126
- end
113
+ value1 = file_weight(file1, file1_data)
114
+ value2 = file_weight(file2, file2_data)
127
115
 
128
116
  if value1 == value2
129
117
  value2 = file1_path
@@ -136,6 +124,13 @@ class TestSuiteSplitter::RspecHelper
136
124
 
137
125
  private
138
126
 
127
+ def file_weight(file, file_data)
128
+ seconds = file_data&.fetch(:seconds)
129
+ return seconds if seconds&.positive?
130
+
131
+ file.fetch(:points)
132
+ end
133
+
139
134
  def dry_result
140
135
  @dry_result ||= begin
141
136
  require "json"
@@ -168,6 +163,7 @@ private
168
163
  type = type_from_path(file_path_id)
169
164
  points = points_from_type(type)
170
165
 
166
+ next if ignore_path?(file_path)
171
167
  next if ignore_type?(type)
172
168
 
173
169
  result[file_path] = {examples: 0, path: file_path, points: 0, type: type} unless result.key?(file_path)
@@ -186,6 +182,12 @@ private
186
182
  false
187
183
  end
188
184
 
185
+ def ignore_path?(file_path)
186
+ exclude_path_prefixes&.any? do |exclude_path_prefix|
187
+ file_path.start_with?(exclude_path_prefix)
188
+ end
189
+ end
190
+
189
191
  def points_from_type(type)
190
192
  if type == "system"
191
193
  20
@@ -1,6 +1,7 @@
1
1
  module TestSuiteSplitter
2
2
  path = "#{__dir__}/test_suite_splitter"
3
3
 
4
+ autoload :Cli, "#{path}/cli"
4
5
  autoload :Release, "#{path}/release"
5
6
  autoload :RspecHelper, "#{path}/rspec_helper"
6
7
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: test_suite_splitter
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.3
4
+ version: 0.0.5
5
5
  platform: ruby
6
6
  authors:
7
7
  - kaspernj
@@ -125,6 +125,7 @@ description: Split your RSpec test suite up into several groups and run them in
125
125
  email: k@spernj.org
126
126
  executables:
127
127
  - test_suite_splitter
128
+ - test_suite_splitter_rspec
128
129
  extensions: []
129
130
  extra_rdoc_files:
130
131
  - LICENSE.txt
@@ -134,7 +135,9 @@ files:
134
135
  - README.md
135
136
  - Rakefile
136
137
  - bin/test_suite_splitter
138
+ - bin/test_suite_splitter_rspec
137
139
  - lib/test_suite_splitter.rb
140
+ - lib/test_suite_splitter/cli.rb
138
141
  - lib/test_suite_splitter/release.rb
139
142
  - lib/test_suite_splitter/rspec_helper.rb
140
143
  homepage: http://github.com/kaspernj/test_suite_splitter