Initial runtime test compilation

Allows for compiling single examples at runtime.
This commit is contained in:
Michael Miller 2020-08-16 10:59:15 -06:00
parent 53c9dd0445
commit 5688e58025
No known key found for this signature in database
GPG key ID: FB9F12F7C646A4AD
5 changed files with 192 additions and 0 deletions

71
spec/helpers/example.cr Normal file
View file

@ -0,0 +1,71 @@
require "ecr"
require "json"
require "./result"
module Spectator::SpecHelpers
# Wrapper for compiling and running an example at runtime and getting a result.
class Example
# Creates the example.
# The *spec_helper_path* is the path to spec_helper.cr file.
# The name or ID of the example is given by *example_id*.
# Lastly, the source code for the example is given by *example_code*.
def initialize(@spec_helper_path : String, @example_id : String, @example_code : String)
end
# Instructs the Crystal compiler to compile the test.
# Returns an instance of `JSON::Any`.
# This will be the outcome and information about the test.
# Output will be surpressed for the test.
# If an error occurs while attempting to compile and run the test, an error will be raised.
def compile
# Create a temporary file containing the test.
with_tempfile do |source_file|
args = ["run", "--no-color", source_file, "--", "--json"]
Process.run(crystal_executable, args) do |process|
JSON.parse(process.output)
rescue JSON::ParseException
raise "Compilation of example #{@example_id} failed\n\n#{process.error.gets_to_end}"
end
end
end
# Same as `#compile`, but returns the result of the first example in the test.
# Returns a `SpectatorHelpers::Result` instance.
def result
output = compile
example = output["examples"][0]
Result.from_json_any(example)
end
# Constructs the string representation of the example.
# This produces the Crystal source code.
# *io* is the file handle to write to.
# The *dir* is the directory of the file being written to.
# This is needed to resolve the relative path to the spec_helper.cr file.
private def write(io, dir)
spec_helper_path = Path[@spec_helper_path].relative_to(dir)
ECR.embed(__DIR__ + "/example.ecr", io)
end
# Creates a temporary file containing the compilable example code.
# Yields the path of the temporary file.
# Ensures the file is deleted after it is done being used.
private def with_tempfile
tempfile = File.tempfile("_#{@example_id}_spec.cr") do |file|
dir = File.dirname(file.path)
write(file, dir)
end
begin
yield tempfile.path
ensure
tempfile.delete
end
end
# Attempts to find the Crystal compiler on the system or raises an error.
private def crystal_executable
Process.find_executable("crystal") || raise("Could not find Crystal compiler")
end
end
end

5
spec/helpers/example.ecr Normal file
View file

@ -0,0 +1,5 @@
require "<%= spec_helper_path %>"
Spectator.describe "<%= @example_id %>" do
<%= @example_code %>
end

59
spec/helpers/result.cr Normal file
View file

@ -0,0 +1,59 @@
module Spectator::SpecHelpers
# Information about an example compiled and run at runtime.
class Result
# Status of the example after running.
enum Outcome
Success
Failure
Error
Unknown
end
# Full name and description of the example.
getter name : String
# Status of the example after running.
getter outcome : Outcome
# Creates the result.
def initialize(@name, @outcome)
end
# Checks if the example was successful.
def success?
outcome.success?
end
# :ditto:
def successful?
outcome.success?
end
# Checks if the example failed, but did not error.
def failure?
outcome.failure?
end
# Checks if the example encountered an error.
def error?
outcome.error?
end
# Extracts the result information from a `JSON::Any` object.
def self.from_json_any(object : JSON::Any)
name = object["name"].as_s
outcome = parse_outcome_string(object["result"].as_s)
new(name, outcome)
end
# Converts a result string, such as "fail" to an enum value.
private def self.parse_outcome_string(string)
case string
when /success/i then Outcome::Success
when /fail/i then Outcome::Failure
when /error/i then Outcome::Error
else Outcome::Unknown
end
end
end
end