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