liquid-c 4.0.1 → 4.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.github/workflows/cla.yml +23 -0
- data/.github/workflows/liquid.yml +36 -11
- data/.gitignore +4 -0
- data/.rubocop.yml +14 -0
- data/Gemfile +15 -5
- data/README.md +32 -8
- data/Rakefile +12 -63
- data/ext/liquid_c/block.c +493 -60
- data/ext/liquid_c/block.h +28 -2
- data/ext/liquid_c/c_buffer.c +42 -0
- data/ext/liquid_c/c_buffer.h +76 -0
- data/ext/liquid_c/context.c +233 -0
- data/ext/liquid_c/context.h +70 -0
- data/ext/liquid_c/document_body.c +97 -0
- data/ext/liquid_c/document_body.h +59 -0
- data/ext/liquid_c/expression.c +116 -0
- data/ext/liquid_c/expression.h +24 -0
- data/ext/liquid_c/extconf.rb +21 -9
- data/ext/liquid_c/intutil.h +22 -0
- data/ext/liquid_c/lexer.c +39 -3
- data/ext/liquid_c/lexer.h +18 -3
- data/ext/liquid_c/liquid.c +76 -6
- data/ext/liquid_c/liquid.h +24 -1
- data/ext/liquid_c/liquid_vm.c +618 -0
- data/ext/liquid_c/liquid_vm.h +25 -0
- data/ext/liquid_c/parse_context.c +76 -0
- data/ext/liquid_c/parse_context.h +13 -0
- data/ext/liquid_c/parser.c +153 -65
- data/ext/liquid_c/parser.h +4 -2
- data/ext/liquid_c/raw.c +136 -0
- data/ext/liquid_c/raw.h +6 -0
- data/ext/liquid_c/resource_limits.c +279 -0
- data/ext/liquid_c/resource_limits.h +23 -0
- data/ext/liquid_c/stringutil.h +44 -0
- data/ext/liquid_c/tokenizer.c +149 -35
- data/ext/liquid_c/tokenizer.h +20 -9
- data/ext/liquid_c/usage.c +18 -0
- data/ext/liquid_c/usage.h +9 -0
- data/ext/liquid_c/variable.c +196 -20
- data/ext/liquid_c/variable.h +18 -1
- data/ext/liquid_c/variable_lookup.c +44 -0
- data/ext/liquid_c/variable_lookup.h +8 -0
- data/ext/liquid_c/vm_assembler.c +491 -0
- data/ext/liquid_c/vm_assembler.h +240 -0
- data/ext/liquid_c/vm_assembler_pool.c +99 -0
- data/ext/liquid_c/vm_assembler_pool.h +26 -0
- data/lib/liquid/c/compile_ext.rb +44 -0
- data/lib/liquid/c/version.rb +3 -1
- data/lib/liquid/c.rb +226 -48
- data/liquid-c.gemspec +16 -10
- data/performance/c_profile.rb +23 -0
- data/performance.rb +6 -4
- data/rakelib/compile.rake +15 -0
- data/rakelib/integration_test.rake +43 -0
- data/rakelib/performance.rake +43 -0
- data/rakelib/rubocop.rake +6 -0
- data/rakelib/unit_test.rake +14 -0
- data/test/integration_test.rb +11 -0
- data/test/liquid_test_helper.rb +21 -0
- data/test/test_helper.rb +21 -2
- data/test/unit/block_test.rb +137 -0
- data/test/unit/context_test.rb +85 -0
- data/test/unit/expression_test.rb +191 -0
- data/test/unit/gc_stress_test.rb +28 -0
- data/test/unit/raw_test.rb +93 -0
- data/test/unit/resource_limits_test.rb +50 -0
- data/test/unit/tokenizer_test.rb +90 -20
- data/test/unit/variable_test.rb +279 -60
- metadata +60 -11
- data/test/liquid_test.rb +0 -11
@@ -0,0 +1,137 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "test_helper"
|
4
|
+
|
5
|
+
class BlockTest < Minitest::Test
|
6
|
+
def test_no_allocation_of_trimmed_strings
|
7
|
+
template = Liquid::Template.parse("{{ a -}} {{- b }}")
|
8
|
+
assert_equal(2, template.root.nodelist.size)
|
9
|
+
|
10
|
+
template = Liquid::Template.parse("{{ a -}} foo {{- b }}")
|
11
|
+
assert_equal(3, template.root.nodelist.size)
|
12
|
+
end
|
13
|
+
|
14
|
+
def test_raise_on_output_with_non_utf8_encoding
|
15
|
+
output = String.new(encoding: Encoding::ASCII)
|
16
|
+
template = Liquid::Template.parse("ascii text")
|
17
|
+
exc = assert_raises(Encoding::CompatibilityError) do
|
18
|
+
template.render!({}, output: output)
|
19
|
+
end
|
20
|
+
assert_equal("non-UTF8 encoded output (US-ASCII) not supported", exc.message)
|
21
|
+
end
|
22
|
+
|
23
|
+
def test_write_unicode_characters
|
24
|
+
output = String.new(encoding: Encoding::UTF_8)
|
25
|
+
template = Liquid::Template.parse("ü{{ unicode_char }}")
|
26
|
+
assert_equal("üñ", template.render!({ "unicode_char" => "ñ" }, output: output))
|
27
|
+
end
|
28
|
+
|
29
|
+
def test_op_write_raw_w
|
30
|
+
source = "a" * 2**8
|
31
|
+
template = Liquid::Template.parse(source)
|
32
|
+
assert_equal(source, template.render!)
|
33
|
+
end
|
34
|
+
|
35
|
+
def test_raise_for_non_c_parse_context
|
36
|
+
parse_context = Liquid::ParseContext.new
|
37
|
+
assert_raises(RuntimeError) do
|
38
|
+
Liquid::C::BlockBody.new(parse_context)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
# Test for bug: https://github.com/Shopify/liquid-c/pull/120
|
43
|
+
def test_bug_120_instrument
|
44
|
+
calls = []
|
45
|
+
Liquid::Usage.stub(:increment, ->(name) { calls << name }) do
|
46
|
+
Liquid::Template.parse("{{ -.1 }}")
|
47
|
+
end
|
48
|
+
assert_equal(["liquid_c_negative_float_without_integer"], calls)
|
49
|
+
|
50
|
+
calls = []
|
51
|
+
Liquid::Usage.stub(:increment, ->(name) { calls << name }) do
|
52
|
+
Liquid::Template.parse("{{ .1 }}")
|
53
|
+
end
|
54
|
+
assert_equal([], calls)
|
55
|
+
end
|
56
|
+
|
57
|
+
def test_disassemble_raw_w
|
58
|
+
source = "a" * 2**8
|
59
|
+
template = Liquid::Template.parse(source)
|
60
|
+
block_body = template.root.body
|
61
|
+
assert_equal(<<~ASM, block_body.disassemble)
|
62
|
+
0x0000: write_raw_w("#{source}")
|
63
|
+
0x0104: leave
|
64
|
+
ASM
|
65
|
+
end
|
66
|
+
|
67
|
+
def test_disassemble
|
68
|
+
source = <<~LIQUID
|
69
|
+
raw
|
70
|
+
{{- var | default: "none", allow_false: true -}}
|
71
|
+
{%- increment counter -%}
|
72
|
+
LIQUID
|
73
|
+
template = Liquid::Template.parse(source, line_numbers: true)
|
74
|
+
block_body = template.root.body
|
75
|
+
increment_node = block_body.nodelist[2]
|
76
|
+
assert_instance_of(Liquid::Increment, increment_node)
|
77
|
+
assert_equal(<<~ASM, block_body.disassemble)
|
78
|
+
0x0000: write_raw("raw")
|
79
|
+
0x0005: render_variable_rescue(line_number: 2)
|
80
|
+
0x0009: find_static_var("var")
|
81
|
+
0x000c: push_const(\"none\")
|
82
|
+
0x000f: push_const(\"allow_false\")
|
83
|
+
0x0012: push_true
|
84
|
+
0x0013: hash_new(1)
|
85
|
+
0x0015: builtin_filter(name: :default, num_args: 3)
|
86
|
+
0x0018: pop_write
|
87
|
+
0x0019: write_node(#{increment_node.inspect})
|
88
|
+
0x001c: leave
|
89
|
+
ASM
|
90
|
+
end
|
91
|
+
|
92
|
+
def test_exception_renderer_exception
|
93
|
+
original_error = Liquid::Error.new("original")
|
94
|
+
handler_error = RuntimeError.new("exception handler error")
|
95
|
+
context = Liquid::Context.new("raise_error" => ->(_ctx) { raise(original_error) })
|
96
|
+
context.exception_renderer = lambda do |exc|
|
97
|
+
if exc == original_error
|
98
|
+
raise(handler_error)
|
99
|
+
end
|
100
|
+
exc
|
101
|
+
end
|
102
|
+
template = Liquid::Template.parse("{% assign x = raise_error %}")
|
103
|
+
exc = assert_raises(RuntimeError) do
|
104
|
+
template.render(context)
|
105
|
+
end
|
106
|
+
assert_equal(handler_error, exc)
|
107
|
+
end
|
108
|
+
|
109
|
+
StubFileSystem = Struct.new(:partials) do
|
110
|
+
def read_template_file(template_path)
|
111
|
+
partials.fetch(template_path)
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
def test_include_partial_with_syntax_error
|
116
|
+
old_file_system = Liquid::Template.file_system
|
117
|
+
begin
|
118
|
+
Liquid::Template.file_system = StubFileSystem.new({
|
119
|
+
"invalid" => "{% foo %}",
|
120
|
+
"valid" => '{% include "nested" %}',
|
121
|
+
"nested" => "valid",
|
122
|
+
})
|
123
|
+
|
124
|
+
template = Liquid::Template.parse("{% include 'invalid' %},{% include 'valid' %}")
|
125
|
+
assert_equal("Liquid syntax error: Unknown tag 'foo',valid", template.render)
|
126
|
+
ensure
|
127
|
+
Liquid::Template.file_system = old_file_system
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
def test_assign_filter_argument_exception
|
132
|
+
source = "{% assign v = 'IN' | truncate: 123, liquid_error %}{{ v | default: 'err swallowed' }}"
|
133
|
+
template = Liquid::Template.parse(source)
|
134
|
+
output = template.render({ "liquid_error" => -> { raise Liquid::Error, "var lookup error" } })
|
135
|
+
assert_equal("err swallowed", output)
|
136
|
+
end
|
137
|
+
end
|
@@ -0,0 +1,85 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "test_helper"
|
4
|
+
require "bigdecimal"
|
5
|
+
|
6
|
+
class ContextTest < Minitest::Test
|
7
|
+
def test_evaluate_works_with_normal_values
|
8
|
+
context = Liquid::Context.new
|
9
|
+
|
10
|
+
["abc", 123, false, 1.21, BigDecimal(42)].each do |value|
|
11
|
+
assert_equal(value, context.evaluate(value))
|
12
|
+
end
|
13
|
+
|
14
|
+
assert_nil(context.evaluate(nil))
|
15
|
+
end
|
16
|
+
|
17
|
+
def test_evaluate_works_with_classes_that_have_an_evaluate_method
|
18
|
+
class_with_evaluate = Class.new do
|
19
|
+
def evaluate(_context)
|
20
|
+
42
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
assert_equal(42, Liquid::Context.new.evaluate(class_with_evaluate.new))
|
25
|
+
end
|
26
|
+
|
27
|
+
def test_evaluate_works_with_variable_lookup
|
28
|
+
assert_equal(42, Liquid::Context.new({ "var" => 42 }).evaluate(Liquid::C::Expression.strict_parse("var")))
|
29
|
+
end
|
30
|
+
|
31
|
+
def test_evaluating_a_variable_entirely_within_c
|
32
|
+
skip("TracePoint :call not yet supported") if RUBY_ENGINE == "truffleruby"
|
33
|
+
|
34
|
+
context = Liquid::Context.new({ "var" => 42 })
|
35
|
+
lookup = Liquid::C::Expression.strict_parse("var")
|
36
|
+
context.evaluate(lookup) # memoize vm_internal_new calls
|
37
|
+
|
38
|
+
called_ruby_method_count = 0
|
39
|
+
called_c_method_count = 0
|
40
|
+
|
41
|
+
test_thread = Thread.current
|
42
|
+
begin
|
43
|
+
call_trace = TracePoint.trace(:call) do |t|
|
44
|
+
unless t.self == TracePoint || t.self.is_a?(TracePoint) || Thread.current != test_thread
|
45
|
+
called_ruby_method_count += 1
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
c_call_trace = TracePoint.trace(:c_call) do |t|
|
50
|
+
unless t.self == TracePoint || t.self.is_a?(TracePoint) || Thread.current != test_thread
|
51
|
+
called_c_method_count += 1
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
context.evaluate(lookup)
|
56
|
+
ensure
|
57
|
+
call_trace&.disable
|
58
|
+
c_call_trace&.disable
|
59
|
+
end
|
60
|
+
|
61
|
+
assert_equal(0, called_ruby_method_count)
|
62
|
+
assert_equal(1, called_c_method_count) # context.evaluate call
|
63
|
+
end
|
64
|
+
|
65
|
+
class TestDrop < Liquid::Drop
|
66
|
+
def is_filtering # rubocop:disable Naming/PredicateName
|
67
|
+
@context.send(:c_filtering?)
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
def test_c_filtering_predicate
|
72
|
+
context = Liquid::Context.new({ "test" => [TestDrop.new] })
|
73
|
+
template = Liquid::Template.parse('{{ test[0].is_filtering }},{{ test | map: "is_filtering" }}')
|
74
|
+
|
75
|
+
assert_equal("false,true", template.render!(context))
|
76
|
+
assert_equal(false, context.send(:c_filtering?))
|
77
|
+
end
|
78
|
+
|
79
|
+
def test_strict_variables=
|
80
|
+
context = Liquid::Context.new
|
81
|
+
assert_equal(false, context.strict_variables)
|
82
|
+
context.strict_variables = true
|
83
|
+
assert_equal(true, context.strict_variables)
|
84
|
+
end
|
85
|
+
end
|
@@ -0,0 +1,191 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "test_helper"
|
4
|
+
|
5
|
+
class ExpressionTest < Minitest::Test
|
6
|
+
def test_constant_literals
|
7
|
+
assert_equal(true, Liquid::C::Expression.strict_parse("true"))
|
8
|
+
assert_equal(false, Liquid::C::Expression.strict_parse("false"))
|
9
|
+
assert_nil(Liquid::C::Expression.strict_parse("nil"))
|
10
|
+
assert_nil(Liquid::C::Expression.strict_parse("null"))
|
11
|
+
|
12
|
+
empty = Liquid::C::Expression.strict_parse("empty")
|
13
|
+
assert_equal("", empty)
|
14
|
+
assert_same(empty, Liquid::C::Expression.strict_parse("blank"))
|
15
|
+
end
|
16
|
+
|
17
|
+
def test_push_literals
|
18
|
+
assert_nil(compile_and_eval("nil"))
|
19
|
+
assert_equal(true, compile_and_eval("true"))
|
20
|
+
assert_equal(false, compile_and_eval("false"))
|
21
|
+
end
|
22
|
+
|
23
|
+
def test_constant_integer
|
24
|
+
assert_equal(42, Liquid::C::Expression.strict_parse("42"))
|
25
|
+
end
|
26
|
+
|
27
|
+
def test_push_int8
|
28
|
+
assert_equal(127, compile_and_eval("127"))
|
29
|
+
assert_equal(-128, compile_and_eval("-128"))
|
30
|
+
end
|
31
|
+
|
32
|
+
def test_push_int16
|
33
|
+
assert_equal(128, compile_and_eval("128"))
|
34
|
+
assert_equal(-129, compile_and_eval("-129"))
|
35
|
+
assert_equal(32767, compile_and_eval("32767"))
|
36
|
+
assert_equal(-32768, compile_and_eval("-32768"))
|
37
|
+
end
|
38
|
+
|
39
|
+
def test_push_large_fixnum
|
40
|
+
assert_equal(32768, compile_and_eval("32768"))
|
41
|
+
assert_equal(-2147483648, compile_and_eval("-2147483648"))
|
42
|
+
assert_equal(2147483648, compile_and_eval("2147483648"))
|
43
|
+
assert_equal(4611686018427387903, compile_and_eval("4611686018427387903"))
|
44
|
+
end
|
45
|
+
|
46
|
+
def test_push_big_int
|
47
|
+
num = 1 << 128
|
48
|
+
assert_equal(num, compile_and_eval(num.to_s))
|
49
|
+
end
|
50
|
+
|
51
|
+
def test_float
|
52
|
+
assert_equal(123.4, Liquid::C::Expression.strict_parse("123.4"))
|
53
|
+
assert_equal(-1.5, compile_and_eval("-1.5"))
|
54
|
+
end
|
55
|
+
|
56
|
+
def test_string
|
57
|
+
assert_equal("hello", Liquid::C::Expression.strict_parse('"hello"'))
|
58
|
+
assert_equal("world", compile_and_eval("'world'"))
|
59
|
+
end
|
60
|
+
|
61
|
+
def test_find_static_variable
|
62
|
+
context = Liquid::Context.new({ "x" => 123 })
|
63
|
+
expr = Liquid::C::Expression.strict_parse("x")
|
64
|
+
|
65
|
+
assert_instance_of(Liquid::C::Expression, expr)
|
66
|
+
assert_equal(123, context.evaluate(expr))
|
67
|
+
end
|
68
|
+
|
69
|
+
def test_find_dynamic_variable
|
70
|
+
context = Liquid::Context.new({ "x" => "y", "y" => 42 })
|
71
|
+
expr = Liquid::C::Expression.strict_parse("[x]")
|
72
|
+
assert_equal(42, context.evaluate(expr))
|
73
|
+
end
|
74
|
+
|
75
|
+
def test_find_missing_variable
|
76
|
+
context = Liquid::Context.new({})
|
77
|
+
expr = Liquid::C::Expression.strict_parse("missing")
|
78
|
+
|
79
|
+
assert_nil(context.evaluate(expr))
|
80
|
+
|
81
|
+
context.strict_variables = true
|
82
|
+
|
83
|
+
assert_raises(Liquid::UndefinedVariable) do
|
84
|
+
context.evaluate(expr)
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
def test_lookup_const_key
|
89
|
+
context = Liquid::Context.new({ "obj" => { "prop" => "some value" } })
|
90
|
+
|
91
|
+
expr = Liquid::C::Expression.strict_parse("obj.prop")
|
92
|
+
assert_equal("some value", context.evaluate(expr))
|
93
|
+
|
94
|
+
expr = Liquid::C::Expression.strict_parse('obj["prop"]')
|
95
|
+
assert_equal("some value", context.evaluate(expr))
|
96
|
+
end
|
97
|
+
|
98
|
+
def test_lookup_variable_key
|
99
|
+
context = Liquid::Context.new({ "field_name" => "prop", "obj" => { "prop" => "another value" } })
|
100
|
+
expr = Liquid::C::Expression.strict_parse("obj[field_name]")
|
101
|
+
assert_equal("another value", context.evaluate(expr))
|
102
|
+
end
|
103
|
+
|
104
|
+
def test_lookup_command
|
105
|
+
context = Liquid::Context.new({ "ary" => ["a", "b", "c"] })
|
106
|
+
assert_equal(3, context.evaluate(Liquid::C::Expression.strict_parse("ary.size")))
|
107
|
+
assert_equal("a", context.evaluate(Liquid::C::Expression.strict_parse("ary.first")))
|
108
|
+
assert_equal("c", context.evaluate(Liquid::C::Expression.strict_parse("ary.last")))
|
109
|
+
end
|
110
|
+
|
111
|
+
def test_lookup_missing_key
|
112
|
+
context = Liquid::Context.new({ "obj" => {} })
|
113
|
+
expr = Liquid::C::Expression.strict_parse("obj.missing")
|
114
|
+
|
115
|
+
assert_nil(context.evaluate(expr))
|
116
|
+
|
117
|
+
context.strict_variables = true
|
118
|
+
|
119
|
+
assert_raises(Liquid::UndefinedVariable) do
|
120
|
+
context.evaluate(expr)
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
def test_lookup_on_var_with_literal_name
|
125
|
+
context = Liquid::Context.new({ "blank" => { "x" => "result" } })
|
126
|
+
|
127
|
+
assert_equal("result", context.evaluate(Liquid::C::Expression.strict_parse("blank.x")))
|
128
|
+
assert_equal("result", context.evaluate(Liquid::C::Expression.strict_parse('blank["x"]')))
|
129
|
+
end
|
130
|
+
|
131
|
+
def test_const_range
|
132
|
+
assert_equal((1..2), Liquid::C::Expression.strict_parse("(1..2)"))
|
133
|
+
end
|
134
|
+
|
135
|
+
def test_dynamic_range
|
136
|
+
context = Liquid::Context.new({ "var" => 42 })
|
137
|
+
expr = Liquid::C::Expression.strict_parse("(1..var)")
|
138
|
+
assert_instance_of(Liquid::C::Expression, expr)
|
139
|
+
assert_equal((1..42), context.evaluate(expr))
|
140
|
+
end
|
141
|
+
|
142
|
+
def test_disassemble
|
143
|
+
expression = Liquid::C::Expression.strict_parse("foo.bar[123]")
|
144
|
+
assert_equal(<<~ASM, expression.disassemble)
|
145
|
+
0x0000: find_static_var("foo")
|
146
|
+
0x0003: lookup_const_key("bar")
|
147
|
+
0x0006: push_int8(123)
|
148
|
+
0x0008: lookup_key
|
149
|
+
0x0009: leave
|
150
|
+
ASM
|
151
|
+
end
|
152
|
+
|
153
|
+
def test_disassemble_int16
|
154
|
+
assert_equal(<<~ASM, Liquid::C::Expression.strict_parse("[12345]").disassemble)
|
155
|
+
0x0000: push_int16(12345)
|
156
|
+
0x0003: find_var
|
157
|
+
0x0004: leave
|
158
|
+
ASM
|
159
|
+
end
|
160
|
+
|
161
|
+
def test_disable_c_nodes
|
162
|
+
context = Liquid::Context.new({ "x" => 123, "y" => { 123 => 42 } })
|
163
|
+
|
164
|
+
expr = Liquid::ParseContext.new.parse_expression("x")
|
165
|
+
assert_instance_of(Liquid::C::Expression, expr)
|
166
|
+
assert_equal(123, context.evaluate(expr))
|
167
|
+
|
168
|
+
expr = Liquid::ParseContext.new(disable_liquid_c_nodes: true).parse_expression("x")
|
169
|
+
assert_instance_of(Liquid::VariableLookup, expr)
|
170
|
+
assert_equal(123, context.evaluate(expr))
|
171
|
+
|
172
|
+
expr = Liquid::ParseContext.new(disable_liquid_c_nodes: true).parse_expression("y[x]")
|
173
|
+
assert_instance_of(Liquid::VariableLookup, expr)
|
174
|
+
assert_instance_of(Liquid::VariableLookup, expr.lookups.first)
|
175
|
+
assert_equal(42, context.evaluate(expr))
|
176
|
+
end
|
177
|
+
|
178
|
+
private
|
179
|
+
|
180
|
+
class ReturnKeyDrop < Liquid::Drop
|
181
|
+
def liquid_method_missing(key)
|
182
|
+
key
|
183
|
+
end
|
184
|
+
end
|
185
|
+
|
186
|
+
def compile_and_eval(source)
|
187
|
+
context = Liquid::Context.new({ "ret_key" => ReturnKeyDrop.new })
|
188
|
+
expr = Liquid::C::Expression.strict_parse("ret_key[#{source}]")
|
189
|
+
context.evaluate(expr)
|
190
|
+
end
|
191
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
require "test_helper"
|
5
|
+
|
6
|
+
# Help catch bugs from objects not being marked at all
|
7
|
+
# GC opportunities.
|
8
|
+
class GCStressTest < Minitest::Test
|
9
|
+
def test_compile_and_render
|
10
|
+
source = "{% assign x = 1 %}{% if x -%} x: {{ x | plus: 2 }}{% endif %}"
|
11
|
+
result = gc_stress do
|
12
|
+
Liquid::Template.parse(source).render!
|
13
|
+
end
|
14
|
+
assert_equal("x: 3", result)
|
15
|
+
end
|
16
|
+
|
17
|
+
private
|
18
|
+
|
19
|
+
def gc_stress
|
20
|
+
old_value = GC.stress
|
21
|
+
GC.stress = true
|
22
|
+
begin
|
23
|
+
yield
|
24
|
+
ensure
|
25
|
+
GC.stress = old_value
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,93 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "test_helper"
|
4
|
+
|
5
|
+
class RawTest < Minitest::Test
|
6
|
+
class RawWrapper < Liquid::Raw
|
7
|
+
def render_to_output_buffer(_context, output)
|
8
|
+
output << "<"
|
9
|
+
super
|
10
|
+
output << ">"
|
11
|
+
end
|
12
|
+
end
|
13
|
+
Liquid::Template.register_tag("raw_wrapper", RawWrapper)
|
14
|
+
|
15
|
+
def test_derived_class
|
16
|
+
[
|
17
|
+
"{% raw_wrapper %}body{% endraw_wrapper %}",
|
18
|
+
"{% raw_wrapper %}body{%endraw_wrapper%}",
|
19
|
+
"{% raw_wrapper %}body{%- endraw_wrapper -%}",
|
20
|
+
"{% raw_wrapper %}body{%- endraw_wrapper %}",
|
21
|
+
"{% raw_wrapper %}body{% endraw_wrapper -%}",
|
22
|
+
].each do |template|
|
23
|
+
output = Liquid::Template.parse(template).render!
|
24
|
+
|
25
|
+
assert_equal(
|
26
|
+
"<body>",
|
27
|
+
output,
|
28
|
+
"Template: #{template}"
|
29
|
+
)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def test_allows_extra_string_after_tag_delimiter
|
34
|
+
output = Liquid::Template.parse("{% raw %}message{% endraw this_is_allowed %}").render
|
35
|
+
assert_equal("message", output)
|
36
|
+
|
37
|
+
output = Liquid::Template.parse("{% raw %}message{% endraw r%}").render
|
38
|
+
assert_equal("message", output)
|
39
|
+
end
|
40
|
+
|
41
|
+
def test_ignores_incomplete_tag_delimter
|
42
|
+
output = Liquid::Template.parse("{% raw %}{% endraw {% endraw %}").render
|
43
|
+
assert_equal("{% endraw ", output)
|
44
|
+
|
45
|
+
output = Liquid::Template.parse("{% raw %}{%endraw{% endraw %}").render
|
46
|
+
assert_equal("{%endraw", output)
|
47
|
+
|
48
|
+
output = Liquid::Template.parse("{% raw %}{%- endraw {% endraw %}").render
|
49
|
+
assert_equal("{%- endraw ", output)
|
50
|
+
end
|
51
|
+
|
52
|
+
def test_does_not_allow_nbsp_in_tag_delimiter
|
53
|
+
# these are valid
|
54
|
+
Liquid::Template.parse("{% raw %}body{%endraw%}")
|
55
|
+
Liquid::Template.parse("{% raw %}body{% endraw-%}")
|
56
|
+
Liquid::Template.parse("{% raw %}body{% endraw -%}")
|
57
|
+
Liquid::Template.parse("{% raw %}body{%-endraw %}")
|
58
|
+
Liquid::Template.parse("{% raw %}body{%- endraw %}")
|
59
|
+
Liquid::Template.parse("{% raw %}body{%-endraw-%}")
|
60
|
+
Liquid::Template.parse("{% raw %}body{%- endraw -%}")
|
61
|
+
Liquid::Template.parse("{% raw %}body{% endraw\u00A0%}")
|
62
|
+
Liquid::Template.parse("{% raw %}body{% endraw \u00A0%}")
|
63
|
+
Liquid::Template.parse("{% raw %}body{% endraw\u00A0 %}")
|
64
|
+
Liquid::Template.parse("{% raw %}body{% endraw \u00A0 %}")
|
65
|
+
Liquid::Template.parse("{% raw %}body{% endraw \u00A0 endraw %}")
|
66
|
+
Liquid::Template.parse("{% raw %}body{% endraw\u00A0endraw %}")
|
67
|
+
|
68
|
+
[
|
69
|
+
"{%\u00A0endraw%}",
|
70
|
+
"{%\u00A0 endraw%}",
|
71
|
+
"{% \u00A0endraw%}",
|
72
|
+
"{% \u00A0 endraw%}",
|
73
|
+
"{%\u00A0endraw\u00A0%}",
|
74
|
+
"{% - endraw %}",
|
75
|
+
"{% endnot endraw %}",
|
76
|
+
].each do |bad_delimiter|
|
77
|
+
exception = assert_raises(
|
78
|
+
Liquid::SyntaxError,
|
79
|
+
"#{bad_delimiter.inspect} did not raise Liquid::SyntaxError"
|
80
|
+
) do
|
81
|
+
Liquid::Template.parse(
|
82
|
+
"{% raw %}body#{bad_delimiter}"
|
83
|
+
)
|
84
|
+
end
|
85
|
+
|
86
|
+
assert_equal(
|
87
|
+
exception.message,
|
88
|
+
"Liquid syntax error: 'raw' tag was never closed",
|
89
|
+
"#{bad_delimiter.inspect} raised the wrong exception message",
|
90
|
+
)
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "test_helper"
|
4
|
+
|
5
|
+
class ResourceLimitsTest < Minitest::Test
|
6
|
+
def test_increment_render_score
|
7
|
+
resource_limits = Liquid::ResourceLimits.new(render_score_limit: 5)
|
8
|
+
resource_limits.increment_render_score(4)
|
9
|
+
assert_raises(Liquid::MemoryError) do
|
10
|
+
resource_limits.increment_render_score(2)
|
11
|
+
end
|
12
|
+
assert_equal(6, resource_limits.render_score)
|
13
|
+
end
|
14
|
+
|
15
|
+
def test_increment_assign_score
|
16
|
+
resource_limits = Liquid::ResourceLimits.new(assign_score_limit: 5)
|
17
|
+
resource_limits.increment_assign_score(5)
|
18
|
+
assert_raises(Liquid::MemoryError) do
|
19
|
+
resource_limits.increment_assign_score(1)
|
20
|
+
end
|
21
|
+
assert_equal(6, resource_limits.assign_score)
|
22
|
+
end
|
23
|
+
|
24
|
+
def test_increment_write_score
|
25
|
+
resource_limits = Liquid::ResourceLimits.new(render_length_limit: 5)
|
26
|
+
output = "a" * 10
|
27
|
+
assert_raises(Liquid::MemoryError) do
|
28
|
+
resource_limits.increment_write_score(output)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def test_raise_limits_reached
|
33
|
+
resource_limits = Liquid::ResourceLimits.new({})
|
34
|
+
assert_raises(Liquid::MemoryError) do
|
35
|
+
resource_limits.raise_limits_reached
|
36
|
+
end
|
37
|
+
assert(resource_limits.reached?)
|
38
|
+
end
|
39
|
+
|
40
|
+
def test_with_capture
|
41
|
+
resource_limits = Liquid::ResourceLimits.new(assign_score_limit: 5)
|
42
|
+
output = "foo"
|
43
|
+
|
44
|
+
resource_limits.with_capture do
|
45
|
+
resource_limits.increment_write_score(output)
|
46
|
+
end
|
47
|
+
|
48
|
+
assert_equal(3, resource_limits.assign_score)
|
49
|
+
end
|
50
|
+
end
|