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
@@ -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 '../..', __FILE__
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
- protected
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.spec_path = RspecAdapter.spec_path(current_example_group)
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.spec_path(example_group)
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
@@ -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 report_node_specs
10
- @report_node_specs ||= @report_distributor.specs_for_current_node
8
+ def report_node_tests
9
+ @report_node_tests ||= @report_distributor.tests_for_current_node
11
10
  end
12
11
 
13
- def leftover_node_specs
14
- @leftover_node_specs ||= @leftover_distributor.specs_for_current_node
12
+ def leftover_node_tests
13
+ @leftover_node_tests ||= @leftover_distributor.tests_for_current_node
15
14
  end
16
15
 
17
- def node_specs
18
- @node_specs ||= report_node_specs + leftover_node_specs
16
+ def node_tests
17
+ @node_tests ||= report_node_tests + leftover_node_tests
19
18
  end
20
19
 
21
- def stringify_node_specs
22
- node_specs.join(' ')
20
+ def stringify_node_tests
21
+ node_tests.join(' ')
23
22
  end
24
23
 
25
- def spec_dir
26
- @spec_pattern.gsub(/^(.*?)\//).first
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, :node_specs, :spec_pattern
4
+ attr_reader :report, :node_tests, :test_file_pattern
5
5
 
6
6
  def initialize(args={})
7
- @report = args[:report] || default_report
8
- @ci_node_total = args[:ci_node_total] || config.ci_node_total
9
- @ci_node_index = args[:ci_node_index] || config.ci_node_index
10
- @spec_pattern = args[:spec_pattern] || config.spec_pattern
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 specs_for_current_node
22
- specs_for_node(ci_node_index)
21
+ def tests_for_current_node
22
+ tests_for_node(ci_node_index)
23
23
  end
24
24
 
25
- def specs_for_node(node_index)
26
- assign_spec_files_to_node
27
- post_specs_for_node(node_index)
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 assign_spec_files_to_node
31
- default_node_specs
32
- post_assign_spec_files_to_node
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 all_specs
36
- @all_specs ||= Dir[spec_pattern]
35
+ def all_tests
36
+ @all_tests ||= Dir[test_file_pattern]
37
37
  end
38
38
 
39
39
  protected
40
40
 
41
- def post_specs_for_node(node_index)
41
+ def post_tests_for_node(node_index)
42
42
  raise NotImplementedError
43
43
  end
44
44
 
45
- def post_assign_spec_files_to_node
45
+ def post_assign_test_files_to_node
46
46
  raise NotImplementedError
47
47
  end
48
48
 
49
- def default_node_specs
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 report_specs
5
- @report_specs ||= report.keys
4
+ def report_tests
5
+ @report_tests ||= report.keys
6
6
  end
7
7
 
8
- def leftover_specs
9
- @leftover_specs ||= all_specs - report_specs
8
+ def leftover_tests
9
+ @leftover_tests ||= all_tests - report_tests
10
10
  end
11
11
 
12
12
  private
13
13
 
14
- def post_assign_spec_files_to_node
14
+ def post_assign_test_files_to_node
15
15
  node_index = 0
16
- leftover_specs.each do |spec_file|
17
- node_specs[node_index] << spec_file
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 post_specs_for_node(node_index)
24
- spec_files = node_specs[node_index]
25
- return unless spec_files
26
- spec_files
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 default_node_specs
30
- @node_specs = []
29
+ def default_node_tests
30
+ @node_tests = []
31
31
  ci_node_total.times do |index|
32
- @node_specs[index] = []
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 { |spec_path, time| time }.reverse
5
+ @sorted_report ||= report.sort_by { |test_path, time| time }.reverse
6
6
  end
7
7
 
8
- def sorted_report_with_existing_specs
9
- @sorted_report_with_existing_specs ||= sorted_report.select { |spec_path, time| all_specs.include?(spec_path) }
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 ||= sorted_report_with_existing_specs.map(&:last).reduce(0, :+).to_f
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 post_assign_spec_files_to_node
23
- assign_slow_spec_files
24
- assign_remaining_spec_files
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 post_specs_for_node(node_index)
28
- node_spec = node_specs[node_index]
29
- return unless node_spec
30
- node_spec[:spec_files_with_time].map(&:first)
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 default_node_specs
34
- @node_specs = []
33
+ def default_node_tests
34
+ @node_tests = []
35
35
  ci_node_total.times do |index|
36
- @node_specs << {
36
+ @node_tests << {
37
37
  node_index: index,
38
38
  time_left: node_time_execution,
39
- spec_files_with_time: []
39
+ test_files_with_time: []
40
40
  }
41
41
  end
42
42
  end
43
43
 
44
- def assign_slow_spec_files
45
- @not_assigned_spec_files = []
44
+ def assign_slow_test_files
45
+ @not_assigned_test_files = []
46
46
  node_index = 0
47
- sorted_report_with_existing_specs.each do |spec_file_with_time|
48
- assign_slow_spec_file(node_index, spec_file_with_time)
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 assign_slow_spec_file(node_index, spec_file_with_time)
55
- time = spec_file_with_time[1]
56
- time_left = node_specs[node_index][:time_left] - time
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 node_specs[node_index][:spec_files_with_time].empty?
59
- node_specs[node_index][:time_left] -= time
60
- node_specs[node_index][:spec_files_with_time] << spec_file_with_time
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
- @not_assigned_spec_files << spec_file_with_time
62
+ @not_assigned_test_files << test_file_with_time
63
63
  end
64
64
  end
65
65
 
66
- def assign_remaining_spec_files
67
- @not_assigned_spec_files.each do |spec_file_with_time|
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 = spec_file_with_time[1]
70
- node_specs[index][:time_left] -= time
71
- node_specs[index][:spec_files_with_time] << spec_file_with_time
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
- node_spec = node_specs.max { |a,b| a[:time_left] <=> b[:time_left] }
77
- node_spec[:node_index]
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