treye-semaphore_test_boosters 2.5.1

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.
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "test_boosters"
4
+
5
+ exit(TestBoosters::Boosters::ExUnit.new.run)
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "test_boosters"
4
+
5
+ exit(TestBoosters::Boosters::GoTest.new.run)
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "test_boosters"
4
+
5
+ exit(TestBoosters::Boosters::Minitest.new.run)
data/exe/rspec_booster ADDED
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "test_boosters"
4
+
5
+ exit(TestBoosters::Boosters::Rspec.new.run)
@@ -0,0 +1,28 @@
1
+ require "uri"
2
+ require "optparse"
3
+ require "json"
4
+ require "cucumber_booster_config"
5
+
6
+ module TestBoosters
7
+ require "test_boosters/version"
8
+
9
+ require "test_boosters/cli_parser"
10
+ require "test_boosters/logger"
11
+ require "test_boosters/shell"
12
+ require "test_boosters/insights_uploader"
13
+ require "test_boosters/project_info"
14
+ require "test_boosters/job"
15
+
16
+ require "test_boosters/files/distributor"
17
+ require "test_boosters/files/leftover_files"
18
+ require "test_boosters/files/split_configuration"
19
+
20
+ require "test_boosters/boosters/base"
21
+ require "test_boosters/boosters/rspec"
22
+ require "test_boosters/boosters/cucumber"
23
+ require "test_boosters/boosters/go_test"
24
+ require "test_boosters/boosters/ex_unit"
25
+ require "test_boosters/boosters/minitest"
26
+
27
+ ROOT_PATH = File.absolute_path(File.dirname(__FILE__) + "/..")
28
+ end
@@ -0,0 +1,81 @@
1
+ module TestBoosters
2
+ module Boosters
3
+ class Base
4
+
5
+ def initialize(file_pattern, split_configuration_path, command)
6
+ @command = command
7
+ @file_pattern = file_pattern
8
+ @split_configuration_path = split_configuration_path
9
+ end
10
+
11
+ # :reek:TooManyStatements
12
+ def run
13
+ display_header
14
+
15
+ before_job # execute some activities when the before the job starts
16
+
17
+ distribution.display_info
18
+
19
+ known, leftover = distribution.files_for(job_index)
20
+
21
+ if cli_options[:dry_run]
22
+ show_files_for_dry_run("known", known)
23
+ show_files_for_dry_run("leftover", leftover)
24
+ return 0
25
+ end
26
+
27
+ exit_status = TestBoosters::Job.run(@command, known, leftover)
28
+
29
+ after_job # execute some activities when the job finishes
30
+
31
+ exit_status
32
+ end
33
+
34
+ def show_files_for_dry_run(label, files)
35
+ if files.empty?
36
+ puts "[DRY RUN] No #{label} files."
37
+ return
38
+ end
39
+
40
+ puts "\n[DRY RUN] Running tests for #{label} files:"
41
+ puts files.map { |file| "- #{file}" }.join("\n")
42
+ end
43
+
44
+ def before_job
45
+ # Do nothing
46
+ end
47
+
48
+ def after_job
49
+ # Do nothing
50
+ end
51
+
52
+ def display_header
53
+ version = "Test Booster v#{TestBoosters::VERSION}"
54
+ job_info = "Job #{job_index + 1} out of #{job_count}"
55
+
56
+ TestBoosters::Shell.display_title("#{version} - #{job_info}")
57
+ end
58
+
59
+ def distribution
60
+ @distribution ||= TestBoosters::Files::Distributor.new(@split_configuration_path,
61
+ @file_pattern,
62
+ job_count)
63
+ end
64
+
65
+ def job_index
66
+ @job_index ||= cli_options[:job_index] - 1
67
+ end
68
+
69
+ def job_count
70
+ @job_count ||= cli_options[:job_count]
71
+ end
72
+
73
+ private
74
+
75
+ def cli_options
76
+ @cli_options ||= TestBoosters::CliParser.parse
77
+ end
78
+
79
+ end
80
+ end
81
+ end
@@ -0,0 +1,37 @@
1
+ module TestBoosters
2
+ module Boosters
3
+ class Cucumber < Base
4
+
5
+ FILE_PATTERN = "features/**/*.feature".freeze
6
+
7
+ def initialize
8
+ super(FILE_PATTERN, split_configuration_path, "bundle exec cucumber")
9
+ end
10
+
11
+ def before_job
12
+ CucumberBoosterConfig::Injection.new(Dir.pwd, report_path).run
13
+ end
14
+
15
+ def after_job
16
+ TestBoosters::InsightsUploader.upload("cucumber", report_path)
17
+ end
18
+
19
+ def display_header
20
+ super
21
+
22
+ TestBoosters::ProjectInfo.display_ruby_version
23
+ TestBoosters::ProjectInfo.display_bundler_version
24
+ TestBoosters::ProjectInfo.display_cucumber_version
25
+ end
26
+
27
+ def report_path
28
+ @report_path ||= ENV["REPORT_PATH"] || "#{ENV["HOME"]}/cucumber_report.json"
29
+ end
30
+
31
+ def split_configuration_path
32
+ ENV["CUCUMBER_SPLIT_CONFIGURATION_PATH"] || "#{ENV["HOME"]}/cucumber_split_configuration.json"
33
+ end
34
+
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,17 @@
1
+ module TestBoosters
2
+ module Boosters
3
+ class ExUnit < Base
4
+
5
+ FILE_PATTERN = "test/**/*_test.exs".freeze
6
+
7
+ def initialize
8
+ super(FILE_PATTERN, split_configuration_path, "mix test")
9
+ end
10
+
11
+ def split_configuration_path
12
+ ENV["EX_UNIT_SPLIT_CONFIGURATION_PATH"] || "#{ENV["HOME"]}/ex_unit_split_configuration.json"
13
+ end
14
+
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,17 @@
1
+ module TestBoosters
2
+ module Boosters
3
+ class GoTest < Base
4
+
5
+ FILE_PATTERN = "**/*/*_test.go".freeze
6
+
7
+ def initialize
8
+ super(FILE_PATTERN, split_configuration_path, "go test")
9
+ end
10
+
11
+ def split_configuration_path
12
+ ENV["GO_TEST_SPLIT_CONFIGURATION_PATH"] || "#{ENV["HOME"]}/go_test_split_configuration.json"
13
+ end
14
+
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,21 @@
1
+ module TestBoosters
2
+ module Boosters
3
+ class Minitest < Base
4
+
5
+ FILE_PATTERN = "test/**/*_test.rb".freeze
6
+
7
+ def initialize
8
+ super(FILE_PATTERN, split_configuration_path, command)
9
+ end
10
+
11
+ def command
12
+ "ruby -e 'ARGV.each { |f| require \"./\#{f}\" }'"
13
+ end
14
+
15
+ def split_configuration_path
16
+ ENV["MINITEST_SPLIT_CONFIGURATION_PATH"] || "#{ENV["HOME"]}/minitest_split_configuration.json"
17
+ end
18
+
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,49 @@
1
+ module TestBoosters
2
+ module Boosters
3
+ class Rspec < Base
4
+ def initialize
5
+ super(file_pattern, split_configuration_path, command)
6
+ end
7
+
8
+ def display_header
9
+ super
10
+
11
+ TestBoosters::ProjectInfo.display_ruby_version
12
+ TestBoosters::ProjectInfo.display_bundler_version
13
+ TestBoosters::ProjectInfo.display_rspec_version
14
+ end
15
+
16
+ def after_job
17
+ TestBoosters::InsightsUploader.upload("rspec", report_path)
18
+ end
19
+
20
+ def command
21
+ @command ||= "bundle exec rspec #{rspec_options}"
22
+ end
23
+
24
+ def rspec_options
25
+ @rspec_options ||= begin
26
+ output_formatter = ENV.fetch("TB_RSPEC_FORMATTER", "documentation")
27
+ # rubocop:disable LineLength
28
+ "#{ENV["TB_RSPEC_OPTIONS"]} --format #{output_formatter} --require #{formatter_path} --format SemaphoreFormatter --out #{report_path}"
29
+ end
30
+ end
31
+
32
+ def report_path
33
+ @report_path ||= ENV["REPORT_PATH"] || "#{ENV["HOME"]}/rspec_report.json"
34
+ end
35
+
36
+ def split_configuration_path
37
+ ENV["RSPEC_SPLIT_CONFIGURATION_PATH"] || "#{ENV["HOME"]}/rspec_split_configuration.json"
38
+ end
39
+
40
+ def formatter_path
41
+ @formatter_path ||= File.join(::TestBoosters::ROOT_PATH, "rspec_formatters/semaphore_rspec3_json_formatter.rb")
42
+ end
43
+
44
+ def file_pattern
45
+ ENV["TEST_BOOSTERS_RSPEC_TEST_FILE_PATTERN"] || "spec/**/*_spec.rb"
46
+ end
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,49 @@
1
+ module TestBoosters
2
+ module CliParser
3
+ module_function
4
+
5
+ # :reek:TooManyStatements
6
+ # :reek:NestedIterators
7
+ # :reek:DuplicateMethodCall
8
+ def parse
9
+ options = {}
10
+
11
+ parser = OptionParser.new do |opts|
12
+ opts.on(
13
+ "--thread INDEX",
14
+ "[DEPRECATED] Use the '--job' option instead"
15
+ ) do |parameter|
16
+ puts "[DEPRECATION WARNING] The '--thread' parameter is deprecated. Please use '--job' instead."
17
+
18
+ options.merge!(parse_job_params(parameter))
19
+ end
20
+
21
+ opts.on(
22
+ "--job INDEX",
23
+ "The job index and number of total jobs. e.g. --job 4/32"
24
+ ) do |parameter|
25
+ options.merge!(parse_job_params(parameter))
26
+ end
27
+
28
+ opts.on(
29
+ "--dry-run",
30
+ "Only print the files that will be run for this job index"
31
+ ) do |parameter|
32
+ options.merge!(:dry_run => parameter)
33
+ end
34
+ end
35
+
36
+ parser.parse!
37
+
38
+ options
39
+ end
40
+
41
+ # parses input like '1/32' and outputs { :job_index => 1, :job_count => 32 }
42
+ def parse_job_params(input_parameter)
43
+ job_index, job_count, _rest = input_parameter.split("/")
44
+
45
+ { :job_index => job_index.to_i, :job_count => job_count.to_i }
46
+ end
47
+
48
+ end
49
+ end
@@ -0,0 +1,45 @@
1
+ module TestBoosters
2
+ module Files
3
+
4
+ #
5
+ # Distributes test files based on split configuration, file pattern, and their file size
6
+ #
7
+ class Distributor
8
+
9
+ def initialize(split_configuration_path, file_pattern, job_count)
10
+ @split_configuration_path = split_configuration_path
11
+ @file_pattern = file_pattern
12
+ @job_count = job_count
13
+ end
14
+
15
+ def display_info
16
+ puts "Split configuration present: #{split_configuration.present? ? "yes" : "no"}"
17
+ puts "Split configuration valid: #{split_configuration.valid? ? "yes" : "no"}"
18
+ puts "Split configuration file count: #{split_configuration.all_files.size}"
19
+ end
20
+
21
+ def files_for(job_index)
22
+ known = all_files & split_configuration.files_for_job(job_index)
23
+ leftover = leftover_files.select(:index => job_index, :total => @job_count)
24
+
25
+ [known, leftover]
26
+ end
27
+
28
+ def all_files
29
+ @all_files ||= Dir[@file_pattern].sort
30
+ end
31
+
32
+ private
33
+
34
+ def leftover_files
35
+ @leftover_files ||= TestBoosters::Files::LeftoverFiles.new(all_files - split_configuration.all_files)
36
+ end
37
+
38
+ def split_configuration
39
+ @split_configuration ||= TestBoosters::Files::SplitConfiguration.new(@split_configuration_path)
40
+ end
41
+
42
+ end
43
+
44
+ end
45
+ end
@@ -0,0 +1,42 @@
1
+ module TestBoosters
2
+ module Files
3
+ class LeftoverFiles
4
+
5
+ attr_reader :files
6
+
7
+ def initialize(files)
8
+ @files = files
9
+ end
10
+
11
+ def select(options = {})
12
+ index = options.fetch(:index)
13
+ total = options.fetch(:total)
14
+
15
+ file_distribution(total)[index]
16
+ end
17
+
18
+ private
19
+
20
+ def file_distribution(job_count)
21
+ # create N empty boxes
22
+ jobs = Array.new(job_count) { [] }
23
+
24
+ # distribute files in Round Robin fashion
25
+ sorted_files_by_file_size.each.with_index do |file, index|
26
+ jobs[index % job_count] << file
27
+ end
28
+
29
+ jobs
30
+ end
31
+
32
+ def sorted_files_by_file_size
33
+ @sorted_files_by_file_size ||= existing_files.sort_by { |file| -File.size(file) }
34
+ end
35
+
36
+ def existing_files
37
+ @existing_files ||= @files.select { |file| File.file?(file) }
38
+ end
39
+
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,68 @@
1
+ module TestBoosters
2
+ module Files
3
+ class SplitConfiguration
4
+
5
+ Job = Struct.new(:files)
6
+
7
+ def initialize(path)
8
+ @path = path
9
+ @valid = true
10
+ end
11
+
12
+ def present?
13
+ File.exist?(@path)
14
+ end
15
+
16
+ def valid?
17
+ jobs # try to load data into memory
18
+
19
+ @valid
20
+ end
21
+
22
+ def all_files
23
+ @all_files ||= jobs.map(&:files).flatten.sort
24
+ end
25
+
26
+ def files_for_job(job_index)
27
+ job = jobs[job_index]
28
+
29
+ job ? job.files : []
30
+ end
31
+
32
+ def jobs
33
+ @jobs ||= present? ? load_data : []
34
+ end
35
+
36
+ private
37
+
38
+ # :reek:TooManyStatements
39
+ def load_data
40
+ @valid = false
41
+
42
+ content = JSON.parse(File.read(@path)).map do |raw_job|
43
+ files = raw_job.fetch("files").sort
44
+
45
+ TestBoosters::Files::SplitConfiguration::Job.new(files)
46
+ end
47
+
48
+ @valid = true
49
+
50
+ content
51
+ rescue TypeError, KeyError => ex
52
+ log_error("Split Configuration has invalid structure", ex)
53
+
54
+ []
55
+ rescue JSON::ParserError => ex
56
+ log_error("Split Configuration is not parsable", ex)
57
+
58
+ []
59
+ end
60
+
61
+ def log_error(message, exception)
62
+ TestBoosters::Logger.error(message)
63
+ TestBoosters::Logger.error(exception.inspect)
64
+ end
65
+
66
+ end
67
+ end
68
+ end