Compare commits

...

25 Commits

Author SHA1 Message Date
Dylan Thacker-Smith
391a6eb92b Temporarily use liquid-c branch for its corresponding change 2020-08-20 10:12:28 -04:00
Dylan Thacker-Smith
89d206d8c6 Use symbols for filter names now that ruby has symbol GC 2020-08-20 10:06:17 -04:00
Dylan Thacker-Smith
5532df880f Handle disabled tags errors like other liquid errors (#1275) 2020-08-18 11:39:54 -04:00
Dylan Thacker-Smith
2b11efc3ae Fix performance regression from introduction of Template#disable_tags (#1274) 2020-08-18 11:25:51 -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
34 changed files with 336 additions and 508 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

@@ -22,6 +22,6 @@ group :test do
gem 'rubocop-performance', require: false
platform :mri, :truffleruby do
gem 'liquid-c', github: 'Shopify/liquid-c', ref: 'master'
gem 'liquid-c', github: 'Shopify/liquid-c', branch: 'symbol-filter-names'
end
end

View File

@@ -4,6 +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]
* StaticRegisters#fetch to raise on missing key (#1250) [Thierry Joyal]
## 4.0.3 / 2019-03-12

View File

@@ -63,6 +63,8 @@ require 'liquid/expression'
require 'liquid/context'
require 'liquid/parser_switching'
require 'liquid/tag'
require 'liquid/tag/disabler'
require 'liquid/tag/disableable'
require 'liquid/block'
require 'liquid/block_body'
require 'liquid/document'
@@ -86,4 +88,3 @@ require 'liquid/template_factory'
# Load all the tags of the standard library
#
Dir["#{__dir__}/liquid/tags/*.rb"].each { |f| require f }
Dir["#{__dir__}/liquid/registers/*.rb"].each { |f| require f }

View File

@@ -171,13 +171,7 @@ module Liquid
private
def render_node(context, output, node)
if node.disabled?(context)
output << node.disabled_error_message
return
end
disable_tags(context, node.disabled_tags) do
node.render_to_output_buffer(context, output)
end
node.render_to_output_buffer(context, output)
rescue UndefinedVariable, UndefinedDropMethod, UndefinedFilter => e
context.handle_error(e, node.line_number)
rescue ::StandardError => e
@@ -185,11 +179,6 @@ module Liquid
output << context.handle_error(e, line_number)
end
def disable_tags(context, tags, &block)
return yield if tags.empty?
context.registers[:disabled_tags].disable(tags, &block)
end
def raise_if_resource_limits_reached(context, length)
context.resource_limits.render_length += length
return unless context.resource_limits.reached?

View File

@@ -44,6 +44,7 @@ module Liquid
@interrupts = []
@filters = []
@global_filter = nil
@disabled_tags = {}
end
# rubocop:enable Metrics/ParameterLists
@@ -144,6 +145,7 @@ module Liquid
subcontext.strainer = nil
subcontext.errors = errors
subcontext.warnings = warnings
subcontext.disabled_tags = @disabled_tags
end
end
@@ -208,9 +210,24 @@ module Liquid
end
end
def with_disabled_tags(tag_names)
tag_names.each do |name|
@disabled_tags[name] = @disabled_tags.fetch(name, 0) + 1
end
yield
ensure
tag_names.each do |name|
@disabled_tags[name] -= 1
end
end
def tag_disabled?(tag_name)
@disabled_tags.fetch(tag_name, 0) > 0
end
protected
attr_writer :base_scope_depth, :warnings, :errors, :strainer, :filters
attr_writer :base_scope_depth, :warnings, :errors, :strainer, :filters, :disabled_tags
private

View File

@@ -46,7 +46,6 @@ module Liquid
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)
@@ -54,5 +53,6 @@ module Liquid
UndefinedDropMethod = Class.new(Error)
UndefinedFilter = Class.new(Error)
MethodOverrideError = Class.new(Error)
DisabledError = Class.new(Error)
InternalError = Class.new(Error)
end

View File

@@ -1,25 +1,21 @@
# frozen_string_literal: true
module Liquid
class BlockBody
def render_node_with_profiling(context, output, node)
module BlockBodyProfilingHook
def render_node(context, output, node)
Profiler.profile_node_render(node) do
render_node_without_profiling(context, output, node)
super
end
end
alias_method :render_node_without_profiling, :render_node
alias_method :render_node, :render_node_with_profiling
end
BlockBody.prepend(BlockBodyProfilingHook)
class Include < Tag
def render_to_output_buffer_with_profiling(context, output)
module IncludeProfilingHook
def render_to_output_buffer(context, output)
Profiler.profile_children(context.evaluate(@template_name_expr).to_s) do
render_to_output_buffer_without_profiling(context, output)
super
end
end
alias_method :render_to_output_buffer_without_profiling, :render_to_output_buffer
alias_method :render_to_output_buffer, :render_to_output_buffer_with_profiling
end
Include.prepend(IncludeProfilingHook)
end

View File

@@ -1,32 +0,0 @@
# frozen_string_literal: true
module Liquid
class DisabledTags < Register
def initialize
@disabled_tags = {}
end
def disabled?(tag)
@disabled_tags.key?(tag) && @disabled_tags[tag] > 0
end
def disable(tags)
tags.each(&method(:increment))
yield
ensure
tags.each(&method(:decrement))
end
private
def increment(tag)
@disabled_tags[tag] ||= 0
@disabled_tags[tag] += 1
end
def decrement(tag)
@disabled_tags[tag] -= 1
end
end
Template.add_register(:disabled_tags, DisabledTags.new)
end

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

@@ -24,17 +24,17 @@ module Liquid
include(filter)
filter_methods.merge(filter.public_instance_methods.map(&:to_s))
filter.public_instance_methods.each { |name| filter_methods[name] = true }
end
def invokable?(method)
filter_methods.include?(method.to_s)
filter_methods.key?(method.to_sym)
end
private
def filter_methods
@filter_methods ||= Set.new
@filter_methods ||= {}
end
end

View File

@@ -13,15 +13,13 @@ module Liquid
tag
end
def disable_tags(*tags)
disabled_tags.push(*tags)
def disable_tags(*tag_names)
@disabled_tags ||= []
@disabled_tags.concat(tag_names)
prepend(Disabler)
end
private :new
def disabled_tags
@disabled_tags ||= []
end
end
def initialize(tag_name, markup, parse_context)
@@ -46,14 +44,6 @@ module Liquid
''
end
def disabled?(context)
context.registers[:disabled_tags].disabled?(tag_name)
end
def disabled_error_message
"#{tag_name} #{options[:locale].t('errors.disabled.tag')}"
end
# For backwards compatibility with custom tags. In a future release, the semantics
# of the `render_to_output_buffer` method will become the default and the `render`
# method will be removed.
@@ -65,9 +55,5 @@ module Liquid
def blank?
false
end
def disabled_tags
self.class.disabled_tags
end
end
end

View File

@@ -0,0 +1,22 @@
# frozen_string_literal: true
module Liquid
class Tag
module Disableable
def render_to_output_buffer(context, output)
if context.tag_disabled?(tag_name)
output << disabled_error(context)
return
end
super
end
def disabled_error(context)
# raise then rescue the exception so that the Context#exception_renderer can re-raise it
raise DisabledError, "#{tag_name} #{parse_context[:locale].t('errors.disabled.tag')}"
rescue DisabledError => exc
context.handle_error(exc, line_number)
end
end
end
end

View File

@@ -0,0 +1,21 @@
# frozen_string_literal: true
module Liquid
class Tag
module Disabler
module ClassMethods
attr_reader :disabled_tags
end
def self.prepended(base)
base.extend(ClassMethods)
end
def render_to_output_buffer(context, output)
context.with_disabled_tags(self.class.disabled_tags) do
super
end
end
end
end
end

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,6 +16,8 @@ module Liquid
# {% include 'product' for products %}
#
class Include < Tag
prepend Tag::Disableable
SYNTAX = /(#{QuotedFragment}+)(\s+(?:with|for)\s+(#{QuotedFragment}+))?(\s+(?:as)\s+(#{VariableSegment}+))?/o
Syntax = SYNTAX

View File

@@ -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
@@ -97,14 +80,6 @@ module Liquid
tags[name.to_s] = klass
end
attr_accessor :registers
Template.registers = {}
private :registers=
def add_register(name, klass)
registers[name.to_sym] = klass
end
# Pass a module with filter methods which should be available
# to all liquid views. Good for registering the standard library
def register_filter(mod)
@@ -119,14 +94,13 @@ 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)
@resource_limits = ResourceLimits.new(Template.default_resource_limits)
end
# Parse source code.
@@ -212,10 +186,6 @@ module Liquid
context.add_filters(args.pop)
end
Template.registers.each do |key, register|
context_register[key] = register
end
# Retrying a render resets resource usage
context.resource_limits.reset

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)
@@ -125,7 +123,7 @@ module Liquid
filter_args << Expression.parse(a)
end
end
result = [filter_name, filter_args]
result = [filter_name.to_sym, filter_args]
result << keyword_args if keyword_args
result
end
@@ -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

@@ -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

@@ -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

@@ -1,27 +0,0 @@
# frozen_string_literal: true
require 'test_helper'
class DisabledTagsTest < Minitest::Test
include Liquid
class DisableRaw < Block
disable_tags "raw"
end
class DisableRawEcho < Block
disable_tags "raw", "echo"
end
def test_disables_raw
with_custom_tag('disable', DisableRaw) do
assert_template_result 'raw usage is not allowed in this contextfoo', '{% disable %}{% raw %}Foobar{% endraw %}{% echo "foo" %}{% enddisable %}'
end
end
def test_disables_echo_and_raw
with_custom_tag('disable', DisableRawEcho) do
assert_template_result 'raw usage is not allowed in this contextecho usage is not allowed in this context', '{% disable %}{% raw %}Foobar{% endraw %}{% echo "foo" %}{% enddisable %}'
end
end
end

View File

@@ -43,15 +43,22 @@ class SecurityTest < Minitest::Test
assert_equal(expected, Template.parse(text).render!(@assigns, filters: SecurityFilter))
end
def test_does_not_add_filters_to_symbol_table
def test_does_not_permanently_add_filters_to_symbol_table
current_symbols = Symbol.all_symbols
test = %( {{ "some_string" | a_bad_filter }} )
# MRI imprecisely marks objects found on the C stack, which can result
# in uninitialized memory being marked. This can even result in the test failing
# deterministically for a given compilation of ruby. Using a separate thread will
# keep these writes of the symbol pointer on a separate stack that will be garbage
# collected after Thread#join.
Thread.new do
test = %( {{ "some_string" | a_bad_filter }} )
Template.parse(test).render!
nil
end.join
template = Template.parse(test)
assert_equal([], (Symbol.all_symbols - current_symbols))
GC.start
template.render!
assert_equal([], (Symbol.all_symbols - current_symbols))
end

View File

@@ -0,0 +1,51 @@
# frozen_string_literal: true
require 'test_helper'
class TagDisableableTest < Minitest::Test
include Liquid
class DisableRaw < Block
disable_tags "raw"
end
class DisableRawEcho < Block
disable_tags "raw", "echo"
end
class DisableableRaw < Liquid::Raw
prepend Liquid::Tag::Disableable
end
class DisableableEcho < Liquid::Echo
prepend Liquid::Tag::Disableable
end
def test_disables_raw
with_disableable_tags do
with_custom_tag('disable', DisableRaw) do
output = Template.parse('{% disable %}{% raw %}Foobar{% endraw %}{% echo "foo" %}{% enddisable %}').render
assert_equal('Liquid error: raw usage is not allowed in this contextfoo', output)
end
end
end
def test_disables_echo_and_raw
with_disableable_tags do
with_custom_tag('disable', DisableRawEcho) do
output = Template.parse('{% disable %}{% raw %}Foobar{% endraw %}{% echo "foo" %}{% enddisable %}').render
assert_equal('Liquid error: raw usage is not allowed in this contextLiquid error: echo usage is not allowed in this context', output)
end
end
end
private
def with_disableable_tags
with_custom_tag('raw', DisableableRaw) do
with_custom_tag('echo', DisableableEcho) do
yield
end
end
end
end

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 }}")
@@ -155,7 +127,10 @@ class RenderTagTest < Minitest::Test
'test_include' => '{% include "foo" %}'
)
assert_template_result('include usage is not allowed in this context', '{% render "test_include" %}')
exc = assert_raises(Liquid::DisabledError) do
Liquid::Template.parse('{% render "test_include" %}').render!
end
assert_equal('Liquid error: include usage is not allowed in this context', exc.message)
end
def test_includes_will_not_render_inside_nested_sibling_tags
@@ -165,7 +140,8 @@ class RenderTagTest < Minitest::Test
'test_include' => '{% include "foo" %}'
)
assert_template_result('include usage is not allowed in this contextinclude usage is not allowed in this context', '{% render "nested_render_with_sibling_include" %}')
output = Liquid::Template.parse('{% render "nested_render_with_sibling_include" %}').render
assert_equal('Liquid error: include usage is not allowed in this contextLiquid error: include usage is not allowed in this context', output)
end
def test_render_tag_with

View File

@@ -361,12 +361,4 @@ class TemplateTest < Minitest::Test
result = t.render('x' => 1, 'y' => 5)
assert_equal('12345', result)
end
unless taint_supported?
def test_taint_mode
assert_raises(NotImplementedError) do
Template.taint_mode = :warn
end
end
end
end

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
@@ -110,10 +98,17 @@ module Minitest
end
def with_custom_tag(tag_name, tag_class)
Liquid::Template.register_tag(tag_name, tag_class)
yield
ensure
Liquid::Template.tags.delete(tag_name)
old_tag = Liquid::Template.tags[tag_name]
begin
Liquid::Template.register_tag(tag_name, tag_class)
yield
ensure
if old_tag
Liquid::Template.tags[tag_name] = old_tag
else
Liquid::Template.tags.delete(tag_name)
end
end
end
end
end

View File

@@ -563,6 +563,35 @@ class ContextUnitTest < Minitest::Test
assert_equal('my filter result', template.render(subcontext))
end
def test_disables_tag_specified
context = Context.new
context.with_disabled_tags(%w(foo bar)) do
assert_equal true, context.tag_disabled?("foo")
assert_equal true, context.tag_disabled?("bar")
assert_equal false, context.tag_disabled?("unknown")
end
end
def test_disables_nested_tags
context = Context.new
context.with_disabled_tags(["foo"]) do
context.with_disabled_tags(["foo"]) do
assert_equal true, context.tag_disabled?("foo")
assert_equal false, context.tag_disabled?("bar")
end
context.with_disabled_tags(["bar"]) do
assert_equal true, context.tag_disabled?("foo")
assert_equal true, context.tag_disabled?("bar")
context.with_disabled_tags(["foo"]) do
assert_equal true, context.tag_disabled?("foo")
assert_equal true, context.tag_disabled?("bar")
end
end
assert_equal true, context.tag_disabled?("foo")
assert_equal false, context.tag_disabled?("bar")
end
end
private
def assert_no_object_allocations

View File

@@ -1,36 +0,0 @@
# frozen_string_literal: true
require 'test_helper'
class DisabledTagsUnitTest < Minitest::Test
include Liquid
def test_disables_tag_specified
register = DisabledTags.new
register.disable(%w(foo bar)) do
assert_equal true, register.disabled?("foo")
assert_equal true, register.disabled?("bar")
assert_equal false, register.disabled?("unknown")
end
end
def test_disables_nested_tags
register = DisabledTags.new
register.disable(["foo"]) do
register.disable(["foo"]) do
assert_equal true, register.disabled?("foo")
assert_equal false, register.disabled?("bar")
end
register.disable(["bar"]) do
assert_equal true, register.disabled?("foo")
assert_equal true, register.disabled?("bar")
register.disable(["foo"]) do
assert_equal true, register.disabled?("foo")
assert_equal true, register.disabled?("bar")
end
end
assert_equal true, register.disabled?("foo")
assert_equal false, register.disabled?("bar")
end
end
end

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

@@ -60,7 +60,7 @@ class StrainerTemplateUnitTest < Minitest::Test
strainer = Context.new.strainer
with_global_filter do
strainer.class.add_filter(PublicMethodOverrideFilter)
assert(strainer.class.send(:filter_methods).include?('public_filter'))
assert(strainer.class.send(:filter_methods).include?(:public_filter))
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

View File

@@ -13,75 +13,75 @@ class VariableUnitTest < Minitest::Test
def test_filters
var = create_variable('hello | textileze')
assert_equal(VariableLookup.new('hello'), var.name)
assert_equal([['textileze', []]], var.filters)
assert_equal([[:textileze, []]], var.filters)
var = create_variable('hello | textileze | paragraph')
assert_equal(VariableLookup.new('hello'), var.name)
assert_equal([['textileze', []], ['paragraph', []]], var.filters)
assert_equal([[:textileze, []], [:paragraph, []]], var.filters)
var = create_variable(%( hello | strftime: '%Y'))
assert_equal(VariableLookup.new('hello'), var.name)
assert_equal([['strftime', ['%Y']]], var.filters)
assert_equal([[:strftime, ['%Y']]], var.filters)
var = create_variable(%( 'typo' | link_to: 'Typo', true ))
assert_equal('typo', var.name)
assert_equal([['link_to', ['Typo', true]]], var.filters)
assert_equal([[:link_to, ['Typo', true]]], var.filters)
var = create_variable(%( 'typo' | link_to: 'Typo', false ))
assert_equal('typo', var.name)
assert_equal([['link_to', ['Typo', false]]], var.filters)
assert_equal([[:link_to, ['Typo', false]]], var.filters)
var = create_variable(%( 'foo' | repeat: 3 ))
assert_equal('foo', var.name)
assert_equal([['repeat', [3]]], var.filters)
assert_equal([[:repeat, [3]]], var.filters)
var = create_variable(%( 'foo' | repeat: 3, 3 ))
assert_equal('foo', var.name)
assert_equal([['repeat', [3, 3]]], var.filters)
assert_equal([[:repeat, [3, 3]]], var.filters)
var = create_variable(%( 'foo' | repeat: 3, 3, 3 ))
assert_equal('foo', var.name)
assert_equal([['repeat', [3, 3, 3]]], var.filters)
assert_equal([[:repeat, [3, 3, 3]]], var.filters)
var = create_variable(%( hello | strftime: '%Y, okay?'))
assert_equal(VariableLookup.new('hello'), var.name)
assert_equal([['strftime', ['%Y, okay?']]], var.filters)
assert_equal([[:strftime, ['%Y, okay?']]], var.filters)
var = create_variable(%( hello | things: "%Y, okay?", 'the other one'))
assert_equal(VariableLookup.new('hello'), var.name)
assert_equal([['things', ['%Y, okay?', 'the other one']]], var.filters)
assert_equal([[:things, ['%Y, okay?', 'the other one']]], var.filters)
end
def test_filter_with_date_parameter
var = create_variable(%( '2006-06-06' | date: "%m/%d/%Y"))
assert_equal('2006-06-06', var.name)
assert_equal([['date', ['%m/%d/%Y']]], var.filters)
assert_equal([[:date, ['%m/%d/%Y']]], var.filters)
end
def test_filters_without_whitespace
var = create_variable('hello | textileze | paragraph')
assert_equal(VariableLookup.new('hello'), var.name)
assert_equal([['textileze', []], ['paragraph', []]], var.filters)
assert_equal([[:textileze, []], [:paragraph, []]], var.filters)
var = create_variable('hello|textileze|paragraph')
assert_equal(VariableLookup.new('hello'), var.name)
assert_equal([['textileze', []], ['paragraph', []]], var.filters)
assert_equal([[:textileze, []], [:paragraph, []]], var.filters)
var = create_variable("hello|replace:'foo','bar'|textileze")
assert_equal(VariableLookup.new('hello'), var.name)
assert_equal([['replace', ['foo', 'bar']], ['textileze', []]], var.filters)
assert_equal([[:replace, ['foo', 'bar']], [:textileze, []]], var.filters)
end
def test_symbol
var = create_variable("http://disney.com/logo.gif | image: 'med' ", error_mode: :lax)
assert_equal(VariableLookup.new('http://disney.com/logo.gif'), var.name)
assert_equal([['image', ['med']]], var.filters)
assert_equal([[:image, ['med']]], var.filters)
end
def test_string_to_filter
var = create_variable("'http://disney.com/logo.gif' | image: 'med' ")
assert_equal('http://disney.com/logo.gif', var.name)
assert_equal([['image', ['med']]], var.filters)
assert_equal([[:image, ['med']]], var.filters)
end
def test_string_single_quoted
@@ -128,13 +128,13 @@ class VariableUnitTest < Minitest::Test
def test_filter_with_keyword_arguments
var = create_variable(%( hello | things: greeting: "world", farewell: 'goodbye'))
assert_equal(VariableLookup.new('hello'), var.name)
assert_equal([['things', [], { 'greeting' => 'world', 'farewell' => 'goodbye' }]], var.filters)
assert_equal([[:things, [], { 'greeting' => 'world', 'farewell' => 'goodbye' }]], var.filters)
end
def test_lax_filter_argument_parsing
var = create_variable(%( number_of_comments | pluralize: 'comment': 'comments' ), error_mode: :lax)
assert_equal(VariableLookup.new('number_of_comments'), var.name)
assert_equal([['pluralize', ['comment', 'comments']]], var.filters)
assert_equal([[:pluralize, ['comment', 'comments']]], var.filters)
end
def test_strict_filter_argument_parsing