codeclimate-fede 0.85.23
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/bin/check +18 -0
- data/bin/codeclimate +21 -0
- data/bin/prep-release +45 -0
- data/bin/release +41 -0
- data/bin/validate-release +18 -0
- data/config/engines.yml +322 -0
- data/lib/cc/analyzer.rb +50 -0
- data/lib/cc/analyzer/bridge.rb +106 -0
- data/lib/cc/analyzer/composite_container_listener.rb +21 -0
- data/lib/cc/analyzer/container.rb +208 -0
- data/lib/cc/analyzer/container/result.rb +74 -0
- data/lib/cc/analyzer/container_listener.rb +9 -0
- data/lib/cc/analyzer/engine.rb +125 -0
- data/lib/cc/analyzer/engine_output.rb +74 -0
- data/lib/cc/analyzer/engine_output_filter.rb +36 -0
- data/lib/cc/analyzer/engine_output_overrider.rb +31 -0
- data/lib/cc/analyzer/filesystem.rb +50 -0
- data/lib/cc/analyzer/formatters.rb +21 -0
- data/lib/cc/analyzer/formatters/formatter.rb +53 -0
- data/lib/cc/analyzer/formatters/html_formatter.rb +415 -0
- data/lib/cc/analyzer/formatters/json_formatter.rb +38 -0
- data/lib/cc/analyzer/formatters/plain_text_formatter.rb +101 -0
- data/lib/cc/analyzer/formatters/spinner.rb +35 -0
- data/lib/cc/analyzer/issue.rb +69 -0
- data/lib/cc/analyzer/issue_sorter.rb +30 -0
- data/lib/cc/analyzer/issue_validations.rb +26 -0
- data/lib/cc/analyzer/issue_validations/category_validation.rb +32 -0
- data/lib/cc/analyzer/issue_validations/check_name_presence_validation.rb +15 -0
- data/lib/cc/analyzer/issue_validations/content_validation.rb +21 -0
- data/lib/cc/analyzer/issue_validations/description_presence_validation.rb +15 -0
- data/lib/cc/analyzer/issue_validations/location_format_validation.rb +72 -0
- data/lib/cc/analyzer/issue_validations/other_locations_format_validation.rb +41 -0
- data/lib/cc/analyzer/issue_validations/path_existence_validation.rb +15 -0
- data/lib/cc/analyzer/issue_validations/path_is_file_validation.rb +15 -0
- data/lib/cc/analyzer/issue_validations/path_presence_validation.rb +15 -0
- data/lib/cc/analyzer/issue_validations/relative_path_validation.rb +32 -0
- data/lib/cc/analyzer/issue_validations/remediation_points_validation.rb +25 -0
- data/lib/cc/analyzer/issue_validations/severity_validation.rb +39 -0
- data/lib/cc/analyzer/issue_validations/type_validation.rb +15 -0
- data/lib/cc/analyzer/issue_validations/validation.rb +35 -0
- data/lib/cc/analyzer/issue_validator.rb +11 -0
- data/lib/cc/analyzer/location_description.rb +45 -0
- data/lib/cc/analyzer/logging_container_listener.rb +24 -0
- data/lib/cc/analyzer/measurement.rb +22 -0
- data/lib/cc/analyzer/measurement_validations.rb +16 -0
- data/lib/cc/analyzer/measurement_validations/name_validation.rb +23 -0
- data/lib/cc/analyzer/measurement_validations/type_validation.rb +15 -0
- data/lib/cc/analyzer/measurement_validations/validation.rb +27 -0
- data/lib/cc/analyzer/measurement_validations/value_validation.rb +21 -0
- data/lib/cc/analyzer/measurement_validator.rb +11 -0
- data/lib/cc/analyzer/mounted_path.rb +80 -0
- data/lib/cc/analyzer/raising_container_listener.rb +32 -0
- data/lib/cc/analyzer/source_buffer.rb +47 -0
- data/lib/cc/analyzer/source_extractor.rb +79 -0
- data/lib/cc/analyzer/source_fingerprint.rb +40 -0
- data/lib/cc/analyzer/statsd_container_listener.rb +51 -0
- data/lib/cc/analyzer/validator.rb +38 -0
- data/lib/cc/cli.rb +39 -0
- data/lib/cc/cli/analyze.rb +90 -0
- data/lib/cc/cli/analyze/engine_failure.rb +11 -0
- data/lib/cc/cli/command.rb +85 -0
- data/lib/cc/cli/console.rb +12 -0
- data/lib/cc/cli/engines.rb +5 -0
- data/lib/cc/cli/engines/engine_command.rb +15 -0
- data/lib/cc/cli/engines/install.rb +35 -0
- data/lib/cc/cli/engines/list.rb +18 -0
- data/lib/cc/cli/file_store.rb +42 -0
- data/lib/cc/cli/global_cache.rb +47 -0
- data/lib/cc/cli/global_config.rb +35 -0
- data/lib/cc/cli/help.rb +51 -0
- data/lib/cc/cli/output.rb +34 -0
- data/lib/cc/cli/prepare.rb +98 -0
- data/lib/cc/cli/runner.rb +75 -0
- data/lib/cc/cli/validate_config.rb +84 -0
- data/lib/cc/cli/version.rb +16 -0
- data/lib/cc/cli/version_checker.rb +107 -0
- data/lib/cc/config.rb +70 -0
- data/lib/cc/config/checks_adapter.rb +40 -0
- data/lib/cc/config/default_adapter.rb +54 -0
- data/lib/cc/config/engine.rb +41 -0
- data/lib/cc/config/engine_set.rb +47 -0
- data/lib/cc/config/json_adapter.rb +17 -0
- data/lib/cc/config/prepare.rb +92 -0
- data/lib/cc/config/validation/check_validator.rb +34 -0
- data/lib/cc/config/validation/engine_validator.rb +93 -0
- data/lib/cc/config/validation/fetch_validator.rb +78 -0
- data/lib/cc/config/validation/file_validator.rb +112 -0
- data/lib/cc/config/validation/hash_validations.rb +52 -0
- data/lib/cc/config/validation/json.rb +31 -0
- data/lib/cc/config/validation/prepare_validator.rb +40 -0
- data/lib/cc/config/validation/yaml.rb +66 -0
- data/lib/cc/config/yaml_adapter.rb +73 -0
- data/lib/cc/engine_registry.rb +74 -0
- data/lib/cc/resolv.rb +39 -0
- data/lib/cc/workspace.rb +39 -0
- data/lib/cc/workspace/exclusion.rb +34 -0
- data/lib/cc/workspace/path_tree.rb +49 -0
- data/lib/cc/workspace/path_tree/dir_node.rb +67 -0
- data/lib/cc/workspace/path_tree/file_node.rb +31 -0
- metadata +277 -0
data/lib/cc/analyzer.rb
ADDED
@@ -0,0 +1,50 @@
|
|
1
|
+
require "yaml"
|
2
|
+
|
3
|
+
module CC
|
4
|
+
module Analyzer
|
5
|
+
autoload :Bridge, "cc/analyzer/bridge"
|
6
|
+
autoload :CompositeContainerListener, "cc/analyzer/composite_container_listener"
|
7
|
+
autoload :Container, "cc/analyzer/container"
|
8
|
+
autoload :ContainerListener, "cc/analyzer/container_listener"
|
9
|
+
autoload :Engine, "cc/analyzer/engine"
|
10
|
+
autoload :EngineOutput, "cc/analyzer/engine_output"
|
11
|
+
autoload :EngineOutputFilter, "cc/analyzer/engine_output_filter"
|
12
|
+
autoload :EngineOutputOverrider, "cc/analyzer/engine_output_overrider"
|
13
|
+
autoload :Filesystem, "cc/analyzer/filesystem"
|
14
|
+
autoload :Formatters, "cc/analyzer/formatters"
|
15
|
+
autoload :Issue, "cc/analyzer/issue"
|
16
|
+
autoload :IssueSorter, "cc/analyzer/issue_sorter"
|
17
|
+
autoload :IssueValidations, "cc/analyzer/issue_validations"
|
18
|
+
autoload :IssueValidator, "cc/analyzer/issue_validator"
|
19
|
+
autoload :LocationDescription, "cc/analyzer/location_description"
|
20
|
+
autoload :LoggingContainerListener, "cc/analyzer/logging_container_listener"
|
21
|
+
autoload :Measurement, "cc/analyzer/measurement"
|
22
|
+
autoload :MeasurementValidations, "cc/analyzer/measurement_validations"
|
23
|
+
autoload :MeasurementValidator, "cc/analyzer/measurement_validator"
|
24
|
+
autoload :MountedPath, "cc/analyzer/mounted_path"
|
25
|
+
autoload :RaisingContainerListener, "cc/analyzer/raising_container_listener"
|
26
|
+
autoload :SourceBuffer, "cc/analyzer/source_buffer"
|
27
|
+
autoload :SourceExtractor, "cc/analyzer/source_extractor"
|
28
|
+
autoload :SourceFingerprint, "cc/analyzer/source_fingerprint"
|
29
|
+
autoload :StatsdContainerListener, "cc/analyzer/statsd_container_listener"
|
30
|
+
autoload :Validator, "cc/analyzer/validator"
|
31
|
+
|
32
|
+
class DummyStatsd
|
33
|
+
def method_missing(*)
|
34
|
+
yield if block_given?
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
class DummyLogger
|
39
|
+
def method_missing(*)
|
40
|
+
yield if block_given?
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
cattr_accessor :statsd, :logger
|
45
|
+
self.statsd = DummyStatsd.new
|
46
|
+
self.logger = DummyLogger.new
|
47
|
+
|
48
|
+
UnreadableFileError = Class.new(StandardError)
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,106 @@
|
|
1
|
+
module CC
|
2
|
+
module Analyzer
|
3
|
+
# The shared interface, invoked by Builder or CLI::Analyze
|
4
|
+
#
|
5
|
+
# Input:
|
6
|
+
# - config
|
7
|
+
# - engines
|
8
|
+
# - exclude_patterns
|
9
|
+
# - development?
|
10
|
+
# - analysis_paths
|
11
|
+
# - formatter
|
12
|
+
# - started
|
13
|
+
# - engine_running
|
14
|
+
# - finished
|
15
|
+
# - close
|
16
|
+
# - listener
|
17
|
+
# - started(engine, details)
|
18
|
+
# - finished(engine, details, result)
|
19
|
+
# - registry
|
20
|
+
#
|
21
|
+
# Only raises if Listener raises
|
22
|
+
#
|
23
|
+
class Bridge
|
24
|
+
def initialize(config:, formatter:, listener:, registry:)
|
25
|
+
@config = config
|
26
|
+
@formatter = formatter
|
27
|
+
@listener = listener
|
28
|
+
@registry = registry
|
29
|
+
end
|
30
|
+
|
31
|
+
def run
|
32
|
+
formatter.started
|
33
|
+
|
34
|
+
config.engines.each do |engine|
|
35
|
+
next unless engine.enabled?
|
36
|
+
|
37
|
+
formatter.engine_running(engine) do
|
38
|
+
result = nil
|
39
|
+
engine_details = nil
|
40
|
+
|
41
|
+
begin
|
42
|
+
engine_details = registry.fetch_engine_details(
|
43
|
+
engine,
|
44
|
+
development: config.development?,
|
45
|
+
)
|
46
|
+
listener.started(engine, engine_details)
|
47
|
+
result = run_engine(engine, engine_details)
|
48
|
+
rescue CC::EngineRegistry::EngineDetailsNotFoundError => ex
|
49
|
+
result = Container::Result.skipped(ex)
|
50
|
+
end
|
51
|
+
|
52
|
+
listener.finished(engine, engine_details, result)
|
53
|
+
result
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
formatter.finished
|
58
|
+
ensure
|
59
|
+
formatter.close
|
60
|
+
end
|
61
|
+
|
62
|
+
private
|
63
|
+
|
64
|
+
attr_reader :config, :formatter, :listener, :registry
|
65
|
+
|
66
|
+
def run_engine(engine, engine_details)
|
67
|
+
# Analyzer::Engine doesn't have the best interface, but we're limiting
|
68
|
+
# our refactors for now.
|
69
|
+
Engine.new(
|
70
|
+
engine.name,
|
71
|
+
{
|
72
|
+
"image" => engine_details.image,
|
73
|
+
"command" => engine_details.command,
|
74
|
+
"memory" => engine_details.memory,
|
75
|
+
},
|
76
|
+
engine.config.merge(
|
77
|
+
"channel" => engine.channel,
|
78
|
+
"include_paths" => engine_workspace(engine).paths,
|
79
|
+
),
|
80
|
+
engine.container_label,
|
81
|
+
).run(formatter)
|
82
|
+
end
|
83
|
+
|
84
|
+
def engine_workspace(engine)
|
85
|
+
if engine.exclude_patterns.any?
|
86
|
+
workspace.clone.tap do |engine_workspace|
|
87
|
+
engine_workspace.remove(engine.exclude_patterns)
|
88
|
+
end
|
89
|
+
else
|
90
|
+
workspace
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
def workspace
|
95
|
+
@workspace ||= Workspace.new.tap do |workspace|
|
96
|
+
workspace.add(config.analysis_paths)
|
97
|
+
|
98
|
+
unless config.analysis_paths.any?
|
99
|
+
workspace.remove([".git"])
|
100
|
+
workspace.remove(config.exclude_patterns)
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module CC
|
2
|
+
module Analyzer
|
3
|
+
class CompositeContainerListener < ContainerListener
|
4
|
+
def initialize(*listeners)
|
5
|
+
@listeners = listeners
|
6
|
+
end
|
7
|
+
|
8
|
+
def started(*args)
|
9
|
+
listeners.each { |listener| listener.started(*args) }
|
10
|
+
end
|
11
|
+
|
12
|
+
def finished(*args)
|
13
|
+
listeners.each { |listener| listener.finished(*args) }
|
14
|
+
end
|
15
|
+
|
16
|
+
private
|
17
|
+
|
18
|
+
attr_reader :listeners
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,208 @@
|
|
1
|
+
require "posix/spawn"
|
2
|
+
require "thread"
|
3
|
+
|
4
|
+
require "cc/analyzer/container/result"
|
5
|
+
|
6
|
+
module CC
|
7
|
+
module Analyzer
|
8
|
+
#
|
9
|
+
# Running an abstract docker container
|
10
|
+
#
|
11
|
+
# Input:
|
12
|
+
# - image
|
13
|
+
# - name
|
14
|
+
# - command (Optional)
|
15
|
+
#
|
16
|
+
# Output:
|
17
|
+
# - Result
|
18
|
+
# - exit_status
|
19
|
+
# - timed_out?
|
20
|
+
# - duration
|
21
|
+
# - maximum_output_exceeded?
|
22
|
+
# - output_byte_count
|
23
|
+
# - stderr
|
24
|
+
#
|
25
|
+
# Never raises (unless broken)
|
26
|
+
#
|
27
|
+
class Container
|
28
|
+
DEFAULT_TIMEOUT = 15 * 60 # 15m
|
29
|
+
DEFAULT_MAXIMUM_OUTPUT_BYTES = 500_000_000
|
30
|
+
|
31
|
+
def initialize(image:, name:, command: nil)
|
32
|
+
@image = image
|
33
|
+
@name = name
|
34
|
+
@command = command
|
35
|
+
@timed_out = false
|
36
|
+
@maximum_output_exceeded = false
|
37
|
+
@stdout_io = StringIO.new
|
38
|
+
@stderr_io = StringIO.new
|
39
|
+
@output_byte_count = 0
|
40
|
+
@counter_mutex = Mutex.new
|
41
|
+
|
42
|
+
# By default accumulate and include stdout in result
|
43
|
+
@output_delimeter = "\n"
|
44
|
+
@on_output = ->(output) { @stdout_io.puts(output) }
|
45
|
+
end
|
46
|
+
|
47
|
+
def on_output(delimeter = "\n", &block)
|
48
|
+
@output_delimeter = delimeter
|
49
|
+
@on_output = block
|
50
|
+
end
|
51
|
+
|
52
|
+
def run(options = [])
|
53
|
+
started = Time.now
|
54
|
+
|
55
|
+
command = docker_run_command(options)
|
56
|
+
Analyzer.logger.debug("docker run: #{command.inspect}")
|
57
|
+
pid, _, out, err = POSIX::Spawn.popen4(*command)
|
58
|
+
|
59
|
+
@t_out = read_stdout(out)
|
60
|
+
@t_err = read_stderr(err)
|
61
|
+
t_timeout = timeout_thread
|
62
|
+
|
63
|
+
# blocks until the engine stops. this is put in a thread so that we can
|
64
|
+
# explicitly abort it as part of #stop. otherwise a run-away container
|
65
|
+
# could still block here forever if the docker-kill/wait is not
|
66
|
+
# successful. there may still be stdout in flight if it was being
|
67
|
+
# produced more quickly than consumed.
|
68
|
+
@t_wait = Thread.new { _, @status = Process.waitpid2(pid) }
|
69
|
+
@t_wait.join
|
70
|
+
|
71
|
+
# blocks until all readers are done. they're still governed by the
|
72
|
+
# timeout thread at this point. if we hit the timeout while processing
|
73
|
+
# output, the threads will be Thread#killed as part of #stop and this
|
74
|
+
# will unblock with the correct value in @timed_out
|
75
|
+
[@t_out, @t_err].each(&:join)
|
76
|
+
|
77
|
+
duration =
|
78
|
+
if @timed_out
|
79
|
+
timeout * 1000
|
80
|
+
else
|
81
|
+
((Time.now - started) * 1000).round
|
82
|
+
end
|
83
|
+
|
84
|
+
Result.new(
|
85
|
+
container_name: @name,
|
86
|
+
duration: duration,
|
87
|
+
exit_status: @status&.exitstatus,
|
88
|
+
maximum_output_exceeded: @maximum_output_exceeded,
|
89
|
+
output_byte_count: output_byte_count,
|
90
|
+
stderr: @stderr_io.string,
|
91
|
+
stdout: @stdout_io.string,
|
92
|
+
timed_out: @timed_out,
|
93
|
+
)
|
94
|
+
ensure
|
95
|
+
kill_reader_threads
|
96
|
+
t_timeout&.kill
|
97
|
+
end
|
98
|
+
|
99
|
+
def stop(message = nil)
|
100
|
+
reap_running_container(message)
|
101
|
+
kill_reader_threads
|
102
|
+
kill_wait_thread
|
103
|
+
end
|
104
|
+
|
105
|
+
private
|
106
|
+
|
107
|
+
attr_reader :output_byte_count, :counter_mutex
|
108
|
+
|
109
|
+
def docker_run_command(options)
|
110
|
+
[
|
111
|
+
"docker", "run",
|
112
|
+
"--name", @name,
|
113
|
+
options,
|
114
|
+
@image,
|
115
|
+
@command
|
116
|
+
].flatten.compact
|
117
|
+
end
|
118
|
+
|
119
|
+
def read_stdout(out)
|
120
|
+
Thread.new do
|
121
|
+
out.each_line(@output_delimeter) do |chunk|
|
122
|
+
output = chunk.chomp(@output_delimeter)
|
123
|
+
|
124
|
+
Analyzer.logger.debug("engine stdout: #{output}")
|
125
|
+
@on_output.call(output)
|
126
|
+
check_output_bytes(output.bytesize)
|
127
|
+
end
|
128
|
+
ensure
|
129
|
+
out.close
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
def read_stderr(err)
|
134
|
+
Thread.new do
|
135
|
+
err.each_line do |line|
|
136
|
+
Analyzer.logger.debug("engine stderr: #{line.chomp}")
|
137
|
+
@stderr_io.write(line)
|
138
|
+
check_output_bytes(line.bytesize)
|
139
|
+
end
|
140
|
+
ensure
|
141
|
+
err.close
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
def timeout_thread
|
146
|
+
Thread.new do
|
147
|
+
# Doing one long `sleep timeout` seems to fail sometimes, so
|
148
|
+
# we do a series of short timeouts before exiting
|
149
|
+
start_time = Time.now
|
150
|
+
loop do
|
151
|
+
sleep 10
|
152
|
+
duration = Time.now - start_time
|
153
|
+
break if duration >= timeout
|
154
|
+
end
|
155
|
+
|
156
|
+
@timed_out = true
|
157
|
+
stop("timed out")
|
158
|
+
end.run
|
159
|
+
end
|
160
|
+
|
161
|
+
def check_output_bytes(last_read_byte_count)
|
162
|
+
counter_mutex.synchronize do
|
163
|
+
@output_byte_count += last_read_byte_count
|
164
|
+
end
|
165
|
+
|
166
|
+
if output_byte_count > maximum_output_bytes
|
167
|
+
@maximum_output_exceeded = true
|
168
|
+
stop("maximum output exceeded")
|
169
|
+
end
|
170
|
+
end
|
171
|
+
|
172
|
+
def kill_reader_threads
|
173
|
+
@t_out&.kill
|
174
|
+
@t_err&.kill
|
175
|
+
end
|
176
|
+
|
177
|
+
def kill_wait_thread
|
178
|
+
@t_wait&.kill
|
179
|
+
end
|
180
|
+
|
181
|
+
def reap_running_container(message)
|
182
|
+
Analyzer.logger.warn("killing container name=#{@name} message=#{message.inspect}")
|
183
|
+
POSIX::Spawn::Child.new("docker", "kill", @name, timeout: 2.minutes)
|
184
|
+
POSIX::Spawn::Child.new("docker", "wait", @name, timeout: 2.minutes)
|
185
|
+
rescue POSIX::Spawn::TimeoutExceeded
|
186
|
+
Analyzer.logger.error("unable to kill container name=#{@name} message=#{message.inspect}")
|
187
|
+
Analyzer.statsd.increment("container.zombie")
|
188
|
+
Analyzer.statsd.increment("container.zombie.#{metric_name}") if metric_name
|
189
|
+
end
|
190
|
+
|
191
|
+
def timeout
|
192
|
+
ENV.fetch("CONTAINER_TIMEOUT_SECONDS", DEFAULT_TIMEOUT).to_i
|
193
|
+
end
|
194
|
+
|
195
|
+
def maximum_output_bytes
|
196
|
+
ENV.fetch("CONTAINER_MAXIMUM_OUTPUT_BYTES", DEFAULT_MAXIMUM_OUTPUT_BYTES).to_i
|
197
|
+
end
|
198
|
+
|
199
|
+
def metric_name
|
200
|
+
if /^cc-engines-(?<engine>[^-]+)-(?<channel>[^-]+)-/ =~ @name
|
201
|
+
"engine.#{engine}.#{channel}"
|
202
|
+
elsif /^builder-(?<action>[^-]+)-/ =~ @name
|
203
|
+
"builder.#{action}"
|
204
|
+
end
|
205
|
+
end
|
206
|
+
end
|
207
|
+
end
|
208
|
+
end
|
@@ -0,0 +1,74 @@
|
|
1
|
+
module CC
|
2
|
+
module Analyzer
|
3
|
+
class Container
|
4
|
+
class Result
|
5
|
+
attr_reader \
|
6
|
+
:container_name,
|
7
|
+
:duration,
|
8
|
+
:exit_status,
|
9
|
+
:output_byte_count,
|
10
|
+
:stderr,
|
11
|
+
:stdout
|
12
|
+
|
13
|
+
def initialize(
|
14
|
+
container_name: "",
|
15
|
+
duration: 0,
|
16
|
+
exit_status: 0,
|
17
|
+
maximum_output_exceeded: false,
|
18
|
+
output_byte_count: 0,
|
19
|
+
skipped: false,
|
20
|
+
stderr: "",
|
21
|
+
stdout: "",
|
22
|
+
timed_out: false
|
23
|
+
)
|
24
|
+
@container_name = container_name
|
25
|
+
@duration = duration
|
26
|
+
@exit_status = exit_status
|
27
|
+
@maximum_output_exceeded = maximum_output_exceeded
|
28
|
+
@output_byte_count = output_byte_count
|
29
|
+
@skipped = skipped
|
30
|
+
@stderr = stderr
|
31
|
+
@stdout = stdout
|
32
|
+
@timed_out = timed_out
|
33
|
+
end
|
34
|
+
|
35
|
+
def self.skipped(ex)
|
36
|
+
new(
|
37
|
+
exit_status: 0,
|
38
|
+
skipped: true,
|
39
|
+
stderr: ex.message,
|
40
|
+
)
|
41
|
+
end
|
42
|
+
|
43
|
+
def merge_from_exception(ex)
|
44
|
+
self.exit_status = 99
|
45
|
+
self.stderr = ex.message
|
46
|
+
self
|
47
|
+
end
|
48
|
+
|
49
|
+
def timed_out?
|
50
|
+
@timed_out
|
51
|
+
end
|
52
|
+
|
53
|
+
def maximum_output_exceeded?
|
54
|
+
@maximum_output_exceeded
|
55
|
+
end
|
56
|
+
|
57
|
+
def errored?
|
58
|
+
timed_out? ||
|
59
|
+
maximum_output_exceeded? ||
|
60
|
+
exit_status.nil? ||
|
61
|
+
exit_status.nonzero?
|
62
|
+
end
|
63
|
+
|
64
|
+
def skipped?
|
65
|
+
@skipped
|
66
|
+
end
|
67
|
+
|
68
|
+
private
|
69
|
+
|
70
|
+
attr_writer :exit_status, :stderr
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|