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 +4 -4
- data/README.md +12 -0
- data/bin/test_suite_splitter +1 -28
- data/bin/test_suite_splitter_rspec +5 -0
- data/lib/test_suite_splitter/cli.rb +124 -0
- data/lib/test_suite_splitter/rspec_helper.rb +26 -24
- data/lib/test_suite_splitter.rb +1 -0
- metadata +4 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 87ca0053702cb8904a2856c58df606e3bd8f12a48cf98e0edc804c7959d9655a
|
|
4
|
+
data.tar.gz: 9a3b00d07bf4ad69343b1ffc3229914b05b7aa4d138f4446331ad056af23c5f9
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
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
|
data/bin/test_suite_splitter
CHANGED
|
@@ -4,31 +4,4 @@
|
|
|
4
4
|
|
|
5
5
|
require "#{__dir__}/../lib/test_suite_splitter"
|
|
6
6
|
|
|
7
|
-
|
|
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,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:,
|
|
5
|
-
@
|
|
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.
|
|
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
|
-
|
|
117
|
-
|
|
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
|
data/lib/test_suite_splitter.rb
CHANGED
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.
|
|
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
|