liquid-c 4.0.1 → 4.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.github/workflows/liquid.yml +24 -2
- data/.gitignore +4 -0
- data/.rubocop.yml +14 -0
- data/Gemfile +14 -5
- data/README.md +29 -5
- data/Rakefile +13 -62
- data/ext/liquid_c/block.c +488 -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 +89 -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 +19 -9
- data/ext/liquid_c/intutil.h +22 -0
- data/ext/liquid_c/lexer.c +6 -2
- 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/parse_context.c +76 -0
- data/ext/liquid_c/parse_context.h +13 -0
- data/ext/liquid_c/parser.c +141 -65
- data/ext/liquid_c/parser.h +4 -2
- data/ext/liquid_c/raw.c +110 -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.c +588 -0
- data/ext/liquid_c/vm.h +25 -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 +97 -0
- data/ext/liquid_c/vm_assembler_pool.h +27 -0
- data/lib/liquid/c/compile_ext.rb +44 -0
- data/lib/liquid/c/version.rb +3 -1
- data/lib/liquid/c.rb +225 -46
- 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 +14 -2
- data/test/unit/block_test.rb +130 -0
- data/test/unit/context_test.rb +83 -0
- data/test/unit/expression_test.rb +186 -0
- data/test/unit/gc_stress_test.rb +28 -0
- data/test/unit/raw_test.rb +19 -0
- data/test/unit/resource_limits_test.rb +50 -0
- data/test/unit/tokenizer_test.rb +90 -20
- data/test/unit/variable_test.rb +212 -60
- metadata +59 -11
- data/test/liquid_test.rb +0 -11
@@ -0,0 +1,130 @@
|
|
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
|
+
end
|
@@ -0,0 +1,83 @@
|
|
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
|
+
context = Liquid::Context.new({ "var" => 42 })
|
33
|
+
lookup = Liquid::C::Expression.strict_parse("var")
|
34
|
+
context.evaluate(lookup) # memoize vm_internal_new calls
|
35
|
+
|
36
|
+
called_ruby_method_count = 0
|
37
|
+
called_c_method_count = 0
|
38
|
+
|
39
|
+
test_thread = Thread.current
|
40
|
+
begin
|
41
|
+
call_trace = TracePoint.trace(:call) do |t|
|
42
|
+
unless t.self == TracePoint || t.self.is_a?(TracePoint) || Thread.current != test_thread
|
43
|
+
called_ruby_method_count += 1
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
c_call_trace = TracePoint.trace(:c_call) do |t|
|
48
|
+
unless t.self == TracePoint || t.self.is_a?(TracePoint) || Thread.current != test_thread
|
49
|
+
called_c_method_count += 1
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
context.evaluate(lookup)
|
54
|
+
ensure
|
55
|
+
call_trace&.disable
|
56
|
+
c_call_trace&.disable
|
57
|
+
end
|
58
|
+
|
59
|
+
assert_equal(0, called_ruby_method_count)
|
60
|
+
assert_equal(1, called_c_method_count) # context.evaluate call
|
61
|
+
end
|
62
|
+
|
63
|
+
class TestDrop < Liquid::Drop
|
64
|
+
def is_filtering # rubocop:disable Naming/PredicateName
|
65
|
+
@context.send(:c_filtering?)
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
def test_c_filtering_predicate
|
70
|
+
context = Liquid::Context.new({ "test" => [TestDrop.new] })
|
71
|
+
template = Liquid::Template.parse('{{ test[0].is_filtering }},{{ test | map: "is_filtering" }}')
|
72
|
+
|
73
|
+
assert_equal("false,true", template.render!(context))
|
74
|
+
assert_equal(false, context.send(:c_filtering?))
|
75
|
+
end
|
76
|
+
|
77
|
+
def test_strict_variables=
|
78
|
+
context = Liquid::Context.new
|
79
|
+
assert_equal(false, context.strict_variables)
|
80
|
+
context.strict_variables = true
|
81
|
+
assert_equal(true, context.strict_variables)
|
82
|
+
end
|
83
|
+
end
|
@@ -0,0 +1,186 @@
|
|
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 })
|
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
|
+
end
|
172
|
+
|
173
|
+
private
|
174
|
+
|
175
|
+
class ReturnKeyDrop < Liquid::Drop
|
176
|
+
def liquid_method_missing(key)
|
177
|
+
key
|
178
|
+
end
|
179
|
+
end
|
180
|
+
|
181
|
+
def compile_and_eval(source)
|
182
|
+
context = Liquid::Context.new({ "ret_key" => ReturnKeyDrop.new })
|
183
|
+
expr = Liquid::C::Expression.strict_parse("ret_key[#{source}]")
|
184
|
+
context.evaluate(expr)
|
185
|
+
end
|
186
|
+
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,19 @@
|
|
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
|
+
output = Liquid::Template.parse("{% raw_wrapper %}body{% endraw_wrapper %}").render!
|
17
|
+
assert_equal("<body>", output)
|
18
|
+
end
|
19
|
+
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
|
data/test/unit/tokenizer_test.rb
CHANGED
@@ -1,41 +1,111 @@
|
|
1
1
|
# encoding: utf-8
|
2
|
-
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
require "test_helper"
|
5
|
+
|
6
|
+
class TokenizerTest < Minitest::Test
|
7
|
+
def test_tokenizer_nil
|
8
|
+
tokenizer = new_tokenizer(nil)
|
9
|
+
assert_nil(tokenizer.send(:shift))
|
10
|
+
end
|
3
11
|
|
4
|
-
class TokenizerTest < MiniTest::Unit::TestCase
|
5
12
|
def test_tokenize_strings
|
6
|
-
assert_equal
|
7
|
-
assert_equal
|
13
|
+
assert_equal([" "], tokenize(" "))
|
14
|
+
assert_equal(["hello world"], tokenize("hello world"))
|
8
15
|
end
|
9
16
|
|
10
17
|
def test_tokenize_variables
|
11
|
-
assert_equal
|
12
|
-
assert_equal
|
13
|
-
assert_equal
|
14
|
-
assert_equal
|
18
|
+
assert_equal(["{{funk}}"], tokenize("{{funk}}"))
|
19
|
+
assert_equal([" ", "{{funk}}", " "], tokenize(" {{funk}} "))
|
20
|
+
assert_equal([" ", "{{funk}}", " ", "{{so}}", " ", "{{brother}}", " "], tokenize(" {{funk}} {{so}} {{brother}} "))
|
21
|
+
assert_equal([" ", "{{ funk }}", " "], tokenize(" {{ funk }} "))
|
22
|
+
|
23
|
+
# Doesn't strip whitespace
|
24
|
+
assert_equal([" ", " funk ", " "], tokenize(" {{ funk }} ", trimmed: true))
|
15
25
|
end
|
16
26
|
|
17
27
|
def test_tokenize_blocks
|
18
|
-
assert_equal
|
19
|
-
assert_equal
|
28
|
+
assert_equal(["{%comment%}"], tokenize("{%comment%}"))
|
29
|
+
assert_equal([" ", "{%comment%}", " "], tokenize(" {%comment%} "))
|
30
|
+
|
31
|
+
assert_equal([" ", "{%comment%}", " ", "{%endcomment%}", " "], tokenize(" {%comment%} {%endcomment%} "))
|
32
|
+
assert_equal([" ", "{% comment %}", " ", "{% endcomment %}", " "], tokenize(" {% comment %} {% endcomment %} "))
|
33
|
+
|
34
|
+
# Doesn't strip whitespace
|
35
|
+
assert_equal([" ", " comment ", " "], tokenize(" {% comment %} ", trimmed: true))
|
36
|
+
end
|
37
|
+
|
38
|
+
def test_tokenize_for_liquid_tag
|
39
|
+
source = "\nfunk\n\n so | brother \n"
|
40
|
+
|
41
|
+
assert_equal(["", "funk", "", " so | brother "], tokenize(source, for_liquid_tag: true))
|
20
42
|
|
21
|
-
|
22
|
-
assert_equal
|
43
|
+
# Strips whitespace
|
44
|
+
assert_equal(["", "funk", "", "so | brother"], tokenize(source, for_liquid_tag: true, trimmed: true))
|
23
45
|
end
|
24
46
|
|
25
|
-
def
|
26
|
-
|
27
|
-
assert_equal
|
47
|
+
def test_invalid_tags
|
48
|
+
assert_equal([""], tokenize("{%-%}", trimmed: true))
|
49
|
+
assert_equal([""], tokenize("{{-}}", trimmed: true))
|
50
|
+
end
|
51
|
+
|
52
|
+
def test_utf8_encoded_source
|
53
|
+
source = "auswählen"
|
54
|
+
assert_equal(Encoding::UTF_8, source.encoding)
|
28
55
|
output = tokenize(source)
|
29
|
-
assert_equal
|
30
|
-
assert_equal
|
56
|
+
assert_equal([Encoding::UTF_8], output.map(&:encoding))
|
57
|
+
assert_equal([source], output)
|
58
|
+
end
|
59
|
+
|
60
|
+
def test_utf8_compatible_source
|
61
|
+
source = String.new("ascii", encoding: Encoding::ASCII)
|
62
|
+
tokenizer = new_tokenizer(source)
|
63
|
+
output = tokenizer.send(:shift)
|
64
|
+
assert_equal(Encoding::UTF_8, output.encoding)
|
65
|
+
assert_equal(source, output)
|
66
|
+
assert_nil(tokenizer.send(:shift))
|
67
|
+
end
|
68
|
+
|
69
|
+
def test_non_utf8_compatible_source
|
70
|
+
source = "üñicode".dup.force_encoding(Encoding::BINARY) # rubocop:disable Performance/UnfreezeString
|
71
|
+
exc = assert_raises(Encoding::CompatibilityError) do
|
72
|
+
Liquid::C::Tokenizer.new(source, 1, false)
|
73
|
+
end
|
74
|
+
assert_equal("non-UTF8 encoded source (ASCII-8BIT) not supported", exc.message)
|
75
|
+
end
|
76
|
+
|
77
|
+
def test_source_too_large
|
78
|
+
too_large_source = "a" * 2**24
|
79
|
+
max_length_source = too_large_source.chop
|
80
|
+
|
81
|
+
# C safety check
|
82
|
+
err = assert_raises(ArgumentError) do
|
83
|
+
Liquid::C::Tokenizer.new(too_large_source, 1, false)
|
84
|
+
end
|
85
|
+
assert_match(/Source too large, max \d+ bytes/, err.message)
|
86
|
+
|
87
|
+
# ruby patch fallback
|
88
|
+
parse_context = Liquid::ParseContext.new
|
89
|
+
liquid_c_tokenizer = parse_context.new_tokenizer(max_length_source)
|
90
|
+
assert_instance_of(Liquid::C::Tokenizer, liquid_c_tokenizer)
|
91
|
+
refute(parse_context.liquid_c_nodes_disabled?)
|
92
|
+
|
93
|
+
parse_context = Liquid::ParseContext.new
|
94
|
+
fallback_tokenizer = parse_context.new_tokenizer(too_large_source)
|
95
|
+
assert_instance_of(Liquid::Tokenizer, fallback_tokenizer)
|
96
|
+
assert_equal(true, parse_context.liquid_c_nodes_disabled?)
|
31
97
|
end
|
32
98
|
|
33
99
|
private
|
34
100
|
|
35
|
-
def
|
36
|
-
|
101
|
+
def new_tokenizer(source, parse_context: Liquid::ParseContext.new)
|
102
|
+
parse_context.new_tokenizer(source)
|
103
|
+
end
|
104
|
+
|
105
|
+
def tokenize(source, for_liquid_tag: false, trimmed: false)
|
106
|
+
tokenizer = Liquid::C::Tokenizer.new(source, 1, for_liquid_tag)
|
37
107
|
tokens = []
|
38
|
-
while t = tokenizer.shift
|
108
|
+
while (t = trimmed ? tokenizer.send(:shift_trimmed) : tokenizer.send(:shift))
|
39
109
|
tokens << t
|
40
110
|
end
|
41
111
|
tokens
|