Class: CC::CLI::Test

Inherits:
Command show all
Defined in:
lib/cc/cli/test.rb

Constant Summary collapse

ARGUMENT_LIST =
"<engine_name>".freeze
SHORT_HELP =
"Test an engine.".freeze
HELP =
"Validate that an engine is behaving correctly.\n" \
"\n"\
"    <engine_name>    Engine to test".freeze

Constants inherited from Command

Command::CODECLIMATE_YAML, Command::NAMESPACE

Instance Method Summary collapse

Methods inherited from Command

[], abstract!, abstract?, all, command_name, #execute, help, inherited, #initialize, #require_codeclimate_yml, short_help, synopsis

Methods included from Output

#colorize, #fatal, #rainbow, #say, #success, #terminal, #warn

Constructor Details

This class inherits a constructor from CC::CLI::Command

Instance Method Details

#announce_fail(marker, actual_issues) ⇒ Object



154
155
156
157
158
159
160
161
# File 'lib/cc/cli/test.rb', line 154

def announce_fail(marker, actual_issues)
  say colorize(format("FAIL %3d: %s", marker.line, marker.line_text), :red)
  say colorize("Expected:", :yellow)
  say colorize(JSON.pretty_generate(marker.issue), :yellow)
  say "\n"
  say colorize("Actual:", :yellow)
  say colorize(JSON.pretty_generate(actual_issues), :yellow)
end

#announce_pass(marker) ⇒ Object



150
151
152
# File 'lib/cc/cli/test.rb', line 150

def announce_pass(marker)
  say format("PASS %3d: %s", marker.line, marker.line_text)
end

#capture_stdoutObject

Stolen from ActiveSupport (where it was deprecated)



248
249
250
251
252
253
254
255
256
257
258
259
260
261
# File 'lib/cc/cli/test.rb', line 248

def capture_stdout
  captured_stream = Tempfile.new("stdout")
  origin_stream = $stdout.dup
  $stdout.reopen(captured_stream)

  yield

  $stdout.rewind
  return captured_stream.read
ensure
  captured_stream.close
  captured_stream.unlink
  $stdout.reopen(origin_stream)
end

#codeclimate_analyze(relative_path) ⇒ Object



187
188
189
190
191
192
193
194
195
196
# File 'lib/cc/cli/test.rb', line 187

def codeclimate_analyze(relative_path)
  codeclimate_path = File.expand_path(File.join(File.dirname(__FILE__), "../../../bin/codeclimate"))

  system(
    codeclimate_path, "analyze",
    "--engine", @engine_name,
    "-f", "json",
    relative_path
  )
end

#compare_issues(actual_issues, markers) ⇒ Object



122
123
124
125
126
127
128
# File 'lib/cc/cli/test.rb', line 122

def compare_issues(actual_issues, markers)
  markers.each do |marker|
    validate_issue(marker, actual_issues)
  end

  validate_unexpected_issues(actual_issues)
end

#engine_imageObject



243
244
245
# File 'lib/cc/cli/test.rb', line 243

def engine_image
  engine_registry[@engine_name]["channels"]["stable"]
end

#engine_specObject



239
240
241
# File 'lib/cc/cli/test.rb', line 239

def engine_spec
  @engine_spec ||= JSON.parse(`docker run --rm #{engine_image} cat /engine.json`)
end

#fuzzy_match(expected, actual) ⇒ Object



173
174
175
176
177
# File 'lib/cc/cli/test.rb', line 173

def fuzzy_match(expected, actual)
  expected.all? do |key, value|
    actual[key] == value
  end
end

#issues_in(test_directory) ⇒ Object



179
180
181
182
183
184
185
# File 'lib/cc/cli/test.rb', line 179

def issues_in(test_directory)
  issue_docs = capture_stdout do
    codeclimate_analyze(test_directory)
  end

  JSON.parse(issue_docs)
end

#locate_match(actual_issues, marker) ⇒ Object



140
141
142
143
144
145
146
147
148
# File 'lib/cc/cli/test.rb', line 140

def locate_match(actual_issues, marker)
  actual_issues.each_with_index do |actual, index|
    if fuzzy_match(marker.issue, actual)
      return index
    end
  end

  nil
end

#markers_in(test_directory) ⇒ Object



206
207
208
209
210
211
212
213
214
215
216
217
218
219
# File 'lib/cc/cli/test.rb', line 206

def markers_in(test_directory)
  [].tap do |markers|
    Dir[File.join(test_directory, "**/*")].each do |file|
      next unless File.file?(file)
      lines = File.readlines(file)

      lines.each_with_index do |line, index|
        if line =~ /\[issue\].*/
          markers << Marker.from_text(@engine_name, file, index + 1, line)
        end
      end
    end
  end
end

#null_container_idObject



225
226
227
228
229
# File 'lib/cc/cli/test.rb', line 225

def null_container_id
  # docker cp only works with containers, not images so
  # workaround it by creating a throwaway container
  @null_container_id = `docker run -d #{engine_image} false`.chomp
end

#prepare_working_dirObject



198
199
200
201
202
203
204
# File 'lib/cc/cli/test.rb', line 198

def prepare_working_dir
  `git init`

  File.open(".codeclimate.yml", "w") do |config|
    config.write("engines:\n  #{@engine_name}:\n    enabled: true")
  end
end

#process_directory(test_directory) ⇒ Object



115
116
117
118
119
120
# File 'lib/cc/cli/test.rb', line 115

def process_directory(test_directory)
  markers = markers_in(test_directory)

  actual_issues = issues_in(test_directory)
  compare_issues(actual_issues, markers)
end

#remove_null_containerObject



231
232
233
# File 'lib/cc/cli/test.rb', line 231

def remove_null_container
  `docker rm -f #{null_container_id}` if null_container_id
end

#runObject



78
79
80
81
82
83
84
85
86
# File 'lib/cc/cli/test.rb', line 78

def run
  @engine_name = @args.first

  if @engine_name.blank?
    fatal "Usage: codeclimate test #{rainbow.wrap("engine_name").underline}"
  end

  test_engine
end

#run_testsObject



108
109
110
111
112
113
# File 'lib/cc/cli/test.rb', line 108

def run_tests
  Dir["*"].each do |file|
    next unless File.directory?(file)
    process_directory(file)
  end
end

#test_engineObject



88
89
90
91
92
93
94
95
96
# File 'lib/cc/cli/test.rb', line 88

def test_engine
  within_tempdir do
    prepare_working_dir
    unpack_tests
    run_tests
  end
ensure
  remove_null_container
end

#test_pathsObject



235
236
237
# File 'lib/cc/cli/test.rb', line 235

def test_paths
  Array.wrap(engine_spec["test_paths"])
end

#unpack(path) ⇒ Object



221
222
223
# File 'lib/cc/cli/test.rb', line 221

def unpack(path)
  system("docker cp #{null_container_id}:#{path} .")
end

#unpack_testsObject



102
103
104
105
106
# File 'lib/cc/cli/test.rb', line 102

def unpack_tests
  test_paths.each do |test_path|
    unpack(test_path)
  end
end

#validate_issue(marker, actual_issues) ⇒ Object



130
131
132
133
134
135
136
137
138
# File 'lib/cc/cli/test.rb', line 130

def validate_issue(marker, actual_issues)
  if (index = locate_match(actual_issues, marker))
    announce_pass(marker)
    actual_issues.delete_at(index)
  else
    announce_fail(marker, actual_issues)
    fatal "Expected issue not found."
  end
end

#validate_unexpected_issues(actual_issues) ⇒ Object



163
164
165
166
167
168
169
170
171
# File 'lib/cc/cli/test.rb', line 163

def validate_unexpected_issues(actual_issues)
  if actual_issues.any?
    say colorize("Actuals not empty after matching.", :red)
    say "\n"
    say colorize("#{actual_issues.size} remaining:", :yellow)
    say colorize(JSON.pretty_generate(actual_issues), :yellow)
    fatal "Unexpected issue found."
  end
end

#within_tempdir(&block) ⇒ Object



98
99
100
# File 'lib/cc/cli/test.rb', line 98

def within_tempdir(&block)
  Dir.mktmpdir { |tmp| Dir.chdir(tmp, &block) }
end