liquid-c 4.0.1 → 4.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (71) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/cla.yml +23 -0
  3. data/.github/workflows/liquid.yml +36 -11
  4. data/.gitignore +4 -0
  5. data/.rubocop.yml +14 -0
  6. data/Gemfile +15 -5
  7. data/README.md +32 -8
  8. data/Rakefile +12 -63
  9. data/ext/liquid_c/block.c +493 -60
  10. data/ext/liquid_c/block.h +28 -2
  11. data/ext/liquid_c/c_buffer.c +42 -0
  12. data/ext/liquid_c/c_buffer.h +76 -0
  13. data/ext/liquid_c/context.c +233 -0
  14. data/ext/liquid_c/context.h +70 -0
  15. data/ext/liquid_c/document_body.c +97 -0
  16. data/ext/liquid_c/document_body.h +59 -0
  17. data/ext/liquid_c/expression.c +116 -0
  18. data/ext/liquid_c/expression.h +24 -0
  19. data/ext/liquid_c/extconf.rb +21 -9
  20. data/ext/liquid_c/intutil.h +22 -0
  21. data/ext/liquid_c/lexer.c +39 -3
  22. data/ext/liquid_c/lexer.h +18 -3
  23. data/ext/liquid_c/liquid.c +76 -6
  24. data/ext/liquid_c/liquid.h +24 -1
  25. data/ext/liquid_c/liquid_vm.c +618 -0
  26. data/ext/liquid_c/liquid_vm.h +25 -0
  27. data/ext/liquid_c/parse_context.c +76 -0
  28. data/ext/liquid_c/parse_context.h +13 -0
  29. data/ext/liquid_c/parser.c +153 -65
  30. data/ext/liquid_c/parser.h +4 -2
  31. data/ext/liquid_c/raw.c +136 -0
  32. data/ext/liquid_c/raw.h +6 -0
  33. data/ext/liquid_c/resource_limits.c +279 -0
  34. data/ext/liquid_c/resource_limits.h +23 -0
  35. data/ext/liquid_c/stringutil.h +44 -0
  36. data/ext/liquid_c/tokenizer.c +149 -35
  37. data/ext/liquid_c/tokenizer.h +20 -9
  38. data/ext/liquid_c/usage.c +18 -0
  39. data/ext/liquid_c/usage.h +9 -0
  40. data/ext/liquid_c/variable.c +196 -20
  41. data/ext/liquid_c/variable.h +18 -1
  42. data/ext/liquid_c/variable_lookup.c +44 -0
  43. data/ext/liquid_c/variable_lookup.h +8 -0
  44. data/ext/liquid_c/vm_assembler.c +491 -0
  45. data/ext/liquid_c/vm_assembler.h +240 -0
  46. data/ext/liquid_c/vm_assembler_pool.c +99 -0
  47. data/ext/liquid_c/vm_assembler_pool.h +26 -0
  48. data/lib/liquid/c/compile_ext.rb +44 -0
  49. data/lib/liquid/c/version.rb +3 -1
  50. data/lib/liquid/c.rb +226 -48
  51. data/liquid-c.gemspec +16 -10
  52. data/performance/c_profile.rb +23 -0
  53. data/performance.rb +6 -4
  54. data/rakelib/compile.rake +15 -0
  55. data/rakelib/integration_test.rake +43 -0
  56. data/rakelib/performance.rake +43 -0
  57. data/rakelib/rubocop.rake +6 -0
  58. data/rakelib/unit_test.rake +14 -0
  59. data/test/integration_test.rb +11 -0
  60. data/test/liquid_test_helper.rb +21 -0
  61. data/test/test_helper.rb +21 -2
  62. data/test/unit/block_test.rb +137 -0
  63. data/test/unit/context_test.rb +85 -0
  64. data/test/unit/expression_test.rb +191 -0
  65. data/test/unit/gc_stress_test.rb +28 -0
  66. data/test/unit/raw_test.rb +93 -0
  67. data/test/unit/resource_limits_test.rb +50 -0
  68. data/test/unit/tokenizer_test.rb +90 -20
  69. data/test/unit/variable_test.rb +279 -60
  70. metadata +60 -11
  71. 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