knapsack 0.5.0 → 1.0.0
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/.travis.yml +16 -16
- data/CHANGELOG.md +6 -0
- data/README.md +162 -61
- data/TODO.md +1 -1
- data/bin/print_header.sh +5 -0
- data/knapsack.gemspec +4 -2
- data/{knapsack_report.json → knapsack_rspec_report.json} +0 -0
- data/lib/knapsack.rb +5 -2
- data/lib/knapsack/adapters/base_adapter.rb +17 -1
- data/lib/knapsack/adapters/cucumber_adapter.rb +40 -0
- data/lib/knapsack/adapters/rspec_adapter.rb +5 -2
- data/lib/knapsack/allocator.rb +10 -11
- data/lib/knapsack/allocator_builder.rb +33 -0
- data/lib/knapsack/config/env.rb +30 -0
- data/lib/knapsack/config/tracker.rb +19 -0
- data/lib/knapsack/distributors/base_distributor.rb +18 -28
- data/lib/knapsack/distributors/leftover_distributor.rb +14 -14
- data/lib/knapsack/distributors/report_distributor.rb +33 -33
- data/lib/knapsack/presenter.rb +4 -4
- data/lib/knapsack/report.rb +13 -11
- data/lib/knapsack/tracker.rb +20 -15
- data/lib/knapsack/version.rb +1 -1
- data/lib/tasks/knapsack_cucumber.rake +19 -0
- data/lib/tasks/knapsack_rspec.rake +19 -0
- data/spec/knapsack/adapters/cucumber_adapter_spec.rb +69 -0
- data/spec/knapsack/adapters/rspec_adapter_spec.rb +26 -18
- data/spec/knapsack/allocator_builder_spec.rb +88 -0
- data/spec/knapsack/allocator_spec.rb +26 -27
- data/spec/knapsack/{config_spec.rb → config/env_spec.rb} +9 -32
- data/spec/knapsack/config/tracker_spec.rb +24 -0
- data/spec/knapsack/distributors/base_distributor_spec.rb +26 -39
- data/spec/knapsack/distributors/leftover_distributor_spec.rb +49 -31
- data/spec/knapsack/distributors/report_distributor_spec.rb +35 -41
- data/spec/knapsack/presenter_spec.rb +5 -5
- data/spec/knapsack/report_spec.rb +5 -9
- data/spec/knapsack/task_loader_spec.rb +4 -2
- data/spec/knapsack/tracker_spec.rb +14 -14
- data/spec/support/mocks/cucumber.rb +12 -0
- data/spec/support/shared_examples/adapter.rb +0 -4
- data/spec_examples/leftover/1_spec.rb +1 -1
- data/spec_examples/leftover/a_spec.rb +1 -1
- data/spec_examples/spec_helper.rb +1 -1
- metadata +54 -11
- data/lib/knapsack/config.rb +0 -40
- data/lib/tasks/knapsack.rake +0 -20
File without changes
|
data/lib/knapsack.rb
CHANGED
@@ -1,17 +1,20 @@
|
|
1
1
|
require 'singleton'
|
2
2
|
require_relative 'knapsack/version'
|
3
|
-
require_relative 'knapsack/config'
|
3
|
+
require_relative 'knapsack/config/env'
|
4
|
+
require_relative 'knapsack/config/tracker'
|
4
5
|
require_relative 'knapsack/logger'
|
5
6
|
require_relative 'knapsack/tracker'
|
6
7
|
require_relative 'knapsack/presenter'
|
7
8
|
require_relative 'knapsack/report'
|
8
9
|
require_relative 'knapsack/allocator'
|
10
|
+
require_relative 'knapsack/allocator_builder'
|
9
11
|
require_relative 'knapsack/task_loader'
|
10
12
|
require_relative 'knapsack/distributors/base_distributor'
|
11
13
|
require_relative 'knapsack/distributors/report_distributor'
|
12
14
|
require_relative 'knapsack/distributors/leftover_distributor'
|
13
15
|
require_relative 'knapsack/adapters/base_adapter'
|
14
16
|
require_relative 'knapsack/adapters/rspec_adapter'
|
17
|
+
require_relative 'knapsack/adapters/cucumber_adapter'
|
15
18
|
|
16
19
|
module Knapsack
|
17
20
|
class << self
|
@@ -26,7 +29,7 @@ module Knapsack
|
|
26
29
|
end
|
27
30
|
|
28
31
|
def root
|
29
|
-
File.expand_path
|
32
|
+
File.expand_path('../..', __FILE__)
|
30
33
|
end
|
31
34
|
|
32
35
|
def load_tasks
|
@@ -1,6 +1,10 @@
|
|
1
1
|
module Knapsack
|
2
2
|
module Adapters
|
3
3
|
class BaseAdapter
|
4
|
+
# Just examples, please overwrite constants in subclasses
|
5
|
+
TEST_DIR_PATTERN = 'test/**/*_test.rb'
|
6
|
+
REPORT_PATH = 'knapsack_base_report.json'
|
7
|
+
|
4
8
|
def self.bind
|
5
9
|
adapter = new
|
6
10
|
adapter.bind
|
@@ -8,6 +12,8 @@ module Knapsack
|
|
8
12
|
end
|
9
13
|
|
10
14
|
def bind
|
15
|
+
update_report_config
|
16
|
+
|
11
17
|
if tracker.config[:generate_report]
|
12
18
|
Knapsack.logger.info 'Knapsack report generator started!'
|
13
19
|
bind_time_tracker
|
@@ -33,11 +39,21 @@ module Knapsack
|
|
33
39
|
raise NotImplementedError
|
34
40
|
end
|
35
41
|
|
36
|
-
|
42
|
+
private
|
37
43
|
|
38
44
|
def tracker
|
39
45
|
Knapsack.tracker
|
40
46
|
end
|
47
|
+
|
48
|
+
def update_report_config
|
49
|
+
current_test_file_pattern = Knapsack.report.config[:test_file_pattern]
|
50
|
+
current_report_path = Knapsack.report.config[:report_path]
|
51
|
+
|
52
|
+
Knapsack.report.config({
|
53
|
+
test_file_pattern: Knapsack::Config::Env.test_file_pattern || current_test_file_pattern || self.class::TEST_DIR_PATTERN,
|
54
|
+
report_path: Knapsack::Config::Env.report_path || current_report_path || self.class::REPORT_PATH
|
55
|
+
})
|
56
|
+
end
|
41
57
|
end
|
42
58
|
end
|
43
59
|
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
module Knapsack
|
2
|
+
module Adapters
|
3
|
+
class CucumberAdapter < BaseAdapter
|
4
|
+
TEST_DIR_PATTERN = 'features/**/*.feature'
|
5
|
+
REPORT_PATH = 'knapsack_cucumber_report.json'
|
6
|
+
|
7
|
+
def bind_time_tracker
|
8
|
+
Around do |scenario, block|
|
9
|
+
Knapsack.tracker.test_path = scenario.file
|
10
|
+
Knapsack.tracker.start_timer
|
11
|
+
block.call
|
12
|
+
Knapsack.tracker.stop_timer
|
13
|
+
end
|
14
|
+
|
15
|
+
::Kernel.at_exit do
|
16
|
+
Knapsack.logger.info(Presenter.global_time)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def bind_report_generator
|
21
|
+
::Kernel.at_exit do
|
22
|
+
Knapsack.report.save
|
23
|
+
Knapsack.logger.info(Presenter.report_details)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def bind_time_offset_warning
|
28
|
+
::Kernel.at_exit do
|
29
|
+
Knapsack.logger.warn(Presenter.time_offset_warning)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
private
|
34
|
+
|
35
|
+
def Around(*tag_expressions, &proc)
|
36
|
+
::Cucumber::RbSupport::RbDsl.register_rb_hook('around', tag_expressions, proc)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -1,6 +1,9 @@
|
|
1
1
|
module Knapsack
|
2
2
|
module Adapters
|
3
3
|
class RspecAdapter < BaseAdapter
|
4
|
+
TEST_DIR_PATTERN = 'spec/**/*_spec.rb'
|
5
|
+
REPORT_PATH = 'knapsack_rspec_report.json'
|
6
|
+
|
4
7
|
def bind_time_tracker
|
5
8
|
::RSpec.configure do |config|
|
6
9
|
config.before(:each) do
|
@@ -10,7 +13,7 @@ module Knapsack
|
|
10
13
|
else
|
11
14
|
example.metadata
|
12
15
|
end
|
13
|
-
Knapsack.tracker.
|
16
|
+
Knapsack.tracker.test_path = RspecAdapter.test_path(current_example_group)
|
14
17
|
Knapsack.tracker.start_timer
|
15
18
|
end
|
16
19
|
|
@@ -41,7 +44,7 @@ module Knapsack
|
|
41
44
|
end
|
42
45
|
end
|
43
46
|
|
44
|
-
def self.
|
47
|
+
def self.test_path(example_group)
|
45
48
|
until example_group[:parent_example_group].nil?
|
46
49
|
example_group = example_group[:parent_example_group]
|
47
50
|
end
|
data/lib/knapsack/allocator.rb
CHANGED
@@ -1,29 +1,28 @@
|
|
1
1
|
module Knapsack
|
2
2
|
class Allocator
|
3
3
|
def initialize(args={})
|
4
|
-
@spec_pattern = args[:spec_pattern] || Config.spec_pattern
|
5
4
|
@report_distributor = Knapsack::Distributors::ReportDistributor.new(args)
|
6
5
|
@leftover_distributor = Knapsack::Distributors::LeftoverDistributor.new(args)
|
7
6
|
end
|
8
7
|
|
9
|
-
def
|
10
|
-
@
|
8
|
+
def report_node_tests
|
9
|
+
@report_node_tests ||= @report_distributor.tests_for_current_node
|
11
10
|
end
|
12
11
|
|
13
|
-
def
|
14
|
-
@
|
12
|
+
def leftover_node_tests
|
13
|
+
@leftover_node_tests ||= @leftover_distributor.tests_for_current_node
|
15
14
|
end
|
16
15
|
|
17
|
-
def
|
18
|
-
@
|
16
|
+
def node_tests
|
17
|
+
@node_tests ||= report_node_tests + leftover_node_tests
|
19
18
|
end
|
20
19
|
|
21
|
-
def
|
22
|
-
|
20
|
+
def stringify_node_tests
|
21
|
+
node_tests.join(' ')
|
23
22
|
end
|
24
23
|
|
25
|
-
def
|
26
|
-
@
|
24
|
+
def test_dir
|
25
|
+
@report_distributor.test_file_pattern.gsub(/^(.*?)\//).first
|
27
26
|
end
|
28
27
|
end
|
29
28
|
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
module Knapsack
|
2
|
+
class AllocatorBuilder
|
3
|
+
def initialize(adapter_class)
|
4
|
+
@adapter_class = adapter_class
|
5
|
+
set_report_path
|
6
|
+
end
|
7
|
+
|
8
|
+
def allocator
|
9
|
+
Knapsack::Allocator.new({
|
10
|
+
report: Knapsack.report.open,
|
11
|
+
test_file_pattern: test_file_pattern,
|
12
|
+
ci_node_total: Knapsack::Config::Env.ci_node_total,
|
13
|
+
ci_node_index: Knapsack::Config::Env.ci_node_index
|
14
|
+
})
|
15
|
+
end
|
16
|
+
|
17
|
+
private
|
18
|
+
|
19
|
+
def set_report_path
|
20
|
+
Knapsack.report.config({
|
21
|
+
report_path: report_path
|
22
|
+
})
|
23
|
+
end
|
24
|
+
|
25
|
+
def report_path
|
26
|
+
Knapsack::Config::Env.report_path || @adapter_class::REPORT_PATH
|
27
|
+
end
|
28
|
+
|
29
|
+
def test_file_pattern
|
30
|
+
Knapsack::Config::Env.test_file_pattern || @adapter_class::TEST_DIR_PATTERN
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
module Knapsack
|
2
|
+
module Config
|
3
|
+
class Env
|
4
|
+
class << self
|
5
|
+
def report_path
|
6
|
+
ENV['KNAPSACK_REPORT_PATH']
|
7
|
+
end
|
8
|
+
|
9
|
+
def ci_node_total
|
10
|
+
ENV['CI_NODE_TOTAL'] || ENV['CIRCLE_NODE_TOTAL'] || ENV['SEMAPHORE_THREAD_COUNT'] || 1
|
11
|
+
end
|
12
|
+
|
13
|
+
def ci_node_index
|
14
|
+
ENV['CI_NODE_INDEX'] || ENV['CIRCLE_NODE_INDEX'] || semaphore_current_thread || 0
|
15
|
+
end
|
16
|
+
|
17
|
+
def test_file_pattern
|
18
|
+
ENV['KNAPSACK_TEST_FILE_PATTERN']
|
19
|
+
end
|
20
|
+
|
21
|
+
private
|
22
|
+
|
23
|
+
def semaphore_current_thread
|
24
|
+
index = ENV['SEMAPHORE_CURRENT_THREAD']
|
25
|
+
index.to_i - 1 if index
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module Knapsack
|
2
|
+
module Config
|
3
|
+
class Tracker
|
4
|
+
class << self
|
5
|
+
def enable_time_offset_warning
|
6
|
+
true
|
7
|
+
end
|
8
|
+
|
9
|
+
def time_offset_in_seconds
|
10
|
+
30
|
11
|
+
end
|
12
|
+
|
13
|
+
def generate_report
|
14
|
+
ENV['KNAPSACK_GENERATE_REPORT'] || false
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -1,13 +1,13 @@
|
|
1
1
|
module Knapsack
|
2
2
|
module Distributors
|
3
3
|
class BaseDistributor
|
4
|
-
attr_reader :report, :
|
4
|
+
attr_reader :report, :node_tests, :test_file_pattern
|
5
5
|
|
6
6
|
def initialize(args={})
|
7
|
-
@report = args[:report] ||
|
8
|
-
@
|
9
|
-
@
|
10
|
-
@
|
7
|
+
@report = args[:report] || raise('Missing report')
|
8
|
+
@test_file_pattern = args[:test_file_pattern] || raise('Missing test_file_pattern')
|
9
|
+
@ci_node_total = args[:ci_node_total] || raise('Missing ci_node_total')
|
10
|
+
@ci_node_index = args[:ci_node_index] || raise('Missing ci_node_index')
|
11
11
|
end
|
12
12
|
|
13
13
|
def ci_node_total
|
@@ -18,47 +18,37 @@ module Knapsack
|
|
18
18
|
@ci_node_index.to_i
|
19
19
|
end
|
20
20
|
|
21
|
-
def
|
22
|
-
|
21
|
+
def tests_for_current_node
|
22
|
+
tests_for_node(ci_node_index)
|
23
23
|
end
|
24
24
|
|
25
|
-
def
|
26
|
-
|
27
|
-
|
25
|
+
def tests_for_node(node_index)
|
26
|
+
assign_test_files_to_node
|
27
|
+
post_tests_for_node(node_index)
|
28
28
|
end
|
29
29
|
|
30
|
-
def
|
31
|
-
|
32
|
-
|
30
|
+
def assign_test_files_to_node
|
31
|
+
default_node_tests
|
32
|
+
post_assign_test_files_to_node
|
33
33
|
end
|
34
34
|
|
35
|
-
def
|
36
|
-
@
|
35
|
+
def all_tests
|
36
|
+
@all_tests ||= Dir[test_file_pattern]
|
37
37
|
end
|
38
38
|
|
39
39
|
protected
|
40
40
|
|
41
|
-
def
|
41
|
+
def post_tests_for_node(node_index)
|
42
42
|
raise NotImplementedError
|
43
43
|
end
|
44
44
|
|
45
|
-
def
|
45
|
+
def post_assign_test_files_to_node
|
46
46
|
raise NotImplementedError
|
47
47
|
end
|
48
48
|
|
49
|
-
def
|
49
|
+
def default_node_tests
|
50
50
|
raise NotImplementedError
|
51
51
|
end
|
52
|
-
|
53
|
-
private
|
54
|
-
|
55
|
-
def config
|
56
|
-
Knapsack::Config
|
57
|
-
end
|
58
|
-
|
59
|
-
def default_report
|
60
|
-
Knapsack.report.open
|
61
|
-
end
|
62
52
|
end
|
63
53
|
end
|
64
54
|
end
|
@@ -1,35 +1,35 @@
|
|
1
1
|
module Knapsack
|
2
2
|
module Distributors
|
3
3
|
class LeftoverDistributor < BaseDistributor
|
4
|
-
def
|
5
|
-
@
|
4
|
+
def report_tests
|
5
|
+
@report_tests ||= report.keys
|
6
6
|
end
|
7
7
|
|
8
|
-
def
|
9
|
-
@
|
8
|
+
def leftover_tests
|
9
|
+
@leftover_tests ||= all_tests - report_tests
|
10
10
|
end
|
11
11
|
|
12
12
|
private
|
13
13
|
|
14
|
-
def
|
14
|
+
def post_assign_test_files_to_node
|
15
15
|
node_index = 0
|
16
|
-
|
17
|
-
|
16
|
+
leftover_tests.each do |test_file|
|
17
|
+
node_tests[node_index] << test_file
|
18
18
|
node_index += 1
|
19
19
|
node_index %= ci_node_total
|
20
20
|
end
|
21
21
|
end
|
22
22
|
|
23
|
-
def
|
24
|
-
|
25
|
-
return unless
|
26
|
-
|
23
|
+
def post_tests_for_node(node_index)
|
24
|
+
test_files = node_tests[node_index]
|
25
|
+
return unless test_files
|
26
|
+
test_files
|
27
27
|
end
|
28
28
|
|
29
|
-
def
|
30
|
-
@
|
29
|
+
def default_node_tests
|
30
|
+
@node_tests = []
|
31
31
|
ci_node_total.times do |index|
|
32
|
-
@
|
32
|
+
@node_tests[index] = []
|
33
33
|
end
|
34
34
|
end
|
35
35
|
end
|
@@ -2,15 +2,15 @@ module Knapsack
|
|
2
2
|
module Distributors
|
3
3
|
class ReportDistributor < BaseDistributor
|
4
4
|
def sorted_report
|
5
|
-
@sorted_report ||= report.sort_by { |
|
5
|
+
@sorted_report ||= report.sort_by { |test_path, time| time }.reverse
|
6
6
|
end
|
7
7
|
|
8
|
-
def
|
9
|
-
@
|
8
|
+
def sorted_report_with_existing_tests
|
9
|
+
@sorted_report_with_existing_tests ||= sorted_report.select { |test_path, time| all_tests.include?(test_path) }
|
10
10
|
end
|
11
11
|
|
12
12
|
def total_time_execution
|
13
|
-
@total_time_execution ||=
|
13
|
+
@total_time_execution ||= sorted_report_with_existing_tests.map(&:last).reduce(0, :+).to_f
|
14
14
|
end
|
15
15
|
|
16
16
|
def node_time_execution
|
@@ -19,62 +19,62 @@ module Knapsack
|
|
19
19
|
|
20
20
|
private
|
21
21
|
|
22
|
-
def
|
23
|
-
|
24
|
-
|
22
|
+
def post_assign_test_files_to_node
|
23
|
+
assign_slow_test_files
|
24
|
+
assign_remaining_test_files
|
25
25
|
end
|
26
26
|
|
27
|
-
def
|
28
|
-
|
29
|
-
return unless
|
30
|
-
|
27
|
+
def post_tests_for_node(node_index)
|
28
|
+
node_test = node_tests[node_index]
|
29
|
+
return unless node_test
|
30
|
+
node_test[:test_files_with_time].map(&:first)
|
31
31
|
end
|
32
32
|
|
33
|
-
def
|
34
|
-
@
|
33
|
+
def default_node_tests
|
34
|
+
@node_tests = []
|
35
35
|
ci_node_total.times do |index|
|
36
|
-
@
|
36
|
+
@node_tests << {
|
37
37
|
node_index: index,
|
38
38
|
time_left: node_time_execution,
|
39
|
-
|
39
|
+
test_files_with_time: []
|
40
40
|
}
|
41
41
|
end
|
42
42
|
end
|
43
43
|
|
44
|
-
def
|
45
|
-
@
|
44
|
+
def assign_slow_test_files
|
45
|
+
@not_assigned_test_files = []
|
46
46
|
node_index = 0
|
47
|
-
|
48
|
-
|
47
|
+
sorted_report_with_existing_tests.each do |test_file_with_time|
|
48
|
+
assign_slow_test_file(node_index, test_file_with_time)
|
49
49
|
node_index += 1
|
50
50
|
node_index %= ci_node_total
|
51
51
|
end
|
52
52
|
end
|
53
53
|
|
54
|
-
def
|
55
|
-
time =
|
56
|
-
time_left =
|
54
|
+
def assign_slow_test_file(node_index, test_file_with_time)
|
55
|
+
time = test_file_with_time[1]
|
56
|
+
time_left = node_tests[node_index][:time_left] - time
|
57
57
|
|
58
|
-
if time_left >= 0 or
|
59
|
-
|
60
|
-
|
58
|
+
if time_left >= 0 or node_tests[node_index][:test_files_with_time].empty?
|
59
|
+
node_tests[node_index][:time_left] -= time
|
60
|
+
node_tests[node_index][:test_files_with_time] << test_file_with_time
|
61
61
|
else
|
62
|
-
@
|
62
|
+
@not_assigned_test_files << test_file_with_time
|
63
63
|
end
|
64
64
|
end
|
65
65
|
|
66
|
-
def
|
67
|
-
@
|
66
|
+
def assign_remaining_test_files
|
67
|
+
@not_assigned_test_files.each do |test_file_with_time|
|
68
68
|
index = node_with_max_time_left
|
69
|
-
time =
|
70
|
-
|
71
|
-
|
69
|
+
time = test_file_with_time[1]
|
70
|
+
node_tests[index][:time_left] -= time
|
71
|
+
node_tests[index][:test_files_with_time] << test_file_with_time
|
72
72
|
end
|
73
73
|
end
|
74
74
|
|
75
75
|
def node_with_max_time_left
|
76
|
-
|
77
|
-
|
76
|
+
node_test = node_tests.max { |a,b| a[:time_left] <=> b[:time_left] }
|
77
|
+
node_test[:node_index]
|
78
78
|
end
|
79
79
|
end
|
80
80
|
end
|