Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .rubocop_todo.yml
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ Metrics/MethodLength:
# Offense count: 1
# Configuration parameters: CountComments, CountAsOne.
Metrics/ModuleLength:
Max: 114
Max: 124

# Offense count: 1
# Configuration parameters: ExpectMatchingDefinition, CheckDefinitionPathHierarchy, CheckDefinitionPathHierarchyRoots, Regex, IgnoreExecutableScripts, AllowedAcronyms.
Expand Down
19 changes: 18 additions & 1 deletion lib/morph-cli.rb
Original file line number Diff line number Diff line change
Expand Up @@ -31,13 +31,23 @@ def self.execute(directory, _development, env_config, skip_data: false)

file = MorphCLI.create_tar(directory, all_paths)

scraper_output = run(file, env_config)

puts "Scraper didn't output anything, but it ran successfully." unless scraper_output
end

# Uploads the code to the server, streams the run output to the local
# stdout/stderr and returns whether the scraper itself wrote anything
# to stdout or stderr
def self.run(file, env_config)
connection = Faraday.new(url: env_config[:base_url]) do |f|
f.request :multipart
f.response :raise_error
f.adapter Faraday.default_adapter
end

buffer = +""
scraper_output = false
connection.post("/run") do |req|
req.body = {
api_key: env_config[:api_key],
Expand All @@ -52,12 +62,18 @@ def self.execute(directory, _development, env_config, skip_data: false)

before, match, after = chunk.rpartition("\n")
buffer << before << match
buffer.split("\n").each { |l| log(l) }
buffer.split("\n").each do |l|
stream = log(l)
scraper_output = true if %w[stdout stderr].include?(stream)
end
buffer = after
end
end
scraper_output
end

Comment thread
sentry[bot] marked this conversation as resolved.
# Writes the line to the local stdout/stderr and returns the name of the
# stream it came from
def self.log(line)
return if line.empty?

Expand All @@ -72,6 +88,7 @@ def self.log(line)
end

s.puts a["text"]
a["stream"]
end

def self.config_path
Expand Down
59 changes: 55 additions & 4 deletions spec/morph_cli_spec.rb
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
require "fileutils"
require "tmpdir"
require "stringio"
require "zlib"

require "spec_helper"

Expand All @@ -20,6 +22,23 @@ def with_scraper_directory
end
end

# Extracts tar entry names from a multipart request body that contains
# a gzip-compressed tar as one of its parts.
def tar_entry_names(body)
binary = body.b
gzip_start = binary.index("\x1f\x8b".b)
return [] unless gzip_start

gzip_io = StringIO.new(binary[gzip_start..])
names = []
Zlib::GzipReader.wrap(gzip_io) do |gz|
Minitar::Input.open(gz) { |tar| tar.each { |entry| names << entry.full_name } }
end
names
rescue StandardError
[]
end

it "uploads the scraper and streams the run output to stdout" do
stub_request(:post, "https://morph.io/run")
.to_return(status: 200, body: %({"stream":"stdout","text":"hello from morph"}\n))
Expand All @@ -30,6 +49,38 @@ def with_scraper_directory
end
end

it "tells the user the run succeeded when the scraper produces no output" do
stub_request(:post, "https://morph.io/run")
.to_return(status: 200, body: %({"stream":"internalout","text":"Injecting configuration"}\n))

with_scraper_directory do |dir|
expect { described_class.execute(dir, false, env_config) }
.to output(/Scraper didn't output anything, but it ran successfully\./).to_stdout
end
end

it "doesn't add a message when the scraper writes to stdout" do
stub_request(:post, "https://morph.io/run")
.to_return(status: 200, body: %({"stream":"stdout","text":"hello from morph"}\n))

with_scraper_directory do |dir|
expect { described_class.execute(dir, false, env_config) }
.not_to output(/ran successfully/).to_stdout
end
end

it "doesn't add a message when the scraper writes to stderr" do
stub_request(:post, "https://morph.io/run")
.to_return(status: 200, body: %({"stream":"stderr","text":"oops"}\n))

with_scraper_directory do |dir|
expect do
expect { described_class.execute(dir, false, env_config) }
.not_to output(/ran successfully/).to_stdout
end.to output("oops\n").to_stderr
end
end

it "posts the API key and the gzipped code as multipart form data" do
stub_request(:post, "https://morph.io/run").to_return(status: 200, body: "")

Expand All @@ -52,11 +103,11 @@ def with_scraper_directory
File.write(File.join(dir, "data.sqlite"), "sqlite data")

expect { described_class.execute(dir, false, env_config) }
.to output(/\AUploading 21\.00 B \(including data\.sqlite\)\.\.\.\n\z/).to_stdout
.to output(/\AUploading 21\.00 B \(including data\.sqlite\)\.\.\.\n/).to_stdout
end

expect(WebMock).to(have_requested(:post, "https://morph.io/run").with do |req|
req.body.include?("data.sqlite")
tar_entry_names(req.body).include?("data.sqlite")
end)
end

Expand All @@ -67,11 +118,11 @@ def with_scraper_directory
File.write(File.join(dir, "data.sqlite"), "sqlite data")

expect { described_class.execute(dir, false, env_config, skip_data: true) }
.to output(/\AUploading 10\.00 B\.\.\.\n\z/).to_stdout
.to output(/\AUploading 10\.00 B\.\.\.\n/).to_stdout
end

expect(WebMock).to(have_requested(:post, "https://morph.io/run").with do |req|
!req.body.include?("data.sqlite")
!tar_entry_names(req.body).include?("data.sqlite")
end)
end

Expand Down