codeclimate-fede 0.85.21

Sign up to get free protection for your applications and to get access to all the features.
Files changed (102) hide show
  1. checksums.yaml +7 -0
  2. data/bin/check +18 -0
  3. data/bin/codeclimate +21 -0
  4. data/bin/prep-release +45 -0
  5. data/bin/publish +47 -0
  6. data/bin/release +41 -0
  7. data/bin/validate-release +18 -0
  8. data/config/engines.yml +322 -0
  9. data/lib/cc/analyzer/bridge.rb +106 -0
  10. data/lib/cc/analyzer/composite_container_listener.rb +21 -0
  11. data/lib/cc/analyzer/container/result.rb +74 -0
  12. data/lib/cc/analyzer/container.rb +208 -0
  13. data/lib/cc/analyzer/container_listener.rb +9 -0
  14. data/lib/cc/analyzer/engine.rb +125 -0
  15. data/lib/cc/analyzer/engine_output.rb +74 -0
  16. data/lib/cc/analyzer/engine_output_filter.rb +36 -0
  17. data/lib/cc/analyzer/engine_output_overrider.rb +31 -0
  18. data/lib/cc/analyzer/filesystem.rb +50 -0
  19. data/lib/cc/analyzer/formatters/formatter.rb +53 -0
  20. data/lib/cc/analyzer/formatters/html_formatter.rb +415 -0
  21. data/lib/cc/analyzer/formatters/json_formatter.rb +38 -0
  22. data/lib/cc/analyzer/formatters/plain_text_formatter.rb +101 -0
  23. data/lib/cc/analyzer/formatters/spinner.rb +35 -0
  24. data/lib/cc/analyzer/formatters.rb +21 -0
  25. data/lib/cc/analyzer/issue.rb +69 -0
  26. data/lib/cc/analyzer/issue_sorter.rb +30 -0
  27. data/lib/cc/analyzer/issue_validations/category_validation.rb +32 -0
  28. data/lib/cc/analyzer/issue_validations/check_name_presence_validation.rb +15 -0
  29. data/lib/cc/analyzer/issue_validations/content_validation.rb +21 -0
  30. data/lib/cc/analyzer/issue_validations/description_presence_validation.rb +15 -0
  31. data/lib/cc/analyzer/issue_validations/location_format_validation.rb +72 -0
  32. data/lib/cc/analyzer/issue_validations/other_locations_format_validation.rb +41 -0
  33. data/lib/cc/analyzer/issue_validations/path_existence_validation.rb +15 -0
  34. data/lib/cc/analyzer/issue_validations/path_is_file_validation.rb +15 -0
  35. data/lib/cc/analyzer/issue_validations/path_presence_validation.rb +15 -0
  36. data/lib/cc/analyzer/issue_validations/relative_path_validation.rb +32 -0
  37. data/lib/cc/analyzer/issue_validations/remediation_points_validation.rb +25 -0
  38. data/lib/cc/analyzer/issue_validations/severity_validation.rb +39 -0
  39. data/lib/cc/analyzer/issue_validations/type_validation.rb +15 -0
  40. data/lib/cc/analyzer/issue_validations/validation.rb +35 -0
  41. data/lib/cc/analyzer/issue_validations.rb +26 -0
  42. data/lib/cc/analyzer/issue_validator.rb +11 -0
  43. data/lib/cc/analyzer/location_description.rb +45 -0
  44. data/lib/cc/analyzer/logging_container_listener.rb +24 -0
  45. data/lib/cc/analyzer/measurement.rb +22 -0
  46. data/lib/cc/analyzer/measurement_validations/name_validation.rb +23 -0
  47. data/lib/cc/analyzer/measurement_validations/type_validation.rb +15 -0
  48. data/lib/cc/analyzer/measurement_validations/validation.rb +27 -0
  49. data/lib/cc/analyzer/measurement_validations/value_validation.rb +21 -0
  50. data/lib/cc/analyzer/measurement_validations.rb +16 -0
  51. data/lib/cc/analyzer/measurement_validator.rb +11 -0
  52. data/lib/cc/analyzer/mounted_path.rb +80 -0
  53. data/lib/cc/analyzer/raising_container_listener.rb +32 -0
  54. data/lib/cc/analyzer/source_buffer.rb +47 -0
  55. data/lib/cc/analyzer/source_extractor.rb +79 -0
  56. data/lib/cc/analyzer/source_fingerprint.rb +40 -0
  57. data/lib/cc/analyzer/statsd_container_listener.rb +51 -0
  58. data/lib/cc/analyzer/validator.rb +38 -0
  59. data/lib/cc/analyzer.rb +50 -0
  60. data/lib/cc/cli/analyze/engine_failure.rb +11 -0
  61. data/lib/cc/cli/analyze.rb +90 -0
  62. data/lib/cc/cli/command.rb +85 -0
  63. data/lib/cc/cli/console.rb +12 -0
  64. data/lib/cc/cli/engines/engine_command.rb +15 -0
  65. data/lib/cc/cli/engines/install.rb +35 -0
  66. data/lib/cc/cli/engines/list.rb +18 -0
  67. data/lib/cc/cli/engines.rb +5 -0
  68. data/lib/cc/cli/file_store.rb +42 -0
  69. data/lib/cc/cli/global_cache.rb +47 -0
  70. data/lib/cc/cli/global_config.rb +35 -0
  71. data/lib/cc/cli/help.rb +51 -0
  72. data/lib/cc/cli/output.rb +34 -0
  73. data/lib/cc/cli/prepare.rb +98 -0
  74. data/lib/cc/cli/runner.rb +75 -0
  75. data/lib/cc/cli/validate_config.rb +84 -0
  76. data/lib/cc/cli/version.rb +16 -0
  77. data/lib/cc/cli/version_checker.rb +107 -0
  78. data/lib/cc/cli.rb +39 -0
  79. data/lib/cc/config/checks_adapter.rb +40 -0
  80. data/lib/cc/config/default_adapter.rb +54 -0
  81. data/lib/cc/config/engine.rb +41 -0
  82. data/lib/cc/config/engine_set.rb +47 -0
  83. data/lib/cc/config/json_adapter.rb +17 -0
  84. data/lib/cc/config/prepare.rb +92 -0
  85. data/lib/cc/config/validation/check_validator.rb +34 -0
  86. data/lib/cc/config/validation/engine_validator.rb +93 -0
  87. data/lib/cc/config/validation/fetch_validator.rb +78 -0
  88. data/lib/cc/config/validation/file_validator.rb +112 -0
  89. data/lib/cc/config/validation/hash_validations.rb +52 -0
  90. data/lib/cc/config/validation/json.rb +31 -0
  91. data/lib/cc/config/validation/prepare_validator.rb +40 -0
  92. data/lib/cc/config/validation/yaml.rb +66 -0
  93. data/lib/cc/config/yaml_adapter.rb +73 -0
  94. data/lib/cc/config.rb +70 -0
  95. data/lib/cc/engine_registry.rb +74 -0
  96. data/lib/cc/resolv.rb +39 -0
  97. data/lib/cc/workspace/exclusion.rb +34 -0
  98. data/lib/cc/workspace/path_tree/dir_node.rb +67 -0
  99. data/lib/cc/workspace/path_tree/file_node.rb +31 -0
  100. data/lib/cc/workspace/path_tree.rb +49 -0
  101. data/lib/cc/workspace.rb +39 -0
  102. metadata +279 -0
@@ -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
@@ -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,9 @@
1
+ module CC
2
+ module Analyzer
3
+ class ContainerListener
4
+ def started(_engine, _details); end
5
+
6
+ def finished(_engine, _details, _result); end
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,125 @@
1
+ require "securerandom"
2
+
3
+ module CC
4
+ module Analyzer
5
+ #
6
+ # Running specifically an Engine container
7
+ #
8
+ # Input:
9
+ # - name
10
+ # - metadata
11
+ # - image
12
+ # - command (optional)
13
+ # - config (becomes /config.json)
14
+ # - label
15
+ # - io (to write filtered, validated output)
16
+ #
17
+ # Output:
18
+ # - Container::Result
19
+ #
20
+ class Engine
21
+ Error = Class.new(StandardError)
22
+
23
+ def initialize(name, metadata, config, label)
24
+ @name = name
25
+ @metadata = metadata
26
+ @config = config
27
+ @label = label.to_s
28
+ @error = nil
29
+ end
30
+
31
+ def run(io)
32
+ write_config_file
33
+
34
+ container = Container.new(
35
+ image: metadata.fetch("image"),
36
+ command: metadata["command"],
37
+ name: container_name,
38
+ )
39
+
40
+ container.on_output("\0") do |output|
41
+ handle_output(container, io, output)
42
+ end
43
+
44
+ container.run(container_options).tap do |result|
45
+ result.merge_from_exception(error) if error.present?
46
+ end
47
+ ensure
48
+ delete_config_file
49
+ end
50
+
51
+ private
52
+
53
+ attr_reader :name, :metadata
54
+ attr_accessor :error
55
+
56
+ def handle_output(container, io, raw_output)
57
+ output = EngineOutput.new(name, raw_output)
58
+
59
+ return if output_filter.filter?(output)
60
+
61
+ unless output.valid?
62
+ self.error = Error.new("engine produced invalid output: #{output.error}")
63
+ container.stop("output invalid")
64
+ end
65
+
66
+ unless io.write(output_overrider.apply(output).to_json)
67
+ self.error = Error.new("#{io.class}#write returned false, indicating an error")
68
+ container.stop("output error")
69
+ end
70
+ end
71
+
72
+ def qualified_name
73
+ "#{name}:#{@config.fetch("channel", "stable")}"
74
+ end
75
+
76
+ def container_options
77
+ options = [
78
+ "--cap-drop", "all",
79
+ "--label", "com.codeclimate.label=#{@label}",
80
+ "--log-driver", "none",
81
+ "--memory-swap", "-1",
82
+ "--net", "none",
83
+ "--rm",
84
+ "--volume", "#{code.host_path}:/code:ro",
85
+ "--volume", "#{config_file.host_path}:/config.json:ro",
86
+ "--user", "9000:9000"
87
+ ]
88
+ if (memory = metadata["memory"]).present?
89
+ options.concat(["--memory", memory.to_s])
90
+ end
91
+ options
92
+ end
93
+
94
+ def container_name
95
+ @container_name ||= "cc-engines-#{qualified_name.tr(":", "-")}-#{SecureRandom.uuid}"
96
+ end
97
+
98
+ def write_config_file
99
+ @config["debug"] = ENV["CODECLIMATE_DEBUG"]
100
+ Analyzer.logger.debug "/config.json content: #{@config.inspect}"
101
+ config_file.write(@config.to_json)
102
+ end
103
+
104
+ def delete_config_file
105
+ config_file.delete if config_file.file?
106
+ end
107
+
108
+ def code
109
+ @code ||= MountedPath.code
110
+ end
111
+
112
+ def config_file
113
+ @config_file ||= MountedPath.tmp.join(SecureRandom.uuid)
114
+ end
115
+
116
+ def output_filter
117
+ @output_filter ||= EngineOutputFilter.new(@config)
118
+ end
119
+
120
+ def output_overrider
121
+ @output_overrider ||= EngineOutputOverrider.new(@config)
122
+ end
123
+ end
124
+ end
125
+ end
@@ -0,0 +1,74 @@
1
+ module CC
2
+ module Analyzer
3
+ class EngineOutput
4
+ delegate :blank?, to: :raw_output
5
+
6
+ def initialize(name, raw_output)
7
+ @name = name
8
+ @raw_output = raw_output
9
+ end
10
+
11
+ def issue?
12
+ valid_with_type?("issue")
13
+ end
14
+
15
+ def measurement?
16
+ valid_with_type?("measurement")
17
+ end
18
+
19
+ def as_issue
20
+ Issue.new(name, raw_output)
21
+ end
22
+
23
+ def to_json
24
+ if issue?
25
+ as_issue.to_json
26
+ elsif measurement?
27
+ Measurement.new(name, raw_output).to_json
28
+ end
29
+ end
30
+
31
+ def valid?
32
+ valid_json? && validator && validator.valid?
33
+ end
34
+
35
+ def error
36
+ if !valid_json?
37
+ { message: "Invalid JSON", output: raw_output }
38
+ elsif !validator.present?
39
+ { message: "Unsupported document type", output: raw_output }
40
+ else
41
+ validator.error
42
+ end
43
+ end
44
+
45
+ private
46
+
47
+ attr_accessor :name, :raw_output
48
+
49
+ def valid_json?
50
+ parsed_output.present?
51
+ end
52
+
53
+ def valid_with_type?(type)
54
+ parsed_output &&
55
+ parsed_output["type"].present? &&
56
+ parsed_output["type"].downcase == type
57
+ end
58
+
59
+ def parsed_output
60
+ @parsed_output ||= JSON.parse(raw_output)
61
+ rescue JSON::ParserError
62
+ nil
63
+ end
64
+
65
+ def validator
66
+ if issue?
67
+ IssueValidator.new(parsed_output)
68
+ elsif measurement?
69
+ MeasurementValidator.new(parsed_output)
70
+ end
71
+ end
72
+ end
73
+ end
74
+ end
@@ -0,0 +1,36 @@
1
+ module CC
2
+ module Analyzer
3
+ class EngineOutputFilter
4
+ ISSUE_TYPE = "issue".freeze
5
+
6
+ def initialize(config = {})
7
+ @config = config
8
+ end
9
+
10
+ def filter?(output)
11
+ output.blank? || (output.issue? && ignore_issue?(output.as_issue))
12
+ end
13
+
14
+ private
15
+
16
+ def ignore_issue?(issue)
17
+ check_disabled?(issue) || ignore_fingerprint?(issue)
18
+ end
19
+
20
+ def check_disabled?(issue)
21
+ !check_config(issue.check_name).fetch("enabled", true)
22
+ end
23
+
24
+ def ignore_fingerprint?(issue)
25
+ @config.fetch("exclude_fingerprints", []).include?(issue.fingerprint)
26
+ rescue SourceExtractor::InvalidLocation
27
+ false
28
+ end
29
+
30
+ def check_config(check_name)
31
+ @checks ||= @config.fetch("checks", {})
32
+ @checks.fetch(check_name, {})
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,31 @@
1
+ module CC
2
+ module Analyzer
3
+ class EngineOutputOverrider
4
+ def initialize(config = {})
5
+ @config = config
6
+ end
7
+
8
+ def apply(output)
9
+ if output.issue?
10
+ override_severity(output.as_issue.as_json)
11
+ else
12
+ output
13
+ end
14
+ end
15
+
16
+ private
17
+
18
+ attr_reader :config
19
+
20
+ def override_severity(issue)
21
+ issue.merge(override("severity"))
22
+ end
23
+
24
+ def override(name)
25
+ config.
26
+ fetch("issue_override", {}).
27
+ slice(name)
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,50 @@
1
+ module CC
2
+ module Analyzer
3
+ class Filesystem
4
+ attr_reader :root
5
+
6
+ def initialize(root)
7
+ @root = root
8
+ end
9
+
10
+ def exist?(path)
11
+ File.exist?(path_for(path))
12
+ end
13
+
14
+ def source_buffer_for(path)
15
+ SourceBuffer.new(path, read_path(path))
16
+ end
17
+
18
+ def read_path(path)
19
+ File.read(path_for(path))
20
+ end
21
+
22
+ def write_path(path, content)
23
+ File.write(path_for(path), content)
24
+ File.chown(root_uid, root_gid, path_for(path))
25
+ end
26
+
27
+ def ls
28
+ Dir.entries(root).reject { |entry| [".", ".."].include?(entry) }
29
+ end
30
+
31
+ private
32
+
33
+ def path_for(path)
34
+ File.join(root, path)
35
+ end
36
+
37
+ def root_uid
38
+ root_stat.uid
39
+ end
40
+
41
+ def root_gid
42
+ root_stat.gid
43
+ end
44
+
45
+ def root_stat
46
+ @root_stat ||= File.stat(root)
47
+ end
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,53 @@
1
+ module CC
2
+ module Analyzer
3
+ module Formatters
4
+ class Formatter
5
+ def initialize(filesystem, output = $stdout)
6
+ @filesystem = filesystem
7
+ @output = output
8
+ end
9
+
10
+ def write(data)
11
+ json = JSON.parse(data)
12
+ json["engine_name"] = current_engine.name
13
+
14
+ case json["type"].downcase
15
+ when "issue"
16
+ issues << json
17
+ when "warning"
18
+ warnings << json
19
+ when "measurement"
20
+ measurements << json
21
+ else
22
+ raise "Invalid type found: #{json["type"]}"
23
+ end
24
+ end
25
+
26
+ def started
27
+ end
28
+
29
+ def engine_running(engine)
30
+ @current_engine = engine
31
+ yield
32
+ ensure
33
+ @current_engine = nil
34
+ end
35
+
36
+ def finished
37
+ end
38
+
39
+ def close
40
+ end
41
+
42
+ def failed(_output)
43
+ end
44
+
45
+ InvalidFormatterError = Class.new(StandardError)
46
+
47
+ private
48
+
49
+ attr_reader :current_engine
50
+ end
51
+ end
52
+ end
53
+ end