ci_runner 0.1.0 → 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
|