selective-ruby-core 0.1.5-x86_64-linux → 0.1.7-x86_64-linux
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/bin/build_env.sh +14 -7
- data/lib/bin/file_correlation_collector.sh +141 -0
- data/lib/selective/ruby/core/controller.rb +79 -126
- data/lib/selective/ruby/core/file_correlator.rb +38 -0
- data/lib/selective/ruby/core/helper.rb +76 -0
- data/lib/selective/ruby/core/version.rb +1 -1
- data/lib/selective-ruby-core.rb +5 -0
- metadata +5 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 3ee42cddb33203a7cf1604187c67426b3b7f5af1fa0e6fbb0d1516724c3e6c8e
|
4
|
+
data.tar.gz: d58c25995521db66960c51a860a778303bdb5eb5be5d11b81cf00c066d552b26
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 85d543fc9a3fd71ce55d8968eb4093c327a900841717f23307edc03a0476b7f10df81622edc4133b0d6fd9c7164689cc031d2a68faefc9700819a64026917ccc
|
7
|
+
data.tar.gz: 36648d460cfb7c7e3604d066645571e936ed3a00b2dcfb44326aa399758190a446313e18575f06e3b9ebcb4fb6748982c9dc066f1d8b22863d1fb253a79783a8
|
data/lib/bin/build_env.sh
CHANGED
@@ -2,26 +2,32 @@
|
|
2
2
|
|
3
3
|
# Detect the platform (only GitHub Actions in this case)
|
4
4
|
if [ -n "$GITHUB_ACTIONS" ]; then
|
5
|
-
# Get environment variables
|
6
5
|
platform=github_actions
|
7
6
|
branch=${SELECTIVE_BRANCH:-${GITHUB_HEAD_REF:-$GITHUB_REF_NAME}}
|
8
|
-
pr_title=$SELECTIVE_PR_TITLE
|
9
7
|
target_branch=${SELECTIVE_TARGET_BRANCH:-$GITHUB_BASE_REF}
|
10
8
|
actor=$GITHUB_ACTOR
|
11
9
|
sha=${SELECTIVE_SHA:-$GITHUB_SHA}
|
12
10
|
run_id=${SELECTIVE_RUN_ID:-$GITHUB_RUN_ID}
|
13
11
|
run_attempt=${SELECTIVE_RUN_ATTEMPT:-$GITHUB_RUN_ATTEMPT}
|
14
|
-
|
12
|
+
runner_id=$SELECTIVE_RUNNER_ID
|
13
|
+
elif [ -n "$CIRCLECI" ]; then
|
14
|
+
platform=circleci
|
15
|
+
branch=${SELECTIVE_BRANCH:-$CIRCLE_BRANCH}
|
16
|
+
target_branch=$SELECTIVE_TARGET_BRANCH
|
17
|
+
actor=${SELECTIVE_ACTOR:-${CIRCLE_USERNAME:-$CIRCLE_PR_USERNAME}}
|
18
|
+
sha=${SELECTIVE_SHA:-$CIRCLE_SHA1}
|
19
|
+
run_id=$SELECTIVE_RUN_ID
|
20
|
+
run_attempt=${SELECTIVE_RUN_ATTEMPT:-$CIRCLE_BUILD_NUM}
|
21
|
+
runner_id=${SELECTIVE_RUNNER_ID:-$CIRCLE_NODE_INDEX}
|
15
22
|
else
|
16
23
|
platform=$SELECTIVE_PLATFORM
|
17
24
|
branch=$SELECTIVE_BRANCH
|
18
|
-
pr_title=$SELECTIVE_PR_TITLE
|
19
25
|
target_branch=$SELECTIVE_TARGET_BRANCH
|
20
26
|
actor=$SELECTIVE_ACTOR
|
21
27
|
sha=$SELECTIVE_SHA
|
22
28
|
run_id=$SELECTIVE_RUN_ID
|
23
29
|
run_attempt=$SELECTIVE_RUN_ATTEMPT
|
24
|
-
|
30
|
+
runner_id=$SELECTIVE_RUNNER_ID
|
25
31
|
fi
|
26
32
|
|
27
33
|
# Output the JSON
|
@@ -29,12 +35,13 @@ cat <<EOF
|
|
29
35
|
{
|
30
36
|
"platform": "$platform",
|
31
37
|
"branch": "$branch",
|
32
|
-
"pr_title": "$
|
38
|
+
"pr_title": "$SELECTIVE_PR_TITLE",
|
33
39
|
"target_branch": "$target_branch",
|
34
40
|
"actor": "$actor",
|
35
41
|
"sha": "$sha",
|
36
42
|
"run_id": "$run_id",
|
37
43
|
"run_attempt": "$run_attempt",
|
38
|
-
"commit_message": "$
|
44
|
+
"commit_message": "$(git log --format=%s -n 1 $sha)",
|
45
|
+
"runner_id": "$runner_id"
|
39
46
|
}
|
40
47
|
EOF
|
@@ -0,0 +1,141 @@
|
|
1
|
+
#!/bin/bash
|
2
|
+
|
3
|
+
# The first argument is the number of commits to process
|
4
|
+
branch=$1
|
5
|
+
num_commits=$2
|
6
|
+
|
7
|
+
# Initialize an associative array to hold the files to check
|
8
|
+
declare -A files_to_check
|
9
|
+
|
10
|
+
# Populate the array with the script arguments, starting from the second argument
|
11
|
+
for file in "${@:3}"
|
12
|
+
do
|
13
|
+
files_to_check["$file"]=1
|
14
|
+
done
|
15
|
+
|
16
|
+
# Get a list of all commit hashes, in reverse order
|
17
|
+
all_commits=$(git log origin/$branch --no-merges --format=%H --reverse -n $num_commits)
|
18
|
+
|
19
|
+
# Initialize an associative array to store the test files
|
20
|
+
declare -A test_files
|
21
|
+
declare -A uncorrelated_test_files
|
22
|
+
|
23
|
+
# Initialize an array to store the files changed in the previous commit
|
24
|
+
prev_changed_files=()
|
25
|
+
|
26
|
+
# For each commit...
|
27
|
+
for commit in $all_commits
|
28
|
+
do
|
29
|
+
# Get a list of all files that were changed in the current commit
|
30
|
+
files=$(git diff-tree --no-commit-id --name-only -r $commit)
|
31
|
+
|
32
|
+
declare -A correlated_test_files
|
33
|
+
|
34
|
+
# # For each file in the list of files changed in the previous commit...
|
35
|
+
for file in "${prev_changed_files[@]}"
|
36
|
+
do
|
37
|
+
# If the file is in the list of files to check...
|
38
|
+
if [[ ${files_to_check[$file]} ]]; then
|
39
|
+
# For each file...
|
40
|
+
for test_file in $files
|
41
|
+
do
|
42
|
+
# If the file is in the test/ directory and ends with _test.rb...
|
43
|
+
if [[ $test_file == spec/*_spec.rb ]]
|
44
|
+
then
|
45
|
+
# Increment the count in the associative array
|
46
|
+
test_files["$file|$test_file"]=$((test_files["$file|$test_file"]+1))
|
47
|
+
# Add the test file to the correlated_test_files array
|
48
|
+
correlated_test_files["$test_file"]=1
|
49
|
+
fi
|
50
|
+
done
|
51
|
+
fi
|
52
|
+
done
|
53
|
+
|
54
|
+
# For each file in the list of files changed in the current commit...
|
55
|
+
for file in $files
|
56
|
+
do
|
57
|
+
# If the file is in the list of files to check...
|
58
|
+
if [[ ${files_to_check[$file]} ]]; then
|
59
|
+
# For each file...
|
60
|
+
for test_file in $files
|
61
|
+
do
|
62
|
+
# If the file is in the test/ directory and ends with _test.rb...
|
63
|
+
if [[ $test_file == spec/*_spec.rb ]]
|
64
|
+
then
|
65
|
+
# Increment the count in the associative array
|
66
|
+
test_files["$file|$test_file"]=$((test_files["$file|$test_file"]+1))
|
67
|
+
# Add the test file to the correlated_test_files array
|
68
|
+
correlated_test_files["$test_file"]=1
|
69
|
+
fi
|
70
|
+
done
|
71
|
+
fi
|
72
|
+
done
|
73
|
+
|
74
|
+
# For each file...
|
75
|
+
for test_file in $files
|
76
|
+
do
|
77
|
+
# If the file is in the test/ directory and ends with _test.rb...
|
78
|
+
if [[ $test_file == spec/*_spec.rb ]]
|
79
|
+
then
|
80
|
+
# If the test file is not correlated to any of the files to check in the current commit...
|
81
|
+
if [[ -z ${correlated_test_files[$test_file]} ]]
|
82
|
+
then
|
83
|
+
# Increment the count in the associative array
|
84
|
+
uncorrelated_test_files["$test_file"]=$((uncorrelated_test_files["$test_file"]+1))
|
85
|
+
fi
|
86
|
+
fi
|
87
|
+
done
|
88
|
+
|
89
|
+
# Clear the correlated_test_files array for the next commit
|
90
|
+
unset correlated_test_files
|
91
|
+
|
92
|
+
# Store the list of files changed in this commit for the next iteration
|
93
|
+
prev_changed_files=($files)
|
94
|
+
done
|
95
|
+
|
96
|
+
# OUTPUT
|
97
|
+
|
98
|
+
# Initialize an associative array to hold the JSON strings for each file
|
99
|
+
declare -A file_jsons
|
100
|
+
|
101
|
+
# Add the test_files to the file_jsons associative array
|
102
|
+
for key in "${!test_files[@]}"
|
103
|
+
do
|
104
|
+
file=${key%|*}
|
105
|
+
test_file=${key#*|}
|
106
|
+
count=${test_files[$key]}
|
107
|
+
# Append to the JSON string for this file
|
108
|
+
file_jsons["$file"]+="\"$test_file\": $count,"
|
109
|
+
done
|
110
|
+
|
111
|
+
# Initialize an empty string for the test_files JSON
|
112
|
+
correlated_files_json=""
|
113
|
+
|
114
|
+
# Add the file_jsons to the correlated_files_json string
|
115
|
+
for file in "${!file_jsons[@]}"
|
116
|
+
do
|
117
|
+
# Remove the trailing comma from the JSON string for this file
|
118
|
+
file_json=${file_jsons[$file]%?}
|
119
|
+
# Append to the correlated_files_json string
|
120
|
+
correlated_files_json+="\"$file\": { $file_json },"
|
121
|
+
done
|
122
|
+
|
123
|
+
# Remove the trailing comma from the correlated_files_json string
|
124
|
+
correlated_files_json=${correlated_files_json%?}
|
125
|
+
|
126
|
+
# Initialize an empty string for the uncorrelated_test_files JSON
|
127
|
+
uncorrelated_files_json=""
|
128
|
+
|
129
|
+
# Add the uncorrelated_test_files to the uncorrelated_files_json string
|
130
|
+
for key in "${!uncorrelated_test_files[@]}"
|
131
|
+
do
|
132
|
+
count=${uncorrelated_test_files[$key]}
|
133
|
+
# Append to the uncorrelated_files_json string
|
134
|
+
uncorrelated_files_json+="\"$key\": $count,"
|
135
|
+
done
|
136
|
+
|
137
|
+
# Remove the trailing comma from the uncorrelated_files_json string
|
138
|
+
uncorrelated_files_json=${uncorrelated_files_json%?}
|
139
|
+
|
140
|
+
# Output the JSON
|
141
|
+
echo "{ \"correlated_files\": { $correlated_files_json }, \"uncorrelated_files\": { $uncorrelated_files_json } }"
|
@@ -1,26 +1,25 @@
|
|
1
1
|
require "logger"
|
2
2
|
require "uri"
|
3
|
-
require "json"
|
4
3
|
require "fileutils"
|
5
|
-
require "open3"
|
6
4
|
|
7
5
|
module Selective
|
8
6
|
module Ruby
|
9
7
|
module Core
|
10
8
|
class Controller
|
9
|
+
include Helper
|
11
10
|
@@selective_suppress_reporting = false
|
12
11
|
|
13
12
|
def initialize(runner, debug: false, log: false)
|
14
13
|
@debug = debug
|
15
14
|
@runner = runner
|
16
15
|
@retries = 0
|
17
|
-
@runner_id = safe_filename(
|
16
|
+
@runner_id = safe_filename(get_runner_id)
|
18
17
|
@logger = init_logger(log)
|
19
18
|
end
|
20
19
|
|
21
20
|
def start(reconnect: false)
|
22
21
|
@pipe = NamedPipe.new("/tmp/#{runner_id}_2", "/tmp/#{runner_id}_1")
|
23
|
-
@transport_pid = spawn_transport_process(reconnect
|
22
|
+
@transport_pid = spawn_transport_process(reconnect: reconnect)
|
24
23
|
|
25
24
|
handle_termination_signals(transport_pid)
|
26
25
|
wait_for_connectivity
|
@@ -51,9 +50,14 @@ module Selective
|
|
51
50
|
|
52
51
|
private
|
53
52
|
|
54
|
-
attr_reader :runner, :pipe, :transport_pid, :retries, :logger, :runner_id
|
53
|
+
attr_reader :runner, :pipe, :transport_pid, :retries, :logger, :runner_id, :diff
|
55
54
|
|
56
|
-
|
55
|
+
def get_runner_id
|
56
|
+
runner_id = build_env.delete("runner_id")
|
57
|
+
return generate_runner_id if runner_id.nil? || runner_id.empty?
|
58
|
+
|
59
|
+
runner_id
|
60
|
+
end
|
57
61
|
|
58
62
|
def init_logger(enabled)
|
59
63
|
if enabled
|
@@ -70,9 +74,7 @@ module Selective
|
|
70
74
|
response = JSON.parse(message, symbolize_names: true)
|
71
75
|
|
72
76
|
@logger.info("Received Command: #{response}")
|
73
|
-
|
74
|
-
|
75
|
-
break
|
77
|
+
break if handle_command(response) == :break
|
76
78
|
end
|
77
79
|
end
|
78
80
|
|
@@ -97,7 +99,7 @@ module Selective
|
|
97
99
|
"selgen-#{SecureRandom.hex(4)}"
|
98
100
|
end
|
99
101
|
|
100
|
-
def transport_url
|
102
|
+
def transport_url(reconnect: false)
|
101
103
|
@transport_url ||= begin
|
102
104
|
api_key = ENV.fetch("SELECTIVE_API_KEY")
|
103
105
|
host = ENV.fetch("SELECTIVE_HOST", "wss://app.selective.ci")
|
@@ -113,9 +115,16 @@ module Selective
|
|
113
115
|
"run_id" => run_id,
|
114
116
|
"run_attempt" => run_attempt,
|
115
117
|
"api_key" => api_key,
|
116
|
-
"runner_id" => runner_id
|
118
|
+
"runner_id" => runner_id,
|
119
|
+
"language" => "ruby",
|
120
|
+
"core_version" => Selective::Ruby::Core::VERSION,
|
121
|
+
"framework" => runner.framework,
|
122
|
+
"framework_version" => runner.framework_version,
|
123
|
+
"framework_wrapper_version" => runner.wrapper_version,
|
117
124
|
}.merge(metadata: build_env.to_json)
|
118
125
|
|
126
|
+
prams[:reconnect] = true if reconnect
|
127
|
+
|
119
128
|
query_string = URI.encode_www_form(params)
|
120
129
|
|
121
130
|
"#{host}/transport/websocket?#{query_string}"
|
@@ -124,15 +133,14 @@ module Selective
|
|
124
133
|
|
125
134
|
def build_env
|
126
135
|
@build_env ||= begin
|
127
|
-
result = `#{
|
136
|
+
result = `#{File.join(ROOT_GEM_PATH, "lib", "bin", "build_env.sh")}`
|
128
137
|
JSON.parse(result)
|
129
138
|
end
|
130
139
|
end
|
131
140
|
|
132
|
-
def spawn_transport_process(
|
133
|
-
|
134
|
-
|
135
|
-
get_transport_path = File.join(root_path, "bin", "get_transport")
|
141
|
+
def spawn_transport_process(reconnect: false)
|
142
|
+
transport_path = File.join(ROOT_GEM_PATH, "lib", "bin", "transport")
|
143
|
+
get_transport_path = File.join(ROOT_GEM_PATH, "bin", "get_transport")
|
136
144
|
|
137
145
|
# The get_transport script is not released with the gem, so this
|
138
146
|
# code is intended for development/CI purposes.
|
@@ -147,7 +155,7 @@ module Selective
|
|
147
155
|
end
|
148
156
|
end
|
149
157
|
|
150
|
-
Process.spawn(transport_path,
|
158
|
+
Process.spawn(transport_path, transport_url(reconnect: reconnect), runner_id).tap do |pid|
|
151
159
|
Process.detach(pid)
|
152
160
|
end
|
153
161
|
end
|
@@ -211,154 +219,99 @@ module Selective
|
|
211
219
|
# Process already gone noop
|
212
220
|
end
|
213
221
|
|
214
|
-
def handle_command(
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
handle_test_manifest
|
220
|
-
when "run_test_cases"
|
221
|
-
handle_run_test_cases(response[:test_case_ids])
|
222
|
-
when "remove_failed_test_case_result"
|
223
|
-
handle_remove_failed_test_case_result(response[:test_case_id])
|
224
|
-
when "reconnect"
|
225
|
-
handle_reconnect
|
226
|
-
when "print_message"
|
227
|
-
handle_print_message(response[:message])
|
228
|
-
when "close"
|
229
|
-
handle_close(response[:exit_status])
|
230
|
-
# This return is here for the sake of test where
|
231
|
-
# we cannot exit but we need to break the loop
|
232
|
-
return false
|
233
|
-
else
|
234
|
-
raise "Unknown command received: #{response[:command]}" if debug?
|
235
|
-
end
|
222
|
+
def handle_command(data)
|
223
|
+
send("handle_#{data[:command]}", data)
|
224
|
+
rescue NoMethodError
|
225
|
+
raise "Unknown command received: #{data[:command]}" if debug?
|
226
|
+
end
|
236
227
|
|
237
|
-
|
228
|
+
def handle_print_notice(data)
|
229
|
+
print_notice(data[:message])
|
238
230
|
end
|
239
231
|
|
240
|
-
def handle_reconnect
|
232
|
+
def handle_reconnect(_data)
|
241
233
|
kill_transport
|
242
234
|
pipe.reset!
|
243
235
|
start(reconnect: true)
|
244
236
|
end
|
245
237
|
|
246
|
-
def handle_test_manifest
|
238
|
+
def handle_test_manifest(_data)
|
247
239
|
self.class.restore_reporting!
|
248
240
|
@logger.info("Sending Response: test_manifest")
|
249
241
|
data = {test_cases: runner.manifest["examples"]}
|
250
|
-
|
242
|
+
num_commits = build_env["num_commits"] || 1000
|
243
|
+
if (diff = get_diff(num_commits))
|
244
|
+
data[:modified_test_files] = modified_test_files(diff)
|
245
|
+
data[:correlated_files] = correlated_files(diff, num_commits)
|
246
|
+
end
|
251
247
|
write({type: "test_manifest", data: data})
|
252
248
|
end
|
253
249
|
|
254
|
-
def handle_run_test_cases(
|
255
|
-
runner.run_test_cases(
|
250
|
+
def handle_run_test_cases(data)
|
251
|
+
runner.run_test_cases(data[:test_case_ids], method(:test_case_callback))
|
256
252
|
end
|
257
253
|
|
258
|
-
def
|
259
|
-
|
260
|
-
write({type: "test_case_result", data: test_case})
|
261
|
-
end
|
262
|
-
|
263
|
-
def handle_remove_failed_test_case_result(test_case_id)
|
264
|
-
runner.remove_failed_test_case_result(test_case_id)
|
254
|
+
def handle_remove_failed_test_case_result(data)
|
255
|
+
runner.remove_failed_test_case_result(data[:test_case_id])
|
265
256
|
end
|
266
257
|
|
267
|
-
def
|
268
|
-
|
269
|
-
target_branch = build_env["target_branch"]
|
270
|
-
return [] if target_branch.nil? || target_branch.empty?
|
271
|
-
|
272
|
-
output, status = Open3.capture2e("git diff #{target_branch} --name-only")
|
273
|
-
|
274
|
-
if status.success?
|
275
|
-
output.split("\n").filter do |f|
|
276
|
-
f.match?(/^#{runner.base_test_path}/)
|
277
|
-
end
|
278
|
-
end
|
279
|
-
end
|
258
|
+
def handle_print_message(data)
|
259
|
+
print_warning(data[:message])
|
280
260
|
end
|
281
261
|
|
282
|
-
def
|
283
|
-
|
284
|
-
end
|
285
|
-
|
286
|
-
def handle_close(exit_status = nil)
|
262
|
+
def handle_close(data)
|
263
|
+
exit_status = data[:exit_status]
|
287
264
|
self.class.restore_reporting!
|
288
265
|
runner.finish unless exit_status.is_a?(Integer)
|
289
266
|
|
290
267
|
kill_transport
|
291
268
|
pipe.delete_pipes
|
292
269
|
exit(exit_status || runner.exit_status)
|
270
|
+
# This :break is here for the sake of test where
|
271
|
+
# we cannot exit but we need to break the loop
|
272
|
+
:break
|
293
273
|
end
|
294
274
|
|
295
|
-
def
|
296
|
-
|
275
|
+
def correlated_files(diff, num_commits)
|
276
|
+
Selective::Ruby::Core::FileCorrelator.new(diff, num_commits, build_env["target_branch"]).correlate
|
297
277
|
end
|
298
278
|
|
299
|
-
def
|
300
|
-
|
301
|
-
|
302
|
-
.gsub(/^\.+|\.+$/, '')
|
303
|
-
.strip[0, 255]
|
279
|
+
def test_case_callback(test_case)
|
280
|
+
@logger.info("Sending Response: test_case_result: #{test_case[:id]}")
|
281
|
+
write({type: "test_case_result", data: test_case})
|
304
282
|
end
|
305
283
|
|
306
|
-
def
|
307
|
-
|
308
|
-
|
309
|
-
|
310
|
-
|
311
|
-
An error occurred. Please rerun with --debug
|
312
|
-
and contact support at https://selective.ci/support
|
313
|
-
TEXT
|
314
|
-
|
315
|
-
unless @banner_displayed
|
316
|
-
header = <<~TEXT
|
317
|
-
#{banner}
|
318
|
-
|
319
|
-
#{header}
|
320
|
-
TEXT
|
284
|
+
def modified_test_files(diff)
|
285
|
+
@modified_test_files ||= begin
|
286
|
+
diff.filter do |f|
|
287
|
+
f.match?(/^#{runner.base_test_path}/)
|
288
|
+
end
|
321
289
|
end
|
322
|
-
|
323
|
-
puts_indented <<~TEXT
|
324
|
-
\e[31m
|
325
|
-
#{header if include_header}
|
326
|
-
#{e.message}
|
327
|
-
\e[0m
|
328
|
-
TEXT
|
329
|
-
|
330
|
-
exit 1
|
331
290
|
end
|
332
291
|
|
333
|
-
def
|
334
|
-
|
335
|
-
|
336
|
-
#{message}
|
337
|
-
\e[0m
|
338
|
-
TEXT
|
339
|
-
end
|
292
|
+
def get_diff(num_commits)
|
293
|
+
target_branch = build_env["target_branch"]
|
294
|
+
return [] if target_branch.nil? || target_branch.empty?
|
340
295
|
|
341
|
-
|
342
|
-
|
343
|
-
|
344
|
-
|
345
|
-
|
346
|
-
|
296
|
+
Open3.capture2e("git fetch origin #{target_branch} --depth=#{num_commits}").then do |output, status|
|
297
|
+
unless status.success?
|
298
|
+
print_warning "Selective was unable to fetch the target branch. This may result in a sub-optimal test order. If the issue persists, please contact support. The output was:\n\n#{output}"
|
299
|
+
return []
|
300
|
+
end
|
301
|
+
end
|
347
302
|
|
348
|
-
|
349
|
-
|
303
|
+
Open3.capture2e("git diff origin/#{target_branch} --name-only").then do |output, status|
|
304
|
+
if status.success?
|
305
|
+
output.split("\n")
|
306
|
+
else
|
307
|
+
print_warning "Selective was unable to diff with the target branch. This may result in a sub-optimal test order. If the issue persists, please contact support. The output was:\n\n#{output}"
|
308
|
+
[]
|
309
|
+
end
|
310
|
+
end
|
350
311
|
end
|
351
312
|
|
352
|
-
def
|
353
|
-
@
|
354
|
-
<<~BANNER
|
355
|
-
____ _ _ _
|
356
|
-
/ ___| ___| | ___ ___| |_(_)_ _____
|
357
|
-
\\___ \\ / _ \\ |/ _ \\/ __| __| \\ \\ / / _ \\
|
358
|
-
___) | __/ | __/ (__| |_| |\\ V / __/
|
359
|
-
|____/ \\___|_|\\___|\\___|\\__|_| \\_/ \\___|
|
360
|
-
________________________________________
|
361
|
-
BANNER
|
313
|
+
def debug?
|
314
|
+
@debug
|
362
315
|
end
|
363
316
|
end
|
364
317
|
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
module Selective
|
2
|
+
module Ruby
|
3
|
+
module Core
|
4
|
+
class FileCorrelator
|
5
|
+
include Helper
|
6
|
+
|
7
|
+
class FileCorrelatorError < StandardError; end
|
8
|
+
|
9
|
+
FILE_CORRELATION_COLLECTOR_PATH = File.join(ROOT_GEM_PATH, "lib", "bin", "file_correlation_collector.sh")
|
10
|
+
|
11
|
+
def initialize(diff, num_commits, target_branch)
|
12
|
+
@diff = diff
|
13
|
+
@num_commits = num_commits
|
14
|
+
@target_branch = target_branch
|
15
|
+
end
|
16
|
+
|
17
|
+
def correlate
|
18
|
+
JSON.parse(get_correlated_files, symbolize_names: true)
|
19
|
+
rescue FileCorrelatorError, JSON::ParserError
|
20
|
+
print_warning "Selective was unable to correlate the diff to test files. This may result in a sub-optimal test order. If the issue persists, please contact support."
|
21
|
+
end
|
22
|
+
|
23
|
+
private
|
24
|
+
|
25
|
+
attr_reader :diff, :num_commits, :target_branch
|
26
|
+
|
27
|
+
def get_correlated_files
|
28
|
+
Open3.capture2e("#{FILE_CORRELATION_COLLECTOR_PATH} #{target_branch} #{num_commits} #{diff.join(" ")}").then do |output, status|
|
29
|
+
|
30
|
+
raise FileCorrelatorError unless status.success?
|
31
|
+
|
32
|
+
output
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,76 @@
|
|
1
|
+
module Selective
|
2
|
+
module Ruby
|
3
|
+
module Core
|
4
|
+
module Helper
|
5
|
+
def safe_filename(filename)
|
6
|
+
filename
|
7
|
+
.gsub(/[\/\\:*?"<>|\n\r]+/, '_')
|
8
|
+
.gsub(/^\.+|\.+$/, '')
|
9
|
+
.strip[0, 255]
|
10
|
+
end
|
11
|
+
|
12
|
+
def with_error_handling(include_header: true)
|
13
|
+
yield
|
14
|
+
rescue => e
|
15
|
+
raise e if debug?
|
16
|
+
header = <<~TEXT
|
17
|
+
An error occurred. Please rerun with --debug
|
18
|
+
and contact support at https://selective.ci/support
|
19
|
+
TEXT
|
20
|
+
|
21
|
+
unless $selective_banner_displayed
|
22
|
+
header = <<~TEXT
|
23
|
+
#{banner}
|
24
|
+
|
25
|
+
#{header}
|
26
|
+
TEXT
|
27
|
+
end
|
28
|
+
|
29
|
+
puts_indented <<~TEXT
|
30
|
+
\e[31m
|
31
|
+
#{header if include_header}
|
32
|
+
#{e.message}
|
33
|
+
\e[0m
|
34
|
+
TEXT
|
35
|
+
|
36
|
+
exit 1
|
37
|
+
end
|
38
|
+
|
39
|
+
def print_warning(message)
|
40
|
+
puts_indented <<~TEXT
|
41
|
+
\e[33m
|
42
|
+
#{message}
|
43
|
+
\e[0m
|
44
|
+
TEXT
|
45
|
+
end
|
46
|
+
|
47
|
+
def print_notice(message)
|
48
|
+
puts_indented <<~TEXT
|
49
|
+
#{banner unless $selective_banner_displayed}
|
50
|
+
#{message}
|
51
|
+
TEXT
|
52
|
+
end
|
53
|
+
|
54
|
+
def puts_indented(text)
|
55
|
+
puts text.gsub(/^/, " ")
|
56
|
+
end
|
57
|
+
|
58
|
+
def banner
|
59
|
+
Helper.banner
|
60
|
+
end
|
61
|
+
|
62
|
+
def self.banner
|
63
|
+
$selective_banner_displayed = true
|
64
|
+
<<~BANNER
|
65
|
+
____ _ _ _
|
66
|
+
/ ___| ___| | ___ ___| |_(_)_ _____
|
67
|
+
\\___ \\ / _ \\ |/ _ \\/ __| __| \\ \\ / / _ \\
|
68
|
+
___) | __/ | __/ (__| |_| |\\ V / __/
|
69
|
+
|____/ \\___|_|\\___|\\___|\\__|_| \\_/ \\___|
|
70
|
+
________________________________________
|
71
|
+
BANNER
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
data/lib/selective-ruby-core.rb
CHANGED
@@ -1,6 +1,9 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require "zeitwerk"
|
4
|
+
require "json"
|
5
|
+
require "open3"
|
6
|
+
require "#{__dir__}/selective/ruby/core/version"
|
4
7
|
|
5
8
|
loader = Zeitwerk::Loader.for_gem(warn_on_extra_files: false)
|
6
9
|
loader.ignore("#{__dir__}/selective-ruby-core.rb")
|
@@ -12,6 +15,8 @@ module Selective
|
|
12
15
|
module Core
|
13
16
|
class Error < StandardError; end
|
14
17
|
|
18
|
+
ROOT_GEM_PATH = Gem.loaded_specs["selective-ruby-core"].full_gem_path
|
19
|
+
|
15
20
|
@@available_runners = {}
|
16
21
|
|
17
22
|
def self.register_runner(name, runner_class)
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: selective-ruby-core
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.7
|
5
5
|
platform: x86_64-linux
|
6
6
|
authors:
|
7
7
|
- Benjamin Wood
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: exe
|
11
11
|
cert_chain: []
|
12
|
-
date:
|
12
|
+
date: 2024-01-05 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: zeitwerk
|
@@ -41,9 +41,12 @@ files:
|
|
41
41
|
- Rakefile
|
42
42
|
- exe/selective
|
43
43
|
- lib/bin/build_env.sh
|
44
|
+
- lib/bin/file_correlation_collector.sh
|
44
45
|
- lib/bin/transport
|
45
46
|
- lib/selective-ruby-core.rb
|
46
47
|
- lib/selective/ruby/core/controller.rb
|
48
|
+
- lib/selective/ruby/core/file_correlator.rb
|
49
|
+
- lib/selective/ruby/core/helper.rb
|
47
50
|
- lib/selective/ruby/core/named_pipe.rb
|
48
51
|
- lib/selective/ruby/core/version.rb
|
49
52
|
homepage: https://www.selective.ci
|