knapsack_pro 11.0.1 → 11.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.
- checksums.yaml +4 -4
- data/lib/knapsack_pro/adapters/cucumber_adapter.rb +3 -28
- data/lib/knapsack_pro/adapters/minitest_adapter.rb +25 -20
- data/lib/knapsack_pro/adapters/rspec_adapter.rb +5 -5
- data/lib/knapsack_pro/formatters/cucumber/failures_tracker.rb +43 -0
- data/lib/knapsack_pro/formatters/rspec/time_tracker.rb +211 -0
- data/lib/knapsack_pro/formatters/rspec/time_tracker_fetcher.rb +22 -0
- data/lib/knapsack_pro/pure/queue/rspec_pure.rb +1 -1
- data/lib/knapsack_pro/queue_allocator.rb +14 -15
- data/lib/knapsack_pro/report.rb +21 -10
- data/lib/knapsack_pro/runners/base_runner.rb +1 -1
- data/lib/knapsack_pro/runners/cucumber_runner.rb +12 -1
- data/lib/knapsack_pro/runners/queue/base_runner.rb +2 -2
- data/lib/knapsack_pro/runners/queue/cucumber_runner.rb +16 -4
- data/lib/knapsack_pro/runners/queue/minitest_runner.rb +3 -1
- data/lib/knapsack_pro/runners/queue/rspec_runner.rb +6 -6
- data/lib/knapsack_pro/runners/rspec_runner.rb +2 -2
- data/lib/knapsack_pro/tracker.rb +33 -5
- data/lib/knapsack_pro/version.rb +1 -1
- metadata +6 -5
- data/lib/knapsack_pro/formatters/time_tracker.rb +0 -209
- data/lib/knapsack_pro/formatters/time_tracker_fetcher.rb +0 -20
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 4048ec74df7fa6a88b8f44df5afe764f841c2f8823ae08964e03c6ae87e5639c
|
|
4
|
+
data.tar.gz: 312475082e64ade4db5a8b32160b9ddb294878140149140f1eb6921e08c4d242
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 4c85eb9aa82c714abf8bbbd609725e8f9f05f7c862fd46daf75cb4db4f52ea45e50d90fe731669af1bb592380ed1644441ed73362f90a6f6468a477edbbbf32f
|
|
7
|
+
data.tar.gz: a8cc23ab19ffb5a4c193942e3ae712c3ff28904098a246583389dafc691f95ed61b4c04509aa9dc6b6115d0470a30a09a45c61000c0f3a609e7c3142e4739555
|
|
@@ -6,28 +6,7 @@ module KnapsackPro
|
|
|
6
6
|
TEST_DIR_PATTERN = 'features/**{,/*/**}/*.feature'
|
|
7
7
|
|
|
8
8
|
def self.test_path(object)
|
|
9
|
-
|
|
10
|
-
test_case = object
|
|
11
|
-
test_case.location.file
|
|
12
|
-
else
|
|
13
|
-
if object.respond_to?(:scenario_outline)
|
|
14
|
-
if object.scenario_outline.respond_to?(:feature)
|
|
15
|
-
# Cucumber < 1.3
|
|
16
|
-
object.scenario_outline.feature.file
|
|
17
|
-
else
|
|
18
|
-
# Cucumber >= 1.3
|
|
19
|
-
object.scenario_outline.file
|
|
20
|
-
end
|
|
21
|
-
else
|
|
22
|
-
if object.respond_to?(:feature)
|
|
23
|
-
# Cucumber < 1.3
|
|
24
|
-
object.feature.file
|
|
25
|
-
else
|
|
26
|
-
# Cucumber >= 1.3
|
|
27
|
-
object.file
|
|
28
|
-
end
|
|
29
|
-
end
|
|
30
|
-
end
|
|
9
|
+
object.location.file
|
|
31
10
|
end
|
|
32
11
|
|
|
33
12
|
def bind_time_tracker
|
|
@@ -50,7 +29,7 @@ module KnapsackPro
|
|
|
50
29
|
exit_status = latest_error.status if latest_error.is_a?(SystemExit)
|
|
51
30
|
# saving report makes API call which changes exit status
|
|
52
31
|
# from cucumber so we need to preserve cucumber exit status
|
|
53
|
-
KnapsackPro::Report.save
|
|
32
|
+
KnapsackPro::Report.save(KnapsackPro.tracker.to_a, KnapsackPro.tracker.current_batch_failed_paths)
|
|
54
33
|
::Kernel.exit exit_status if exit_status
|
|
55
34
|
end
|
|
56
35
|
end
|
|
@@ -75,11 +54,7 @@ module KnapsackPro
|
|
|
75
54
|
private
|
|
76
55
|
|
|
77
56
|
def Around(*tag_expressions, &proc)
|
|
78
|
-
|
|
79
|
-
::Cucumber::Glue::Dsl.register_rb_hook('around', tag_expressions, proc)
|
|
80
|
-
else
|
|
81
|
-
::Cucumber::RbSupport::RbDsl.register_rb_hook('around', tag_expressions, proc)
|
|
82
|
-
end
|
|
57
|
+
::Cucumber::Glue::Dsl.register_rb_hook('around', tag_expressions, proc)
|
|
83
58
|
end
|
|
84
59
|
end
|
|
85
60
|
end
|
|
@@ -23,29 +23,33 @@ module KnapsackPro
|
|
|
23
23
|
end
|
|
24
24
|
|
|
25
25
|
module BindTimeTrackerMinitestPlugin
|
|
26
|
-
def
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
26
|
+
def run
|
|
27
|
+
path = KnapsackPro::Adapters::MinitestAdapter.test_path(self)
|
|
28
|
+
started_at = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
|
29
|
+
|
|
30
|
+
result = super
|
|
31
|
+
elapsed = Process.clock_gettime(Process::CLOCK_MONOTONIC) - started_at
|
|
32
|
+
|
|
33
|
+
failed = result.failures.any? { |failure| !failure.is_a?(::Minitest::Skip) }
|
|
34
|
+
KnapsackPro.tracker.stage(path, elapsed, failed: failed)
|
|
31
35
|
|
|
32
|
-
|
|
33
|
-
KnapsackPro.tracker.stop_timer
|
|
34
|
-
super
|
|
36
|
+
result
|
|
35
37
|
end
|
|
36
38
|
end
|
|
37
39
|
|
|
38
40
|
def bind_time_tracker
|
|
39
|
-
::Minitest::Test.send(:
|
|
41
|
+
::Minitest::Test.send(:prepend, BindTimeTrackerMinitestPlugin)
|
|
40
42
|
|
|
41
43
|
add_post_run_callback do
|
|
44
|
+
KnapsackPro.tracker.flush_staged_tests
|
|
42
45
|
KnapsackPro.logger.debug(KnapsackPro::Presenter.global_time)
|
|
43
46
|
end
|
|
44
47
|
end
|
|
45
48
|
|
|
46
49
|
def bind_save_report
|
|
47
50
|
add_post_run_callback do
|
|
48
|
-
KnapsackPro
|
|
51
|
+
KnapsackPro.tracker.flush_staged_tests
|
|
52
|
+
KnapsackPro::Report.save(KnapsackPro.tracker.to_a, KnapsackPro.tracker.current_batch_failed_paths)
|
|
49
53
|
end
|
|
50
54
|
end
|
|
51
55
|
|
|
@@ -55,29 +59,30 @@ module KnapsackPro
|
|
|
55
59
|
end
|
|
56
60
|
|
|
57
61
|
module BindNormalModeMinitestPlugin
|
|
58
|
-
def
|
|
59
|
-
super
|
|
60
|
-
|
|
62
|
+
def run
|
|
61
63
|
unless ENV['KNAPSACK_PRO_BEFORE_QUEUE_HOOK_CALLED']
|
|
62
64
|
KnapsackPro::Hooks::Queue.call_before_queue
|
|
63
65
|
ENV['KNAPSACK_PRO_BEFORE_QUEUE_HOOK_CALLED'] = 'true'
|
|
64
66
|
end
|
|
65
67
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
68
|
+
path = KnapsackPro::Adapters::MinitestAdapter.test_path(self)
|
|
69
|
+
started_at = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
|
70
|
+
|
|
71
|
+
result = super
|
|
72
|
+
elapsed = Process.clock_gettime(Process::CLOCK_MONOTONIC) - started_at
|
|
69
73
|
|
|
70
|
-
|
|
71
|
-
KnapsackPro.tracker.
|
|
74
|
+
failed = result.failures.any? { |failure| !failure.is_a?(::Minitest::Skip) }
|
|
75
|
+
KnapsackPro.tracker.stage(path, elapsed, failed: failed)
|
|
72
76
|
|
|
73
|
-
|
|
77
|
+
result
|
|
74
78
|
end
|
|
75
79
|
end
|
|
76
80
|
|
|
77
81
|
def bind_normal_mode
|
|
78
|
-
::Minitest::Test.send(:
|
|
82
|
+
::Minitest::Test.send(:prepend, BindNormalModeMinitestPlugin)
|
|
79
83
|
|
|
80
84
|
add_post_run_callback do
|
|
85
|
+
KnapsackPro.tracker.flush_staged_tests
|
|
81
86
|
KnapsackPro.logger.debug(KnapsackPro::Presenter.global_time)
|
|
82
87
|
end
|
|
83
88
|
end
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require_relative '../formatters/time_tracker_fetcher'
|
|
3
|
+
require_relative '../formatters/rspec/time_tracker_fetcher'
|
|
4
4
|
|
|
5
5
|
module KnapsackPro
|
|
6
6
|
module Adapters
|
|
@@ -136,7 +136,7 @@ module KnapsackPro
|
|
|
136
136
|
def log_tests_duration
|
|
137
137
|
::RSpec.configure do |config|
|
|
138
138
|
config.append_after(:suite) do
|
|
139
|
-
time_tracker = KnapsackPro::Formatters::TimeTrackerFetcher.call
|
|
139
|
+
time_tracker = KnapsackPro::Formatters::RSpec::TimeTrackerFetcher.call
|
|
140
140
|
formatted = KnapsackPro::Presenter.global_time(time_tracker.duration)
|
|
141
141
|
KnapsackPro.logger.debug(formatted)
|
|
142
142
|
end
|
|
@@ -148,7 +148,7 @@ module KnapsackPro
|
|
|
148
148
|
|
|
149
149
|
::RSpec.configure do |config|
|
|
150
150
|
config.append_before(:suite) do
|
|
151
|
-
time_tracker = KnapsackPro::Formatters::TimeTrackerFetcher.call
|
|
151
|
+
time_tracker = KnapsackPro::Formatters::RSpec::TimeTrackerFetcher.call
|
|
152
152
|
time_tracker.schedule(KnapsackPro::Adapters::RSpecAdapter.scheduled_paths)
|
|
153
153
|
end
|
|
154
154
|
end
|
|
@@ -157,8 +157,8 @@ module KnapsackPro
|
|
|
157
157
|
def bind_save_report
|
|
158
158
|
::RSpec.configure do |config|
|
|
159
159
|
config.after(:suite) do
|
|
160
|
-
time_tracker = KnapsackPro::Formatters::TimeTrackerFetcher.call
|
|
161
|
-
KnapsackPro::Report.save(time_tracker.batch)
|
|
160
|
+
time_tracker = KnapsackPro::Formatters::RSpec::TimeTrackerFetcher.call
|
|
161
|
+
KnapsackPro::Report.save(time_tracker.batch, time_tracker.current_batch_failed_paths)
|
|
162
162
|
end
|
|
163
163
|
end
|
|
164
164
|
end
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module KnapsackPro
|
|
4
|
+
module Formatters
|
|
5
|
+
module Cucumber
|
|
6
|
+
class FailuresTracker
|
|
7
|
+
def initialize(config)
|
|
8
|
+
failed_locations = {}
|
|
9
|
+
|
|
10
|
+
config.on_event :test_case_finished do |event|
|
|
11
|
+
location = event.test_case.location
|
|
12
|
+
id_path = "#{location.file}:#{location.line}"
|
|
13
|
+
|
|
14
|
+
ok =
|
|
15
|
+
if config.respond_to?(:strict)
|
|
16
|
+
event.result.ok?(strict: config.strict)
|
|
17
|
+
else
|
|
18
|
+
event.result.ok?
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
if ok
|
|
22
|
+
failed_locations.delete(id_path)
|
|
23
|
+
else
|
|
24
|
+
failed_locations[id_path] = location
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
config.on_event :test_run_finished do |_event|
|
|
29
|
+
paths = failed_locations.map do |id_path, location|
|
|
30
|
+
# TODO: Tracker currently only tracks by file_path. Track id_path and use the following:
|
|
31
|
+
# KnapsackPro::Config::Env.test_files_encrypted? ? location.file : id_path
|
|
32
|
+
location.file
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
paths.uniq.each do |path|
|
|
36
|
+
KnapsackPro.tracker.mark_failed(path)
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'stringio'
|
|
4
|
+
require 'set'
|
|
5
|
+
require_relative '../../utils'
|
|
6
|
+
|
|
7
|
+
module KnapsackPro
|
|
8
|
+
module Formatters
|
|
9
|
+
module RSpec
|
|
10
|
+
class TimeTracker
|
|
11
|
+
::RSpec::Core::Formatters.register self,
|
|
12
|
+
:example_group_started,
|
|
13
|
+
:example_started,
|
|
14
|
+
:example_finished,
|
|
15
|
+
:example_group_finished
|
|
16
|
+
|
|
17
|
+
attr_reader :output # RSpec < v3.10.2
|
|
18
|
+
|
|
19
|
+
def initialize(_output)
|
|
20
|
+
@output = StringIO.new
|
|
21
|
+
@time_each = nil
|
|
22
|
+
@time_all = nil
|
|
23
|
+
@time_all_by_group_id_path = Hash.new(0)
|
|
24
|
+
@group = {}
|
|
25
|
+
@paths = {}
|
|
26
|
+
@suite_started = now
|
|
27
|
+
@batched_scheduled_paths = []
|
|
28
|
+
@split_by_test_example_file_paths = Set.new
|
|
29
|
+
@current_batch_examples = []
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def current_batch_failed_paths
|
|
33
|
+
examples_by_file_path = @current_batch_examples
|
|
34
|
+
.group_by { |example| file_path_for(example) }
|
|
35
|
+
|
|
36
|
+
paths =
|
|
37
|
+
examples_by_file_path.flat_map do |file_path, examples|
|
|
38
|
+
failed_id_paths = examples.filter { |example| example.execution_result.status.to_s == "failed" }.map(&:id)
|
|
39
|
+
next [] if failed_id_paths.none?
|
|
40
|
+
next file_path if KnapsackPro::Config::Env.test_files_encrypted?
|
|
41
|
+
# Other nodes may have run some examples from this file, it's not safe to compact.
|
|
42
|
+
next failed_id_paths if rspec_split_by_test_example?(file_path)
|
|
43
|
+
next file_path if failed_id_paths.size == examples.size
|
|
44
|
+
|
|
45
|
+
failed_id_paths
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
paths.map { |path| KnapsackPro::TestFileCleaner.clean(path) }
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def schedule(paths)
|
|
52
|
+
@current_batch_examples = []
|
|
53
|
+
@batched_scheduled_paths << paths
|
|
54
|
+
|
|
55
|
+
paths.each do |path|
|
|
56
|
+
next unless KnapsackPro::Adapters::RSpecAdapter.id_path?(path)
|
|
57
|
+
|
|
58
|
+
file_path = KnapsackPro::Adapters::RSpecAdapter.parse_file_path(path)
|
|
59
|
+
@split_by_test_example_file_paths << KnapsackPro::TestFileCleaner.clean(file_path)
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def example_group_started(notification)
|
|
64
|
+
record_time_all(notification.group.parent_groups[1], @time_all_by_group_id_path, @time_all)
|
|
65
|
+
@time_all = now
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def example_started(notification)
|
|
69
|
+
record_time_all(notification.example.example_group, @time_all_by_group_id_path, @time_all)
|
|
70
|
+
@time_each = now
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def example_finished(notification)
|
|
74
|
+
record_example(@group, notification.example, @time_each)
|
|
75
|
+
@time_all = now
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def example_group_finished(notification)
|
|
79
|
+
record_time_all(notification.group, @time_all_by_group_id_path, @time_all)
|
|
80
|
+
@time_all = now
|
|
81
|
+
return unless top_level_group?(notification.group)
|
|
82
|
+
|
|
83
|
+
add_hooks_time(@group, @time_all_by_group_id_path)
|
|
84
|
+
@time_all_by_group_id_path = Hash.new(0)
|
|
85
|
+
merge!(@paths, @group)
|
|
86
|
+
@group = {}
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def queue
|
|
90
|
+
recorded_paths = @paths.values.map do |example|
|
|
91
|
+
KnapsackPro::Adapters::RSpecAdapter.parse_file_path(example[:path])
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
missing = (@batched_scheduled_paths.flatten - recorded_paths).each_with_object({}) do |path, object|
|
|
95
|
+
object[path] = { path: path, time_execution: 0.0 }
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
merge!(@paths, missing)
|
|
99
|
+
|
|
100
|
+
@paths.values.map do |example|
|
|
101
|
+
example.transform_keys(&:to_s)
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
def batch
|
|
106
|
+
@paths.values.map do |example|
|
|
107
|
+
example.transform_keys(&:to_s)
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
def duration
|
|
112
|
+
now - @suite_started
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
def unexecuted_test_paths
|
|
116
|
+
pending_paths = @paths.values
|
|
117
|
+
.filter { |example| example[:time_execution] == 0.0 }
|
|
118
|
+
.map { |example| example[:path] }
|
|
119
|
+
|
|
120
|
+
not_run_paths = @batched_scheduled_paths.flatten -
|
|
121
|
+
@paths.values
|
|
122
|
+
.map { |example| example[:path] }
|
|
123
|
+
|
|
124
|
+
pending_paths + not_run_paths
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
private
|
|
128
|
+
|
|
129
|
+
def top_level_group?(group)
|
|
130
|
+
group.metadata[:parent_example_group].nil?
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
def add_hooks_time(group, time_all_by_group_id_path)
|
|
134
|
+
group.each do |_, example|
|
|
135
|
+
next if example[:time_execution] == 0.0
|
|
136
|
+
|
|
137
|
+
example[:time_execution] += time_all_by_group_id_path.reduce(0.0) do |sum, (group_id_path, time)|
|
|
138
|
+
# :path is a file path (a_spec.rb), sum any before/after(:all) in the file
|
|
139
|
+
next sum + time if group_id_path.start_with?(example[:path])
|
|
140
|
+
# :path is an id path (a_spec.rb[1:1]), sum any before/after(:all) above it
|
|
141
|
+
next sum + time if example[:path].start_with?(group_id_path[0..-2])
|
|
142
|
+
sum
|
|
143
|
+
end
|
|
144
|
+
end
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
def record_example(accumulator, example, started_at)
|
|
148
|
+
path = path_for(example)
|
|
149
|
+
return if path.nil?
|
|
150
|
+
|
|
151
|
+
@current_batch_examples << example
|
|
152
|
+
|
|
153
|
+
time_execution = time_execution_for(example, started_at)
|
|
154
|
+
if accumulator.key?(path)
|
|
155
|
+
accumulator[path][:time_execution] += time_execution
|
|
156
|
+
else
|
|
157
|
+
accumulator[path] = { path: path, time_execution: time_execution }
|
|
158
|
+
end
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
def record_time_all(group, time_all_by_group_id_path, time_all)
|
|
162
|
+
return unless group # above top level group
|
|
163
|
+
|
|
164
|
+
group_id_path = KnapsackPro::TestFileCleaner.clean(group.id)
|
|
165
|
+
time_all_by_group_id_path[group_id_path] += now - time_all
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
def path_for(example)
|
|
169
|
+
file_path = file_path_for(example)
|
|
170
|
+
return nil if file_path == ""
|
|
171
|
+
|
|
172
|
+
if rspec_split_by_test_example?(file_path)
|
|
173
|
+
KnapsackPro::TestFileCleaner.clean(example.id)
|
|
174
|
+
else
|
|
175
|
+
file_path
|
|
176
|
+
end
|
|
177
|
+
end
|
|
178
|
+
|
|
179
|
+
def rspec_split_by_test_example?(file_path)
|
|
180
|
+
@split_by_test_example_file_paths.include?(file_path)
|
|
181
|
+
end
|
|
182
|
+
|
|
183
|
+
def file_path_for(example)
|
|
184
|
+
KnapsackPro::TestFileCleaner.clean(KnapsackPro::Adapters::RSpecAdapter.file_path_for(example))
|
|
185
|
+
end
|
|
186
|
+
|
|
187
|
+
def time_execution_for(example, started_at)
|
|
188
|
+
if example.execution_result.status.to_s == "pending"
|
|
189
|
+
0.0
|
|
190
|
+
else
|
|
191
|
+
(now - started_at).to_f
|
|
192
|
+
end
|
|
193
|
+
end
|
|
194
|
+
|
|
195
|
+
def merge!(h1, h2)
|
|
196
|
+
h2.each do |path, example|
|
|
197
|
+
if recorded = h1[path]
|
|
198
|
+
recorded[:time_execution] += example[:time_execution]
|
|
199
|
+
else
|
|
200
|
+
h1[path] = example
|
|
201
|
+
end
|
|
202
|
+
end
|
|
203
|
+
end
|
|
204
|
+
|
|
205
|
+
def now
|
|
206
|
+
KnapsackPro::Utils.time_now
|
|
207
|
+
end
|
|
208
|
+
end
|
|
209
|
+
end
|
|
210
|
+
end
|
|
211
|
+
end
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module KnapsackPro
|
|
4
|
+
module Formatters
|
|
5
|
+
module RSpec
|
|
6
|
+
class TimeTrackerFetcher
|
|
7
|
+
def self.call
|
|
8
|
+
::RSpec
|
|
9
|
+
.configuration
|
|
10
|
+
.formatters
|
|
11
|
+
.find { |f| f.class.to_s == "KnapsackPro::Formatters::RSpec::TimeTracker" }
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def self.unexecuted_test_paths
|
|
15
|
+
time_tracker = call
|
|
16
|
+
return [] unless time_tracker
|
|
17
|
+
time_tracker.unexecuted_test_paths
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
@@ -51,27 +51,27 @@ module KnapsackPro
|
|
|
51
51
|
@current_batch = nil
|
|
52
52
|
end
|
|
53
53
|
|
|
54
|
-
def test_file_paths(can_initialize_queue, executed_test_files,
|
|
54
|
+
def test_file_paths(can_initialize_queue, executed_test_files, failed_paths)
|
|
55
55
|
@batch_index += 1
|
|
56
56
|
return [] if @fallback_mode
|
|
57
57
|
|
|
58
|
-
batch = pull_tests_from_queue(can_initialize_queue,
|
|
58
|
+
batch = pull_tests_from_queue(can_initialize_queue, failed_paths)
|
|
59
59
|
|
|
60
60
|
return switch_to_fallback_mode(executed_test_files: executed_test_files) if batch.connection_failed?
|
|
61
61
|
return normalize_test_files(batch.test_files) if batch.queue_exists?
|
|
62
62
|
|
|
63
63
|
test_files_result = test_suite.calculate_test_files
|
|
64
64
|
|
|
65
|
-
return try_initializing_queue(test_files_result.test_files.map { |test| test.fetch("path") },
|
|
65
|
+
return try_initializing_queue(test_files_result.test_files.map { |test| test.fetch("path") }, failed_paths) if test_files_result.quick?
|
|
66
66
|
|
|
67
67
|
# The tests to run were found slowly. By that time, the queue could have already been initialized by another CI node.
|
|
68
68
|
# Attempt to pull tests from the queue to avoid the attempt to initialize the queue unnecessarily (queue initialization is an expensive request with a big test files payload).
|
|
69
|
-
batch = pull_tests_from_queue(can_initialize_queue,
|
|
69
|
+
batch = pull_tests_from_queue(can_initialize_queue, failed_paths)
|
|
70
70
|
|
|
71
71
|
return switch_to_fallback_mode(executed_test_files: executed_test_files) if batch.connection_failed?
|
|
72
72
|
return normalize_test_files(batch.test_files) if batch.queue_exists?
|
|
73
73
|
|
|
74
|
-
try_initializing_queue(test_files_result.test_files.map { |test| test.fetch("path") },
|
|
74
|
+
try_initializing_queue(test_files_result.test_files.map { |test| test.fetch("path") }, failed_paths)
|
|
75
75
|
end
|
|
76
76
|
|
|
77
77
|
private
|
|
@@ -87,16 +87,15 @@ module KnapsackPro
|
|
|
87
87
|
KnapsackPro::TestFilePresenter.paths(decrypted_test_files)
|
|
88
88
|
end
|
|
89
89
|
|
|
90
|
-
|
|
91
|
-
def build_action(can_initialize_queue:, attempt_connect_to_queue:, time_tracker:, paths: nil)
|
|
90
|
+
def build_action(can_initialize_queue:, attempt_connect_to_queue:, failed_paths:, paths: nil)
|
|
92
91
|
if can_initialize_queue && !attempt_connect_to_queue
|
|
93
92
|
raise 'Test files are required when initializing a new queue.' if paths.nil?
|
|
94
93
|
paths = KnapsackPro::Crypto::Encryptor.paths(paths)
|
|
95
94
|
end
|
|
96
95
|
|
|
97
96
|
failed_paths =
|
|
98
|
-
if
|
|
99
|
-
KnapsackPro::Crypto::Encryptor.paths(
|
|
97
|
+
if failed_paths
|
|
98
|
+
KnapsackPro::Crypto::Encryptor.paths(failed_paths)
|
|
100
99
|
elsif @batch_index.positive?
|
|
101
100
|
KnapsackPro::Crypto::Encryptor.paths(@current_batch.paths)
|
|
102
101
|
end
|
|
@@ -116,22 +115,22 @@ module KnapsackPro
|
|
|
116
115
|
)
|
|
117
116
|
end
|
|
118
117
|
|
|
119
|
-
def pull_tests_from_queue(can_initialize_queue,
|
|
120
|
-
action = build_action(can_initialize_queue: can_initialize_queue, attempt_connect_to_queue: can_initialize_queue,
|
|
118
|
+
def pull_tests_from_queue(can_initialize_queue, failed_paths)
|
|
119
|
+
action = build_action(can_initialize_queue: can_initialize_queue, attempt_connect_to_queue: can_initialize_queue, failed_paths: failed_paths)
|
|
121
120
|
connection = KnapsackPro::Client::Connection.new(action)
|
|
122
121
|
response = connection.call
|
|
123
122
|
@current_batch = Batch.new(connection, response)
|
|
124
123
|
end
|
|
125
124
|
|
|
126
|
-
def initialize_queue(paths,
|
|
127
|
-
action = build_action(can_initialize_queue: true, attempt_connect_to_queue: false, paths: paths,
|
|
125
|
+
def initialize_queue(paths, failed_paths)
|
|
126
|
+
action = build_action(can_initialize_queue: true, attempt_connect_to_queue: false, paths: paths, failed_paths: failed_paths)
|
|
128
127
|
connection = KnapsackPro::Client::Connection.new(action)
|
|
129
128
|
response = connection.call
|
|
130
129
|
@current_batch = Batch.new(connection, response)
|
|
131
130
|
end
|
|
132
131
|
|
|
133
|
-
def try_initializing_queue(paths,
|
|
134
|
-
result = initialize_queue(paths,
|
|
132
|
+
def try_initializing_queue(paths, failed_paths)
|
|
133
|
+
result = initialize_queue(paths, failed_paths)
|
|
135
134
|
return switch_to_fallback_mode(executed_test_files: []) if result.connection_failed?
|
|
136
135
|
|
|
137
136
|
normalize_test_files(result.test_files)
|
data/lib/knapsack_pro/report.rb
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
module KnapsackPro
|
|
4
4
|
class Report
|
|
5
|
-
def self.save(test_files = nil)
|
|
5
|
+
def self.save(test_files = nil, failed_paths = nil)
|
|
6
6
|
if test_files.nil?
|
|
7
7
|
test_files = KnapsackPro.tracker.to_a
|
|
8
8
|
end
|
|
@@ -11,31 +11,39 @@ module KnapsackPro
|
|
|
11
11
|
KnapsackPro.logger.info("No tests were executed because the test queue is empty.")
|
|
12
12
|
end
|
|
13
13
|
|
|
14
|
-
acknowledge_single_batch(test_files)
|
|
14
|
+
acknowledge_single_batch(test_files, failed_paths)
|
|
15
15
|
create_build_subset(test_files)
|
|
16
16
|
end
|
|
17
17
|
|
|
18
18
|
def self.save_subset_queue_to_file
|
|
19
19
|
test_files = KnapsackPro.tracker.to_a
|
|
20
|
-
|
|
21
20
|
subset_queue_id = KnapsackPro::Config::Env.subset_queue_id
|
|
22
|
-
|
|
23
21
|
KnapsackPro::Config::TempFiles.ensure_temp_directory_exists!
|
|
24
22
|
FileUtils.mkdir_p(queue_path)
|
|
25
|
-
|
|
26
23
|
subset_queue_file_name = "#{subset_queue_id}.json"
|
|
27
24
|
report_path = File.join(queue_path, subset_queue_file_name)
|
|
28
25
|
report_json = JSON.pretty_generate(test_files)
|
|
29
|
-
|
|
30
26
|
File.open(report_path, 'w+') do |f|
|
|
31
27
|
f.write(report_json)
|
|
32
28
|
end
|
|
29
|
+
|
|
30
|
+
failed_paths = KnapsackPro.tracker.current_batch_failed_paths
|
|
31
|
+
json = JSON.pretty_generate(failed_paths)
|
|
32
|
+
File.open(current_batch_failed_paths_path, 'w+') do |f|
|
|
33
|
+
f.write(json)
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def self.current_batch_failed_paths
|
|
38
|
+
JSON.parse(File.read(current_batch_failed_paths_path))
|
|
33
39
|
end
|
|
34
40
|
|
|
35
41
|
def self.save_node_queue_to_api(test_files = nil)
|
|
36
42
|
if test_files.nil?
|
|
37
43
|
test_files = []
|
|
38
44
|
Dir.glob("#{queue_path}/*.json").each do |file|
|
|
45
|
+
next if file == current_batch_failed_paths_path
|
|
46
|
+
|
|
39
47
|
report = JSON.parse(File.read(file))
|
|
40
48
|
test_files += report
|
|
41
49
|
end
|
|
@@ -59,11 +67,10 @@ module KnapsackPro
|
|
|
59
67
|
create_build_subset(test_files)
|
|
60
68
|
end
|
|
61
69
|
|
|
62
|
-
|
|
63
|
-
def self.acknowledge_single_batch(test_files)
|
|
70
|
+
def self.acknowledge_single_batch(test_files, failed_paths)
|
|
64
71
|
repository_adapter = KnapsackPro::RepositoryAdapterInitiator.call
|
|
65
72
|
paths = KnapsackPro::Utils.unsymbolize(test_files).map { |test_file| test_file.fetch('path') }
|
|
66
|
-
failed_paths =
|
|
73
|
+
failed_paths = paths if failed_paths.nil?
|
|
67
74
|
action = KnapsackPro::Client::API::V2::Queues.queue(
|
|
68
75
|
attempt_connect_to_queue: false,
|
|
69
76
|
batch_id: ENV['KNAPSACK_PRO_BATCH_ID'], # nil in Fallback Mode
|
|
@@ -71,7 +78,7 @@ module KnapsackPro
|
|
|
71
78
|
branch: KnapsackPro::Crypto::BranchEncryptor.call(repository_adapter.branch),
|
|
72
79
|
can_initialize_queue: false,
|
|
73
80
|
commit_hash: repository_adapter.commit_hash,
|
|
74
|
-
failed_paths: failed_paths,
|
|
81
|
+
failed_paths: KnapsackPro::Crypto::Encryptor.paths(failed_paths),
|
|
75
82
|
node_total: KnapsackPro::Config::Env.ci_node_total,
|
|
76
83
|
node_index: KnapsackPro::Config::Env.ci_node_index,
|
|
77
84
|
node_uuid: ENV.fetch('KNAPSACK_PRO_NODE_UUID'),
|
|
@@ -115,5 +122,9 @@ module KnapsackPro
|
|
|
115
122
|
def self.queue_path
|
|
116
123
|
"#{KnapsackPro::Config::TempFiles::TEMP_DIRECTORY_PATH}/queue/#{KnapsackPro::Config::Env.queue_id}"
|
|
117
124
|
end
|
|
125
|
+
|
|
126
|
+
def self.current_batch_failed_paths_path
|
|
127
|
+
File.join(KnapsackPro::Report.queue_path, "current_batch_failed_paths.json")
|
|
128
|
+
end
|
|
118
129
|
end
|
|
119
130
|
end
|
|
@@ -26,7 +26,7 @@ module KnapsackPro
|
|
|
26
26
|
|
|
27
27
|
def test_files_to_execute_exist?
|
|
28
28
|
if test_file_paths.empty?
|
|
29
|
-
KnapsackPro.logger.info("
|
|
29
|
+
KnapsackPro.logger.info("No tests were executed because the test queue is empty.")
|
|
30
30
|
false
|
|
31
31
|
else
|
|
32
32
|
true
|
|
@@ -24,8 +24,19 @@ module KnapsackPro
|
|
|
24
24
|
Rake::Task[task_name].clear
|
|
25
25
|
end
|
|
26
26
|
|
|
27
|
+
require "cucumber/cli/main"
|
|
28
|
+
require "shellwords"
|
|
29
|
+
formats = Cucumber::Cli::Main.new(Shellwords.split(args || "")).configuration.formats
|
|
30
|
+
formats << ["KnapsackPro::Formatters::Cucumber::FailuresTracker", {}, File::NULL]
|
|
31
|
+
formats_arg = formats.flat_map do |name, options, output|
|
|
32
|
+
format = [name, *options.map { |key, value| "#{key}=#{value}" }].join(",")
|
|
33
|
+
formats = ["--format", format]
|
|
34
|
+
formats.concat(["--out", output]) if output.is_a?(String)
|
|
35
|
+
formats
|
|
36
|
+
end
|
|
37
|
+
|
|
27
38
|
::Cucumber::Rake::Task.new(task_name) do |t|
|
|
28
|
-
t.cucumber_opts = "#{args} --require #{runner.test_dir} -- #{runner.stringify_test_file_paths}"
|
|
39
|
+
t.cucumber_opts = "#{args} --require #{runner.test_dir} #{formats_arg.shelljoin} -- #{runner.stringify_test_file_paths}"
|
|
29
40
|
end
|
|
30
41
|
Rake::Task[task_name].invoke
|
|
31
42
|
end
|
|
@@ -25,8 +25,8 @@ module KnapsackPro
|
|
|
25
25
|
def test_file_paths(args)
|
|
26
26
|
can_initialize_queue = args.fetch(:can_initialize_queue)
|
|
27
27
|
executed_test_files = args.fetch(:executed_test_files)
|
|
28
|
-
|
|
29
|
-
allocator.test_file_paths(can_initialize_queue, executed_test_files,
|
|
28
|
+
failed_paths = args.fetch(:failed_paths)
|
|
29
|
+
allocator.test_file_paths(can_initialize_queue, executed_test_files, failed_paths)
|
|
30
30
|
end
|
|
31
31
|
|
|
32
32
|
def test_dir
|
|
@@ -21,7 +21,7 @@ module KnapsackPro
|
|
|
21
21
|
can_initialize_queue: true,
|
|
22
22
|
args: args,
|
|
23
23
|
exitstatus: 0,
|
|
24
|
-
node_test_file_paths: []
|
|
24
|
+
node_test_file_paths: []
|
|
25
25
|
}
|
|
26
26
|
while accumulator[:status] == :next
|
|
27
27
|
handle_signal!
|
|
@@ -43,7 +43,8 @@ module KnapsackPro
|
|
|
43
43
|
|
|
44
44
|
test_file_paths = runner.test_file_paths(
|
|
45
45
|
can_initialize_queue: can_initialize_queue,
|
|
46
|
-
executed_test_files: node_test_file_paths
|
|
46
|
+
executed_test_files: node_test_file_paths,
|
|
47
|
+
failed_paths: can_initialize_queue ? [] : KnapsackPro::Report.current_batch_failed_paths
|
|
47
48
|
)
|
|
48
49
|
|
|
49
50
|
if test_file_paths.empty?
|
|
@@ -83,7 +84,7 @@ module KnapsackPro
|
|
|
83
84
|
can_initialize_queue: false,
|
|
84
85
|
args: args,
|
|
85
86
|
exitstatus: exitstatus,
|
|
86
|
-
node_test_file_paths: node_test_file_paths
|
|
87
|
+
node_test_file_paths: node_test_file_paths
|
|
87
88
|
}
|
|
88
89
|
end
|
|
89
90
|
end
|
|
@@ -91,11 +92,22 @@ module KnapsackPro
|
|
|
91
92
|
private
|
|
92
93
|
|
|
93
94
|
def self.cucumber_run(runner, test_file_paths, args)
|
|
95
|
+
require "cucumber/cli/main"
|
|
96
|
+
require "shellwords"
|
|
97
|
+
formats = Cucumber::Cli::Main.new(Shellwords.split(args || "")).configuration.formats
|
|
98
|
+
formats << ["KnapsackPro::Formatters::Cucumber::FailuresTracker", {}, File::NULL]
|
|
99
|
+
formats_arg = formats.flat_map do |name, options, output|
|
|
100
|
+
format = [name, *options.map { |key, value| "#{key}=#{value}" }].join(",")
|
|
101
|
+
formats = ["--format", format]
|
|
102
|
+
formats.concat(["--out", output]) if output.is_a?(String)
|
|
103
|
+
formats
|
|
104
|
+
end
|
|
105
|
+
|
|
94
106
|
stringify_test_file_paths = KnapsackPro::TestFilePresenter.stringify_paths(test_file_paths)
|
|
95
107
|
|
|
96
108
|
cmd = [
|
|
97
109
|
KnapsackPro::Config::Env.cucumber_queue_prefix,
|
|
98
|
-
%Q[cucumber #{args} --require #{runner.test_dir} -- #{stringify_test_file_paths}]
|
|
110
|
+
%Q[cucumber #{args} --require #{runner.test_dir} #{formats_arg.shelljoin} -- #{stringify_test_file_paths}]
|
|
99
111
|
].join(' ')
|
|
100
112
|
|
|
101
113
|
Kernel.system(cmd)
|
|
@@ -54,7 +54,8 @@ module KnapsackPro
|
|
|
54
54
|
|
|
55
55
|
test_file_paths = runner.test_file_paths(
|
|
56
56
|
can_initialize_queue: can_initialize_queue,
|
|
57
|
-
executed_test_files: node_test_file_paths
|
|
57
|
+
executed_test_files: node_test_file_paths,
|
|
58
|
+
failed_paths: can_initialize_queue ? [] : KnapsackPro::Report.current_batch_failed_paths
|
|
58
59
|
)
|
|
59
60
|
|
|
60
61
|
if test_file_paths.empty?
|
|
@@ -86,6 +87,7 @@ module KnapsackPro
|
|
|
86
87
|
|
|
87
88
|
KnapsackPro::Hooks::Queue.call_after_subset_queue
|
|
88
89
|
|
|
90
|
+
KnapsackPro.tracker.flush_staged_tests
|
|
89
91
|
KnapsackPro::Report.save_subset_queue_to_file
|
|
90
92
|
|
|
91
93
|
return {
|
|
@@ -7,8 +7,8 @@ module KnapsackPro
|
|
|
7
7
|
def self.run(args, stream_error = $stderr, stream_out = $stdout)
|
|
8
8
|
require 'rspec/core'
|
|
9
9
|
require_relative '../../extensions/rspec_extension'
|
|
10
|
-
require_relative '../../formatters/time_tracker'
|
|
11
|
-
require_relative '../../formatters/time_tracker_fetcher'
|
|
10
|
+
require_relative '../../formatters/rspec/time_tracker'
|
|
11
|
+
require_relative '../../formatters/rspec/time_tracker_fetcher'
|
|
12
12
|
|
|
13
13
|
KnapsackPro::Extensions::RSpecExtension.setup!
|
|
14
14
|
|
|
@@ -163,7 +163,7 @@ module KnapsackPro
|
|
|
163
163
|
|
|
164
164
|
log_rspec_queue_command
|
|
165
165
|
|
|
166
|
-
time_tracker = KnapsackPro::Formatters::TimeTrackerFetcher.call
|
|
166
|
+
time_tracker = KnapsackPro::Formatters::RSpec::TimeTrackerFetcher.call
|
|
167
167
|
KnapsackPro::Report.save_node_queue_to_api(time_tracker&.queue)
|
|
168
168
|
|
|
169
169
|
Kernel.exit(exit_code)
|
|
@@ -178,11 +178,11 @@ module KnapsackPro
|
|
|
178
178
|
end
|
|
179
179
|
|
|
180
180
|
def pull_tests_from_queue(can_initialize_queue: false)
|
|
181
|
-
time_tracker = KnapsackPro::Formatters::TimeTrackerFetcher.call
|
|
181
|
+
time_tracker = KnapsackPro::Formatters::RSpec::TimeTrackerFetcher.call
|
|
182
182
|
test_file_paths = test_file_paths(
|
|
183
183
|
can_initialize_queue: can_initialize_queue,
|
|
184
184
|
executed_test_files: @node_test_file_paths,
|
|
185
|
-
|
|
185
|
+
failed_paths: time_tracker.current_batch_failed_paths
|
|
186
186
|
)
|
|
187
187
|
@node_test_file_paths += test_file_paths
|
|
188
188
|
time_tracker.schedule(test_file_paths)
|
|
@@ -225,7 +225,7 @@ module KnapsackPro
|
|
|
225
225
|
end
|
|
226
226
|
|
|
227
227
|
def unexecuted_test_paths
|
|
228
|
-
KnapsackPro::Formatters::TimeTrackerFetcher.unexecuted_test_paths
|
|
228
|
+
KnapsackPro::Formatters::RSpec::TimeTrackerFetcher.unexecuted_test_paths
|
|
229
229
|
end
|
|
230
230
|
end
|
|
231
231
|
end
|
|
@@ -39,7 +39,7 @@ module KnapsackPro
|
|
|
39
39
|
|
|
40
40
|
# Use RSpec::Core::ConfigurationOptions to respect external configurations like .rspec
|
|
41
41
|
def self.formatters
|
|
42
|
-
require_relative '../formatters/time_tracker'
|
|
42
|
+
require_relative '../formatters/rspec/time_tracker'
|
|
43
43
|
|
|
44
44
|
formatters = ::RSpec::Core::ConfigurationOptions
|
|
45
45
|
.new([])
|
|
@@ -51,7 +51,7 @@ module KnapsackPro
|
|
|
51
51
|
arg
|
|
52
52
|
end
|
|
53
53
|
formatters = ['--format progress'] if formatters.empty?
|
|
54
|
-
formatters += ["--format #{KnapsackPro::Formatters::TimeTracker}"]
|
|
54
|
+
formatters += ["--format #{KnapsackPro::Formatters::RSpec::TimeTracker}"]
|
|
55
55
|
formatters.join(' ')
|
|
56
56
|
end
|
|
57
57
|
end
|
data/lib/knapsack_pro/tracker.rb
CHANGED
|
@@ -6,7 +6,7 @@ module KnapsackPro
|
|
|
6
6
|
|
|
7
7
|
DEFAULT_TEST_FILE_TIME = 0.0 # seconds
|
|
8
8
|
|
|
9
|
-
attr_reader :global_time, :test_files_with_time, :prerun_tests_loaded
|
|
9
|
+
attr_reader :global_time, :test_files_with_time, :prerun_tests_loaded, :current_batch_failed_paths
|
|
10
10
|
attr_writer :current_test_path
|
|
11
11
|
|
|
12
12
|
def initialize
|
|
@@ -74,20 +74,48 @@ module KnapsackPro
|
|
|
74
74
|
load_prerun_tests unless prerun_tests_loaded
|
|
75
75
|
|
|
76
76
|
test_files = []
|
|
77
|
+
|
|
77
78
|
@test_files_with_time.each do |path, hash|
|
|
78
79
|
test_files << {
|
|
79
80
|
path: path,
|
|
80
81
|
time_execution: hash[:time_execution]
|
|
81
82
|
}
|
|
82
83
|
end
|
|
84
|
+
|
|
83
85
|
test_files
|
|
84
86
|
end
|
|
85
87
|
|
|
88
|
+
def mark_failed(path)
|
|
89
|
+
@current_batch_failed_paths << KnapsackPro::TestFileCleaner.clean(path)
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
# Thread-safe: first stage then flush_staged_tests
|
|
93
|
+
def stage(path, execution_time, failed: false)
|
|
94
|
+
@staged_tests << [KnapsackPro::TestFileCleaner.clean(path), execution_time, failed]
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
# Thread-safe: first stage then flush_staged_tests
|
|
98
|
+
def flush_staged_tests
|
|
99
|
+
loop do
|
|
100
|
+
begin
|
|
101
|
+
path, execution_time, failed = @staged_tests.pop(true)
|
|
102
|
+
rescue ThreadError
|
|
103
|
+
break
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
update_global_time(execution_time)
|
|
107
|
+
update_test_file_time(execution_time, path)
|
|
108
|
+
mark_failed(path) if failed
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
|
|
86
112
|
private
|
|
87
113
|
|
|
88
114
|
def set_defaults
|
|
115
|
+
@staged_tests = ::Queue.new
|
|
89
116
|
@global_time = 0
|
|
90
117
|
@test_files_with_time = {}
|
|
118
|
+
@current_batch_failed_paths = []
|
|
91
119
|
@current_test_path = nil
|
|
92
120
|
@prerun_tests_loaded = false
|
|
93
121
|
end
|
|
@@ -134,13 +162,13 @@ module KnapsackPro
|
|
|
134
162
|
@global_time += execution_time
|
|
135
163
|
end
|
|
136
164
|
|
|
137
|
-
def update_test_file_time(execution_time)
|
|
138
|
-
@test_files_with_time[
|
|
165
|
+
def update_test_file_time(execution_time, path = current_test_path)
|
|
166
|
+
@test_files_with_time[path] ||= {
|
|
139
167
|
time_execution: 0,
|
|
140
168
|
measured_time: false,
|
|
141
169
|
}
|
|
142
170
|
|
|
143
|
-
hash = @test_files_with_time[
|
|
171
|
+
hash = @test_files_with_time[path]
|
|
144
172
|
|
|
145
173
|
if hash[:measured_time]
|
|
146
174
|
hash[:time_execution] += execution_time
|
|
@@ -149,7 +177,7 @@ module KnapsackPro
|
|
|
149
177
|
hash[:measured_time] = true
|
|
150
178
|
end
|
|
151
179
|
|
|
152
|
-
@test_files_with_time[
|
|
180
|
+
@test_files_with_time[path] = hash
|
|
153
181
|
end
|
|
154
182
|
|
|
155
183
|
def now_without_mock_time
|
data/lib/knapsack_pro/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: knapsack_pro
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 11.0
|
|
4
|
+
version: 11.1.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- ArturT
|
|
@@ -57,14 +57,14 @@ dependencies:
|
|
|
57
57
|
requirements:
|
|
58
58
|
- - ">="
|
|
59
59
|
- !ruby/object:Gem::Version
|
|
60
|
-
version: '
|
|
60
|
+
version: '8'
|
|
61
61
|
type: :development
|
|
62
62
|
prerelease: false
|
|
63
63
|
version_requirements: !ruby/object:Gem::Requirement
|
|
64
64
|
requirements:
|
|
65
65
|
- - ">="
|
|
66
66
|
- !ruby/object:Gem::Version
|
|
67
|
-
version: '
|
|
67
|
+
version: '8'
|
|
68
68
|
- !ruby/object:Gem::Dependency
|
|
69
69
|
name: minitest
|
|
70
70
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -242,9 +242,10 @@ files:
|
|
|
242
242
|
- lib/knapsack_pro/crypto/digestor.rb
|
|
243
243
|
- lib/knapsack_pro/crypto/encryptor.rb
|
|
244
244
|
- lib/knapsack_pro/extensions/rspec_extension.rb
|
|
245
|
+
- lib/knapsack_pro/formatters/cucumber/failures_tracker.rb
|
|
246
|
+
- lib/knapsack_pro/formatters/rspec/time_tracker.rb
|
|
247
|
+
- lib/knapsack_pro/formatters/rspec/time_tracker_fetcher.rb
|
|
245
248
|
- lib/knapsack_pro/formatters/rspec_json_formatter.rb
|
|
246
|
-
- lib/knapsack_pro/formatters/time_tracker.rb
|
|
247
|
-
- lib/knapsack_pro/formatters/time_tracker_fetcher.rb
|
|
248
249
|
- lib/knapsack_pro/hooks/queue.rb
|
|
249
250
|
- lib/knapsack_pro/mask_string.rb
|
|
250
251
|
- lib/knapsack_pro/presenter.rb
|
|
@@ -1,209 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require 'stringio'
|
|
4
|
-
require 'set'
|
|
5
|
-
require_relative '../utils'
|
|
6
|
-
|
|
7
|
-
module KnapsackPro
|
|
8
|
-
module Formatters
|
|
9
|
-
class TimeTracker
|
|
10
|
-
::RSpec::Core::Formatters.register self,
|
|
11
|
-
:example_group_started,
|
|
12
|
-
:example_started,
|
|
13
|
-
:example_finished,
|
|
14
|
-
:example_group_finished
|
|
15
|
-
|
|
16
|
-
attr_reader :output # RSpec < v3.10.2
|
|
17
|
-
|
|
18
|
-
def initialize(_output)
|
|
19
|
-
@output = StringIO.new
|
|
20
|
-
@time_each = nil
|
|
21
|
-
@time_all = nil
|
|
22
|
-
@time_all_by_group_id_path = Hash.new(0)
|
|
23
|
-
@group = {}
|
|
24
|
-
@paths = {}
|
|
25
|
-
@suite_started = now
|
|
26
|
-
@batched_scheduled_paths = []
|
|
27
|
-
@split_by_test_example_file_paths = Set.new
|
|
28
|
-
@current_batch_examples = []
|
|
29
|
-
end
|
|
30
|
-
|
|
31
|
-
def current_batch_failed_paths
|
|
32
|
-
examples_by_file_path = @current_batch_examples
|
|
33
|
-
.group_by { |example| file_path_for(example) }
|
|
34
|
-
|
|
35
|
-
paths =
|
|
36
|
-
examples_by_file_path.flat_map do |file_path, examples|
|
|
37
|
-
failed_id_paths = examples.filter { |example| example.execution_result.status.to_s == "failed" }.map(&:id)
|
|
38
|
-
next [] if failed_id_paths.none?
|
|
39
|
-
next file_path if KnapsackPro::Config::Env.test_files_encrypted?
|
|
40
|
-
# Other nodes may have run some examples from this file, it's not safe to compact.
|
|
41
|
-
next failed_id_paths if rspec_split_by_test_example?(file_path)
|
|
42
|
-
next file_path if failed_id_paths.size == examples.size
|
|
43
|
-
|
|
44
|
-
failed_id_paths
|
|
45
|
-
end
|
|
46
|
-
|
|
47
|
-
paths.map { |path| KnapsackPro::TestFileCleaner.clean(path) }
|
|
48
|
-
end
|
|
49
|
-
|
|
50
|
-
def schedule(paths)
|
|
51
|
-
@current_batch_examples = []
|
|
52
|
-
@batched_scheduled_paths << paths
|
|
53
|
-
|
|
54
|
-
paths.each do |path|
|
|
55
|
-
next unless KnapsackPro::Adapters::RSpecAdapter.id_path?(path)
|
|
56
|
-
|
|
57
|
-
file_path = KnapsackPro::Adapters::RSpecAdapter.parse_file_path(path)
|
|
58
|
-
@split_by_test_example_file_paths << KnapsackPro::TestFileCleaner.clean(file_path)
|
|
59
|
-
end
|
|
60
|
-
end
|
|
61
|
-
|
|
62
|
-
def example_group_started(notification)
|
|
63
|
-
record_time_all(notification.group.parent_groups[1], @time_all_by_group_id_path, @time_all)
|
|
64
|
-
@time_all = now
|
|
65
|
-
end
|
|
66
|
-
|
|
67
|
-
def example_started(notification)
|
|
68
|
-
record_time_all(notification.example.example_group, @time_all_by_group_id_path, @time_all)
|
|
69
|
-
@time_each = now
|
|
70
|
-
end
|
|
71
|
-
|
|
72
|
-
def example_finished(notification)
|
|
73
|
-
record_example(@group, notification.example, @time_each)
|
|
74
|
-
@time_all = now
|
|
75
|
-
end
|
|
76
|
-
|
|
77
|
-
def example_group_finished(notification)
|
|
78
|
-
record_time_all(notification.group, @time_all_by_group_id_path, @time_all)
|
|
79
|
-
@time_all = now
|
|
80
|
-
return unless top_level_group?(notification.group)
|
|
81
|
-
|
|
82
|
-
add_hooks_time(@group, @time_all_by_group_id_path)
|
|
83
|
-
@time_all_by_group_id_path = Hash.new(0)
|
|
84
|
-
merge!(@paths, @group)
|
|
85
|
-
@group = {}
|
|
86
|
-
end
|
|
87
|
-
|
|
88
|
-
def queue
|
|
89
|
-
recorded_paths = @paths.values.map do |example|
|
|
90
|
-
KnapsackPro::Adapters::RSpecAdapter.parse_file_path(example[:path])
|
|
91
|
-
end
|
|
92
|
-
|
|
93
|
-
missing = (@batched_scheduled_paths.flatten - recorded_paths).each_with_object({}) do |path, object|
|
|
94
|
-
object[path] = { path: path, time_execution: 0.0 }
|
|
95
|
-
end
|
|
96
|
-
|
|
97
|
-
merge!(@paths, missing)
|
|
98
|
-
|
|
99
|
-
@paths.values.map do |example|
|
|
100
|
-
example.transform_keys(&:to_s)
|
|
101
|
-
end
|
|
102
|
-
end
|
|
103
|
-
|
|
104
|
-
def batch
|
|
105
|
-
@paths.values.map do |example|
|
|
106
|
-
example.transform_keys(&:to_s)
|
|
107
|
-
end
|
|
108
|
-
end
|
|
109
|
-
|
|
110
|
-
def duration
|
|
111
|
-
now - @suite_started
|
|
112
|
-
end
|
|
113
|
-
|
|
114
|
-
def unexecuted_test_paths
|
|
115
|
-
pending_paths = @paths.values
|
|
116
|
-
.filter { |example| example[:time_execution] == 0.0 }
|
|
117
|
-
.map { |example| example[:path] }
|
|
118
|
-
|
|
119
|
-
not_run_paths = @batched_scheduled_paths.flatten -
|
|
120
|
-
@paths.values
|
|
121
|
-
.map { |example| example[:path] }
|
|
122
|
-
|
|
123
|
-
pending_paths + not_run_paths
|
|
124
|
-
end
|
|
125
|
-
|
|
126
|
-
private
|
|
127
|
-
|
|
128
|
-
def top_level_group?(group)
|
|
129
|
-
group.metadata[:parent_example_group].nil?
|
|
130
|
-
end
|
|
131
|
-
|
|
132
|
-
def add_hooks_time(group, time_all_by_group_id_path)
|
|
133
|
-
group.each do |_, example|
|
|
134
|
-
next if example[:time_execution] == 0.0
|
|
135
|
-
|
|
136
|
-
example[:time_execution] += time_all_by_group_id_path.reduce(0.0) do |sum, (group_id_path, time)|
|
|
137
|
-
# :path is a file path (a_spec.rb), sum any before/after(:all) in the file
|
|
138
|
-
next sum + time if group_id_path.start_with?(example[:path])
|
|
139
|
-
# :path is an id path (a_spec.rb[1:1]), sum any before/after(:all) above it
|
|
140
|
-
next sum + time if example[:path].start_with?(group_id_path[0..-2])
|
|
141
|
-
sum
|
|
142
|
-
end
|
|
143
|
-
end
|
|
144
|
-
end
|
|
145
|
-
|
|
146
|
-
def record_example(accumulator, example, started_at)
|
|
147
|
-
path = path_for(example)
|
|
148
|
-
return if path.nil?
|
|
149
|
-
|
|
150
|
-
@current_batch_examples << example
|
|
151
|
-
|
|
152
|
-
time_execution = time_execution_for(example, started_at)
|
|
153
|
-
if accumulator.key?(path)
|
|
154
|
-
accumulator[path][:time_execution] += time_execution
|
|
155
|
-
else
|
|
156
|
-
accumulator[path] = { path: path, time_execution: time_execution }
|
|
157
|
-
end
|
|
158
|
-
end
|
|
159
|
-
|
|
160
|
-
def record_time_all(group, time_all_by_group_id_path, time_all)
|
|
161
|
-
return unless group # above top level group
|
|
162
|
-
|
|
163
|
-
group_id_path = KnapsackPro::TestFileCleaner.clean(group.id)
|
|
164
|
-
time_all_by_group_id_path[group_id_path] += now - time_all
|
|
165
|
-
end
|
|
166
|
-
|
|
167
|
-
def path_for(example)
|
|
168
|
-
file_path = file_path_for(example)
|
|
169
|
-
return nil if file_path == ""
|
|
170
|
-
|
|
171
|
-
if rspec_split_by_test_example?(file_path)
|
|
172
|
-
KnapsackPro::TestFileCleaner.clean(example.id)
|
|
173
|
-
else
|
|
174
|
-
file_path
|
|
175
|
-
end
|
|
176
|
-
end
|
|
177
|
-
|
|
178
|
-
def rspec_split_by_test_example?(file_path)
|
|
179
|
-
@split_by_test_example_file_paths.include?(file_path)
|
|
180
|
-
end
|
|
181
|
-
|
|
182
|
-
def file_path_for(example)
|
|
183
|
-
KnapsackPro::TestFileCleaner.clean(KnapsackPro::Adapters::RSpecAdapter.file_path_for(example))
|
|
184
|
-
end
|
|
185
|
-
|
|
186
|
-
def time_execution_for(example, started_at)
|
|
187
|
-
if example.execution_result.status.to_s == "pending"
|
|
188
|
-
0.0
|
|
189
|
-
else
|
|
190
|
-
(now - started_at).to_f
|
|
191
|
-
end
|
|
192
|
-
end
|
|
193
|
-
|
|
194
|
-
def merge!(h1, h2)
|
|
195
|
-
h2.each do |path, example|
|
|
196
|
-
if recorded = h1[path]
|
|
197
|
-
recorded[:time_execution] += example[:time_execution]
|
|
198
|
-
else
|
|
199
|
-
h1[path] = example
|
|
200
|
-
end
|
|
201
|
-
end
|
|
202
|
-
end
|
|
203
|
-
|
|
204
|
-
def now
|
|
205
|
-
KnapsackPro::Utils.time_now
|
|
206
|
-
end
|
|
207
|
-
end
|
|
208
|
-
end
|
|
209
|
-
end
|
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module KnapsackPro
|
|
4
|
-
module Formatters
|
|
5
|
-
class TimeTrackerFetcher
|
|
6
|
-
def self.call
|
|
7
|
-
::RSpec
|
|
8
|
-
.configuration
|
|
9
|
-
.formatters
|
|
10
|
-
.find { |f| f.class.to_s == "KnapsackPro::Formatters::TimeTracker" }
|
|
11
|
-
end
|
|
12
|
-
|
|
13
|
-
def self.unexecuted_test_paths
|
|
14
|
-
time_tracker = call
|
|
15
|
-
return [] unless time_tracker
|
|
16
|
-
time_tracker.unexecuted_test_paths
|
|
17
|
-
end
|
|
18
|
-
end
|
|
19
|
-
end
|
|
20
|
-
end
|