knapsack_pro 0.0.1 → 0.1.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 (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,94 @@
1
+ module KnapsackPro
2
+ module Client
3
+ class Connection
4
+ TIMEOUT = 15
5
+
6
+ def initialize(action)
7
+ @action = action
8
+ end
9
+
10
+ def call
11
+ send(action.http_method)
12
+ end
13
+
14
+ def success?
15
+ !!response
16
+ end
17
+
18
+ def errors?
19
+ !!(response && (response['errors'] || response['error']))
20
+ end
21
+
22
+ private
23
+
24
+ attr_reader :action, :response
25
+
26
+ def logger
27
+ KnapsackPro.logger
28
+ end
29
+
30
+ def endpoint
31
+ KnapsackPro::Config::Env.endpoint
32
+ end
33
+
34
+ def endpoint_url
35
+ endpoint + action.endpoint_path
36
+ end
37
+
38
+ def request_hash
39
+ action
40
+ .request_hash
41
+ .merge({
42
+ test_suite_token: test_suite_token
43
+ })
44
+ end
45
+
46
+ def request_body
47
+ request_hash.to_json
48
+ end
49
+
50
+ def test_suite_token
51
+ KnapsackPro::Config::Env.test_suite_token
52
+ end
53
+
54
+ def json_headers
55
+ {
56
+ 'Content-Type' => 'application/json',
57
+ 'Accept' => 'application/json'
58
+ }
59
+ end
60
+
61
+ def parse_response(body)
62
+ return '' if body == '' || body.nil?
63
+ JSON.parse(body)
64
+ rescue JSON::ParserError
65
+ nil
66
+ end
67
+
68
+ def post
69
+ uri = URI.parse(endpoint_url)
70
+ http = Net::HTTP.new(uri.host, uri.port)
71
+ http.use_ssl = (uri.scheme == 'https')
72
+ http.open_timeout = TIMEOUT
73
+ http.read_timeout = TIMEOUT
74
+
75
+ http_response = http.post(uri.path, request_body, json_headers)
76
+ @response = parse_response(http_response.body)
77
+
78
+ request_uuid = http_response.header['X-Request-Id']
79
+
80
+ logger.info("API request UUID: #{request_uuid}")
81
+ logger.info('API response:')
82
+ if errors?
83
+ logger.error(response)
84
+ else
85
+ logger.info(response)
86
+ end
87
+
88
+ response
89
+ rescue Errno::ECONNREFUSED, EOFError, Net::OpenTimeout, Net::ReadTimeout => e
90
+ logger.warn(e.inspect)
91
+ end
92
+ end
93
+ end
94
+ end
@@ -0,0 +1,22 @@
1
+ module KnapsackPro
2
+ module Config
3
+ module CI
4
+ class Base
5
+ def node_total
6
+ end
7
+
8
+ def node_index
9
+ end
10
+
11
+ def commit_hash
12
+ end
13
+
14
+ def branch
15
+ end
16
+
17
+ def project_dir
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,27 @@
1
+ module KnapsackPro
2
+ module Config
3
+ module CI
4
+ class Buildkite < Base
5
+ def node_total
6
+ ENV['BUILDKITE_PARALLEL_JOB_COUNT']
7
+ end
8
+
9
+ def node_index
10
+ ENV['BUILDKITE_PARALLEL_JOB']
11
+ end
12
+
13
+ def commit_hash
14
+ ENV['BUILDKITE_COMMIT']
15
+ end
16
+
17
+ def branch
18
+ ENV['BUILDKITE_BRANCH']
19
+ end
20
+
21
+ def project_dir
22
+ ENV['BUILDKITE_BUILD_CHECKOUT_PATH']
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,29 @@
1
+ module KnapsackPro
2
+ module Config
3
+ module CI
4
+ class Circle < Base
5
+ def node_total
6
+ ENV['CIRCLE_NODE_TOTAL']
7
+ end
8
+
9
+ def node_index
10
+ ENV['CIRCLE_NODE_INDEX']
11
+ end
12
+
13
+ def commit_hash
14
+ ENV['CIRCLE_SHA1']
15
+ end
16
+
17
+ def branch
18
+ ENV['CIRCLE_BRANCH']
19
+ end
20
+
21
+ def project_dir
22
+ project_repo_name = ENV['CIRCLE_PROJECT_REPONAME']
23
+ "/home/ubuntu/#{project_repo_name}" if project_repo_name
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
29
+
@@ -0,0 +1,28 @@
1
+ module KnapsackPro
2
+ module Config
3
+ module CI
4
+ class Semaphore < Base
5
+ def node_total
6
+ ENV['SEMAPHORE_THREAD_COUNT']
7
+ end
8
+
9
+ def node_index
10
+ index = ENV['SEMAPHORE_CURRENT_THREAD']
11
+ index.to_i - 1 if index
12
+ end
13
+
14
+ def commit_hash
15
+ ENV['REVISION']
16
+ end
17
+
18
+ def branch
19
+ ENV['BRANCH_NAME']
20
+ end
21
+
22
+ def project_dir
23
+ ENV['SEMAPHORE_PROJECT_DIR']
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,111 @@
1
+ module KnapsackPro
2
+ module Config
3
+ class Env
4
+ class << self
5
+ def ci_node_total
6
+ (ENV['KNAPSACK_PRO_CI_NODE_TOTAL'] ||
7
+ ci_env_for(:node_total) ||
8
+ 1).to_i
9
+ end
10
+
11
+ def ci_node_index
12
+ (ENV['KNAPSACK_PRO_CI_NODE_INDEX'] ||
13
+ ci_env_for(:node_index) ||
14
+ 0).to_i
15
+ end
16
+
17
+ def commit_hash
18
+ ENV['KNAPSACK_PRO_COMMIT_HASH'] ||
19
+ ci_env_for(:commit_hash)
20
+ end
21
+
22
+ def branch
23
+ ENV['KNAPSACK_PRO_BRANCH'] ||
24
+ ci_env_for(:branch)
25
+ end
26
+
27
+ def project_dir
28
+ ENV['KNAPSACK_PRO_PROJECT_DIR'] ||
29
+ ci_env_for(:project_dir)
30
+ end
31
+
32
+ def test_file_pattern
33
+ ENV['KNAPSACK_PRO_TEST_FILE_PATTERN']
34
+ end
35
+
36
+ def repository_adapter
37
+ ENV['KNAPSACK_PRO_REPOSITORY_ADAPTER']
38
+ end
39
+
40
+ def recording_enabled
41
+ ENV['KNAPSACK_PRO_RECORDING_ENABLED']
42
+ end
43
+
44
+ def recording_enabled?
45
+ recording_enabled == 'true'
46
+ end
47
+
48
+ def endpoint
49
+ env_name = 'KNAPSACK_PRO_ENDPOINT'
50
+ return ENV[env_name] if ENV[env_name]
51
+
52
+ case mode
53
+ when :development
54
+ 'http://api.knapsackpro.dev:3000'
55
+ when :test
56
+ 'http://api-staging.knapsackpro.com'
57
+ when :production
58
+ 'http://api.knapsackpro.com'
59
+ else
60
+ required_env(env_name)
61
+ end
62
+ end
63
+
64
+ def test_suite_token
65
+ required_env('KNAPSACK_PRO_TEST_SUITE_TOKEN')
66
+ end
67
+
68
+ def test_suite_token_rspec
69
+ ENV['KNAPSACK_PRO_TEST_SUITE_TOKEN_RSPEC']
70
+ end
71
+
72
+ def test_suite_token_minitest
73
+ ENV['KNAPSACK_PRO_TEST_SUITE_TOKEN_MINITEST']
74
+ end
75
+
76
+ def test_suite_token_cucumber
77
+ ENV['KNAPSACK_PRO_TEST_SUITE_TOKEN_CUCUMBER']
78
+ end
79
+
80
+ def mode
81
+ mode = ENV['KNAPSACK_PRO_MODE']
82
+ return :production if mode.nil?
83
+ mode = mode.to_sym
84
+ if [:development, :test, :production].include?(mode)
85
+ mode
86
+ else
87
+ raise ArgumentError.new('Wrong mode name')
88
+ end
89
+ end
90
+
91
+ def ci_env_for(env_name)
92
+ value = nil
93
+ ci_list = KnapsackPro::Config::CI.constants - [:Base]
94
+ ci_list.each do |ci_name|
95
+ ci_class = Object.const_get("KnapsackPro::Config::CI::#{ci_name}")
96
+ ci = ci_class.new
97
+ value = ci.send(env_name)
98
+ break unless value.nil?
99
+ end
100
+ value
101
+ end
102
+
103
+ private
104
+
105
+ def required_env(env_name)
106
+ ENV[env_name] || raise("Missing environment variable #{env_name}")
107
+ end
108
+ end
109
+ end
110
+ end
111
+ end
@@ -0,0 +1,15 @@
1
+ module KnapsackPro
2
+ class LoggerWrapper
3
+ def initialize(logger)
4
+ @logger = ::ActiveSupport::TaggedLogging.new(logger)
5
+ end
6
+
7
+ private
8
+
9
+ attr_reader :logger
10
+
11
+ def method_missing(m, *args, &block)
12
+ logger.tagged('knapsack_pro') { logger.send(m, *args, &block) }
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,25 @@
1
+ module KnapsackPro
2
+ class Presenter
3
+ class << self
4
+ def global_time
5
+ global_time = pretty_seconds(KnapsackPro.tracker.global_time)
6
+ "Global time execution for tests: #{global_time}"
7
+ end
8
+
9
+ def pretty_seconds(seconds)
10
+ sign = ''
11
+
12
+ if seconds < 0
13
+ seconds = seconds*-1
14
+ sign = '-'
15
+ end
16
+
17
+ return "#{sign}#{seconds}s" if seconds.abs < 1
18
+
19
+ time = Time.at(seconds).gmtime.strftime('%Hh %Mm %Ss')
20
+ time_without_zeros = time.gsub(/00(h|m|s)/, '').strip
21
+ sign + time_without_zeros
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,20 @@
1
+ module KnapsackPro
2
+ class Report
3
+ def self.save
4
+ repository_adapter = KnapsackPro::RepositoryAdapterInitiator.call
5
+ action = KnapsackPro::Client::API::V1::BuildSubsets.create(
6
+ commit_hash: repository_adapter.commit_hash,
7
+ branch: repository_adapter.branch,
8
+ node_total: KnapsackPro::Config::Env.ci_node_total,
9
+ node_index: KnapsackPro::Config::Env.ci_node_index,
10
+ test_files: KnapsackPro.tracker.to_a,
11
+ )
12
+ connection = KnapsackPro::Client::Connection.new(action)
13
+ response = connection.call
14
+ if connection.success?
15
+ raise ArgumentError.new(response) if connection.errors?
16
+ KnapsackPro.logger.info('Saved time execution report on API server.')
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,12 @@
1
+ module KnapsackPro
2
+ class RepositoryAdapterInitiator
3
+ def self.call
4
+ case KnapsackPro::Config::Env.repository_adapter
5
+ when 'git'
6
+ KnapsackPro::RepositoryAdapters::GitAdapter.new
7
+ else
8
+ KnapsackPro::RepositoryAdapters::EnvAdapter.new
9
+ end
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,14 @@
1
+ module KnapsackPro
2
+ module RepositoryAdapters
3
+ class BaseAdapter
4
+ def commit_hash
5
+ raise NotImplementedError
6
+ end
7
+
8
+ def branch
9
+ raise NotImplementedError
10
+ end
11
+ end
12
+ end
13
+ end
14
+
@@ -0,0 +1,13 @@
1
+ module KnapsackPro
2
+ module RepositoryAdapters
3
+ class EnvAdapter < BaseAdapter
4
+ def commit_hash
5
+ KnapsackPro::Config::Env.commit_hash
6
+ end
7
+
8
+ def branch
9
+ KnapsackPro::Config::Env.branch
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,19 @@
1
+ module KnapsackPro
2
+ module RepositoryAdapters
3
+ class GitAdapter < BaseAdapter
4
+ def commit_hash
5
+ `git -C "#{working_dir}" rev-parse HEAD`.strip
6
+ end
7
+
8
+ def branch
9
+ `git -C "#{working_dir}" rev-parse --abbrev-ref HEAD`.strip
10
+ end
11
+
12
+ private
13
+
14
+ def working_dir
15
+ KnapsackPro::Config::Env.project_dir
16
+ end
17
+ end
18
+ end
19
+ end