# -*- coding: us-ascii -*- # frozen_string_literal: false require 'test/unit' require 'erb' require 'stringio' class TestERB < Test::Unit::TestCase class MyError < RuntimeError ; end def test_without_filename erb = ERB.new("<% raise ::TestERB::MyError %>") e = assert_raise(MyError) { erb.result } assert_match(/\A\(erb\):1\b/, e.backtrace[0]) end def test_with_filename erb = ERB.new("<% raise ::TestERB::MyError %>") erb.filename = "test filename" e = assert_raise(MyError) { erb.result } assert_match(/\Atest filename:1\b/, e.backtrace[0]) end # [deprecated] This will be removed later def test_without_filename_with_safe_level erb = EnvUtil.suppress_warning do ERB.new("<% raise ::TestERB::MyError %>", 1) end e = assert_raise(MyError) { erb.result } assert_match(/\A\(erb\):1\b/, e.backtrace[0]) end # [deprecated] This will be removed later def test_with_filename_and_safe_level erb = EnvUtil.suppress_warning do ERB.new("<% raise ::TestERB::MyError %>", 1) end erb.filename = "test filename" e = assert_raise(MyError) { erb.result } assert_match(/\Atest filename:1\b/, e.backtrace[0]) end def test_with_filename_lineno erb = ERB.new("<% raise ::TestERB::MyError %>") erb.filename = "test filename" erb.lineno = 100 e = assert_raise(MyError) { erb.result } assert_match(/\Atest filename:101\b/, e.backtrace[0]) end def test_with_location erb = ERB.new("<% raise ::TestERB::MyError %>") erb.location = ["test filename", 200] e = assert_raise(MyError) { erb.result } assert_match(/\Atest filename:201\b/, e.backtrace[0]) end def test_html_escape assert_equal(" !"\#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~", ERB::Util.html_escape(" !\"\#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~")) assert_equal("", ERB::Util.html_escape("")) assert_equal("abc", ERB::Util.html_escape("abc")) assert_equal("<<", ERB::Util.html_escape("<\<")) assert_equal("'&"><", ERB::Util.html_escape("'&\"><")) assert_equal("", ERB::Util.html_escape(nil)) assert_equal("123", ERB::Util.html_escape(123)) end def test_html_escape_to_s object = Object.new def object.to_s "object" end assert_equal("object", ERB::Util.html_escape(object)) end def test_html_escape_extension assert_nil(ERB::Util.method(:html_escape).source_location) end if RUBY_ENGINE == 'ruby' def test_concurrent_default_binding # This test randomly fails with JRuby -- NameError: undefined local variable or method `template2' pend if RUBY_ENGINE == 'jruby' template1 = 'one <%= ERB.new(template2).result %>' eval 'template2 = "two"', TOPLEVEL_BINDING bug7046 = '[ruby-core:47638]' assert_equal("one two", ERB.new(template1).result, bug7046) end end class TestERBCore < Test::Unit::TestCase def setup @erb = ERB end def test_version assert_equal(String, @erb.version.class) end def test_core # [deprecated] Fix initializer later EnvUtil.suppress_warning do _test_core(nil) _test_core(0) _test_core(1) end end def _test_core(safe) erb = @erb.new("hello") assert_equal("hello", erb.result) erb = @erb.new("hello", safe, 0) assert_equal("hello", erb.result) erb = @erb.new("hello", safe, 1) assert_equal("hello", erb.result) erb = @erb.new("hello", safe, 2) assert_equal("hello", erb.result) src = < % n=0 * <%= n %> <% end %> EOS ans = <') assert_equal(ans.chomp, erb.result) ans = <') assert_equal(ans, erb.result) ans = <') assert_equal(ans.chomp, erb.result) ans = <') assert_equal(ans, erb.result) end def test_trim_line1_with_carriage_return erb = @erb.new("<% 3.times do %>\r\nline\r\n<% end %>\r\n", trim_mode: '>') assert_equal("line\r\n" * 3, erb.result) erb = @erb.new("<% 3.times do %>\r\nline\r\n<% end %>\r\n", trim_mode: '%>') assert_equal("line\r\n" * 3, erb.result) end def test_trim_line2_with_carriage_return erb = @erb.new("<% 3.times do %>\r\nline\r\n<% end %>\r\n", trim_mode: '<>') assert_equal("line\r\n" * 3, erb.result) erb = @erb.new("<% 3.times do %>\r\nline\r\n<% end %>\r\n", trim_mode: '%<>') assert_equal("line\r\n" * 3, erb.result) end def test_explicit_trim_line_with_carriage_return erb = @erb.new("<%- 3.times do -%>\r\nline\r\n<%- end -%>\r\n", trim_mode: '-') assert_equal("line\r\n" * 3, erb.result) erb = @erb.new("<%- 3.times do -%>\r\nline\r\n<%- end -%>\r\n", trim_mode: '%-') assert_equal("line\r\n" * 3, erb.result) end def test_invalid_trim_mode pend if RUBY_ENGINE == 'truffleruby' assert_warning(/#{__FILE__}:#{__LINE__ + 1}/) do @erb.new("", trim_mode: 'abc-def') end assert_warning(/Invalid ERB trim mode/) do @erb.new("", trim_mode: 'abc-def') end assert_warning(/Invalid ERB trim mode/) do @erb.new("", trim_mode: '%<') end assert_warning(/Invalid ERB trim mode/) do @erb.new("", trim_mode: '%<>-') end assert_warning(/Invalid ERB trim mode/) do @erb.new("", trim_mode: 3) end end def test_run out = StringIO.new orig, $stdout = $stdout, out num = 3 @erb.new('<%= num * 3 %>').run(binding) $stdout = orig out.rewind assert_equal('9', out.read) return unless num # to remove warning end class Foo; end def test_def_class erb = @erb.new('hello') cls = erb.def_class assert_equal(Object, cls.superclass) assert_respond_to(cls.new, 'result') cls = erb.def_class(Foo) assert_equal(Foo, cls.superclass) assert_respond_to(cls.new, 'result') cls = erb.def_class(Object, 'erb') assert_equal(Object, cls.superclass) assert_respond_to(cls.new, 'erb') end def test_percent src = < EOS assert_equal("1\n", ERB.new(src, trim_mode: '%').result(binding)) src = < EOS ans = "\n" assert_equal(ans, ERB.new(src, trim_mode: '%').result(binding)) src = "<%\n%>" # ans = "\n" ans = "" assert_equal(ans, ERB.new(src, trim_mode: '%').result(binding)) src = <<%= n%> EOS assert_equal("1\n", ERB.new(src, trim_mode: '%').result(binding)) src = < %% %%><%%<%= i%><% end%> %%% EOS ans = <<%0 % %%><%1 %% EOS assert_equal(ans, ERB.new(src, trim_mode: '%').result(binding)) end def test_def_erb_method klass = Class.new klass.module_eval do extend ERB::DefMethod fname = File.join(File.dirname(File.expand_path(__FILE__)), 'hello.erb') def_erb_method('hello', fname) end assert_respond_to(klass.new, 'hello') assert_not_respond_to(klass.new, 'hello_world') erb = @erb.new('hello, world') klass.module_eval do def_erb_method('hello_world', erb) end assert_respond_to(klass.new, 'hello_world') end def test_def_method_without_filename klass = Class.new erb = ERB.new("<% raise ::TestERB::MyError %>") erb.filename = "test filename" assert_not_respond_to(klass.new, 'my_error') erb.def_method(klass, 'my_error') e = assert_raise(::TestERB::MyError) { klass.new.my_error } assert_match(/\A\(ERB\):1\b/, e.backtrace[0]) end def test_def_method_with_fname klass = Class.new erb = ERB.new("<% raise ::TestERB::MyError %>") erb.filename = "test filename" assert_not_respond_to(klass.new, 'my_error') erb.def_method(klass, 'my_error', 'test fname') e = assert_raise(::TestERB::MyError) { klass.new.my_error } assert_match(/\Atest fname:1\b/, e.backtrace[0]) end def test_def_module klass = Class.new klass.include ERB.new('<%= val %>').def_module('render(val)') assert_equal('1', klass.new.render(1)) end def test_escape src = < 2.%%> : <%="%%>"%> 3. % x = "foo" <%=x%> 4. %% print "foo" 5. %% <%="foo"%> 6.<%=" % print 'foo' "%> 7.<%=" %% print 'foo' "%> EOS ans = < : %> 3. foo 4. % print "foo" 5. % foo 6. % print 'foo' 7. %% print 'foo' EOS assert_equal(ans, ERB.new(src, trim_mode: '%').result) end def test_keep_lineno src = < % raise("lineno") EOS erb = ERB.new(src, trim_mode: '%') e = assert_raise(RuntimeError) { erb.result } assert_match(/\A\(erb\):4\b/, e.backtrace[0].to_s) src = < Hello,\s <% x = "World%%> "%> <%= x%> EOS ans = <Hello,\s World%> EOS assert_equal(ans, ERB.new(src, trim_mode: '>').result) ans = < Hello,\s World%> EOS assert_equal(ans, ERB.new(src, trim_mode: '<>').result) ans = < Hello,\s World%> EOS assert_equal(ans, ERB.new(src).result) src = < <%= x%> <% raise("lineno") %> EOS erb = ERB.new(src) e = assert_raise(RuntimeError) { erb.result } assert_match(/\A\(erb\):5\b/, e.backtrace[0].to_s) erb = ERB.new(src, trim_mode: '>') e = assert_raise(RuntimeError) { erb.result } assert_match(/\A\(erb\):5\b/, e.backtrace[0].to_s) erb = ERB.new(src, trim_mode: '<>') e = assert_raise(RuntimeError) { erb.result } assert_match(/\A\(erb\):5\b/, e.backtrace[0].to_s) src = < <%= x %><%- x = nil -%>\s <% raise("lineno") %> EOS erb = ERB.new(src, trim_mode: '-') e = assert_raise(RuntimeError) { erb.result } assert_match(/\A\(erb\):5\b/, e.backtrace[0].to_s) erb = ERB.new(src, trim_mode: '%-') e = assert_raise(RuntimeError) { erb.result } assert_match(/\A\(erb\):5\b/, e.backtrace[0].to_s) end def test_explicit src = < NotSkip <%- y = x -%> NotSkip <% x.each do |w| -%> <%- up = w.upcase -%> * <%= up %> <% end -%> <%- z = nil -%> NotSkip <%- z = x %> <%- z.each do |w| -%> <%- down = w.downcase -%> * <%= down %> <%- up = w.upcase -%> * <%= up %> <%- end -%> KeepNewLine <%- z = nil -%>\s EOS ans = <%", trim_mode: "%").result) end def test_token_extension extended_erb = Class.new(ERB) extended_erb.module_eval do def make_compiler(trim_mode) compiler = Class.new(ERB::Compiler) compiler.module_eval do def compile_stag(stag, out, scanner) case stag when '<%==' scanner.stag = stag add_put_cmd(out, content) if content.size > 0 self.content = '' else super end end def compile_content(stag, out) case stag when '<%==' out.push("#{@insert_cmd}(::ERB::Util.html_escape(#{content}))") else super end end def make_scanner(src) scanner = Class.new(ERB::Compiler::SimpleScanner) scanner.module_eval do def stags ['<%=='] + super end end scanner.new(src, @trim_mode, @percent) end end compiler.new(trim_mode) end end src = <<~EOS <% tag = '<>' \%> <\%= tag \%> <\%== tag \%> EOS ans = <<~EOS <> <> EOS assert_equal(ans, extended_erb.new(src).result) end def test_frozen_string_literal bug12031 = '[ruby-core:73561] [Bug #12031]' e = @erb.new("<%#encoding: us-ascii%>a") e.src.sub!(/\A#(?:-\*-)?(.*)(?:-\*-)?/) { '# -*- \1; frozen-string-literal: true -*-' } assert_equal("a", e.result, bug12031) %w(false true).each do |flag| erb = @erb.new("<%#frozen-string-literal: #{flag}%><%=''.frozen?%>") assert_equal(flag, erb.result) end end def test_result_with_hash erb = @erb.new("<%= foo %>") assert_equal("1", erb.result_with_hash(foo: "1")) end def test_result_with_hash_does_not_use_caller_local_variables erb = @erb.new("<%= foo %>") foo = 1 assert_raise(NameError) { erb.result_with_hash({}) } assert_equal("1", erb.result_with_hash(foo: foo)) end def test_result_with_hash_does_not_modify_caller_binding erb = @erb.new("<%= foo %>") erb.result_with_hash(foo: "1") assert_equal(false, binding.local_variable_defined?(:foo)) end def test_result_with_hash_does_not_modify_toplevel_binding erb = @erb.new("<%= foo %>") erb.result_with_hash(foo: "1") assert_equal(false, TOPLEVEL_BINDING.local_variable_defined?(:foo)) TOPLEVEL_BINDING.eval 'template2 = "two"' erb = @erb.new("<%= template2 %>") erb.result_with_hash(template2: "TWO") assert_equal "two", TOPLEVEL_BINDING.local_variable_get("template2") end # This depends on the behavior that #local_variable_set raises TypeError by invalid key. def test_result_with_hash_with_invalid_keys_raises_type_error erb = @erb.new("<%= 1 %>") assert_raise(TypeError) { erb.result_with_hash({ 1 => "1" }) } end # Bug#14243 def test_half_working_comment_backward_compatibility assert_nothing_raised do @erb.new("<% # comment %>\n").result end end # [deprecated] These interfaces will be removed later def test_deprecated_interface_warnings [nil, 0, 1, 2].each do |safe| assert_warn(/2nd argument of ERB.new is deprecated/) do ERB.new('', safe) end end [nil, '', '%', '%<>'].each do |trim| assert_warn(/3rd argument of ERB.new is deprecated/) do ERB.new('', nil, trim) end end [nil, '_erbout', '_hamlout'].each do |eoutvar| assert_warn(/4th argument of ERB.new is deprecated/) do ERB.new('', nil, nil, eoutvar) end end end def test_prohibited_marshal_dump erb = ERB.new("") assert_raise(TypeError) {Marshal.dump(erb)} end def test_prohibited_marshal_load erb = ERB.allocate erb.instance_variable_set(:@src, "") erb.instance_variable_set(:@lineno, 1) erb.instance_variable_set(:@_init, true) erb = Marshal.load(Marshal.dump(erb)) assert_raise(ArgumentError) {erb.result} end end class TestERBCoreWOStrScan < TestERBCore def setup @save_map = ERB::Compiler::Scanner.instance_variable_get('@scanner_map') map = {[nil, false]=>ERB::Compiler::SimpleScanner} ERB::Compiler::Scanner.instance_variable_set('@scanner_map', map) super end def teardown ERB::Compiler::Scanner.instance_variable_set('@scanner_map', @save_map) end end