knapsack 0.5.0 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
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