Compare commits

..

24 Commits

Author SHA1 Message Date
Dylan Thacker-Smith
d5c31861f9 Fix theme runner for benchmarks 2020-08-18 10:04:42 -04:00
Dylan Thacker-Smith
895e63e40a Remove now unnecessary squash_instance_assigns_with_environments
Since it was just there to make sure instance assigns were overriden
when rending more than once with the same template instance.  It is
no longer necessary because the template no longer stores instance_assigns
which need to be overriden.
2020-08-18 10:04:42 -04:00
Dylan Thacker-Smith
219168e89f Remove mutable render state from Liquid::Template
Use Liquid::Context to access this mutable state after rendering or to have
state that is persisted between mutations
2020-08-18 10:04:41 -04:00
Thierry Joyal
a1d982ca76 Merge pull request #1272 from Shopify/StaticRegisters/add-test-coverage
[StaticRegisters] Add test coverage
2020-08-04 08:31:45 -04:00
Thierry Joyal
03be7f1ee3 [StaticRegisters] Add test coverage 2020-07-28 10:23:51 -04:00
Dylan Thacker-Smith
1ced4eaf10 Merge pull request #1268 from Shopify/remove-taint-checking
Remove support for taint checking
2020-07-25 21:27:46 -04:00
Dylan Thacker-Smith
4970167726 Bump rake development dependency
Gets rid of a deprecation warning when running the tests.
2020-07-23 16:23:18 -04:00
Dylan Thacker-Smith
065ccbc4aa Remove support for taint checking 2020-07-23 16:22:46 -04:00
Feken Baboyan
1feaa63813 Merge pull request #1258 from Shopify/fix-context-overriding-in-templates
Fix how Template overrides static registers when #render is invoked
2020-05-28 09:32:31 -04:00
Feken Baboyan
8541c6be35 make Template override static registers only when the register key is not defined 2020-05-28 09:08:03 -04:00
Thierry Joyal
18654526c8 Merge pull request #1257 from Shopify/StaticRegisters/remove-registers-attr-reader
[StaticRegisters] Remove registers attr_reader
2020-05-22 14:01:37 -04:00
Thierry Joyal
bd1f7f9492 [StaticRegisters] Remove assertion for delete to not remove static content 2020-05-22 13:42:44 -04:00
Thierry Joyal
40d75dd283 Update test/unit/static_registers_unit_test.rb
Co-authored-by: Dylan Thacker-Smith <dylan.smith@shopify.com>
2020-05-22 12:15:52 -04:00
Thierry Joyal
f5011365f1 [StaticRegisters] Remove registers attr_reader 2020-05-22 10:51:07 -04:00
Thierry Joyal
ebbd046c92 Merge pull request #1250 from Shopify/static-registers/fetch-raise-on-missing
[StaticRegisters] Fetch raise on missing
2020-05-22 09:56:05 -04:00
Thierry Joyal
b9979088ec [StaticRegisters] Fetch raise on missing
Co-authored-by: Dylan Thacker-Smith <dylan.smith@shopify.com>
2020-05-22 09:35:47 -04:00
Dylan Thacker-Smith
bd0e53bd2e Merge pull request #1239 from Shopify/remove-bad-arity-assumption
Fix ParseTreeVisitorTest for ruby-head
2020-05-21 14:02:04 -04:00
Thierry Joyal
4b586f4105 Merge pull request #1251 from Shopify/travis/optional-head
[Travis] Optional head
2020-05-21 13:51:11 -04:00
Thierry Joyal
0410119d5f [Travis] Optional head 2020-05-21 12:45:14 -04:00
Dylan Thacker-Smith
c2f67398d0 Allow ruby-head failures
Ignore an object allocation test failure on ruby-head for now.
2020-03-31 10:53:49 -04:00
Dylan Thacker-Smith
81149344a5 Fix ParseTreeVisitorTest for ruby-head 2020-03-31 10:53:46 -04:00
Dylan Thacker-Smith
e9b649b345 Fix Liquid::Template inheritance (#1227)
self.class.default_resource_limits would return `nil` in a subclass, since
the attribute isn't set on subclasses.
2020-01-21 15:09:22 -05:00
Celso Dantas
9c538f4237 Merge pull request #1207 from Shopify/moving-const-to-const
Use String literal instead
2020-01-20 12:37:57 -05:00
Celso Dantas
c08a358a2b Use String literal instead of using a class method
The class method string definition is not needed here, so it can be removed.
2020-01-16 09:42:32 -05:00
24 changed files with 359 additions and 602 deletions

View File

@@ -13,6 +13,8 @@ matrix:
- rvm: *latest_ruby
script: bundle exec rake memory_profile:run
name: Profiling Memory Usage
allow_failures:
- rvm: ruby-head
branches:
only:

View File

@@ -4,7 +4,7 @@
* Split Strainer class as a factory and a template (#1208) [Thierry Joyal]
* Remove handling of a nil context in the Strainer class (#1218) [Thierry Joyal]
* Change `Liquid::MemoryError` message to be more specific about which limit was reached. (#1206) [Alan Tan]
* StaticRegisters#fetch to raise on missing key (#1250) [Thierry Joyal]
## 4.0.3 / 2019-03-12

View File

@@ -80,22 +80,24 @@ It is also recommended that you use it in the template editors of existing apps
By default, the renderer doesn't raise or in any other way notify you if some variables or filters are missing, i.e. not passed to the `render` method.
You can improve this situation by passing `strict_variables: true` and/or `strict_filters: true` options to the `render` method.
When one of these options is set to true, all errors about undefined variables and undefined filters will be stored in `errors` array of a `Liquid::Template` instance.
When one of these options is set to true, all errors about undefined variables and undefined filters will be stored in an `errors` array on the `Liquid::Context` instance used for rendering.
Here are some examples:
```ruby
template = Liquid::Template.parse("{{x}} {{y}} {{z.a}} {{z.b}}")
template.render({ 'x' => 1, 'z' => { 'a' => 2 } }, { strict_variables: true })
context = Liquid::Context.new({ 'x' => 1, 'z' => { 'a' => 2 } })
template.render(context, { strict_variables: true })
#=> '1 2 ' # when a variable is undefined, it's rendered as nil
template.errors
context.errors
#=> [#<Liquid::UndefinedVariable: Liquid error: undefined variable y>, #<Liquid::UndefinedVariable: Liquid error: undefined variable b>]
```
```ruby
template = Liquid::Template.parse("{{x | filter1 | upcase}}")
template.render({ 'x' => 'foo' }, { strict_filters: true })
context = Liquid::Context.new({ 'x' => 'foo' })
template.render(context, { strict_filters: true })
#=> '' # when at least one filter in the filter chain is undefined, a whole expression is rendered as nil
template.errors
context.errors
#=> [#<Liquid::UndefinedFilter: Liquid error: undefined filter filter1>]
```
@@ -111,4 +113,4 @@ template.render!({ 'x' => 1}, { strict_variables: true })
To help track usages of a feature or code path in production, we have released opt-in usage tracking. To enable this, we provide an empty `Liquid:: Usage.increment` method which you can customize to your needs. The feature is well suited to https://github.com/Shopify/statsd-instrument. However, the choice of implementation is up to you.
Once you have enabled usage tracking, we recommend reporting any events through Github Issues that your system may be logging. It is highly likely this event has been added to consider deprecating or improving code specific to this event, so please raise any concerns.
Once you have enabled usage tracking, we recommend reporting any events through Github Issues that your system may be logging. It is highly likely this event has been added to consider deprecating or improving code specific to this event, so please raise any concerns.

View File

@@ -192,18 +192,8 @@ module Liquid
def raise_if_resource_limits_reached(context, length)
context.resource_limits.render_length += length
error_message =
if context.resource_limits.render_length_reached?
MemoryError::RENDER_LENGTH_ERROR_MESSAGE
elsif context.resource_limits.render_score_reached?
MemoryError::RENDER_SCORE_ERROR_MESSAGE
elsif context.resource_limits.assign_score_reached?
MemoryError::ASSIGN_SCORE_ERROR_MESSAGE
end
return unless error_message
raise MemoryError, error_message
return unless context.resource_limits.reached?
raise MemoryError, "Memory limits exceeded"
end
def create_variable(token, parse_context)

View File

@@ -34,7 +34,6 @@ module Liquid
@strict_variables = false
@resource_limits = resource_limits || ResourceLimits.new(Template.default_resource_limits)
@base_scope_depth = 0
squash_instance_assigns_with_environments
self.exception_renderer = Template.default_exception_renderer
if rethrow_errors
@@ -246,16 +245,5 @@ module Liquid
rescue Liquid::InternalError => exc
exc
end
def squash_instance_assigns_with_environments
@scopes.last.each_key do |k|
@environments.each do |env|
if env.key?(k)
scopes.last[k] = lookup_and_evaluate(env, k)
break
end
end
end
end # squash_instance_assigns_with_environments
end # Context
end # Liquid

View File

@@ -23,14 +23,11 @@ module Liquid
def message_prefix
str = +""
str <<
if is_a?(SyntaxError)
"Liquid syntax error"
elsif is_a?(MemoryError)
"Liquid memory limit error"
else
"Liquid error"
end
str << if is_a?(SyntaxError)
"Liquid syntax error"
else
"Liquid error"
end
if line_number
str << " ("
@@ -43,19 +40,13 @@ module Liquid
end
end
class MemoryError < Error
RENDER_LENGTH_ERROR_MESSAGE = 'Too many bytes rendered.'
RENDER_SCORE_ERROR_MESSAGE = 'Too many tags rendered.'
ASSIGN_SCORE_ERROR_MESSAGE = 'Too many bytes assigned to variables.'
end
ArgumentError = Class.new(Error)
ContextError = Class.new(Error)
FileSystemError = Class.new(Error)
StandardError = Class.new(Error)
SyntaxError = Class.new(Error)
StackLevelError = Class.new(Error)
TaintedError = Class.new(Error)
MemoryError = Class.new(Error)
ZeroDivisionError = Class.new(Error)
FloatDomainError = Class.new(Error)
UndefinedVariable = Class.new(Error)

View File

@@ -12,16 +12,10 @@ module Liquid
reset
end
def render_length_reached?
@render_length_limit && @render_length > @render_length_limit
end
def render_score_reached?
@render_score_limit && @render_score > @render_score_limit
end
def assign_score_reached?
@assign_score_limit && @assign_score > @assign_score_limit
def reached?
(@render_length_limit && @render_length > @render_length_limit) ||
(@render_score_limit && @render_score > @render_score_limit) ||
(@assign_score_limit && @assign_score > @assign_score_limit)
end
def reset

View File

@@ -41,7 +41,7 @@ module Liquid
end
def escape(input)
CGI.escapeHTML(input.to_s).untaint unless input.nil?
CGI.escapeHTML(input.to_s) unless input.nil?
end
alias_method :h, :escape

View File

@@ -2,7 +2,7 @@
module Liquid
class StaticRegisters
attr_reader :static, :registers
attr_reader :static
def initialize(registers = {})
@static = registers.is_a?(StaticRegisters) ? registers.static : registers
@@ -25,8 +25,16 @@ module Liquid
@registers.delete(key)
end
def fetch(key, default = nil)
key?(key) ? self[key] : default
UNDEFINED = Object.new
def fetch(key, default = UNDEFINED, &block)
if @registers.key?(key)
@registers.fetch(key)
elsif default != UNDEFINED
@static.fetch(key, default, &block)
else
@static.fetch(key, &block)
end
end
def key?(key)

View File

@@ -12,10 +12,6 @@ module Liquid
class Assign < Tag
Syntax = /(#{VariableSignature}+)\s*=\s*(.*)\s*/om
def self.syntax_error_translation_key
"errors.syntax.assign"
end
attr_reader :to, :from
def initialize(tag_name, markup, options)
@@ -24,7 +20,7 @@ module Liquid
@to = Regexp.last_match(1)
@from = Variable.new(Regexp.last_match(2), options)
else
raise SyntaxError, options[:locale].t(self.class.syntax_error_translation_key)
raise SyntaxError, options[:locale].t('errors.syntax.assign')
end
end

View File

@@ -16,7 +16,7 @@ module Liquid
#
class Template
attr_accessor :root
attr_reader :resource_limits, :warnings
attr_reader :warnings
class TagRegistry
include Enumerable
@@ -64,23 +64,6 @@ module Liquid
attr_accessor :error_mode
Template.error_mode = :lax
attr_reader :taint_mode
# Sets how strict the taint checker should be.
# :lax is the default, and ignores the taint flag completely
# :warn adds a warning, but does not interrupt the rendering
# :error raises an error when tainted output is used
# @deprecated Since it is being deprecated in ruby itself.
def taint_mode=(mode)
taint_supported = Object.new.taint.tainted?
if mode != :lax && !taint_supported
raise NotImplementedError, "#{RUBY_ENGINE} #{RUBY_VERSION} doesn't support taint checking"
end
@taint_mode = mode
end
Template.taint_mode = :lax
attr_accessor :default_exception_renderer
Template.default_exception_renderer = lambda do |exception|
exception
@@ -119,16 +102,10 @@ module Liquid
# To enable profiling, pass in <tt>profile: true</tt> as an option.
# See Liquid::Profiler for more information
def parse(source, options = {})
template = Template.new
template.parse(source, options)
new.parse(source, options)
end
end
def initialize
@rethrow_errors = false
@resource_limits = ResourceLimits.new(self.class.default_resource_limits)
end
# Parse source code.
# Returns self for easy chaining
def parse(source, options = {})
@@ -141,22 +118,6 @@ module Liquid
self
end
def registers
@registers ||= {}
end
def assigns
@assigns ||= {}
end
def instance_assigns
@instance_assigns ||= {}
end
def errors
@errors ||= []
end
# Render takes a hash with local variables.
#
# if you use the same filters over and over again consider registering them globally
@@ -171,37 +132,18 @@ module Liquid
# * <tt>registers</tt> : hash with register variables. Those can be accessed from
# filters and tags and might be useful to integrate liquid more with its host application
#
def render(*args)
def render(assigns_or_context = nil, options = nil)
return '' if @root.nil?
context = case args.first
when Liquid::Context
c = args.shift
if @rethrow_errors
c.exception_renderer = ->(_e) { raise }
end
c
when Liquid::Drop
drop = args.shift
drop.context = Context.new([drop, assigns], instance_assigns, registers, @rethrow_errors, @resource_limits)
when Hash
Context.new([args.shift, assigns], instance_assigns, registers, @rethrow_errors, @resource_limits)
when nil
Context.new(assigns, instance_assigns, registers, @rethrow_errors, @resource_limits)
else
raise ArgumentError, "Expected Hash or Liquid::Context as parameter"
end
context = coerce_context(assigns_or_context)
output = nil
context_register = context.registers.is_a?(StaticRegisters) ? context.registers.static : context.registers
case args.last
case options
when Hash
options = args.pop
output = options[:output] if options[:output]
output = options[:output] if options[:output]
options[:registers]&.each do |key, register|
context_register[key] = register
@@ -209,11 +151,11 @@ module Liquid
apply_options_to_context(context, options)
when Module, Array
context.add_filters(args.pop)
context.add_filters(options)
end
Template.registers.each do |key, register|
context_register[key] = register
context_register[key] = register unless context_register.key?(key)
end
# Retrying a render resets resource usage
@@ -227,14 +169,15 @@ module Liquid
end
rescue Liquid::MemoryError => e
context.handle_error(e)
ensure
@errors = context.errors
end
end
def render!(*args)
@rethrow_errors = true
render(*args)
def render!(assigns_or_context = nil, options = nil)
context = coerce_context(assigns_or_context)
# rethrow errors
context.exception_renderer = ->(_e) { raise }
render(context, options)
end
def render_to_output_buffer(context, output)
@@ -243,6 +186,22 @@ module Liquid
private
def coerce_context(assigns_or_context)
case assigns_or_context
when Liquid::Context
assigns_or_context
when Liquid::Drop
drop = assigns_or_context
drop.context = Context.build(environments: [drop])
when Hash
Context.build(environments: [assigns_or_context])
when nil
Context.build
else
raise ArgumentError, "Expected Hash or Liquid::Context as parameter"
end
end
def tokenize(source)
Tokenizer.new(source, @line_numbers)
end

View File

@@ -86,9 +86,7 @@ module Liquid
context.invoke(filter_name, output, *filter_args)
end
obj = context.apply_global_filter(obj)
taint_check(context, obj)
obj
context.apply_global_filter(obj)
end
def render_to_output_buffer(context, output)
@@ -142,25 +140,6 @@ module Liquid
parsed_args
end
def taint_check(context, obj)
return if Template.taint_mode == :lax
return unless obj.tainted?
@markup =~ QuotedFragment
name = Regexp.last_match(0)
error = TaintedError.new("variable '#{name}' is tainted and was not escaped")
error.line_number = line_number
error.template_name = context.template_name
case Template.taint_mode
when :warn
context.warnings << error
when :error
raise error
end
end
class ParseTreeVisitor < Liquid::ParseTreeVisitor
def children
[@node.name] + @node.filters.flatten

View File

@@ -27,6 +27,6 @@ Gem::Specification.new do |s|
s.require_path = "lib"
s.add_development_dependency('rake', '~> 11.3')
s.add_development_dependency('rake', '~> 13.0')
s.add_development_dependency('minitest')
end

View File

@@ -73,26 +73,29 @@ class ThemeRunner
private
def compile_and_render(template, layout, assigns, page_template, template_file)
compiled_test = compile_test(template, layout, assigns, page_template, template_file)
def compile_and_render(template, layout, assigns, page_template)
assigns = assigns.merge(
'page_title' => 'Page title',
'template' => page_template,
)
compiled_test = compile_test(template, layout, assigns)
assigns['content_for_layout'] = compiled_test[:tmpl].render!(assigns)
compiled_test[:layout].render!(assigns) if layout
end
def compile_all_tests
@compiled_tests = []
each_test do |liquid, layout, assigns, page_template, template_name|
@compiled_tests << compile_test(liquid, layout, assigns, page_template, template_name)
each_test do |liquid, layout, assigns, _page_template, _template_name|
@compiled_tests << compile_test(liquid, layout, assigns)
end
@compiled_tests
end
def compile_test(template, layout, assigns, page_template, template_file)
tmpl = init_template(page_template, template_file)
parsed_template = tmpl.parse(template).dup
def compile_test(template, layout, assigns)
parsed_template = Liquid::Template.parse(template).dup
if layout
parsed_layout = tmpl.parse(layout)
parsed_layout = Liquid::Template.parse(layout)
{ tmpl: parsed_template, assigns: assigns, layout: parsed_layout }
else
{ tmpl: parsed_template, assigns: assigns }
@@ -107,16 +110,7 @@ class ThemeRunner
@tests.each do |test_hash|
# Compute page_template outside of profiler run, uninteresting to profiler
page_template = File.basename(test_hash[:template_name], File.extname(test_hash[:template_name]))
yield(test_hash[:liquid], test_hash[:layout], assigns, page_template, test_hash[:template_name])
yield(test_hash[:liquid], test_hash[:layout], assigns, page_template)
end
end
# set up a new Liquid::Template object for use in `compile_and_render` and `compile_test`
def init_template(page_template, template_file)
tmpl = Liquid::Template.new
tmpl.assigns['page_title'] = 'Page title'
tmpl.assigns['template'] = page_template
tmpl.registers[:file_system] = ThemeRunner::FileSystem.new(File.dirname(template_file))
tmpl
end
end

View File

@@ -49,10 +49,6 @@ class ProductDrop < Liquid::Drop
ContextDrop.new
end
def user_input
(+"foo").taint
end
protected
def callmenot
@@ -114,34 +110,6 @@ class DropsTest < Minitest::Test
assert_equal(' ', tpl.render!('product' => ProductDrop.new))
end
if taint_supported?
def test_rendering_raises_on_tainted_attr
with_taint_mode(:error) do
tpl = Liquid::Template.parse('{{ product.user_input }}')
assert_raises TaintedError do
tpl.render!('product' => ProductDrop.new)
end
end
end
def test_rendering_warns_on_tainted_attr
with_taint_mode(:warn) do
tpl = Liquid::Template.parse('{{ product.user_input }}')
context = Context.new('product' => ProductDrop.new)
tpl.render!(context)
assert_equal [Liquid::TaintedError], context.warnings.map(&:class)
assert_equal "variable 'product.user_input' is tainted and was not escaped", context.warnings.first.to_s(false)
end
end
def test_rendering_doesnt_raise_on_escaped_tainted_attr
with_taint_mode(:error) do
tpl = Liquid::Template.parse('{{ product.user_input | escape }}')
tpl.render!('product' => ProductDrop.new)
end
end
end
def test_drop_does_only_respond_to_whitelisted_methods
assert_equal("", Liquid::Template.parse("{{ product.inspect }}").render!('product' => ProductDrop.new))
assert_equal("", Liquid::Template.parse("{{ product.pretty_inspect }}").render!('product' => ProductDrop.new))
@@ -281,7 +249,7 @@ class DropsTest < Minitest::Test
end
def test_invokable_methods
assert_equal(%w(to_liquid catchall user_input context texts).to_set, ProductDrop.invokable_methods)
assert_equal(%w(to_liquid catchall context texts).to_set, ProductDrop.invokable_methods)
assert_equal(%w(to_liquid scopes_as_array loop_pos scopes).to_set, ContextDrop.invokable_methods)
assert_equal(%w(to_liquid size max min first count).to_set, EnumerableDrop.invokable_methods)
assert_equal(%w(to_liquid max min sort count first).to_set, RealEnumerableDrop.invokable_methods)

View File

@@ -40,26 +40,29 @@ class ErrorHandlingTest < Minitest::Test
def test_standard_error
template = Liquid::Template.parse(' {{ errors.standard_error }} ')
assert_equal(' Liquid error: standard error ', template.render('errors' => ErrorDrop.new))
context = Liquid::Context.new('errors' => ErrorDrop.new)
assert_equal(' Liquid error: standard error ', template.render(context))
assert_equal(1, template.errors.size)
assert_equal(StandardError, template.errors.first.class)
assert_equal(1, context.errors.size)
assert_equal(StandardError, context.errors.first.class)
end
def test_syntax
template = Liquid::Template.parse(' {{ errors.syntax_error }} ')
assert_equal(' Liquid syntax error: syntax error ', template.render('errors' => ErrorDrop.new))
context = Liquid::Context.new('errors' => ErrorDrop.new)
assert_equal(' Liquid syntax error: syntax error ', template.render(context))
assert_equal(1, template.errors.size)
assert_equal(SyntaxError, template.errors.first.class)
assert_equal(1, context.errors.size)
assert_equal(SyntaxError, context.errors.first.class)
end
def test_argument
template = Liquid::Template.parse(' {{ errors.argument_error }} ')
assert_equal(' Liquid error: argument error ', template.render('errors' => ErrorDrop.new))
context = Liquid::Context.new('errors' => ErrorDrop.new)
assert_equal(' Liquid error: argument error ', template.render(context))
assert_equal(1, template.errors.size)
assert_equal(ArgumentError, template.errors.first.class)
assert_equal(1, context.errors.size)
assert_equal(ArgumentError, context.errors.first.class)
end
def test_missing_endtag_parse_time_error
@@ -78,9 +81,10 @@ class ErrorHandlingTest < Minitest::Test
def test_lax_unrecognized_operator
template = Liquid::Template.parse(' {% if 1 =! 2 %}ok{% endif %} ', error_mode: :lax)
assert_equal(' Liquid error: Unknown operator =! ', template.render)
assert_equal(1, template.errors.size)
assert_equal(Liquid::ArgumentError, template.errors.first.class)
context = Liquid::Context.new('errors' => ErrorDrop.new)
assert_equal(' Liquid error: Unknown operator =! ', template.render(context))
assert_equal(1, context.errors.size)
assert_equal(Liquid::ArgumentError, context.errors.first.class)
end
def test_with_line_numbers_adds_numbers_to_parser_errors
@@ -202,10 +206,11 @@ class ErrorHandlingTest < Minitest::Test
def test_default_exception_renderer_with_internal_error
template = Liquid::Template.parse('This is a runtime error: {{ errors.runtime_error }}', line_numbers: true)
output = template.render('errors' => ErrorDrop.new)
context = Liquid::Context.new('errors' => ErrorDrop.new)
output = template.render(context)
assert_equal('This is a runtime error: Liquid error (line 1): internal', output)
assert_equal([Liquid::InternalError], template.errors.map(&:class))
assert_equal([Liquid::InternalError], context.errors.map(&:class))
end
def test_setting_default_exception_renderer
@@ -217,10 +222,11 @@ class ErrorHandlingTest < Minitest::Test
}
template = Liquid::Template.parse('This is a runtime error: {{ errors.argument_error }}')
output = template.render('errors' => ErrorDrop.new)
context = Liquid::Context.new('errors' => ErrorDrop.new)
output = template.render(context)
assert_equal('This is a runtime error: ', output)
assert_equal([Liquid::ArgumentError], template.errors.map(&:class))
assert_equal([Liquid::ArgumentError], context.errors.map(&:class))
ensure
Liquid::Template.default_exception_renderer = old_exception_renderer if old_exception_renderer
end
@@ -233,11 +239,12 @@ class ErrorHandlingTest < Minitest::Test
e.cause
}
output = template.render({ 'errors' => ErrorDrop.new }, exception_renderer: handler)
context = Liquid::Context.new('errors' => ErrorDrop.new)
output = template.render(context, exception_renderer: handler)
assert_equal('This is a runtime error: runtime error', output)
assert_equal([Liquid::InternalError], exceptions.map(&:class))
assert_equal(exceptions, template.errors)
assert_equal(exceptions, context.errors)
assert_equal('#<RuntimeError: runtime error>', exceptions.first.cause.inspect)
end
@@ -250,15 +257,16 @@ class ErrorHandlingTest < Minitest::Test
def test_included_template_name_with_line_numbers
old_file_system = Liquid::Template.file_system
context = Liquid::Context.new('errors' => ErrorDrop.new)
begin
Liquid::Template.file_system = TestFileSystem.new
template = Liquid::Template.parse("Argument error:\n{% include 'product' %}", line_numbers: true)
page = template.render('errors' => ErrorDrop.new)
page = template.render(context)
ensure
Liquid::Template.file_system = old_file_system
end
assert_equal("Argument error:\nLiquid error (product line 1): argument error", page)
assert_equal("product", template.errors.first.template_name)
assert_equal("product", context.errors.first.template_name)
end
end

View File

@@ -238,7 +238,7 @@ class ParseTreeVisitorTest < Minitest::Test
def traversal(template)
ParseTreeVisitor
.for(Template.parse(template).root)
.add_callback_for(VariableLookup, &:name)
.add_callback_for(VariableLookup) { |node| node.name } # rubocop:disable Style/SymbolProc
end
def visit(template)

View File

@@ -217,8 +217,9 @@ class IncludeTagTest < Minitest::Test
Liquid::Template.file_system = TestFileSystem.new
a = Liquid::Template.parse(' {% include "nested_template" %}')
a.render!
assert_empty(a.errors)
context = Liquid::Context.new
a.render!(context)
assert_empty(context.errors)
end
def test_passing_options_to_included_templates
@@ -257,9 +258,10 @@ class IncludeTagTest < Minitest::Test
def test_including_with_strict_variables
template = Liquid::Template.parse("{% include 'simple' %}", error_mode: :warn)
template.render(nil, strict_variables: true)
context = Liquid::Context.new
template.render(context, strict_variables: true)
assert_equal([], template.errors)
assert_equal([], context.errors)
end
def test_break_through_include

View File

@@ -42,34 +42,6 @@ class RenderTagTest < Minitest::Test
assert_template_result('', "{% assign snippet = 'should not be visible' %}{% render 'snippet' %}")
end
if taint_supported?
def test_render_sets_the_correct_template_name_for_errors
Liquid::Template.file_system = StubFileSystem.new('snippet' => '{{ unsafe }}')
with_taint_mode :error do
template = Liquid::Template.parse('{% render "snippet", unsafe: unsafe %}')
context = Context.new('unsafe' => (+'unsafe').tap(&:taint))
template.render(context)
assert_equal [Liquid::TaintedError], template.errors.map(&:class)
assert_equal 'snippet', template.errors.first.template_name
end
end
def test_render_sets_the_correct_template_name_for_warnings
Liquid::Template.file_system = StubFileSystem.new('snippet' => '{{ unsafe }}')
with_taint_mode :warn do
template = Liquid::Template.parse('{% render "snippet", unsafe: unsafe %}')
context = Context.new('unsafe' => (+'unsafe').tap(&:taint))
template.render(context)
assert_equal [Liquid::TaintedError], context.warnings.map(&:class)
assert_equal 'snippet', context.warnings.first.template_name
end
end
end
def test_render_does_not_mutate_parent_scope
Liquid::Template.file_system = StubFileSystem.new('snippet' => '{% assign inner = 1 %}')
assert_template_result('', "{% render 'snippet' %}{{ inner }}")

View File

@@ -38,12 +38,6 @@ end
class TemplateTest < Minitest::Test
include Liquid
def test_instance_assigns_persist_on_same_template_object_between_parses
t = Template.new
assert_equal('from instance assigns', t.parse("{% assign foo = 'from instance assigns' %}{{ foo }}").render!)
assert_equal('from instance assigns', t.parse("{{ foo }}").render!)
end
def test_warnings_is_not_exponential_time
str = "false"
100.times do
@@ -54,43 +48,15 @@ class TemplateTest < Minitest::Test
assert_equal([], Timeout.timeout(1) { t.warnings })
end
def test_instance_assigns_persist_on_same_template_parsing_between_renders
t = Template.new.parse("{{ foo }}{% assign foo = 'foo' %}{{ foo }}")
assert_equal('foo', t.render!)
assert_equal('foofoo', t.render!)
end
def test_custom_assigns_do_not_persist_on_same_template
t = Template.new
assert_equal('from custom assigns', t.parse("{{ foo }}").render!('foo' => 'from custom assigns'))
assert_equal('', t.parse("{{ foo }}").render!)
end
def test_custom_assigns_squash_instance_assigns
t = Template.new
assert_equal('from instance assigns', t.parse("{% assign foo = 'from instance assigns' %}{{ foo }}").render!)
assert_equal('from custom assigns', t.parse("{{ foo }}").render!('foo' => 'from custom assigns'))
end
def test_persistent_assigns_squash_instance_assigns
t = Template.new
assert_equal('from instance assigns', t.parse("{% assign foo = 'from instance assigns' %}{{ foo }}").render!)
t.assigns['foo'] = 'from persistent assigns'
assert_equal('from persistent assigns', t.parse("{{ foo }}").render!)
end
def test_lambda_is_called_once_from_persistent_assigns_over_multiple_parses_and_renders
t = Template.new
t.assigns['number'] = -> {
@global ||= 0
@global += 1
}
assert_equal('1', t.parse("{{number}}").render!)
assert_equal('1', t.parse("{{number}}").render!)
assert_equal('1', t.render!)
@global = nil
end
def test_lambda_is_called_once_from_custom_assigns_over_multiple_parses_and_renders
t = Template.new
assigns = { 'number' => -> {
@@ -105,112 +71,124 @@ class TemplateTest < Minitest::Test
def test_resource_limits_works_with_custom_length_method
t = Template.parse("{% assign foo = bar %}")
t.resource_limits.render_length_limit = 42
assert_equal("", t.render!("bar" => SomethingWithLength.new))
context = Liquid::Context.new("bar" => SomethingWithLength.new)
context.resource_limits.render_length_limit = 42
assert_equal("", t.render!(context))
end
def test_resource_limits_render_length
t = Template.parse("0123456789")
t.resource_limits.render_length_limit = 5
assert_equal("Liquid memory limit error: #{Liquid::MemoryError::RENDER_LENGTH_ERROR_MESSAGE}", t.render)
assert(t.resource_limits.render_length_reached?)
context = Liquid::Context.new
context.resource_limits.render_length_limit = 5
assert_equal("Liquid error: Memory limits exceeded", t.render(context))
assert(context.resource_limits.reached?)
t.resource_limits.render_length_limit = 10
context.resource_limits.render_length_limit = 10
assert_equal("0123456789", t.render!)
refute_nil(t.resource_limits.render_length)
refute_nil(context.resource_limits.render_length)
end
def test_resource_limits_render_score
t = Template.parse("{% for a in (1..10) %} {% for a in (1..10) %} foo {% endfor %} {% endfor %}")
t.resource_limits.render_score_limit = 50
assert_equal("Liquid memory limit error: #{Liquid::MemoryError::RENDER_SCORE_ERROR_MESSAGE}", t.render)
assert(t.resource_limits.render_score_reached?)
context = Liquid::Context.new
context.resource_limits.render_score_limit = 50
assert_equal("Liquid error: Memory limits exceeded", t.render(context))
assert(context.resource_limits.reached?)
t = Template.parse("{% for a in (1..100) %} foo {% endfor %}")
t.resource_limits.render_score_limit = 50
assert_equal("Liquid memory limit error: #{Liquid::MemoryError::RENDER_SCORE_ERROR_MESSAGE}", t.render)
assert(t.resource_limits.render_score_reached?)
context.resource_limits.render_score_limit = 50
assert_equal("Liquid error: Memory limits exceeded", t.render(context))
assert(context.resource_limits.reached?)
t.resource_limits.render_score_limit = 200
assert_equal((" foo " * 100), t.render!)
refute_nil(t.resource_limits.render_score)
context.resource_limits.render_score_limit = 200
assert_equal((" foo " * 100), t.render!(context))
refute_nil(context.resource_limits.render_score)
end
def test_resource_limits_assign_score
t = Template.parse("{% assign foo = 42 %}{% assign bar = 23 %}")
t.resource_limits.assign_score_limit = 1
assert_equal("Liquid memory limit error: #{Liquid::MemoryError::ASSIGN_SCORE_ERROR_MESSAGE}", t.render)
assert(t.resource_limits.assign_score_reached?)
context = Liquid::Context.new
context.resource_limits.assign_score_limit = 1
assert_equal("Liquid error: Memory limits exceeded", t.render(context))
assert(context.resource_limits.reached?)
t.resource_limits.assign_score_limit = 2
assert_equal("", t.render!)
refute_nil(t.resource_limits.assign_score)
context.resource_limits.assign_score_limit = 2
assert_equal("", t.render!(context))
refute_nil(context.resource_limits.assign_score)
end
def test_resource_limits_assign_score_counts_bytes_not_characters
t = Template.parse("{% assign foo = 'すごい' %}")
t.render
assert_equal(9, t.resource_limits.assign_score)
context = Liquid::Context.new
t.render(context)
assert_equal(9, context.resource_limits.assign_score)
t = Template.parse("{% capture foo %}すごい{% endcapture %}")
t.render
assert_equal(9, t.resource_limits.assign_score)
t.render(context)
assert_equal(9, context.resource_limits.assign_score)
end
def test_resource_limits_assign_score_nested
t = Template.parse("{% assign foo = 'aaaa' | reverse %}")
t.resource_limits.assign_score_limit = 3
assert_equal("Liquid memory limit error: #{Liquid::MemoryError::ASSIGN_SCORE_ERROR_MESSAGE}", t.render)
assert(t.resource_limits.assign_score_reached?)
context = Liquid::Context.new
context.resource_limits.assign_score_limit = 3
assert_equal("Liquid error: Memory limits exceeded", t.render(context))
assert(context.resource_limits.reached?)
t.resource_limits.assign_score_limit = 5
assert_equal("", t.render!)
context.resource_limits.assign_score_limit = 5
assert_equal("", t.render!(context))
end
def test_resource_limits_aborts_rendering_after_first_error
t = Template.parse("{% for a in (1..100) %} foo1 {% endfor %} bar {% for a in (1..100) %} foo2 {% endfor %}")
t.resource_limits.render_score_limit = 50
assert_equal("Liquid memory limit error: #{Liquid::MemoryError::RENDER_SCORE_ERROR_MESSAGE}", t.render)
assert(t.resource_limits.render_score_reached?)
context = Liquid::Context.new
context.resource_limits.render_score_limit = 50
assert_equal("Liquid error: Memory limits exceeded", t.render(context))
assert(context.resource_limits.reached?)
end
def test_resource_limits_hash_in_template_gets_updated_even_if_no_limits_are_set
t = Template.parse("{% for a in (1..100) %} {% assign foo = 1 %} {% endfor %}")
t.render!
assert(t.resource_limits.assign_score > 0)
assert(t.resource_limits.render_score > 0)
assert(t.resource_limits.render_length > 0)
context = Liquid::Context.new
t.render!(context)
assert(context.resource_limits.assign_score > 0)
assert(context.resource_limits.render_score > 0)
assert(context.resource_limits.render_length > 0)
end
def test_render_length_persists_between_blocks
t = Template.parse("{% if true %}aaaa{% endif %}")
t.resource_limits.render_length_limit = 7
assert_equal("Liquid memory limit error: #{Liquid::MemoryError::RENDER_LENGTH_ERROR_MESSAGE}", t.render)
t.resource_limits.render_length_limit = 8
assert_equal("aaaa", t.render)
context = Liquid::Context.new
context.resource_limits.render_length_limit = 7
assert_equal("Liquid error: Memory limits exceeded", t.render(context))
context.resource_limits.render_length_limit = 8
assert_equal("aaaa", t.render(context))
t = Template.parse("{% if true %}aaaa{% endif %}{% if true %}bbb{% endif %}")
t.resource_limits.render_length_limit = 13
assert_equal("Liquid memory limit error: #{Liquid::MemoryError::RENDER_LENGTH_ERROR_MESSAGE}", t.render)
t.resource_limits.render_length_limit = 14
assert_equal("aaaabbb", t.render)
context = Liquid::Context.new
context.resource_limits.render_length_limit = 13
assert_equal("Liquid error: Memory limits exceeded", t.render(context))
context.resource_limits.render_length_limit = 14
assert_equal("aaaabbb", t.render(context))
t = Template.parse("{% if true %}a{% endif %}{% if true %}b{% endif %}{% if true %}a{% endif %}{% if true %}b{% endif %}{% if true %}a{% endif %}{% if true %}b{% endif %}")
t.resource_limits.render_length_limit = 5
assert_equal("Liquid memory limit error: #{Liquid::MemoryError::RENDER_LENGTH_ERROR_MESSAGE}", t.render)
t.resource_limits.render_length_limit = 11
assert_equal("Liquid memory limit error: #{Liquid::MemoryError::RENDER_LENGTH_ERROR_MESSAGE}", t.render)
t.resource_limits.render_length_limit = 12
assert_equal("ababab", t.render)
context = Liquid::Context.new
context.resource_limits.render_length_limit = 5
assert_equal("Liquid error: Memory limits exceeded", t.render(context))
context.resource_limits.render_length_limit = 11
assert_equal("Liquid error: Memory limits exceeded", t.render(context))
context.resource_limits.render_length_limit = 12
assert_equal("ababab", t.render(context))
end
def test_render_length_uses_number_of_bytes_not_characters
t = Template.parse("{% if true %}すごい{% endif %}")
t.resource_limits.render_length_limit = 10
assert_equal("Liquid memory limit error: #{Liquid::MemoryError::RENDER_LENGTH_ERROR_MESSAGE}", t.render)
t.resource_limits.render_length_limit = 18
assert_equal("すごい", t.render)
context = Liquid::Context.new
context.resource_limits.render_length_limit = 10
assert_equal("Liquid error: Memory limits exceeded", t.render(context))
context.resource_limits.render_length_limit = 18
assert_equal("すごい", t.render(context))
end
def test_default_resource_limits_unaffected_by_render_with_context
@@ -224,11 +202,12 @@ class TemplateTest < Minitest::Test
def test_can_use_drop_as_context
t = Template.new
t.registers['lulz'] = 'haha'
drop = TemplateContextDrop.new
assert_equal('fizzbuzz', t.parse('{{foo}}').render!(drop))
assert_equal('bar', t.parse('{{bar}}').render!(drop))
assert_equal('haha', t.parse("{{baz}}").render!(drop))
context = Liquid::Context.build(environments: drop, registers: { 'lulz' => 'haha' })
drop.context = context
assert_equal('fizzbuzz', t.parse('{{foo}}').render!(context))
assert_equal('bar', t.parse('{{bar}}').render!(context))
assert_equal('haha', t.parse("{{baz}}").render!(context))
end
def test_render_bang_force_rethrow_errors_on_passed_context
@@ -280,25 +259,27 @@ class TemplateTest < Minitest::Test
end
def test_undefined_variables
t = Template.parse("{{x}} {{y}} {{z.a}} {{z.b}} {{z.c.d}}")
result = t.render({ 'x' => 33, 'z' => { 'a' => 32, 'c' => { 'e' => 31 } } }, strict_variables: true)
t = Template.parse("{{x}} {{y}} {{z.a}} {{z.b}} {{z.c.d}}")
context = Liquid::Context.new('x' => 33, 'z' => { 'a' => 32, 'c' => { 'e' => 31 } })
result = t.render(context, strict_variables: true)
assert_equal('33 32 ', result)
assert_equal(3, t.errors.count)
assert_instance_of(Liquid::UndefinedVariable, t.errors[0])
assert_equal('Liquid error: undefined variable y', t.errors[0].message)
assert_instance_of(Liquid::UndefinedVariable, t.errors[1])
assert_equal('Liquid error: undefined variable b', t.errors[1].message)
assert_instance_of(Liquid::UndefinedVariable, t.errors[2])
assert_equal('Liquid error: undefined variable d', t.errors[2].message)
assert_equal(3, context.errors.count)
assert_instance_of(Liquid::UndefinedVariable, context.errors[0])
assert_equal('Liquid error: undefined variable y', context.errors[0].message)
assert_instance_of(Liquid::UndefinedVariable, context.errors[1])
assert_equal('Liquid error: undefined variable b', context.errors[1].message)
assert_instance_of(Liquid::UndefinedVariable, context.errors[2])
assert_equal('Liquid error: undefined variable d', context.errors[2].message)
end
def test_nil_value_does_not_raise
Liquid::Template.error_mode = :strict
t = Template.parse("some{{x}}thing")
result = t.render!({ 'x' => nil }, strict_variables: true)
t = Template.parse("some{{x}}thing")
context = Liquid::Context.new('x' => nil)
result = t.render!(context, strict_variables: true)
assert_equal(0, t.errors.count)
assert_equal(0, context.errors.count)
assert_equal('something', result)
end
@@ -313,11 +294,13 @@ class TemplateTest < Minitest::Test
def test_undefined_drop_methods
d = DropWithUndefinedMethod.new
t = Template.new.parse('{{ foo }} {{ woot }}')
result = t.render(d, strict_variables: true)
context = Liquid::Context.new(d)
d.context = context
result = t.render(context, strict_variables: true)
assert_equal('foo ', result)
assert_equal(1, t.errors.count)
assert_instance_of(Liquid::UndefinedDropMethod, t.errors[0])
assert_equal(1, context.errors.count)
assert_instance_of(Liquid::UndefinedDropMethod, context.errors[0])
end
def test_undefined_drop_methods_raise
@@ -336,12 +319,13 @@ class TemplateTest < Minitest::Test
"-#{v}-"
end
end
result = t.render({ 'a' => 123, 'x' => 'foo' }, filters: [filters], strict_filters: true)
context = Liquid::Context.new('a' => 123, 'x' => 'foo')
result = t.render(context, filters: [filters], strict_filters: true)
assert_equal('123 ', result)
assert_equal(1, t.errors.count)
assert_instance_of(Liquid::UndefinedFilter, t.errors[0])
assert_equal('Liquid error: undefined filter somefilter1', t.errors[0].message)
assert_equal(1, context.errors.count)
assert_instance_of(Liquid::UndefinedFilter, context.errors[0])
assert_equal('Liquid error: undefined filter somefilter1', context.errors[0].message)
end
def test_undefined_filters_raise
@@ -362,11 +346,47 @@ class TemplateTest < Minitest::Test
assert_equal('12345', result)
end
unless taint_supported?
def test_taint_mode
assert_raises(NotImplementedError) do
Template.taint_mode = :warn
end
end
def test_render_uses_correct_disabled_tags_instance
Liquid::Template.file_system = StubFileSystem.new(
'foo' => 'bar',
'test_include' => '{% include "foo" %}'
)
disabled_tags = DisabledTags.new
context = Context.build(registers: { disabled_tags: disabled_tags })
source = "{% render 'test_include' %}"
parse_context = Liquid::ParseContext.new(line_numbers: true, error_mode: :strict)
document = Document.parse(Liquid::Tokenizer.new(source, true), parse_context)
assert_equal("include usage is not allowed in this context", document.render(context))
end
def test_render_sets_context_static_register_when_register_key_does_exist
disabled_tags_for_test = DisabledTags.new
Template.add_register(:disabled_tags, disabled_tags_for_test)
t = Template.parse("{% if true %} Test Template {% endif %}")
context = Context.new
refute(context.registers.key?(:disabled_tags))
t.render(context)
assert(context.registers.key?(:disabled_tags))
assert_equal(disabled_tags_for_test, context.registers[:disabled_tags])
end
def test_render_does_not_override_context_static_register_when_register_key_exists
context = Context.new
context.registers[:random_register] = nil
Template.add_register(:random_register, {})
t = Template.parse("{% if true %} Test Template {% endif %}")
t.render(context)
assert_nil(context.registers[:random_register])
assert(context.registers.key?(:random_register))
end
end

View File

@@ -51,29 +51,10 @@ class VariableTest < Minitest::Test
assert_equal('cat', Template.parse("{{ nil | append: 'cat' }}").render!)
end
def test_preset_assigns
template = Template.parse(%({{ test }}))
template.assigns['test'] = 'worked'
assert_equal('worked', template.render!)
end
def test_reuse_parsed_template
template = Template.parse(%({{ greeting }} {{ name }}))
template.assigns['greeting'] = 'Goodbye'
template = Template.parse(%({{ greeting }} {{ name }}))
assert_equal('Hello Tobi', template.render!('greeting' => 'Hello', 'name' => 'Tobi'))
assert_equal('Hello ', template.render!('greeting' => 'Hello', 'unknown' => 'Tobi'))
assert_equal('Hello Brian', template.render!('greeting' => 'Hello', 'name' => 'Brian'))
assert_equal('Goodbye Brian', template.render!('name' => 'Brian'))
assert_equal({ 'greeting' => 'Goodbye' }, template.assigns)
end
def test_assigns_not_polluted_from_template
template = Template.parse(%({{ test }}{% assign test = 'bar' %}{{ test }}))
template.assigns['test'] = 'baz'
assert_equal('bazbar', template.render!)
assert_equal('bazbar', template.render!)
assert_equal('foobar', template.render!('test' => 'foo'))
assert_equal('bazbar', template.render!)
assert_equal('Goodbye Brian', template.render!('greeting' => 'Goodbye', 'name' => 'Brian'))
end
def test_hash_with_default_proc

View File

@@ -32,10 +32,6 @@ module Minitest
def fixture(name)
File.join(File.expand_path(__dir__), "fixtures", name)
end
def self.taint_supported?
Object.new.taint.tainted?
end
end
module Assertions
@@ -93,14 +89,6 @@ module Minitest
Liquid::StrainerFactory.instance_variable_set(:@global_filters, original_global_filters)
end
def with_taint_mode(mode)
old_mode = Liquid::Template.taint_mode
Liquid::Template.taint_mode = mode
yield
ensure
Liquid::Template.taint_mode = old_mode
end
def with_error_mode(mode)
old_mode = Liquid::Template.error_mode
Liquid::Template.error_mode = mode

View File

@@ -5,244 +5,152 @@ require 'test_helper'
class StaticRegistersUnitTest < Minitest::Test
include Liquid
def set
static_register = StaticRegisters.new
static_register[nil] = true
static_register[1] = :one
static_register[:one] = "one"
static_register["two"] = "three"
static_register["two"] = 3
static_register[false] = nil
def test_set
static_register = StaticRegisters.new(a: 1, b: 2)
static_register[:b] = 22
static_register[:c] = 33
assert_equal({ nil => true, 1 => :one, :one => "one", "two" => 3, false => nil }, static_register.registers)
static_register
assert_equal(1, static_register[:a])
assert_equal(22, static_register[:b])
assert_equal(33, static_register[:c])
end
def test_get
static_register = set
def test_get_missing_key
static_register = StaticRegisters.new
assert_equal(true, static_register[nil])
assert_equal(:one, static_register[1])
assert_equal("one", static_register[:one])
assert_equal(3, static_register["two"])
assert_nil(static_register[false])
assert_nil(static_register["unknown"])
assert_nil(static_register[:missing])
end
def test_delete
static_register = set
static_register = StaticRegisters.new(a: 1, b: 2)
static_register[:b] = 22
static_register[:c] = 33
assert_equal(true, static_register.delete(nil))
assert_equal(:one, static_register.delete(1))
assert_equal("one", static_register.delete(:one))
assert_equal(3, static_register.delete("two"))
assert_nil(static_register.delete(false))
assert_nil(static_register.delete("unknown"))
assert_nil(static_register.delete(:a))
assert_equal({}, static_register.registers)
assert_equal(22, static_register.delete(:b))
assert_equal(33, static_register.delete(:c))
assert_nil(static_register[:c])
assert_nil(static_register.delete(:d))
end
def test_fetch
static_register = set
static_register = StaticRegisters.new(a: 1, b: 2)
static_register[:b] = 22
static_register[:c] = 33
assert_equal(true, static_register.fetch(nil))
assert_equal(:one, static_register.fetch(1))
assert_equal("one", static_register.fetch(:one))
assert_equal(3, static_register.fetch("two"))
assert_nil(static_register.fetch(false))
assert_nil(static_register.fetch("unknown"))
end
assert_equal(1, static_register.fetch(:a))
assert_equal(1, static_register.fetch(:a, "default"))
assert_equal(22, static_register.fetch(:b))
assert_equal(22, static_register.fetch(:b, "default"))
assert_equal(33, static_register.fetch(:c))
assert_equal(33, static_register.fetch(:c, "default"))
def test_fetch_default
static_register = StaticRegisters.new
assert_raises(KeyError) do
static_register.fetch(:d)
end
assert_equal("default", static_register.fetch(:d, "default"))
assert_equal(true, static_register.fetch(nil, true))
assert_equal(:one, static_register.fetch(1, :one))
assert_equal("one", static_register.fetch(:one, "one"))
assert_equal(3, static_register.fetch("two", 3))
assert_nil(static_register.fetch(false, nil))
result = static_register.fetch(:d) { "default" }
assert_equal("default", result)
result = static_register.fetch(:d, "default 1") { "default 2" }
assert_equal("default 2", result)
end
def test_key
static_register = set
static_register = StaticRegisters.new(a: 1, b: 2)
static_register[:b] = 22
static_register[:c] = 33
assert_equal(true, static_register.key?(nil))
assert_equal(true, static_register.key?(1))
assert_equal(true, static_register.key?(:one))
assert_equal(true, static_register.key?("two"))
assert_equal(true, static_register.key?(false))
assert_equal(false, static_register.key?("unknown"))
assert_equal(false, static_register.key?(true))
end
def set_with_static
static_register = StaticRegisters.new(nil => true, 1 => :one, :one => "one", "two" => 3, false => nil)
static_register[nil] = false
static_register["two"] = 4
static_register[true] = "foo"
assert_equal({ nil => true, 1 => :one, :one => "one", "two" => 3, false => nil }, static_register.static)
assert_equal({ nil => false, "two" => 4, true => "foo" }, static_register.registers)
static_register
end
def test_get_with_static
static_register = set_with_static
assert_equal(false, static_register[nil])
assert_equal(:one, static_register[1])
assert_equal("one", static_register[:one])
assert_equal(4, static_register["two"])
assert_equal("foo", static_register[true])
assert_nil(static_register[false])
end
def test_delete_with_static
static_register = set_with_static
assert_equal(false, static_register.delete(nil))
assert_equal(4, static_register.delete("two"))
assert_equal("foo", static_register.delete(true))
assert_nil(static_register.delete("unknown"))
assert_nil(static_register.delete(:one))
assert_equal({}, static_register.registers)
assert_equal({ nil => true, 1 => :one, :one => "one", "two" => 3, false => nil }, static_register.static)
end
def test_fetch_with_static
static_register = set_with_static
assert_equal(false, static_register.fetch(nil))
assert_equal(:one, static_register.fetch(1))
assert_equal("one", static_register.fetch(:one))
assert_equal(4, static_register.fetch("two"))
assert_equal("foo", static_register.fetch(true))
assert_nil(static_register.fetch(false))
end
def test_key_with_static
static_register = set_with_static
assert_equal(true, static_register.key?(nil))
assert_equal(true, static_register.key?(1))
assert_equal(true, static_register.key?(:one))
assert_equal(true, static_register.key?("two"))
assert_equal(true, static_register.key?(false))
assert_equal(false, static_register.key?("unknown"))
assert_equal(true, static_register.key?(true))
assert_equal(true, static_register.key?(:a))
assert_equal(true, static_register.key?(:b))
assert_equal(true, static_register.key?(:c))
assert_equal(false, static_register.key?(:d))
end
def test_static_register_can_be_frozen
static_register = set_with_static
static_register = StaticRegisters.new(a: 1)
static = static_register.static.freeze
static_register.static.freeze
assert_raises(RuntimeError) do
static["two"] = "foo"
static_register.static[:a] = "foo"
end
assert_raises(RuntimeError) do
static["unknown"] = "foo"
static_register.static[:b] = "foo"
end
assert_raises(RuntimeError) do
static.delete("two")
static_register.static.delete(:a)
end
assert_raises(RuntimeError) do
static_register.static.delete(:c)
end
end
def test_new_static_retains_static
static_register = StaticRegisters.new(nil => true, 1 => :one, :one => "one", "two" => 3, false => nil)
static_register["one"] = 1
static_register["two"] = 2
static_register["three"] = 3
static_register = StaticRegisters.new(a: 1, b: 2)
static_register[:b] = 22
static_register[:c] = 33
new_register = StaticRegisters.new(static_register)
assert_equal({}, new_register.registers)
new_static_register = StaticRegisters.new(static_register)
new_static_register[:b] = 222
new_register["one"] = 4
new_register["two"] = 5
new_register["three"] = 6
newest_static_register = StaticRegisters.new(new_static_register)
newest_static_register[:c] = 333
newest_register = StaticRegisters.new(new_register)
assert_equal({}, newest_register.registers)
assert_equal(1, static_register[:a])
assert_equal(22, static_register[:b])
assert_equal(33, static_register[:c])
newest_register["one"] = 7
newest_register["two"] = 8
newest_register["three"] = 9
assert_equal(1, new_static_register[:a])
assert_equal(222, new_static_register[:b])
assert_nil(new_static_register[:c])
assert_equal({ "one" => 1, "two" => 2, "three" => 3 }, static_register.registers)
assert_equal({ "one" => 4, "two" => 5, "three" => 6 }, new_register.registers)
assert_equal({ "one" => 7, "two" => 8, "three" => 9 }, newest_register.registers)
assert_equal({ nil => true, 1 => :one, :one => "one", "two" => 3, false => nil }, static_register.static)
assert_equal({ nil => true, 1 => :one, :one => "one", "two" => 3, false => nil }, new_register.static)
assert_equal({ nil => true, 1 => :one, :one => "one", "two" => 3, false => nil }, newest_register.static)
assert_equal(1, newest_static_register[:a])
assert_equal(2, newest_static_register[:b])
assert_equal(333, newest_static_register[:c])
end
def test_multiple_instances_are_unique
static_register = StaticRegisters.new(nil => true, 1 => :one, :one => "one", "two" => 3, false => nil)
static_register["one"] = 1
static_register["two"] = 2
static_register["three"] = 3
static_register_1 = StaticRegisters.new(a: 1, b: 2)
static_register_1[:b] = 22
static_register_1[:c] = 33
new_register = StaticRegisters.new(foo: :bar)
assert_equal({}, new_register.registers)
static_register_2 = StaticRegisters.new(a: 10, b: 20)
static_register_2[:b] = 220
static_register_2[:c] = 330
new_register["one"] = 4
new_register["two"] = 5
new_register["three"] = 6
assert_equal({ a: 1, b: 2 }, static_register_1.static)
assert_equal(1, static_register_1[:a])
assert_equal(22, static_register_1[:b])
assert_equal(33, static_register_1[:c])
newest_register = StaticRegisters.new(bar: :foo)
assert_equal({}, newest_register.registers)
newest_register["one"] = 7
newest_register["two"] = 8
newest_register["three"] = 9
assert_equal({ "one" => 1, "two" => 2, "three" => 3 }, static_register.registers)
assert_equal({ "one" => 4, "two" => 5, "three" => 6 }, new_register.registers)
assert_equal({ "one" => 7, "two" => 8, "three" => 9 }, newest_register.registers)
assert_equal({ nil => true, 1 => :one, :one => "one", "two" => 3, false => nil }, static_register.static)
assert_equal({ foo: :bar }, new_register.static)
assert_equal({ bar: :foo }, newest_register.static)
assert_equal({ a: 10, b: 20 }, static_register_2.static)
assert_equal(10, static_register_2[:a])
assert_equal(220, static_register_2[:b])
assert_equal(330, static_register_2[:c])
end
def test_can_update_static_directly_and_updates_all_instances
static_register = StaticRegisters.new(nil => true, 1 => :one, :one => "one", "two" => 3, false => nil)
static_register["one"] = 1
static_register["two"] = 2
static_register["three"] = 3
def test_initialization_reused_static_same_memory_object
static_register_1 = StaticRegisters.new(a: 1, b: 2)
static_register_1[:b] = 22
static_register_1[:c] = 33
new_register = StaticRegisters.new(static_register)
assert_equal({}, new_register.registers)
static_register_2 = StaticRegisters.new(static_register_1)
assert_equal({ nil => true, 1 => :one, :one => "one", "two" => 3, false => nil }, static_register.static)
assert_equal(1, static_register_2[:a])
assert_equal(2, static_register_2[:b])
assert_nil(static_register_2[:c])
new_register["one"] = 4
new_register["two"] = 5
new_register["three"] = 6
new_register.static["four"] = 10
static_register_1.static[:b] = 222
static_register_1.static[:c] = 333
newest_register = StaticRegisters.new(new_register)
assert_equal({}, newest_register.registers)
assert_equal({ nil => true, 1 => :one, :one => "one", "two" => 3, false => nil, "four" => 10 }, new_register.static)
newest_register["one"] = 7
newest_register["two"] = 8
newest_register["three"] = 9
new_register.static["four"] = 5
new_register.static["five"] = 15
assert_equal({ "one" => 1, "two" => 2, "three" => 3 }, static_register.registers)
assert_equal({ "one" => 4, "two" => 5, "three" => 6 }, new_register.registers)
assert_equal({ "one" => 7, "two" => 8, "three" => 9 }, newest_register.registers)
assert_equal({ nil => true, 1 => :one, :one => "one", "two" => 3, false => nil, "four" => 5, "five" => 15 }, newest_register.static)
assert_equal({ nil => true, 1 => :one, :one => "one", "two" => 3, false => nil, "four" => 5, "five" => 15 }, static_register.static)
assert_equal({ nil => true, 1 => :one, :one => "one", "two" => 3, false => nil, "four" => 5, "five" => 15 }, new_register.static)
assert_same(static_register_1.static, static_register_2.static)
end
end

View File

@@ -77,4 +77,11 @@ class TemplateUnitTest < Minitest::Test
ensure
Template.tags.delete('fake')
end
class TemplateSubclass < Liquid::Template
end
def test_template_inheritance
assert_equal("foo", TemplateSubclass.parse("foo").render)
end
end