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.
Files changed (46) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +16 -16
  3. data/CHANGELOG.md +6 -0
  4. data/README.md +162 -61
  5. data/TODO.md +1 -1
  6. data/bin/print_header.sh +5 -0
  7. data/knapsack.gemspec +4 -2
  8. data/{knapsack_report.json → knapsack_rspec_report.json} +0 -0
  9. data/lib/knapsack.rb +5 -2
  10. data/lib/knapsack/adapters/base_adapter.rb +17 -1
  11. data/lib/knapsack/adapters/cucumber_adapter.rb +40 -0
  12. data/lib/knapsack/adapters/rspec_adapter.rb +5 -2
  13. data/lib/knapsack/allocator.rb +10 -11
  14. data/lib/knapsack/allocator_builder.rb +33 -0
  15. data/lib/knapsack/config/env.rb +30 -0
  16. data/lib/knapsack/config/tracker.rb +19 -0
  17. data/lib/knapsack/distributors/base_distributor.rb +18 -28
  18. data/lib/knapsack/distributors/leftover_distributor.rb +14 -14
  19. data/lib/knapsack/distributors/report_distributor.rb +33 -33
  20. data/lib/knapsack/presenter.rb +4 -4
  21. data/lib/knapsack/report.rb +13 -11
  22. data/lib/knapsack/tracker.rb +20 -15
  23. data/lib/knapsack/version.rb +1 -1
  24. data/lib/tasks/knapsack_cucumber.rake +19 -0
  25. data/lib/tasks/knapsack_rspec.rake +19 -0
  26. data/spec/knapsack/adapters/cucumber_adapter_spec.rb +69 -0
  27. data/spec/knapsack/adapters/rspec_adapter_spec.rb +26 -18
  28. data/spec/knapsack/allocator_builder_spec.rb +88 -0
  29. data/spec/knapsack/allocator_spec.rb +26 -27
  30. data/spec/knapsack/{config_spec.rb → config/env_spec.rb} +9 -32
  31. data/spec/knapsack/config/tracker_spec.rb +24 -0
  32. data/spec/knapsack/distributors/base_distributor_spec.rb +26 -39
  33. data/spec/knapsack/distributors/leftover_distributor_spec.rb +49 -31
  34. data/spec/knapsack/distributors/report_distributor_spec.rb +35 -41
  35. data/spec/knapsack/presenter_spec.rb +5 -5
  36. data/spec/knapsack/report_spec.rb +5 -9
  37. data/spec/knapsack/task_loader_spec.rb +4 -2
  38. data/spec/knapsack/tracker_spec.rb +14 -14
  39. data/spec/support/mocks/cucumber.rb +12 -0
  40. data/spec/support/shared_examples/adapter.rb +0 -4
  41. data/spec_examples/leftover/1_spec.rb +1 -1
  42. data/spec_examples/leftover/a_spec.rb +1 -1
  43. data/spec_examples/spec_helper.rb +1 -1
  44. metadata +54 -11
  45. data/lib/knapsack/config.rb +0 -40
  46. data/lib/tasks/knapsack.rake +0 -20
@@ -5,11 +5,11 @@ module Knapsack
5
5
  class Presenter
6
6
  class << self
7
7
  def report_yml
8
- Knapsack.tracker.spec_files_with_time.to_yaml
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.spec_files_with_time)
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 specs: #{global_time}"
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
- Specs on this CI node took more than time offset.
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.}
@@ -2,19 +2,27 @@ module Knapsack
2
2
  class Report
3
3
  include Singleton
4
4
 
5
- def config(opts={})
6
- @config ||= default_config
7
- @config.merge!(opts)
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(config[:report_path], 'w+') do |f|
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(config[:report_path])
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
@@ -2,8 +2,8 @@ module Knapsack
2
2
  class Tracker
3
3
  include Singleton
4
4
 
5
- attr_reader :global_time, :spec_files_with_time
6
- attr_writer :spec_path
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
- update_spec_file_time
28
+ update_test_file_time
29
29
  @execution_time
30
30
  end
31
31
 
32
- def spec_path
33
- raise("spec_path needs to be set by Knapsack Adapter's bind method") unless @spec_path
34
- @spec_path.sub(/^\.\//, '')
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
- @spec_files_with_time = {}
62
- @spec_path = nil
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 update_spec_file_time
70
- @spec_files_with_time[spec_path] ||= 0
71
- @spec_files_with_time[spec_path] += @execution_time
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
@@ -1,3 +1,3 @@
1
1
  module Knapsack
2
- VERSION = '0.5.0'
2
+ VERSION = '1.0.0'
3
3
  end
@@ -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
- it_behaves_like 'adapter'
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(:spec_path) { 'spec/a_spec.rb' }
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(described_class).to receive(:spec_path).and_return(spec_path)
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(:spec_path=).with(spec_path)
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 '.spec_path' do
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.spec_path(current_example_metadata) }
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