knapsack_pro 0.0.1 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (100) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +3 -3
  3. data/CHANGELOG.md +4 -0
  4. data/README.md +368 -4
  5. data/bin/knapsack_pro +20 -0
  6. data/circle.yml +2 -0
  7. data/knapsack_pro.gemspec +6 -1
  8. data/lib/knapsack_pro.rb +74 -0
  9. data/lib/knapsack_pro/adapters/base_adapter.rb +30 -0
  10. data/lib/knapsack_pro/adapters/cucumber_adapter.rb +40 -0
  11. data/lib/knapsack_pro/adapters/minitest_adapter.rb +52 -0
  12. data/lib/knapsack_pro/adapters/rspec_adapter.rb +48 -0
  13. data/lib/knapsack_pro/allocator.rb +40 -0
  14. data/lib/knapsack_pro/allocator_builder.rb +40 -0
  15. data/lib/knapsack_pro/client/api/action.rb +17 -0
  16. data/lib/knapsack_pro/client/api/v1/base.rb +15 -0
  17. data/lib/knapsack_pro/client/api/v1/build_distributions.rb +29 -0
  18. data/lib/knapsack_pro/client/api/v1/build_subsets.rb +29 -0
  19. data/lib/knapsack_pro/client/connection.rb +94 -0
  20. data/lib/knapsack_pro/config/ci/base.rb +22 -0
  21. data/lib/knapsack_pro/config/ci/buildkite.rb +27 -0
  22. data/lib/knapsack_pro/config/ci/circle.rb +29 -0
  23. data/lib/knapsack_pro/config/ci/semaphore.rb +28 -0
  24. data/lib/knapsack_pro/config/env.rb +111 -0
  25. data/lib/knapsack_pro/logger_wrapper.rb +15 -0
  26. data/lib/knapsack_pro/presenter.rb +25 -0
  27. data/lib/knapsack_pro/report.rb +20 -0
  28. data/lib/knapsack_pro/repository_adapter_initiator.rb +12 -0
  29. data/lib/knapsack_pro/repository_adapters/base_adapter.rb +14 -0
  30. data/lib/knapsack_pro/repository_adapters/env_adapter.rb +13 -0
  31. data/lib/knapsack_pro/repository_adapters/git_adapter.rb +19 -0
  32. data/lib/knapsack_pro/runners/base_runner.rb +31 -0
  33. data/lib/knapsack_pro/runners/cucumber_runner.rb +16 -0
  34. data/lib/knapsack_pro/runners/minitest_runner.rb +26 -0
  35. data/lib/knapsack_pro/runners/rspec_runner.rb +16 -0
  36. data/lib/knapsack_pro/task_loader.rb +11 -0
  37. data/lib/knapsack_pro/test_file_cleaner.rb +7 -0
  38. data/lib/knapsack_pro/test_file_finder.rb +33 -0
  39. data/lib/knapsack_pro/test_file_pattern.rb +7 -0
  40. data/lib/knapsack_pro/test_file_presenter.rb +11 -0
  41. data/lib/knapsack_pro/test_flat_distributor.rb +84 -0
  42. data/lib/knapsack_pro/tracker.rb +64 -0
  43. data/lib/knapsack_pro/version.rb +1 -1
  44. data/lib/tasks/cucumber.rake +7 -0
  45. data/lib/tasks/minitest.rake +7 -0
  46. data/lib/tasks/rspec.rake +7 -0
  47. data/spec/fixtures/vcr_cassettes/api/v1/build_distributions/subset/invalid_test_suite_token.yml +50 -0
  48. data/spec/fixtures/vcr_cassettes/api/v1/build_distributions/subset/success.yml +52 -0
  49. data/spec/fixtures/vcr_cassettes/api/v1/build_subsets/create/invalid_test_suite_token.yml +50 -0
  50. data/spec/fixtures/vcr_cassettes/api/v1/build_subsets/create/success.yml +50 -0
  51. data/spec/integration/api/build_distributions_subset_spec.rb +74 -0
  52. data/spec/integration/api/build_subsets_create_spec.rb +76 -0
  53. data/spec/knapsack_pro/adapters/base_adapter_spec.rb +63 -0
  54. data/spec/knapsack_pro/adapters/cucumber_adapter_spec.rb +71 -0
  55. data/spec/knapsack_pro/adapters/minitest_adapter_spec.rb +107 -0
  56. data/spec/knapsack_pro/adapters/rspec_adapter_spec.rb +94 -0
  57. data/spec/knapsack_pro/allocator_builder_spec.rb +45 -0
  58. data/spec/knapsack_pro/allocator_spec.rb +80 -0
  59. data/spec/knapsack_pro/client/api/action_spec.rb +17 -0
  60. data/spec/knapsack_pro/client/api/v1/base_spec.rb +2 -0
  61. data/spec/knapsack_pro/client/api/v1/build_distributions_spec.rb +35 -0
  62. data/spec/knapsack_pro/client/api/v1/build_subsets_spec.rb +35 -0
  63. data/spec/knapsack_pro/client/connection_spec.rb +138 -0
  64. data/spec/knapsack_pro/config/ci/base_spec.rb +7 -0
  65. data/spec/knapsack_pro/config/ci/buildkite_spec.rb +74 -0
  66. data/spec/knapsack_pro/config/ci/circle_spec.rb +74 -0
  67. data/spec/knapsack_pro/config/ci/semaphore_spec.rb +74 -0
  68. data/spec/knapsack_pro/config/env_spec.rb +368 -0
  69. data/spec/knapsack_pro/logger_wrapper_spec.rb +15 -0
  70. data/spec/knapsack_pro/presenter_spec.rb +57 -0
  71. data/spec/knapsack_pro/report_spec.rb +68 -0
  72. data/spec/knapsack_pro/repository_adapter_initiator_spec.rb +21 -0
  73. data/spec/knapsack_pro/repository_adapters/base_adapter_spec.rb +13 -0
  74. data/spec/knapsack_pro/repository_adapters/env_adapter_spec.rb +27 -0
  75. data/spec/knapsack_pro/repository_adapters/git_adapter_spec.rb +27 -0
  76. data/spec/knapsack_pro/runners/base_runner_spec.rb +61 -0
  77. data/spec/knapsack_pro/runners/cucumber_runner_spec.rb +47 -0
  78. data/spec/knapsack_pro/runners/minitest_runner_spec.rb +34 -0
  79. data/spec/knapsack_pro/runners/rspec_runner_spec.rb +49 -0
  80. data/spec/knapsack_pro/task_loader_spec.rb +14 -0
  81. data/spec/knapsack_pro/test_file_cleaner_spec.rb +11 -0
  82. data/spec/knapsack_pro/test_file_finder_spec.rb +35 -0
  83. data/spec/knapsack_pro/test_file_pattern_spec.rb +23 -0
  84. data/spec/knapsack_pro/test_file_presenter_spec.rb +22 -0
  85. data/spec/knapsack_pro/test_flat_distributor_spec.rb +60 -0
  86. data/spec/knapsack_pro/tracker_spec.rb +102 -0
  87. data/spec/knapsack_pro_spec.rb +56 -0
  88. data/spec/spec_helper.rb +11 -1
  89. data/spec/support/fakes/cucumber.rb +12 -0
  90. data/spec/support/fakes/minitest.rb +12 -0
  91. data/spec/support/shared_examples/adapter.rb +17 -0
  92. data/spec_fake/controllers/users_controller_spec.rb +0 -0
  93. data/spec_fake/models/admin_spec.rb +0 -0
  94. data/spec_fake/models/user_spec.rb +0 -0
  95. data/spec_fake/spec_helper.rb +0 -0
  96. data/test_fake/a_test.rb +0 -0
  97. data/test_fake/b_test.rb +0 -0
  98. metadata +212 -12
  99. data/Gemfile.lock +0 -69
  100. data/spec/knapsack_spec.rb +0 -3
@@ -0,0 +1,31 @@
1
+ module KnapsackPro
2
+ module Runners
3
+ class BaseRunner
4
+ def self.run(args)
5
+ raise NotImplementedError
6
+ end
7
+
8
+ def initialize(adapter_class)
9
+ @allocator_builder = KnapsackPro::AllocatorBuilder.new(adapter_class)
10
+ @allocator = allocator_builder.allocator
11
+ end
12
+
13
+ def test_file_paths
14
+ allocator.test_file_paths
15
+ end
16
+
17
+ def stringify_test_file_paths
18
+ KnapsackPro::TestFilePresenter.stringify_paths(test_file_paths)
19
+ end
20
+
21
+ def test_dir
22
+ allocator_builder.test_dir
23
+ end
24
+
25
+ private
26
+
27
+ attr_reader :allocator_builder,
28
+ :allocator
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,16 @@
1
+ module KnapsackPro
2
+ module Runners
3
+ class CucumberRunner < BaseRunner
4
+ def self.run(args)
5
+ ENV['KNAPSACK_PRO_TEST_SUITE_TOKEN'] = KnapsackPro::Config::Env.test_suite_token_cucumber
6
+
7
+ runner = new(KnapsackPro::Adapters::CucumberAdapter)
8
+
9
+ cmd = %Q[KNAPSACK_PRO_RECORDING_ENABLED=true KNAPSACK_PRO_TEST_SUITE_TOKEN=#{ENV['KNAPSACK_PRO_TEST_SUITE_TOKEN']} bundle exec cucumber #{args} -- #{runner.stringify_test_file_paths}]
10
+
11
+ Kernel.system(cmd)
12
+ Kernel.exit($?.exitstatus) unless $?.exitstatus.zero?
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,26 @@
1
+ module KnapsackPro
2
+ module Runners
3
+ class MinitestRunner < BaseRunner
4
+ def self.run(args)
5
+ ENV['KNAPSACK_PRO_TEST_SUITE_TOKEN'] = KnapsackPro::Config::Env.test_suite_token_minitest
6
+ ENV['KNAPSACK_PRO_RECORDING_ENABLED'] = 'true'
7
+
8
+ runner = new(KnapsackPro::Adapters::MinitestAdapter)
9
+
10
+ task_name = 'knapsack_pro:minitest_run'
11
+
12
+ if Rake::Task.task_defined?(task_name)
13
+ Rake::Task[task_name].clear
14
+ end
15
+
16
+ Rake::TestTask.new(task_name) do |t|
17
+ t.libs << runner.test_dir
18
+ t.test_files = runner.test_file_paths
19
+ t.options = args
20
+ end
21
+
22
+ Rake::Task[task_name].invoke
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,16 @@
1
+ module KnapsackPro
2
+ module Runners
3
+ class RSpecRunner < BaseRunner
4
+ def self.run(args)
5
+ ENV['KNAPSACK_PRO_TEST_SUITE_TOKEN'] = KnapsackPro::Config::Env.test_suite_token_rspec
6
+
7
+ runner = new(KnapsackPro::Adapters::RSpecAdapter)
8
+
9
+ cmd = %Q[KNAPSACK_PRO_RECORDING_ENABLED=true KNAPSACK_PRO_TEST_SUITE_TOKEN=#{ENV['KNAPSACK_PRO_TEST_SUITE_TOKEN']} bundle exec rspec #{args} --default-path #{runner.test_dir} -- #{runner.stringify_test_file_paths}]
10
+
11
+ Kernel.system(cmd)
12
+ Kernel.exit($?.exitstatus) unless $?.exitstatus.zero?
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,11 @@
1
+ require 'rake'
2
+
3
+ module KnapsackPro
4
+ class TaskLoader
5
+ include ::Rake::DSL
6
+
7
+ def load_tasks
8
+ Dir.glob("#{KnapsackPro.root}/lib/tasks/*.rake").each { |r| import r }
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,7 @@
1
+ module KnapsackPro
2
+ class TestFileCleaner
3
+ def self.clean(test_file_path)
4
+ test_file_path.sub(/^\.\//, '')
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,33 @@
1
+ module KnapsackPro
2
+ class TestFileFinder
3
+ def self.call(test_file_pattern)
4
+ new(test_file_pattern).call
5
+ end
6
+
7
+ def initialize(test_file_pattern)
8
+ @test_file_pattern = test_file_pattern
9
+ end
10
+
11
+ def call
12
+ test_file_hashes = []
13
+ test_files.each do |test_file_path|
14
+ test_file_hashes << test_file_hash_for(test_file_path)
15
+ end
16
+ test_file_hashes
17
+ end
18
+
19
+ private
20
+
21
+ attr_reader :test_file_pattern
22
+
23
+ def test_files
24
+ Dir.glob(test_file_pattern).sort
25
+ end
26
+
27
+ def test_file_hash_for(test_file_path)
28
+ {
29
+ 'path' => TestFileCleaner.clean(test_file_path)
30
+ }
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,7 @@
1
+ module KnapsackPro
2
+ class TestFilePattern
3
+ def self.call(adapter_class)
4
+ KnapsackPro::Config::Env.test_file_pattern || adapter_class::TEST_DIR_PATTERN
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,11 @@
1
+ module KnapsackPro
2
+ class TestFilePresenter
3
+ def self.stringify_paths(test_file_paths)
4
+ test_file_paths.join(' ')
5
+ end
6
+
7
+ def self.paths(test_files)
8
+ test_files.map { |t| t['path'] }
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,84 @@
1
+ module KnapsackPro
2
+ class TestFlatDistributor
3
+ DIR_TYPES = [
4
+ :feature,
5
+ :integration,
6
+ :e2e,
7
+ :models,
8
+ :views,
9
+ :controllers,
10
+ ]
11
+
12
+ def initialize(test_files, node_total)
13
+ @test_files = test_files
14
+ @node_total = node_total
15
+ set_default_nodes_hash
16
+ set_grouped_test_files
17
+ end
18
+
19
+ def nodes
20
+ group_test_files_by_directory
21
+ generate_nodes
22
+ end
23
+
24
+ def test_files_for_node(node_index)
25
+ nodes[node_index]
26
+ end
27
+
28
+ private
29
+
30
+ attr_reader :test_files,
31
+ :node_total,
32
+ :nodes_hash,
33
+ :grouped_test_files
34
+
35
+ def set_default_nodes_hash
36
+ @nodes_hash = {}
37
+ node_total.times do |index|
38
+ nodes_hash[index] = []
39
+ end
40
+ end
41
+
42
+ def set_grouped_test_files
43
+ @grouped_test_files = {}
44
+ DIR_TYPES.each do |type|
45
+ grouped_test_files[type] = []
46
+ end
47
+ grouped_test_files[:other] = []
48
+ end
49
+
50
+ def sorted_test_files
51
+ test_files.sort_by { |t| t['path'] }
52
+ end
53
+
54
+ def group_test_files_by_directory
55
+ sorted_test_files.each do |test_file|
56
+ found = false
57
+ DIR_TYPES.each do |type|
58
+ if test_file['path'].match(/#{type}/)
59
+ grouped_test_files[type] << test_file
60
+ found = true
61
+ break
62
+ end
63
+ end
64
+
65
+ unless found
66
+ grouped_test_files[:other] << test_file
67
+ end
68
+ end
69
+ end
70
+
71
+ def generate_nodes
72
+ node_index = 0
73
+ grouped_test_files.each do |_type, test_files|
74
+ test_files.each do |test_file|
75
+ nodes_hash[node_index] << test_file
76
+
77
+ node_index += 1
78
+ node_index %= node_total
79
+ end
80
+ end
81
+ nodes_hash
82
+ end
83
+ end
84
+ end
@@ -0,0 +1,64 @@
1
+ module KnapsackPro
2
+ class Tracker
3
+ include Singleton
4
+
5
+ attr_reader :global_time, :test_files_with_time
6
+ attr_writer :current_test_path
7
+
8
+ def initialize
9
+ set_defaults
10
+ end
11
+
12
+ def reset!
13
+ set_defaults
14
+ end
15
+
16
+ def start_timer
17
+ @start_time = now_without_mock_time.to_f
18
+ end
19
+
20
+ def stop_timer
21
+ @execution_time = now_without_mock_time.to_f - @start_time
22
+ update_global_time
23
+ update_test_file_time
24
+ @execution_time
25
+ end
26
+
27
+ def current_test_path
28
+ raise("current_test_path needs to be set by Knapsack Pro Adapter's bind method") unless @current_test_path
29
+ @current_test_path.sub(/^\.\//, '')
30
+ end
31
+
32
+ def to_a
33
+ test_files = []
34
+ @test_files_with_time.each do |path, time_execution|
35
+ test_files << {
36
+ path: path,
37
+ time_execution: time_execution
38
+ }
39
+ end
40
+ test_files
41
+ end
42
+
43
+ private
44
+
45
+ def set_defaults
46
+ @global_time = 0
47
+ @test_files_with_time = {}
48
+ @test_path = nil
49
+ end
50
+
51
+ def update_global_time
52
+ @global_time += @execution_time
53
+ end
54
+
55
+ def update_test_file_time
56
+ @test_files_with_time[current_test_path] ||= 0
57
+ @test_files_with_time[current_test_path] += @execution_time
58
+ end
59
+
60
+ def now_without_mock_time
61
+ Time.now_without_mock_time
62
+ end
63
+ end
64
+ end
@@ -1,3 +1,3 @@
1
1
  module KnapsackPro
2
- VERSION = '0.0.1'
2
+ VERSION = '0.1.0'
3
3
  end
@@ -0,0 +1,7 @@
1
+ require 'knapsack_pro'
2
+
3
+ namespace :knapsack_pro do
4
+ task :cucumber, [:cucumber_args] do |_, args|
5
+ KnapsackPro::Runners::CucumberRunner.run(args[:cucumber_args])
6
+ end
7
+ end
@@ -0,0 +1,7 @@
1
+ require 'knapsack_pro'
2
+
3
+ namespace :knapsack_pro do
4
+ task :minitest, [:minitest_args] do |_, args|
5
+ KnapsackPro::Runners::MinitestRunner.run(args[:minitest_args])
6
+ end
7
+ end
@@ -0,0 +1,7 @@
1
+ require 'knapsack_pro'
2
+
3
+ namespace :knapsack_pro do
4
+ task :rspec, [:rspec_args] do |_, args|
5
+ KnapsackPro::Runners::RSpecRunner.run(args[:rspec_args])
6
+ end
7
+ end
@@ -0,0 +1,50 @@
1
+ ---
2
+ http_interactions:
3
+ - request:
4
+ method: post
5
+ uri: http://api.knapsackpro.dev:3000/v1/build_distributions/subset
6
+ body:
7
+ encoding: UTF-8
8
+ string: '{"commit_hash":"abcdefg","branch":"master","node_total":"2","node_index":"1","test_files":[{"path":"a_spec.rb"},{"path":"b_spec.rb"}],"test_suite_token":"fake"}'
9
+ headers:
10
+ Content-Type:
11
+ - application/json
12
+ Accept:
13
+ - application/json
14
+ Accept-Encoding:
15
+ - gzip;q=1.0,deflate;q=0.6,identity;q=0.3
16
+ User-Agent:
17
+ - Ruby
18
+ response:
19
+ status:
20
+ code: 403
21
+ message: 'Forbidden '
22
+ headers:
23
+ X-Frame-Options:
24
+ - SAMEORIGIN
25
+ X-Xss-Protection:
26
+ - 1; mode=block
27
+ X-Content-Type-Options:
28
+ - nosniff
29
+ Content-Type:
30
+ - application/json; charset=utf-8
31
+ Cache-Control:
32
+ - no-cache
33
+ X-Request-Id:
34
+ - 6d40fe71-7fad-449a-812f-9773429777ce
35
+ X-Runtime:
36
+ - '0.054890'
37
+ Server:
38
+ - WEBrick/1.3.1 (Ruby/2.2.2/2015-04-13)
39
+ Date:
40
+ - Fri, 31 Jul 2015 16:09:00 GMT
41
+ Content-Length:
42
+ - '39'
43
+ Connection:
44
+ - Keep-Alive
45
+ body:
46
+ encoding: UTF-8
47
+ string: '{"errors":["invalid test suite token"]}'
48
+ http_version:
49
+ recorded_at: Fri, 31 Jul 2015 16:09:00 GMT
50
+ recorded_with: VCR 2.9.3
@@ -0,0 +1,52 @@
1
+ ---
2
+ http_interactions:
3
+ - request:
4
+ method: post
5
+ uri: http://api.knapsackpro.dev:3000/v1/build_distributions/subset
6
+ body:
7
+ encoding: UTF-8
8
+ string: '{"commit_hash":"abcdefg","branch":"master","node_total":"2","node_index":"1","test_files":[{"path":"a_spec.rb"},{"path":"b_spec.rb"}],"test_suite_token":"3fa64859337f6e56409d49f865d13fd7"}'
9
+ headers:
10
+ Content-Type:
11
+ - application/json
12
+ Accept:
13
+ - application/json
14
+ Accept-Encoding:
15
+ - gzip;q=1.0,deflate;q=0.6,identity;q=0.3
16
+ User-Agent:
17
+ - Ruby
18
+ response:
19
+ status:
20
+ code: 200
21
+ message: 'OK '
22
+ headers:
23
+ X-Frame-Options:
24
+ - SAMEORIGIN
25
+ X-Xss-Protection:
26
+ - 1; mode=block
27
+ X-Content-Type-Options:
28
+ - nosniff
29
+ Content-Type:
30
+ - application/json; charset=utf-8
31
+ Etag:
32
+ - W/"2348448eb68a360747d21fe1bb304d5b"
33
+ Cache-Control:
34
+ - max-age=0, private, must-revalidate
35
+ X-Request-Id:
36
+ - 66e07e14-0dfd-43f9-b331-2a930141dfef
37
+ X-Runtime:
38
+ - '0.158064'
39
+ Server:
40
+ - WEBrick/1.3.1 (Ruby/2.2.3/2015-08-18)
41
+ Date:
42
+ - Fri, 23 Oct 2015 20:12:23 GMT
43
+ Content-Length:
44
+ - '74'
45
+ Connection:
46
+ - Keep-Alive
47
+ body:
48
+ encoding: UTF-8
49
+ string: '{"node_index":1,"test_files":[{"path":"b_spec.rb","time_execution":null}]}'
50
+ http_version:
51
+ recorded_at: Fri, 23 Oct 2015 20:12:23 GMT
52
+ recorded_with: VCR 2.9.3