Skip to content
Merged
30 changes: 12 additions & 18 deletions spec/compiler/compiler_spec.cr
Original file line number Diff line number Diff line change
@@ -1,32 +1,26 @@
require "../spec_helper"
require "tempfile"
require "./spec_helper"

describe "Compiler" do
it "compiles a file" do
tempfile = Tempfile.new "compiler_spec_output"
tempfile.close
with_tempfile "compiler_spec_output" do |path|
Crystal::Command.run ["build", datapath("compiler_sample"), "-o", path]

Crystal::Command.run ["build", "#{__DIR__}/data/compiler_sample", "-o", tempfile.path]
File.exists?(path).should be_true

File.exists?(tempfile.path).should be_true

`#{tempfile.path}`.should eq("Hello!")
ensure
File.delete(tempfile.path) if tempfile
`#{path}`.should eq("Hello!")
end
end

it "runs subcommand in preference to a filename " do
Dir.cd "#{__DIR__}/data/" do
tempfile = Tempfile.new "compiler_spec_output"
tempfile.close

Crystal::Command.run ["build", "#{__DIR__}/data/compiler_sample", "-o", tempfile.path]
Dir.cd datapath do
with_tempfile "compiler_spec_output" do |path|
Crystal::Command.run ["build", "compiler_sample", "-o", path]

File.exists?(tempfile.path).should be_true
File.exists?(path).should be_true

`#{tempfile.path}`.should eq("Hello!")
ensure
File.delete(tempfile.path) if tempfile
`#{path}`.should eq("Hello!")
end
end
end
end
9 changes: 2 additions & 7 deletions spec/compiler/crystal/tools/init_spec.cr
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,6 @@ require "ini"
require "spec"
require "yaml"

PROJECT_ROOT_DIR = "#{__DIR__}/../../../.."

private def exec_init(project_name, project_dir = nil, type = "lib", force = false, skip_existing = false)
args = [type, project_name]
args << project_dir if project_dir
Expand All @@ -21,14 +19,11 @@ end
# Creates a temporary directory, cd to it and run the block inside it.
# The directory and its content is deleted when the block return.
private def within_temporary_directory
tmp_path = "#{PROJECT_ROOT_DIR}/tmp/init_spec_tmp_dir-#{Process.pid}"
Dir.mkdir_p(tmp_path)
begin
with_tempfile "init_spec_tmp" do |tmp_path|
Dir.mkdir_p(tmp_path)
Dir.cd(tmp_path) do
yield
end
ensure
FileUtils.rm_rf(tmp_path)
end
end

Expand Down
6 changes: 6 additions & 0 deletions spec/compiler/spec_helper.cr
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
require "../spec_helper"
require "../support/tempfile"

def datapath(*components)
File.join("spec", "compiler", "data", *components)
end
60 changes: 24 additions & 36 deletions spec/spec_helper.cr
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ ENV["CRYSTAL_PATH"] = "#{__DIR__}/../src"
require "spec"
require "../src/compiler/crystal/**"
require "./support/syntax"
require "./support/tempfile"

class Crystal::Program
def union_of(type1, type2, type3)
Expand Down Expand Up @@ -153,7 +154,7 @@ class Crystal::SpecRunOutput
end
end

def run(code, filename = nil, inject_primitives = true, debug = Crystal::Debug::None)
def run(code, filename = nil, inject_primitives = true, debug = Crystal::Debug::None, *, file = __FILE__)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The specs doesn't care about the location of this files.
They are not even generated sometimes (JIT is used unless prelude is required).
I would avoid adding this named arg and just using a fixed tempfile prefix/sufix in this helper.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure I understand why you'd want to remove this. Maybe it's not strictly necessary, but using with_tempfile here certainly doesn't hurt and it's easier than managing a tempfile location manually.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As I said, if specs are failing I would probably run just a few or one. Or even add a debug message for the spec/tempfile association while running specs in some verbose mode.

Having filename and file arguments in the same method seems misleading.

So, between not been able see an addition here, plus the misleading name, plus having more data to cary on runtime on the high populated spec suite of the compiler, I would vote for removing it.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay, I'll revert it.

code = inject_primitives(code) if inject_primitives

# Code that requires the prelude doesn't run in LLVM's MCJIT
Expand All @@ -170,60 +171,47 @@ def run(code, filename = nil, inject_primitives = true, debug = Crystal::Debug::
ast.expressions[-1] = exps
code = ast.to_s

output_filename = Crystal.tempfile("crystal-spec-output")
with_tempfile("crystal-spec-output", file: file) do |output_filename|
compiler = Compiler.new
compiler.debug = debug
compiler.compile Compiler::Source.new("spec", code), output_filename

compiler = Compiler.new
compiler.debug = debug
compiler.compile Compiler::Source.new("spec", code), output_filename
output = `#{output_filename}`

output = `#{output_filename}`
File.delete(output_filename)

SpecRunOutput.new(output)
SpecRunOutput.new(output)
end
else
Program.new.run(code, filename: filename, debug: debug)
end
end

def build_and_run(code)
code_file = Tempfile.new("build_and_run_code")
code_file.close

# write code to the temp file
File.write(code_file.path, code)
def build_and_run(code, *, file = __FILE__)
with_tempfile("build_and_run_code", "build_and_run_bin", file: file) do |code_path, binary_path|

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ditto

# write code to the temp file
File.write(code_path, code)

binary_file = Tempfile.new("build_and_run_bin")
binary_file.close
`bin/crystal build #{code_path.inspect} -o #{binary_path.inspect}`
File.exists?(binary_path).should be_true

`bin/crystal build #{code_file.path.inspect} -o #{binary_file.path.inspect}`
File.exists?(binary_file.path).should be_true
out_io, err_io = IO::Memory.new, IO::Memory.new
status = Process.run(binary_path, output: out_io, error: err_io)

out_io, err_io = IO::Memory.new, IO::Memory.new
status = Process.run(binary_file.path, output: out_io, error: err_io)

{status, out_io.to_s, err_io.to_s}
ensure
File.delete(code_file.path) if code_file
File.delete(binary_file.path) if binary_file
{status, out_io.to_s, err_io.to_s}
end
end

def test_c(c_code, crystal_code)
c_filename = "#{__DIR__}/temp_abi.c"
o_filename = "#{__DIR__}/temp_abi.o"
begin
File.write(c_filename, c_code)
def test_c(c_code, crystal_code, *, file = __FILE__)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ditto

with_tempfile("abi.c", "abi.o", file: file) do |c_path, o_path|
File.write(c_path, c_code)

`#{Crystal::Compiler::CC} #{c_filename} -c -o #{o_filename}`.should be_truthy
`#{Crystal::Compiler::CC} #{c_path} -c -o #{o_path}`.should be_truthy

yield run(%(
require "prelude"

@[Link(ldflags: "#{o_filename}")]
@[Link(ldflags: "#{o_path}")]
#{crystal_code}
))
ensure
File.delete(c_filename)
File.delete(o_filename)
end
end

Expand Down
95 changes: 44 additions & 51 deletions spec/std/callstack_spec.cr
Original file line number Diff line number Diff line change
@@ -1,75 +1,68 @@
require "spec"
require "tempfile"
require "./spec_helper"

describe "Backtrace" do
it "prints file line:colunm" do
tempfile = Tempfile.new("compiler_spec_output")
tempfile.close
sample = "#{__DIR__}/data/backtrace_sample"
with_tempfile("compiler_spec_output") do |path|
sample = datapath("backtrace_sample")

# CallStack tries to make files relative to the current dir,
# so we do the same for tests
current_dir = Dir.current
current_dir += File::SEPARATOR unless current_dir.ends_with?(File::SEPARATOR)
sample = sample.lchop(current_dir)
# CallStack tries to make files relative to the current dir,
# so we do the same for tests
current_dir = Dir.current
current_dir += File::SEPARATOR unless current_dir.ends_with?(File::SEPARATOR)
sample = sample.lchop(current_dir)

`bin/crystal build --debug #{sample.inspect} -o #{tempfile.path.inspect}`
File.exists?(tempfile.path).should be_true
`bin/crystal build --debug #{sample.inspect} -o #{path.inspect}`
File.exists?(path).should be_true

{% if flag?(:darwin) %}
`dsymutil --flat #{tempfile.path}`
{% end %}
{% if flag?(:darwin) %}
`dsymutil --flat #{path}`
{% end %}

output = `#{tempfile.path}`
output = `#{path}`

# resolved file line:column
output.should match(/#{sample}:3:10 in 'callee1'/)
# resolved file line:column
output.should match(/#{sample}:3:10 in 'callee1'/)

unless output =~ /#{sample}:13:5 in 'callee3'/
fail "didn't find callee3 in the backtrace"
end
unless output =~ /#{sample}:13:5 in 'callee3'/
fail "didn't find callee3 in the backtrace"
end

# skipped internal details
output.should_not match(/src\/callstack\.cr/)
output.should_not match(/src\/exception\.cr/)
output.should_not match(/src\/raise\.cr/)
ensure
File.delete(tempfile.path) if tempfile
# skipped internal details
output.should_not match(/src\/callstack\.cr/)
output.should_not match(/src\/exception\.cr/)
output.should_not match(/src\/raise\.cr/)
end
end

it "prints exception backtrace to stderr" do
tempfile = Tempfile.new("compiler_spec_output")
tempfile.close
sample = "#{__DIR__}/data/exception_backtrace_sample"
with_tempfile("compiler_spec_output") do |path|
sample = datapath("exception_backtrace_sample")

`bin/crystal build --debug #{sample.inspect} -o #{tempfile.path.inspect}`
File.exists?(tempfile.path).should be_true
`bin/crystal build --debug #{sample.inspect} -o #{path.inspect}`
File.exists?(path).should be_true

output, error = {IO::Memory.new, IO::Memory.new}.tap do |outio, errio|
Process.run tempfile.path, output: outio, error: errio
end
output, error = {IO::Memory.new, IO::Memory.new}.tap do |outio, errio|
Process.run path, output: outio, error: errio
end

output.to_s.empty?.should be_true
error.to_s.should contain("IndexError")
ensure
File.delete(tempfile.path) if tempfile
output.to_s.empty?.should be_true
error.to_s.should contain("IndexError")
end
end

it "prints crash backtrace to stderr" do
tempfile = Tempfile.new("compiler_spec_output")
tempfile.close
sample = "#{__DIR__}/data/crash_backtrace_sample"
with_tempfile("compiler_spec_output") do |path|
sample = datapath("crash_backtrace_sample")

`bin/crystal build --debug #{sample.inspect} -o #{tempfile.path.inspect}`
File.exists?(tempfile.path).should be_true
`bin/crystal build --debug #{sample.inspect} -o #{path.inspect}`
File.exists?(path).should be_true

output, error = {IO::Memory.new, IO::Memory.new}.tap do |outio, errio|
Process.run tempfile.path, output: outio, error: errio
end
output, error = {IO::Memory.new, IO::Memory.new}.tap do |outio, errio|
Process.run path, output: outio, error: errio
end

output.to_s.empty?.should be_true
error.to_s.should contain("Invalid memory access")
ensure
File.delete(tempfile.path) if tempfile
output.to_s.empty?.should be_true
error.to_s.should contain("Invalid memory access")
end
end
end
File renamed without changes.
File renamed without changes.
Loading