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
data/lib/knapsack/presenter.rb
CHANGED
@@ -5,11 +5,11 @@ module Knapsack
|
|
5
5
|
class Presenter
|
6
6
|
class << self
|
7
7
|
def report_yml
|
8
|
-
Knapsack.tracker.
|
8
|
+
Knapsack.tracker.test_files_with_time.to_yaml
|
9
9
|
end
|
10
10
|
|
11
11
|
def report_json
|
12
|
-
JSON.pretty_generate(Knapsack.tracker.
|
12
|
+
JSON.pretty_generate(Knapsack.tracker.test_files_with_time)
|
13
13
|
end
|
14
14
|
|
15
15
|
def report_details
|
@@ -18,7 +18,7 @@ module Knapsack
|
|
18
18
|
|
19
19
|
def global_time
|
20
20
|
global_time = pretty_seconds(Knapsack.tracker.global_time)
|
21
|
-
"\nKnapsack global time execution for
|
21
|
+
"\nKnapsack global time execution for tests: #{global_time}"
|
22
22
|
end
|
23
23
|
|
24
24
|
def time_offset
|
@@ -43,7 +43,7 @@ module Knapsack
|
|
43
43
|
}
|
44
44
|
if Knapsack.tracker.time_exceeded?
|
45
45
|
str << %{
|
46
|
-
|
46
|
+
Tests on this CI node took more than time offset.
|
47
47
|
Please regenerate your knapsack report.
|
48
48
|
If that didn't help then split your heavy test file
|
49
49
|
or bump time_offset_in_seconds setting.}
|
data/lib/knapsack/report.rb
CHANGED
@@ -2,19 +2,27 @@ module Knapsack
|
|
2
2
|
class Report
|
3
3
|
include Singleton
|
4
4
|
|
5
|
-
def config(
|
6
|
-
@config ||=
|
7
|
-
@config.merge!(
|
5
|
+
def config(args={})
|
6
|
+
@config ||= args
|
7
|
+
@config.merge!(args)
|
8
|
+
end
|
9
|
+
|
10
|
+
def report_path
|
11
|
+
config[:report_path] || raise('Missing report_path')
|
12
|
+
end
|
13
|
+
|
14
|
+
def test_file_pattern
|
15
|
+
config[:test_file_pattern] || raise('Missing test_file_pattern')
|
8
16
|
end
|
9
17
|
|
10
18
|
def save
|
11
|
-
File.open(
|
19
|
+
File.open(report_path, 'w+') do |f|
|
12
20
|
f.write(report_json)
|
13
21
|
end
|
14
22
|
end
|
15
23
|
|
16
24
|
def open
|
17
|
-
report = File.read(
|
25
|
+
report = File.read(report_path)
|
18
26
|
JSON.parse(report)
|
19
27
|
rescue Errno::ENOENT
|
20
28
|
raise "Knapsack report file doesn't exist. Please generate report first!"
|
@@ -22,12 +30,6 @@ module Knapsack
|
|
22
30
|
|
23
31
|
private
|
24
32
|
|
25
|
-
def default_config
|
26
|
-
{
|
27
|
-
report_path: Config.report_path
|
28
|
-
}
|
29
|
-
end
|
30
|
-
|
31
33
|
def report_json
|
32
34
|
Presenter.report_json
|
33
35
|
end
|
data/lib/knapsack/tracker.rb
CHANGED
@@ -2,8 +2,8 @@ module Knapsack
|
|
2
2
|
class Tracker
|
3
3
|
include Singleton
|
4
4
|
|
5
|
-
attr_reader :global_time, :
|
6
|
-
attr_writer :
|
5
|
+
attr_reader :global_time, :test_files_with_time
|
6
|
+
attr_writer :test_path
|
7
7
|
|
8
8
|
def initialize
|
9
9
|
set_defaults
|
@@ -25,13 +25,13 @@ module Knapsack
|
|
25
25
|
def stop_timer
|
26
26
|
@execution_time = Time.now.to_f - @start_time
|
27
27
|
update_global_time
|
28
|
-
|
28
|
+
update_test_file_time
|
29
29
|
@execution_time
|
30
30
|
end
|
31
31
|
|
32
|
-
def
|
33
|
-
raise("
|
34
|
-
@
|
32
|
+
def test_path
|
33
|
+
raise("test_path needs to be set by Knapsack Adapter's bind method") unless @test_path
|
34
|
+
@test_path.sub(/^\.\//, '')
|
35
35
|
end
|
36
36
|
|
37
37
|
def time_exceeded?
|
@@ -50,29 +50,34 @@ module Knapsack
|
|
50
50
|
|
51
51
|
def default_config
|
52
52
|
{
|
53
|
-
enable_time_offset_warning: Config.enable_time_offset_warning,
|
54
|
-
time_offset_in_seconds: Config.time_offset_in_seconds,
|
55
|
-
generate_report: Config.generate_report
|
53
|
+
enable_time_offset_warning: Config::Tracker.enable_time_offset_warning,
|
54
|
+
time_offset_in_seconds: Config::Tracker.time_offset_in_seconds,
|
55
|
+
generate_report: Config::Tracker.generate_report
|
56
56
|
}
|
57
57
|
end
|
58
58
|
|
59
59
|
def set_defaults
|
60
60
|
@global_time = 0
|
61
|
-
@
|
62
|
-
@
|
61
|
+
@test_files_with_time = {}
|
62
|
+
@test_path = nil
|
63
63
|
end
|
64
64
|
|
65
65
|
def update_global_time
|
66
66
|
@global_time += @execution_time
|
67
67
|
end
|
68
68
|
|
69
|
-
def
|
70
|
-
@
|
71
|
-
@
|
69
|
+
def update_test_file_time
|
70
|
+
@test_files_with_time[test_path] ||= 0
|
71
|
+
@test_files_with_time[test_path] += @execution_time
|
72
72
|
end
|
73
73
|
|
74
74
|
def report_distributor
|
75
|
-
@report_distributor ||= Knapsack::Distributors::ReportDistributor.new
|
75
|
+
@report_distributor ||= Knapsack::Distributors::ReportDistributor.new({
|
76
|
+
report: Knapsack.report.open,
|
77
|
+
test_file_pattern: Knapsack::Config::Env.test_file_pattern || Knapsack.report.config[:test_file_pattern],
|
78
|
+
ci_node_total: Knapsack::Config::Env.ci_node_total,
|
79
|
+
ci_node_index: Knapsack::Config::Env.ci_node_index
|
80
|
+
})
|
76
81
|
end
|
77
82
|
end
|
78
83
|
end
|
data/lib/knapsack/version.rb
CHANGED
@@ -0,0 +1,19 @@
|
|
1
|
+
require 'knapsack'
|
2
|
+
|
3
|
+
namespace :knapsack do
|
4
|
+
task :cucumber, [:cucumber_args] do |t, args|
|
5
|
+
allocator = Knapsack::AllocatorBuilder.new(Knapsack::Adapters::CucumberAdapter).allocator
|
6
|
+
|
7
|
+
puts
|
8
|
+
puts 'Report features:'
|
9
|
+
puts allocator.report_node_tests
|
10
|
+
puts
|
11
|
+
puts 'Leftover features:'
|
12
|
+
puts allocator.leftover_node_tests
|
13
|
+
puts
|
14
|
+
|
15
|
+
cmd = %Q[bundle exec cucumber #{args[:cucumber_args]} -- #{allocator.stringify_node_tests}]
|
16
|
+
|
17
|
+
exec(cmd)
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
require 'knapsack'
|
2
|
+
|
3
|
+
namespace :knapsack do
|
4
|
+
task :rspec, [:rspec_args] do |t, args|
|
5
|
+
allocator = Knapsack::AllocatorBuilder.new(Knapsack::Adapters::RspecAdapter).allocator
|
6
|
+
|
7
|
+
puts
|
8
|
+
puts 'Report specs:'
|
9
|
+
puts allocator.report_node_tests
|
10
|
+
puts
|
11
|
+
puts 'Leftover specs:'
|
12
|
+
puts allocator.leftover_node_tests
|
13
|
+
puts
|
14
|
+
|
15
|
+
cmd = %Q[bundle exec rspec #{args[:rspec_args]} --default-path #{allocator.test_dir} -- #{allocator.stringify_node_tests}]
|
16
|
+
|
17
|
+
exec(cmd)
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,69 @@
|
|
1
|
+
describe Knapsack::Adapters::CucumberAdapter do
|
2
|
+
context do
|
3
|
+
before do
|
4
|
+
allow(::Cucumber::RbSupport::RbDsl).to receive(:register_rb_hook)
|
5
|
+
allow(Kernel).to receive(:at_exit)
|
6
|
+
end
|
7
|
+
|
8
|
+
it_behaves_like 'adapter'
|
9
|
+
end
|
10
|
+
|
11
|
+
describe 'bind methods' do
|
12
|
+
let(:file) { 'features/a.feature' }
|
13
|
+
let(:scenario) { double(file: file) }
|
14
|
+
let(:block) { double }
|
15
|
+
let(:logger) { instance_double(Knapsack::Logger) }
|
16
|
+
let(:global_time) { 'Global time: 01m 05s' }
|
17
|
+
|
18
|
+
before do
|
19
|
+
expect(Knapsack).to receive(:logger).and_return(logger)
|
20
|
+
end
|
21
|
+
|
22
|
+
describe '#bind_time_tracker' do
|
23
|
+
let(:tracker) { instance_double(Knapsack::Tracker) }
|
24
|
+
|
25
|
+
it do
|
26
|
+
expect(subject).to receive(:Around).and_yield(scenario, block)
|
27
|
+
allow(Knapsack).to receive(:tracker).and_return(tracker)
|
28
|
+
expect(tracker).to receive(:test_path=).with(file)
|
29
|
+
expect(tracker).to receive(:start_timer)
|
30
|
+
expect(block).to receive(:call)
|
31
|
+
expect(tracker).to receive(:stop_timer)
|
32
|
+
|
33
|
+
expect(::Kernel).to receive(:at_exit).and_yield
|
34
|
+
expect(Knapsack::Presenter).to receive(:global_time).and_return(global_time)
|
35
|
+
expect(logger).to receive(:info).with(global_time)
|
36
|
+
|
37
|
+
subject.bind_time_tracker
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
describe '#bind_report_generator' do
|
42
|
+
let(:report) { instance_double(Knapsack::Report) }
|
43
|
+
let(:report_details) { 'Report details' }
|
44
|
+
|
45
|
+
it do
|
46
|
+
expect(::Kernel).to receive(:at_exit).and_yield
|
47
|
+
expect(Knapsack).to receive(:report).and_return(report)
|
48
|
+
expect(report).to receive(:save)
|
49
|
+
|
50
|
+
expect(Knapsack::Presenter).to receive(:report_details).and_return(report_details)
|
51
|
+
expect(logger).to receive(:info).with(report_details)
|
52
|
+
|
53
|
+
subject.bind_report_generator
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
describe '#bind_time_offset_warning' do
|
58
|
+
let(:time_offset_warning) { 'Time offset warning' }
|
59
|
+
|
60
|
+
it do
|
61
|
+
expect(::Kernel).to receive(:at_exit).and_yield
|
62
|
+
expect(Knapsack::Presenter).to receive(:time_offset_warning).and_return(time_offset_warning)
|
63
|
+
expect(logger).to receive(:warn).with(time_offset_warning)
|
64
|
+
|
65
|
+
subject.bind_time_offset_warning
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
@@ -1,5 +1,8 @@
|
|
1
1
|
describe Knapsack::Adapters::RspecAdapter do
|
2
|
-
|
2
|
+
context do
|
3
|
+
before { expect(::RSpec).to receive(:configure) }
|
4
|
+
it_behaves_like 'adapter'
|
5
|
+
end
|
3
6
|
|
4
7
|
describe 'bind methods' do
|
5
8
|
let(:config) { double }
|
@@ -11,24 +14,31 @@ describe Knapsack::Adapters::RspecAdapter do
|
|
11
14
|
|
12
15
|
describe '#bind_time_tracker' do
|
13
16
|
let(:tracker) { instance_double(Knapsack::Tracker) }
|
14
|
-
let(:
|
17
|
+
let(:test_path) { 'spec/a_spec.rb' }
|
15
18
|
let(:global_time) { 'Global time: 01m 05s' }
|
19
|
+
let(:example_group) { double }
|
20
|
+
let(:current_example) do
|
21
|
+
OpenStruct.new(metadata: {
|
22
|
+
example_group: example_group
|
23
|
+
})
|
24
|
+
end
|
16
25
|
|
17
26
|
it do
|
18
|
-
expect(
|
27
|
+
expect(config).to receive(:before).with(:each).and_yield
|
28
|
+
expect(config).to receive(:after).with(:each).and_yield
|
29
|
+
expect(config).to receive(:after).with(:suite).and_yield
|
30
|
+
expect(::RSpec).to receive(:configure).and_yield(config)
|
31
|
+
|
32
|
+
expect(::RSpec).to receive(:current_example).twice.and_return(current_example)
|
33
|
+
expect(described_class).to receive(:test_path).with(example_group).and_return(test_path)
|
19
34
|
|
20
35
|
allow(Knapsack).to receive(:tracker).and_return(tracker)
|
21
|
-
expect(tracker).to receive(:
|
36
|
+
expect(tracker).to receive(:test_path=).with(test_path)
|
22
37
|
expect(tracker).to receive(:start_timer)
|
38
|
+
|
23
39
|
expect(tracker).to receive(:stop_timer)
|
24
40
|
|
25
41
|
expect(Knapsack::Presenter).to receive(:global_time).and_return(global_time)
|
26
|
-
|
27
|
-
expect(config).to receive(:before).with(:each).and_yield
|
28
|
-
expect(config).to receive(:after).with(:each).and_yield
|
29
|
-
expect(config).to receive(:after).with(:suite).and_yield
|
30
|
-
expect(::RSpec).to receive(:configure).and_yield(config)
|
31
|
-
|
32
42
|
expect(logger).to receive(:info).with(global_time)
|
33
43
|
|
34
44
|
subject.bind_time_tracker
|
@@ -40,14 +50,13 @@ describe Knapsack::Adapters::RspecAdapter do
|
|
40
50
|
let(:report_details) { 'Report details' }
|
41
51
|
|
42
52
|
it do
|
53
|
+
expect(config).to receive(:after).with(:suite).and_yield
|
54
|
+
expect(::RSpec).to receive(:configure).and_yield(config)
|
55
|
+
|
43
56
|
expect(Knapsack).to receive(:report).and_return(report)
|
44
57
|
expect(report).to receive(:save)
|
45
58
|
|
46
59
|
expect(Knapsack::Presenter).to receive(:report_details).and_return(report_details)
|
47
|
-
|
48
|
-
expect(config).to receive(:after).with(:suite).and_yield
|
49
|
-
expect(::RSpec).to receive(:configure).and_yield(config)
|
50
|
-
|
51
60
|
expect(logger).to receive(:info).with(report_details)
|
52
61
|
|
53
62
|
subject.bind_report_generator
|
@@ -58,11 +67,10 @@ describe Knapsack::Adapters::RspecAdapter do
|
|
58
67
|
let(:time_offset_warning) { 'Time offset warning' }
|
59
68
|
|
60
69
|
it do
|
61
|
-
expect(Knapsack::Presenter).to receive(:time_offset_warning).and_return(time_offset_warning)
|
62
|
-
|
63
70
|
expect(config).to receive(:after).with(:suite).and_yield
|
64
71
|
expect(::RSpec).to receive(:configure).and_yield(config)
|
65
72
|
|
73
|
+
expect(Knapsack::Presenter).to receive(:time_offset_warning).and_return(time_offset_warning)
|
66
74
|
expect(logger).to receive(:warn).with(time_offset_warning)
|
67
75
|
|
68
76
|
subject.bind_time_offset_warning
|
@@ -70,7 +78,7 @@ describe Knapsack::Adapters::RspecAdapter do
|
|
70
78
|
end
|
71
79
|
end
|
72
80
|
|
73
|
-
describe '.
|
81
|
+
describe '.test_path' do
|
74
82
|
let(:current_example_metadata) do
|
75
83
|
{
|
76
84
|
file_path: '1_shared_example.rb',
|
@@ -83,7 +91,7 @@ describe Knapsack::Adapters::RspecAdapter do
|
|
83
91
|
}
|
84
92
|
end
|
85
93
|
|
86
|
-
subject { described_class.
|
94
|
+
subject { described_class.test_path(current_example_metadata) }
|
87
95
|
|
88
96
|
it { should eql 'a_spec.rb' }
|
89
97
|
end
|
@@ -0,0 +1,88 @@
|
|
1
|
+
describe Knapsack::AllocatorBuilder do
|
2
|
+
let(:allocator_builder) { described_class.new(adapter_class) }
|
3
|
+
let(:allocator) { double }
|
4
|
+
|
5
|
+
let(:report) { double }
|
6
|
+
let(:knapsack_report) { instance_double(Knapsack::Report) }
|
7
|
+
|
8
|
+
let(:adapter_report_path) { adapter_class::REPORT_PATH }
|
9
|
+
let(:adapter_test_file_pattern) { adapter_class::TEST_DIR_PATTERN }
|
10
|
+
|
11
|
+
let(:env_ci_node_total) { double }
|
12
|
+
let(:env_ci_node_index) { double }
|
13
|
+
let(:env_report_path) { nil }
|
14
|
+
let(:env_test_file_pattern) { nil }
|
15
|
+
|
16
|
+
describe '#allocator' do
|
17
|
+
subject { allocator_builder.allocator }
|
18
|
+
|
19
|
+
before do
|
20
|
+
expect(Knapsack::Config::Env).to receive(:report_path).and_return(env_report_path)
|
21
|
+
expect(Knapsack::Config::Env).to receive(:test_file_pattern).and_return(env_test_file_pattern)
|
22
|
+
expect(Knapsack::Config::Env).to receive(:ci_node_total).and_return(env_ci_node_total)
|
23
|
+
expect(Knapsack::Config::Env).to receive(:ci_node_index).and_return(env_ci_node_index)
|
24
|
+
|
25
|
+
expect(Knapsack).to receive(:report).twice.and_return(knapsack_report)
|
26
|
+
expect(knapsack_report).to receive(:open).and_return(report)
|
27
|
+
|
28
|
+
expect(knapsack_report).to receive(:config).with(report_config)
|
29
|
+
expect(Knapsack::Allocator).to receive(:new).with(allocator_args).and_return(allocator)
|
30
|
+
end
|
31
|
+
|
32
|
+
shared_examples 'allocator builder' do
|
33
|
+
context 'when ENVs are nil' do
|
34
|
+
let(:report_config) { { report_path: adapter_report_path } }
|
35
|
+
let(:allocator_args) do
|
36
|
+
{
|
37
|
+
report: report,
|
38
|
+
test_file_pattern: adapter_test_file_pattern,
|
39
|
+
ci_node_total: env_ci_node_total,
|
40
|
+
ci_node_index: env_ci_node_index
|
41
|
+
}
|
42
|
+
end
|
43
|
+
|
44
|
+
it { should eql allocator }
|
45
|
+
end
|
46
|
+
|
47
|
+
context 'when ENV report_path has value' do
|
48
|
+
let(:env_report_path) { 'knapsack_custom_report.json' }
|
49
|
+
let(:report_config) { { report_path: env_report_path } }
|
50
|
+
let(:allocator_args) do
|
51
|
+
{
|
52
|
+
report: report,
|
53
|
+
test_file_pattern: adapter_test_file_pattern,
|
54
|
+
ci_node_total: env_ci_node_total,
|
55
|
+
ci_node_index: env_ci_node_index
|
56
|
+
}
|
57
|
+
end
|
58
|
+
|
59
|
+
it { should eql allocator }
|
60
|
+
end
|
61
|
+
|
62
|
+
context 'when ENV test_file_pattern has value' do
|
63
|
+
let(:env_test_file_pattern) { 'custom_spec/**/*_spec.rb' }
|
64
|
+
let(:report_config) { { report_path: adapter_report_path } }
|
65
|
+
let(:allocator_args) do
|
66
|
+
{
|
67
|
+
report: report,
|
68
|
+
test_file_pattern: env_test_file_pattern,
|
69
|
+
ci_node_total: env_ci_node_total,
|
70
|
+
ci_node_index: env_ci_node_index
|
71
|
+
}
|
72
|
+
end
|
73
|
+
|
74
|
+
it { should eql allocator }
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
context 'when RspecAdapter' do
|
79
|
+
let(:adapter_class) { Knapsack::Adapters::RspecAdapter }
|
80
|
+
it_behaves_like 'allocator builder'
|
81
|
+
end
|
82
|
+
|
83
|
+
context 'when CucumberAdapter' do
|
84
|
+
let(:adapter_class) { Knapsack::Adapters::CucumberAdapter }
|
85
|
+
it_behaves_like 'allocator builder'
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|