ci_runner 0.1.0 → 0.3.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/ci_runner.gemspec +1 -0
- data/lib/ci_runner/check/base.rb +63 -0
- data/lib/ci_runner/check/buildkite.rb +88 -0
- data/lib/ci_runner/check/circle_ci.rb +128 -0
- data/lib/ci_runner/check/concurrent_download.rb +57 -0
- data/lib/ci_runner/check/github.rb +40 -0
- data/lib/ci_runner/check/unsupported.rb +33 -0
- data/lib/ci_runner/cli.rb +116 -24
- data/lib/ci_runner/client/authenticated_buildkite.rb +67 -0
- data/lib/ci_runner/client/base.rb +78 -0
- data/lib/ci_runner/client/buildkite.rb +60 -0
- data/lib/ci_runner/client/circle_ci.rb +58 -0
- data/lib/ci_runner/client/error.rb +27 -0
- data/lib/ci_runner/client/github.rb +88 -0
- data/lib/ci_runner/configuration/user.rb +48 -0
- data/lib/ci_runner/log_downloader.rb +20 -20
- data/lib/ci_runner/runners/base.rb +6 -4
- data/lib/ci_runner/runners/minitest_runner.rb +1 -1
- data/lib/ci_runner/runners/rspec.rb +1 -1
- data/lib/ci_runner/test_failure.rb +3 -0
- data/lib/ci_runner/test_run_finder.rb +81 -18
- data/lib/ci_runner/version.rb +1 -1
- data/lib/ci_runner/version_verifier.rb +53 -0
- data/lib/ci_runner.rb +22 -6
- metadata +17 -4
- data/lib/ci_runner/github_client.rb +0 -105
@@ -0,0 +1,67 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "base"
|
4
|
+
require "stringio"
|
5
|
+
|
6
|
+
module CIRunner
|
7
|
+
module Client
|
8
|
+
# Client used to retrieve private Resources on buildkite.
|
9
|
+
#
|
10
|
+
# For public resources, the API can be used but only a limited number of users will be
|
11
|
+
# be able to access it as it requires a token scoped for the organization (most users
|
12
|
+
# working on opensource project aren't member of the organization they contribute to).
|
13
|
+
#
|
14
|
+
# @see https://forum.buildkite.community/t/api-access-to-public-builds/1425/2
|
15
|
+
# @see Client::Buildkite
|
16
|
+
#
|
17
|
+
class AuthenticatedBuildkite < Base
|
18
|
+
API_ENDPOINT = "api.buildkite.com"
|
19
|
+
|
20
|
+
# Retrieve URLs to download job logs for all steps.
|
21
|
+
#
|
22
|
+
# @param org [String] The organizatio name.
|
23
|
+
# @param pipeline [String] The pipeline name.
|
24
|
+
# @param number [Integer] The build number.
|
25
|
+
#
|
26
|
+
# @return [Array<String>] An array of URLs
|
27
|
+
#
|
28
|
+
# @see https://buildkite.com/docs/apis/rest-api/builds#get-a-build
|
29
|
+
def job_logs(org, pipeline, number)
|
30
|
+
build = get("/v2/organizations/#{org}/pipelines/#{pipeline}/builds/#{number}")
|
31
|
+
|
32
|
+
build["jobs"].map do |job|
|
33
|
+
job["raw_log_url"]
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
# @param url [String] A URL pointing to a log output resource.
|
38
|
+
#
|
39
|
+
# @return [StringIO]
|
40
|
+
#
|
41
|
+
# @see https://buildkite.com/docs/apis/rest-api/jobs#get-a-jobs-log-output
|
42
|
+
def download_log(url)
|
43
|
+
StringIO.new(get(url))
|
44
|
+
end
|
45
|
+
|
46
|
+
# Get information about an access token. Used to check if the token has the correct scopes.
|
47
|
+
#
|
48
|
+
# @see https://buildkite.com/docs/apis/rest-api/access-token
|
49
|
+
#
|
50
|
+
# @return [Hash] See Buildkite doc
|
51
|
+
def access_token
|
52
|
+
get("/v2/access-token")
|
53
|
+
end
|
54
|
+
|
55
|
+
private
|
56
|
+
|
57
|
+
# Add authentication before making the request.
|
58
|
+
#
|
59
|
+
# @param request [Net::HTTPRequest] A subclass of Net::HTTPRequest.
|
60
|
+
#
|
61
|
+
# @return [void]
|
62
|
+
def authentication(request)
|
63
|
+
request["Authorization"] = "Bearer #{@access_token}"
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
@@ -0,0 +1,78 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "json"
|
4
|
+
require "net/http"
|
5
|
+
require "openssl"
|
6
|
+
|
7
|
+
module CIRunner
|
8
|
+
module Client
|
9
|
+
class Base
|
10
|
+
# @return [Net::HTTP] An instance of Net:HTTP configured to make requests to the GitHub API endpoint.
|
11
|
+
def self.default_client
|
12
|
+
Net::HTTP.new(self::API_ENDPOINT, 443).tap do |http|
|
13
|
+
http.use_ssl = true
|
14
|
+
http.read_timeout = 3
|
15
|
+
http.write_timeout = 3
|
16
|
+
http.verify_mode = OpenSSL::SSL::VERIFY_PEER
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
# @param access_token [String] The access token with "repo" scope.
|
21
|
+
# @param client [Net::HTTP]
|
22
|
+
def initialize(access_token = nil, client = self.class.default_client)
|
23
|
+
@access_token = access_token
|
24
|
+
@client = client
|
25
|
+
end
|
26
|
+
|
27
|
+
# Set a new Client object.
|
28
|
+
# NET::HTTP is not threadsafe so each time we need to make requests concurrently we need to use a new client.
|
29
|
+
#
|
30
|
+
# @return [void]
|
31
|
+
def reset!
|
32
|
+
@client = self.class.default_client
|
33
|
+
end
|
34
|
+
|
35
|
+
private
|
36
|
+
|
37
|
+
# Add authentication before making the request.
|
38
|
+
#
|
39
|
+
# @param request [Net::HTTPRequest] A subclass of Net::HTTPRequest.
|
40
|
+
#
|
41
|
+
# @return [void]
|
42
|
+
def authentication(request)
|
43
|
+
end
|
44
|
+
|
45
|
+
# Perform an authenticated GET request.
|
46
|
+
#
|
47
|
+
# @param path [String] The resource to access.
|
48
|
+
#
|
49
|
+
# @return (See #request)
|
50
|
+
def get(path)
|
51
|
+
request(Net::HTTP::Get, path)
|
52
|
+
end
|
53
|
+
|
54
|
+
# Perform an authenticated request.
|
55
|
+
#
|
56
|
+
# @param verb_class [Net::HTTPRequest] A subclass of Net::HTTPRequest.
|
57
|
+
# @param path [String] The resource to access.
|
58
|
+
#
|
59
|
+
# @return [Hash, String] A decoded JSON response or a String pointing to the Location redirection.
|
60
|
+
def request(verb_class, path)
|
61
|
+
req = verb_class.new(path)
|
62
|
+
req["Accept"] = "application/json"
|
63
|
+
authentication(req)
|
64
|
+
|
65
|
+
response = @client.request(req)
|
66
|
+
|
67
|
+
case response.code.to_i
|
68
|
+
when 200..204
|
69
|
+
response.content_type == "application/json" ? JSON.parse(response.body) : response.body
|
70
|
+
when 302
|
71
|
+
response["Location"]
|
72
|
+
else
|
73
|
+
raise(Error.new(response.code, response.body, self.class.name.split("::").last))
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "base"
|
4
|
+
require "open-uri"
|
5
|
+
|
6
|
+
module CIRunner
|
7
|
+
module Client
|
8
|
+
# Client used for public Buildkite resources.
|
9
|
+
# Allow any users to download log output for builds that are in organizations they
|
10
|
+
# are not a member of.
|
11
|
+
#
|
12
|
+
# This client doesn't use the official buildkite API. The data returned are not exactly the same.
|
13
|
+
class Buildkite < Base
|
14
|
+
API_ENDPOINT = "buildkite.com"
|
15
|
+
|
16
|
+
# Check if the build is public and can be accessed without authentication.
|
17
|
+
#
|
18
|
+
# @param org [String] The organizatio name.
|
19
|
+
# @param pipeline [String] The pipeline name.
|
20
|
+
# @param number [Integer] The build number.
|
21
|
+
#
|
22
|
+
# @return [Boolean]
|
23
|
+
def public_build?(org, pipeline, build_number)
|
24
|
+
job_logs(org, pipeline, build_number)
|
25
|
+
|
26
|
+
true
|
27
|
+
rescue Error => e
|
28
|
+
return false if e.error_code == 403
|
29
|
+
|
30
|
+
raise(e)
|
31
|
+
end
|
32
|
+
|
33
|
+
# Retrieve URL paths to download job logs for all steps.
|
34
|
+
#
|
35
|
+
# @param org [String] The organizatio name.
|
36
|
+
# @param pipeline [String] The pipeline name.
|
37
|
+
# @param number [Integer] The build number.
|
38
|
+
#
|
39
|
+
# @return [Array<String>] An array of URL paths
|
40
|
+
def job_logs(org, pipeline, build_number)
|
41
|
+
@build ||= get("/#{org}/#{pipeline}/builds/#{build_number}")
|
42
|
+
|
43
|
+
@build["jobs"].map do |job|
|
44
|
+
job["base_path"] + "/raw_log"
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
# Download raw log output for a job.
|
49
|
+
#
|
50
|
+
# @param path [String] A URL path
|
51
|
+
#
|
52
|
+
# @return [Tempfile, IO] Depending on the size of the response. Quirk of URI.open.
|
53
|
+
def download_log(path)
|
54
|
+
redirection_url = get(path)
|
55
|
+
|
56
|
+
URI.open(redirection_url)
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "base"
|
4
|
+
|
5
|
+
module CIRunner
|
6
|
+
module Client
|
7
|
+
class CircleCI < Base
|
8
|
+
API_ENDPOINT = "circleci.com"
|
9
|
+
|
10
|
+
# Make an API request to get the authenticated user. Used to verify if the access token
|
11
|
+
# the user has stored in its config is valid.
|
12
|
+
#
|
13
|
+
# @return [Hash] See Circle CI documentation.
|
14
|
+
#
|
15
|
+
# @see https://circleci.com/docs/api/v1/index.html#user
|
16
|
+
def me
|
17
|
+
get("/api/v1.1/me")
|
18
|
+
end
|
19
|
+
|
20
|
+
# @param repository [String] The full repository name including the owner (rails/rails).
|
21
|
+
# @param build_number [Integer] The CircleCI build number.
|
22
|
+
#
|
23
|
+
# @see https://circleci.com/docs/api/v1/index.html#single-job
|
24
|
+
def job(repository, build_number)
|
25
|
+
get("/api/v1.1/project/github/#{repository}/#{build_number}")
|
26
|
+
rescue Error => e
|
27
|
+
reraise_with_reason(e)
|
28
|
+
end
|
29
|
+
|
30
|
+
private
|
31
|
+
|
32
|
+
# Add authentication before making the request.
|
33
|
+
#
|
34
|
+
# @param request [Net::HTTPRequest] A subclass of Net::HTTPRequest.
|
35
|
+
#
|
36
|
+
# @return [void]
|
37
|
+
def authentication(request)
|
38
|
+
request.basic_auth(@access_token, "") if @access_token
|
39
|
+
end
|
40
|
+
|
41
|
+
# @param error [Client::Error]
|
42
|
+
#
|
43
|
+
# @raise [Client::Error] A better error message in case of a 404.
|
44
|
+
def reraise_with_reason(error)
|
45
|
+
if @access_token.nil? && error.error_code == 404
|
46
|
+
raise(error, <<~EOM.rstrip)
|
47
|
+
404 while trying to fetch the CircleCI build.
|
48
|
+
|
49
|
+
{{warning:Please save a CircleCI token in your configuration.}}
|
50
|
+
{{command:ci_runner help circle_ci_token}}
|
51
|
+
EOM
|
52
|
+
else
|
53
|
+
raise(error)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module CIRunner
|
4
|
+
module Client
|
5
|
+
class Error < StandardError
|
6
|
+
attr_reader :error_code
|
7
|
+
|
8
|
+
# @param error_code [String] The HTTP status code.
|
9
|
+
# @param error_body [String] The response from the provider.
|
10
|
+
# @param provider [String] The name of the CI provider.
|
11
|
+
# @param message [String, nil]
|
12
|
+
def initialize(error_code, error_body, provider, message = nil)
|
13
|
+
@error_code = error_code.to_i
|
14
|
+
|
15
|
+
if message
|
16
|
+
super(message)
|
17
|
+
else
|
18
|
+
super(<<~EOM.rstrip)
|
19
|
+
Error while making a request to #{provider}. Code: #{error_code}
|
20
|
+
|
21
|
+
The response was: #{error_body}
|
22
|
+
EOM
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,88 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "base"
|
4
|
+
require "open-uri"
|
5
|
+
|
6
|
+
module CIRunner
|
7
|
+
module Client
|
8
|
+
# A simple client to interact the GitHub API.
|
9
|
+
#
|
10
|
+
# @example Using the client
|
11
|
+
# Github.new("access_token").me
|
12
|
+
class Github < Base
|
13
|
+
API_ENDPOINT = "api.github.com"
|
14
|
+
|
15
|
+
# Make an API request to get the authenticated user. Used to verify if the access token
|
16
|
+
# the user has stored in its config is valid.
|
17
|
+
#
|
18
|
+
# @return [Hash] See GitHub documentation.
|
19
|
+
#
|
20
|
+
# @see https://docs.github.com/en/rest/users/users#get-the-authenticated-user
|
21
|
+
def me
|
22
|
+
get("/user")
|
23
|
+
end
|
24
|
+
|
25
|
+
# Get the latest release of a repository.
|
26
|
+
#
|
27
|
+
# @param repository [String] The full repository name, including the owner (rails/rails)
|
28
|
+
#
|
29
|
+
# @return [Hash] See GitHub documentation.
|
30
|
+
#
|
31
|
+
# https://docs.github.com/en/rest/releases/releases#get-the-latest-release
|
32
|
+
def latest_release(repository)
|
33
|
+
get("/repos/#{repository}/releases/latest")
|
34
|
+
end
|
35
|
+
|
36
|
+
# Makes an API request to get the CI checks for the +commit+.
|
37
|
+
#
|
38
|
+
# @param repository [String] The full repository name, including the owner (rails/rails)
|
39
|
+
# @param commit [String] The Git commit that has been pushed to GitHub.
|
40
|
+
#
|
41
|
+
# @return [Hash] See GitHub documentation.
|
42
|
+
#
|
43
|
+
# @see https://docs.github.com/en/rest/checks/runs#list-check-runs-for-a-git-reference
|
44
|
+
def check_runs(repository, commit)
|
45
|
+
get("/repos/#{repository}/commits/#{commit}/check-runs")
|
46
|
+
end
|
47
|
+
|
48
|
+
# Makes an API request to get the Commit statuses for the +commit+.
|
49
|
+
#
|
50
|
+
# @param repository [String] The full repository name, including the owner (rails/rails)
|
51
|
+
# @param commit [String] The Git commit that has been pushed to GitHub.
|
52
|
+
#
|
53
|
+
# @return [Hash] See GitHub documentation.
|
54
|
+
#
|
55
|
+
# @see https://docs.github.com/en/rest/commits/statuses#list-commit-statuses-for-a-reference
|
56
|
+
def commit_statuses(repository, commit)
|
57
|
+
get("/repos/#{repository}/commits/#{commit}/statuses")
|
58
|
+
end
|
59
|
+
|
60
|
+
# Makes two requests to get the CI log for a check run.
|
61
|
+
# The first request returns a 302 containing a Location header poiting to a short lived url to download the log.
|
62
|
+
# The second request is to actually download the log.
|
63
|
+
#
|
64
|
+
# @param repository [String] The full repository name, including the owner (rails/rails)
|
65
|
+
# @param check_run_id [Integer] The GitHub ID of the check run.
|
66
|
+
#
|
67
|
+
# @return [Tempfile, IO] Depending on the size of the response. Quirk of URI.open.
|
68
|
+
#
|
69
|
+
# @see https://docs.github.com/en/rest/actions/workflow-jobs#download-job-logs-for-a-workflow-run
|
70
|
+
def download_log(repository, check_run_id)
|
71
|
+
download_url = get("/repos/#{repository}/actions/jobs/#{check_run_id}/logs")
|
72
|
+
|
73
|
+
URI.open(download_url)
|
74
|
+
end
|
75
|
+
|
76
|
+
private
|
77
|
+
|
78
|
+
# Add authentication before making the request.
|
79
|
+
#
|
80
|
+
# @param request [Net::HTTPRequest] A subclass of Net::HTTPRequest.
|
81
|
+
#
|
82
|
+
# @return [void]
|
83
|
+
def authentication(request)
|
84
|
+
request.basic_auth("user", @access_token)
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
@@ -39,6 +39,13 @@ module CIRunner
|
|
39
39
|
@yaml_config.dig("github", "token")
|
40
40
|
end
|
41
41
|
|
42
|
+
# Retrieve the stored CircleCI access token of the user.
|
43
|
+
#
|
44
|
+
# @return [String, nil] Depending if the user ran the `ci_runner circle_ci_token TOKEN` command.
|
45
|
+
def circle_ci_token
|
46
|
+
@yaml_config.dig("circle_ci", "token")
|
47
|
+
end
|
48
|
+
|
42
49
|
# Write the GitHub token to the user configuration file
|
43
50
|
#
|
44
51
|
# @param token [String] A valid GitHub access token.
|
@@ -50,6 +57,47 @@ module CIRunner
|
|
50
57
|
save!(@yaml_config)
|
51
58
|
end
|
52
59
|
|
60
|
+
# Write the Circle CI token to the user configuration file
|
61
|
+
#
|
62
|
+
# @param token [String] A valid Circle CI access token.
|
63
|
+
#
|
64
|
+
# @return [void]
|
65
|
+
def save_circle_ci_token(token)
|
66
|
+
@yaml_config["circle_ci"] = { "token" => token }
|
67
|
+
|
68
|
+
save!(@yaml_config)
|
69
|
+
end
|
70
|
+
|
71
|
+
# Retrieve the stored Buildkite access token of the user that has access to the +organization+.
|
72
|
+
#
|
73
|
+
# @return [String, nil] Depending if the user ran the `ci_runner buildkite TOKEN ORGANIZATION` command.
|
74
|
+
def buildkite_token(organization)
|
75
|
+
@yaml_config.dig("buildkite", "tokens", organization.downcase)
|
76
|
+
end
|
77
|
+
|
78
|
+
# Write the Buildkite token to the user configuration file.
|
79
|
+
#
|
80
|
+
# @param token [String] A valid Buildkite access token.
|
81
|
+
# @param organization [String] The name of the organization the token has access to.
|
82
|
+
#
|
83
|
+
# @return [void]
|
84
|
+
def save_buildkite_token(token, organization)
|
85
|
+
existing_tokens = @yaml_config.dig("buildkite", "tokens") || {}
|
86
|
+
existing_tokens[organization.downcase] = token
|
87
|
+
|
88
|
+
@yaml_config["buildkite"] = { "tokens" => existing_tokens }
|
89
|
+
|
90
|
+
save!(@yaml_config)
|
91
|
+
end
|
92
|
+
|
93
|
+
# @return [Pathname] The path of the CI Runner directory configuration.
|
94
|
+
#
|
95
|
+
# @example
|
96
|
+
# puts config_directory # ~/.ci_runner
|
97
|
+
def config_directory
|
98
|
+
config_file.dirname
|
99
|
+
end
|
100
|
+
|
53
101
|
# @return [Pathname] The path of the configuration file.
|
54
102
|
#
|
55
103
|
# @example
|
@@ -8,44 +8,44 @@ module CIRunner
|
|
8
8
|
# A PORO to help download and cache a GitHub CI log.
|
9
9
|
#
|
10
10
|
# @example Using the service
|
11
|
-
# log_dl = LogDownloader.new(
|
11
|
+
# log_dl = LogDownloader.new(
|
12
|
+
# CICheck::GitHub.new(
|
13
|
+
# "catanacorp/catana",
|
14
|
+
# "commit_sha",
|
15
|
+
# "Tests Ruby 2.7",
|
16
|
+
# "failed",
|
17
|
+
# 12345,
|
18
|
+
# )
|
19
|
+
# )
|
12
20
|
# log_file = log_dl.fetch
|
13
21
|
# puts log_file # => File
|
14
|
-
#
|
15
|
-
# @see https://docs.github.com/en/rest/actions/workflow-jobs#download-job-logs-for-a-workflow-run
|
16
22
|
class LogDownloader
|
17
|
-
# @param
|
18
|
-
|
19
|
-
# @param check_run [Hash] A GitHub CI check for which we want to download the log.
|
20
|
-
def initialize(commit, repository, check_run)
|
21
|
-
@commit = commit
|
22
|
-
@repository = repository
|
23
|
+
# @param check_run [Check::Base] A Base::Check subclass for which we want to download the log.
|
24
|
+
def initialize(check_run)
|
23
25
|
@check_run = check_run
|
24
26
|
end
|
25
27
|
|
26
|
-
#
|
28
|
+
# Ask the +@check_run+ to download the log from its CI or retrieve it from disk in case we previously downloaded it.
|
27
29
|
#
|
28
30
|
# @param block [Proc, Lambda] A proc that gets called if fetching the logs from GitHub fails. Allows the CLI to
|
29
31
|
# prematurely exit while cleaning up the CLI::UI frame.
|
30
32
|
#
|
31
|
-
# @return [
|
33
|
+
# @return [Pathname] The path to the log file.
|
32
34
|
def fetch(&block)
|
33
35
|
return cached_log if cached_log
|
34
36
|
|
35
|
-
github_client = GithubClient.new(Configuration::User.instance.github_token)
|
36
37
|
error = nil
|
37
38
|
|
38
|
-
::CLI::UI.spinner("Downloading CI logs from
|
39
|
-
|
40
|
-
|
41
|
-
cache_log(logfile)
|
42
|
-
rescue GithubClient::Error => e
|
39
|
+
::CLI::UI.spinner("Downloading CI logs from #{@check_run.provider}", auto_debrief: false) do
|
40
|
+
cache_log(@check_run.download_log)
|
41
|
+
rescue Client::Error, Error => e
|
43
42
|
error = e
|
44
43
|
|
45
44
|
::CLI::UI::Spinner::TASK_FAILED
|
46
45
|
end
|
47
46
|
|
48
47
|
block.call(error) if error
|
48
|
+
|
49
49
|
cached_log
|
50
50
|
end
|
51
51
|
|
@@ -74,14 +74,14 @@ module CIRunner
|
|
74
74
|
# @example Given a repository "rails/rails". A CI check called "Ruby 3.0". A commit "abcdef".
|
75
75
|
# puts computed_filed_path # ==> /var/tmpdir/T/.../rails/rails/log-abcdef-Ruby 3.0
|
76
76
|
def computed_file_path
|
77
|
-
normalized_run_name = @check_run
|
77
|
+
normalized_run_name = @check_run.name.tr("/", "_")
|
78
78
|
|
79
|
-
log_folder.join("log-#{@commit[0..12]}-#{normalized_run_name}.log")
|
79
|
+
log_folder.join("log-#{@check_run.commit[0..12]}-#{normalized_run_name}.log")
|
80
80
|
end
|
81
81
|
|
82
82
|
# @return [Pathname]
|
83
83
|
def log_folder
|
84
|
-
Pathname(Dir.tmpdir).join(@repository)
|
84
|
+
Pathname(Dir.tmpdir).join(@check_run.repository)
|
85
85
|
end
|
86
86
|
|
87
87
|
# @return [Pathname, false] Depending if the log has been downloaded before.
|
@@ -43,13 +43,15 @@ module CIRunner
|
|
43
43
|
# @return [void]
|
44
44
|
def parse!
|
45
45
|
@ci_log.each_line do |line|
|
46
|
-
|
46
|
+
line_no_ansi_color = line.gsub(/\e\[\d+m/, "")
|
47
|
+
|
48
|
+
case line_no_ansi_color
|
47
49
|
when seed_regex
|
48
50
|
@seed = first_matching_group(Regexp.last_match)
|
49
51
|
when ruby_detection_regex
|
50
52
|
@ruby_version = first_matching_group(Regexp.last_match)
|
51
53
|
|
52
|
-
@buffer <<
|
54
|
+
@buffer << line_no_ansi_color if buffering?
|
53
55
|
when gemfile_detection_regex
|
54
56
|
@gemfile = first_matching_group(Regexp.last_match)
|
55
57
|
when buffer_detection_regex
|
@@ -58,9 +60,9 @@ module CIRunner
|
|
58
60
|
@buffer.clear
|
59
61
|
end
|
60
62
|
|
61
|
-
@buffer <<
|
63
|
+
@buffer << line_no_ansi_color
|
62
64
|
else
|
63
|
-
@buffer <<
|
65
|
+
@buffer << line_no_ansi_color if buffering?
|
64
66
|
end
|
65
67
|
end
|
66
68
|
|
@@ -40,7 +40,7 @@ module CIRunner
|
|
40
40
|
def self.match?(ci_log)
|
41
41
|
default_reporter = %r{(Finished in) \d+\.\d{6}s, \d+\.\d{4} runs/s, \d+\.\d{4} assertions/s\.}
|
42
42
|
|
43
|
-
Regexp.union(default_reporter, SEED_REGEX
|
43
|
+
Regexp.union(default_reporter, SEED_REGEX).match?(ci_log)
|
44
44
|
end
|
45
45
|
|
46
46
|
# @return [String] See Runners::Base#report
|
@@ -56,6 +56,9 @@ module CIRunner
|
|
56
56
|
|
57
57
|
regex = %r{.*/?(test/.*?)\Z}
|
58
58
|
unless path.to_s.match?(regex)
|
59
|
+
# TODO(on: '2022-09-17', to: "edouard-chin") Revisit this as it's too brittle.
|
60
|
+
# If a test file doesn't live the in the `test/` root folder, this will raise an error.
|
61
|
+
# I should instead warn the user and move on.
|
59
62
|
raise "Can't create a relative path."
|
60
63
|
end
|
61
64
|
|