Permalink
Browse files

template backtraces ftw [#198] [#51]

  • Loading branch information...
1 parent 801163e commit cfdf97d49596863f7fecab54074d23b8fbbbfebd S. Brent Faulkner committed with rtomayko Apr 6, 2009
View
@@ -183,13 +183,14 @@
Sinatra.application = nil
end
- specify 'are empty be default' do
+ specify 'default to filename and line of caller' do
get '/' do
haml 'foo'
end
- Haml::Engine.expects(:new).with('foo', {}).returns(stub(:render => 'foo'))
+ Haml::Engine.expects(:new).with('foo', {:filename => __FILE__,
+ :line => (__LINE__-4)}).returns(stub(:render => 'foo'))
get_it '/'
should.be.ok
@@ -202,7 +203,8 @@
haml 'foo', :options => {:format => :html4}
end
- Haml::Engine.expects(:new).with('foo', {:format => :html4}).returns(stub(:render => 'foo'))
+ Haml::Engine.expects(:new).with('foo', {:filename => __FILE__,
+ :line => (__LINE__-4), :format => :html4}).returns(stub(:render => 'foo'))
get_it '/'
should.be.ok
@@ -220,7 +222,8 @@
haml 'foo'
end
- Haml::Engine.expects(:new).with('foo', {:format => :html4,
+ Haml::Engine.expects(:new).with('foo', {:filename => __FILE__,
+ :line => (__LINE__-4), :format => :html4,
:escape_html => true}).returns(stub(:render => 'foo'))
get_it '/'
View
@@ -253,30 +253,35 @@ def render(engine, template, options={}, locals={})
locals = options.delete(:locals) || locals || {}
# render template
- data = lookup_template(engine, template, views)
+ data, options[:filename], options[:line] = lookup_template(engine, template, views)
output = __send__("render_#{engine}", template, data, options, locals)
# render layout
- if layout && data = lookup_layout(engine, layout, views)
- __send__("render_#{engine}", layout, data, options, {}) { output }
- else
- output
+ if layout
+ data, options[:filename], options[:line] = lookup_layout(engine, layout, views)
+ if data
+ output = __send__("render_#{engine}", layout, data, options, {}) { output }
+ end
end
+
+ output
end
- def lookup_template(engine, template, views_dir)
+ def lookup_template(engine, template, views_dir, filename = nil, line = nil)
case template
when Symbol
if cached = self.class.templates[template]
- lookup_template(engine, cached, views_dir)
+ lookup_template(engine, cached[:template], views_dir, cached[:filename], cached[:line])
else
path = ::File.join(views_dir, "#{template}.#{engine}")
- ::File.read(path)
+ [ ::File.read(path), path, 1 ]
end
when Proc
- template.call
+ filename, line = self.class.caller_locations.first if filename.nil?
+ [ template.call, filename, line.to_i ]
when String
- template
+ filename, line = self.class.caller_locations.first if filename.nil?
+ [ template, filename, line.to_i ]
else
raise ArgumentError
end
@@ -295,8 +300,13 @@ def render_erb(template, data, options, locals, &block)
instance = ::ERB.new(data, nil, nil, '@_out_buf')
locals_assigns = locals.to_a.collect { |k,v| "#{k} = locals[:#{k}]" }
- src = "#{locals_assigns.join("\n")}\n#{instance.src}"
- eval src, binding, '(__ERB__)', locals_assigns.length + 1
+ filename = options.delete(:filename) || '(__ERB__)'
+ line = options.delete(:line) || 1
+ line -= 1 if instance.src =~ /^#coding:/
+
+ render_binding = binding
+ eval locals_assigns.join("\n"), render_binding
+ eval instance.src, render_binding, filename, line
@_out_buf, result = original_out_buf, @_out_buf
result
end
@@ -311,9 +321,11 @@ def render_sass(template, data, options, locals, &block)
def render_builder(template, data, options, locals, &block)
options = { :indent => 2 }.merge(options)
+ filename = options.delete(:filename) || '<BUILDER>'
+ line = options.delete(:line) || 1
xml = ::Builder::XmlMarkup.new(options)
if data.respond_to?(:to_str)
- eval data.to_str, binding, '<BUILDER>', 1
+ eval data.to_str, binding, filename, line
elsif data.kind_of?(Proc)
data.call(xml)
end
@@ -619,7 +631,8 @@ def not_found(&block)
# Define a named template. The block must return the template source.
def template(name, &block)
- templates[name] = block
+ filename, line = caller_locations.first
+ templates[name] = { :filename => filename, :line => line, :template => block }
end
# Define the layout template. The block must return the template source.
@@ -631,19 +644,18 @@ def layout(name=:layout, &block)
# when no file is specified.
def use_in_file_templates!(file=nil)
file ||= caller_files.first
-
- begin
- data = ::IO.read(file).split(/^__END__$/)[1]
- rescue
- data = nil
- end
+ app, data =
+ ::IO.read(file).split(/^__END__$/, 2) rescue nil
if data
data.gsub!(/\r\n/, "\n")
+ lines = app.count("\n") + 1
template = nil
data.each_line do |line|
+ lines += 1
if line =~ /^@@\s*(.*)/
- template = templates[$1.to_sym] = ''
+ template = ''
+ templates[$1.to_sym] = { :filename => file, :line => lines, :template => template }
elsif template
template << line
end
@@ -902,6 +914,7 @@ def metadef(message, &block)
send :define_method, message, &block
end
+ public
CALLERS_TO_IGNORE = [
/lib\/sinatra.*\.rb$/, # all sinatra code
/\(.*\)/, # generated code
@@ -0,0 +1,145 @@
+require File.dirname(__FILE__) + '/helper'
+
+require 'sass/error'
+
+class RenderBacktraceTest < Test::Unit::TestCase
+ VIEWS = File.dirname(__FILE__) + '/views'
+
+ def assert_raise_at(filename, line, exception = RuntimeError)
+ f, l = nil
+ assert_raise(exception) do
+ begin
+ get('/')
+ rescue => e
+ f, l = e.backtrace.first.split(':')
+ raise
+ end
+ end
+ assert_equal(filename, f, "expected #{exception.name} in #{filename}, was #{f}")
+ assert_equal(line, l.to_i, "expected #{exception.name} in #{filename} at line #{line}, was at line #{l}")
+ end
+
+ def backtrace_app(&block)
+ mock_app {
+ use_in_file_templates!
+ set :views, RenderBacktraceTest::VIEWS
+ template :builder_template do
+ 'raise "error"'
+ end
+ template :erb_template do
+ '<% raise "error" %>'
+ end
+ template :haml_template do
+ '%h1= raise "error"'
+ end
+ template :sass_template do
+ '+syntax-error'
+ end
+ get '/', &block
+ }
+ end
+
+ it "provides backtrace for Builder template" do
+ backtrace_app { builder :error }
+ assert_raise_at(File.join(VIEWS,'error.builder'), 2)
+ end
+
+ it "provides backtrace for ERB template" do
+ backtrace_app { erb :error }
+ assert_raise_at(File.join(VIEWS,'error.erb'), 2)
+ end
+
+ it "provides backtrace for HAML template" do
+ backtrace_app { haml :error }
+ assert_raise_at(File.join(VIEWS,'error.haml'), 2)
+ end
+
+ it "provides backtrace for Sass template" do
+ backtrace_app { sass :error }
+ assert_raise_at(File.join(VIEWS,'error.sass'), 2, Sass::SyntaxError)
+ end
+
+ it "provides backtrace for ERB template with locals" do
+ backtrace_app { erb :error, {}, :french => true }
+ assert_raise_at(File.join(VIEWS,'error.erb'), 3)
+ end
+
+ it "provides backtrace for HAML template with locals" do
+ backtrace_app { haml :error, {}, :french => true }
+ assert_raise_at(File.join(VIEWS,'error.haml'), 3)
+ end
+
+ it "provides backtrace for inline Builder string" do
+ backtrace_app { builder "raise 'Ack! Thbbbt!'"}
+ assert_raise_at(__FILE__, (__LINE__-1))
+ end
+
+ it "provides backtrace for inline ERB string" do
+ backtrace_app { erb "<% raise 'bidi-bidi-bidi' %>" }
+ assert_raise_at(__FILE__, (__LINE__-1))
+ end
+
+ it "provides backtrace for inline HAML string" do
+ backtrace_app { haml "%h1= raise 'Lions and tigers and bears! Oh, my!'" }
+ assert_raise_at(__FILE__, (__LINE__-1))
+ end
+
+ # it "provides backtrace for inline Sass string" do
+ # backtrace_app { sass '+buh-bye' }
+ # assert_raise_at(__FILE__, (__LINE__-1), Sass::SyntaxError)
+ # end
+
+ it "provides backtrace for named Builder template" do
+ backtrace_app { builder :builder_template }
+ assert_raise_at(__FILE__, (__LINE__-68))
+ end
+
+ it "provides backtrace for named ERB template" do
+ backtrace_app { erb :erb_template }
+ assert_raise_at(__FILE__, (__LINE__-70))
+ end
+
+ it "provides backtrace for named HAML template" do
+ backtrace_app { haml :haml_template }
+ assert_raise_at(__FILE__, (__LINE__-72))
+ end
+
+ # it "provides backtrace for named Sass template" do
+ # backtrace_app { sass :sass_template }
+ # assert_raise_at(__FILE__, (__LINE__-74), Sass::SyntaxError)
+ # end
+
+ it "provides backtrace for in file Builder template" do
+ backtrace_app { builder :builder_in_file }
+ assert_raise_at(__FILE__, (__LINE__+22))
+ end
+
+ it "provides backtrace for in file ERB template" do
+ backtrace_app { erb :erb_in_file }
+ assert_raise_at(__FILE__, (__LINE__+20))
+ end
+
+ it "provides backtrace for in file HAML template" do
+ backtrace_app { haml :haml_in_file }
+ assert_raise_at(__FILE__, (__LINE__+18))
+ end
+
+ # it "provides backtrace for in file Sass template" do
+ # backtrace_app { sass :sass_in_file }
+ # assert_raise_at(__FILE__, (__LINE__+16), Sass::SyntaxError)
+ # end
+end
+
+__END__
+
+@@ builder_in_file
+raise "bif"
+
+@@ erb_in_file
+<% raise "bam" %>
+
+@@ haml_in_file
+%h1= raise "pow"
+
+@@ sass_in_file
++blam
@@ -72,8 +72,8 @@ def with_default_layout
mock_app {
use_in_file_templates!
}
- assert_equal "this is foo\n\n", @app.templates[:foo]
- assert_equal "X\n= yield\nX\n", @app.templates[:layout]
+ assert_equal "this is foo\n\n", @app.templates[:foo][:template]
+ assert_equal "X\n= yield\nX\n", @app.templates[:layout][:template]
end
test 'use_in_file_templates simply ignores IO errors' do
@@ -0,0 +1,3 @@
+xml.error do
+ raise "goodbye"
+end
@@ -0,0 +1,3 @@
+Hello <%= 'World' %>
+<% raise 'Goodbye' unless defined?(french) && french %>
+<% raise 'Au revoir' if defined?(french) && french %>
@@ -0,0 +1,3 @@
+%h1 Hello From Haml
+= raise 'goodbye' unless defined?(french) && french
+= raise 'au revoir' if defined?(french) && french
@@ -0,0 +1,2 @@
+#sass
+ +argle-bargle

0 comments on commit cfdf97d

Please sign in to comment.