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
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.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.shift
|
64
|
+
assert_equal(Encoding::UTF_8, output.encoding)
|
65
|
+
assert_equal(source, output)
|
66
|
+
assert_nil(tokenizer.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.shift)
|
39
109
|
tokens << t
|
40
110
|
end
|
41
111
|
tokens
|
data/test/unit/variable_test.rb
CHANGED
@@ -1,110 +1,329 @@
|
|
1
1
|
# encoding: utf-8
|
2
|
-
|
2
|
+
# frozen_string_literal: true
|
3
3
|
|
4
|
-
|
4
|
+
require "test_helper"
|
5
|
+
|
6
|
+
class VariableTest < Minitest::Test
|
5
7
|
def test_variable_parse
|
6
|
-
assert_equal
|
7
|
-
assert_equal
|
8
|
-
assert_equal
|
9
|
-
assert_equal
|
10
|
-
assert_equal
|
11
|
-
assert_equal
|
12
|
-
assert_equal
|
13
|
-
assert_equal [lookup('a-2'), []], variable_parse('a-2')
|
8
|
+
assert_equal("world", variable_strict_parse("hello").render!({ "hello" => "world" }))
|
9
|
+
assert_equal("world", variable_strict_parse('"world"').render!)
|
10
|
+
assert_equal("answer", variable_strict_parse('hello["world"]').render!({ "hello" => { "world" => "answer" } }))
|
11
|
+
assert_equal("answer", variable_strict_parse("question?").render!({ "question?" => "answer" }))
|
12
|
+
assert_equal("value", variable_strict_parse("[meta]").render!({ "meta" => "key", "key" => "value" }))
|
13
|
+
assert_equal("result", variable_strict_parse("a-b").render!({ "a-b" => "result" }))
|
14
|
+
assert_equal("result", variable_strict_parse("a-2").render!({ "a-2" => "result" }))
|
14
15
|
end
|
15
16
|
|
16
17
|
def test_strictness
|
17
|
-
assert_raises(Liquid::SyntaxError) {
|
18
|
-
assert_raises(Liquid::SyntaxError) {
|
19
|
-
assert_raises(Liquid::SyntaxError) {
|
20
|
-
assert_raises(Liquid::SyntaxError) {
|
21
|
-
assert_raises(Liquid::SyntaxError) {
|
22
|
-
|
23
|
-
['a .b', 'a. b', 'a . b'].each do |var|
|
24
|
-
assert_raises(Liquid::SyntaxError) { variable_parse(var) }
|
25
|
-
end
|
18
|
+
assert_raises(Liquid::SyntaxError) { variable_strict_parse(' hello["world\']" ') }
|
19
|
+
assert_raises(Liquid::SyntaxError) { variable_strict_parse(" -..") }
|
20
|
+
assert_raises(Liquid::SyntaxError) { variable_strict_parse("question?mark") }
|
21
|
+
assert_raises(Liquid::SyntaxError) { variable_strict_parse("123.foo") }
|
22
|
+
assert_raises(Liquid::SyntaxError) { variable_strict_parse(" | nothing") }
|
26
23
|
|
27
|
-
[
|
28
|
-
assert_raises(Liquid::SyntaxError) {
|
24
|
+
["a -b", "a- b", "a - b"].each do |var|
|
25
|
+
assert_raises(Liquid::SyntaxError) { variable_strict_parse(var) }
|
29
26
|
end
|
30
27
|
end
|
31
28
|
|
32
29
|
def test_literals
|
33
|
-
assert_equal
|
34
|
-
assert_equal
|
35
|
-
assert_equal
|
30
|
+
assert_equal("", variable_strict_parse("").render!)
|
31
|
+
assert_equal("true", variable_strict_parse("true").render!)
|
32
|
+
assert_equal("", variable_strict_parse("nil").render!)
|
33
|
+
assert_equal("123.4", variable_strict_parse("123.4").render!)
|
34
|
+
|
35
|
+
assert_equal("blank_value", variable_strict_parse("[blank]").render!({ "" => "blank_value" }))
|
36
|
+
assert_equal("result", variable_strict_parse("[true][blank]").render!({ true => { "" => "result" } }))
|
37
|
+
assert_equal("result", variable_strict_parse('x["size"]').render!({ "x" => { "size" => "result" } }))
|
38
|
+
assert_equal("result", variable_strict_parse("blank.x").render!({ "blank" => { "x" => "result" } }))
|
39
|
+
assert_equal("result", variable_strict_parse('blank["x"]').render!({ "blank" => { "x" => "result" } }))
|
40
|
+
end
|
41
|
+
|
42
|
+
module InspectCallFilters
|
43
|
+
def filter1(input, *args)
|
44
|
+
inspect_call(__method__, input, args)
|
45
|
+
end
|
46
|
+
|
47
|
+
def filter2(input, *args)
|
48
|
+
inspect_call(__method__, input, args)
|
49
|
+
end
|
36
50
|
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
51
|
+
private
|
52
|
+
|
53
|
+
def inspect_call(filter_name, input, args)
|
54
|
+
"{ filter: #{filter_name.inspect}, input: #{input.inspect}, args: #{args.inspect} }"
|
55
|
+
end
|
41
56
|
end
|
42
57
|
|
43
58
|
def test_variable_filter
|
44
|
-
|
45
|
-
|
46
|
-
|
59
|
+
context = { "name" => "Bob" }
|
60
|
+
|
61
|
+
filter1_output = variable_strict_parse("name | filter1").render!(context, filters: [InspectCallFilters])
|
62
|
+
assert_equal('{ filter: :filter1, input: "Bob", args: [] }', filter1_output)
|
63
|
+
|
64
|
+
filter2_output = variable_strict_parse("name | filter1 | filter2").render!(context, filters: [InspectCallFilters])
|
65
|
+
assert_equal("{ filter: :filter2, input: #{filter1_output.inspect}, args: [] }", filter2_output)
|
47
66
|
end
|
48
67
|
|
49
68
|
def test_variable_filter_args
|
50
|
-
|
51
|
-
|
69
|
+
context = { "name" => "Bob", "abc" => "xyz" }
|
70
|
+
render_opts = { filters: [InspectCallFilters] }
|
71
|
+
|
72
|
+
filter1_output = variable_strict_parse("name | filter1: abc").render!(context, render_opts)
|
73
|
+
assert_equal('{ filter: :filter1, input: "Bob", args: ["xyz"] }', filter1_output)
|
52
74
|
|
53
|
-
|
75
|
+
filter2_output = variable_strict_parse("name | filter1: abc | filter2: abc").render!(context, render_opts)
|
76
|
+
assert_equal("{ filter: :filter2, input: #{filter1_output.inspect}, args: [\"xyz\"] }", filter2_output)
|
54
77
|
|
55
|
-
|
56
|
-
variable_parse(' name | filter1: abc | filter2: abc ')
|
78
|
+
context = { "name" => "Bob", "a" => 1, "c" => 3, "e" => 5 }
|
57
79
|
|
58
|
-
|
59
|
-
|
80
|
+
output = variable_strict_parse("name | filter1 : a , b : c , d : e").render!(context, render_opts)
|
81
|
+
assert_equal('{ filter: :filter1, input: "Bob", args: [1, {"b"=>3, "d"=>5}] }', output)
|
60
82
|
|
61
|
-
assert_raises
|
62
|
-
|
83
|
+
assert_raises(Liquid::SyntaxError) do
|
84
|
+
variable_strict_parse("name | filter : a : b : c : d : e")
|
63
85
|
end
|
64
86
|
end
|
65
87
|
|
66
88
|
def test_unicode_strings
|
67
|
-
|
68
|
-
|
89
|
+
string_content = "å߀êùidhtлsԁѵ߀ráƙìstɦeƅêstpcmáѕterrãcêcհèrr"
|
90
|
+
assert_equal(string_content, variable_strict_parse("\"#{string_content}\"").render!)
|
69
91
|
end
|
70
92
|
|
71
93
|
def test_broken_unicode_errors
|
72
94
|
err = assert_raises(Liquid::SyntaxError) do
|
73
95
|
Liquid::Template.parse("test {{ \xC2\xA0 test }}", error_mode: :strict)
|
74
96
|
end
|
75
|
-
assert
|
97
|
+
assert(err.message)
|
76
98
|
end
|
77
99
|
|
78
100
|
def test_callbacks
|
79
|
-
variable_parses = 0
|
80
101
|
variable_fallbacks = 0
|
81
102
|
|
82
103
|
callbacks = {
|
83
|
-
|
84
|
-
variable_fallback: lambda { variable_fallbacks += 1 }
|
104
|
+
variable_fallback: lambda { variable_fallbacks += 1 },
|
85
105
|
}
|
86
106
|
|
87
|
-
|
88
|
-
assert_equal
|
89
|
-
assert_equal 0, variable_fallbacks
|
107
|
+
Liquid::Template.parse("{{abc}}", error_mode: :lax, stats_callbacks: callbacks)
|
108
|
+
assert_equal(0, variable_fallbacks)
|
90
109
|
|
91
|
-
|
92
|
-
assert_equal
|
93
|
-
assert_equal 1, variable_fallbacks
|
110
|
+
Liquid::Template.parse("{{@!#}}", error_mode: :lax, stats_callbacks: callbacks)
|
111
|
+
assert_equal(1, variable_fallbacks)
|
94
112
|
end
|
95
113
|
|
96
|
-
|
114
|
+
def test_write_string
|
115
|
+
output = Liquid::Template.parse("{{ str }}").render({ "str" => "foo" })
|
116
|
+
assert_equal("foo", output)
|
117
|
+
end
|
118
|
+
|
119
|
+
def test_write_fixnum
|
120
|
+
output = Liquid::Template.parse("{{ num }}").render({ "num" => 123456 })
|
121
|
+
assert_equal("123456", output)
|
122
|
+
end
|
97
123
|
|
98
|
-
def
|
99
|
-
Liquid::
|
124
|
+
def test_write_array
|
125
|
+
output = Liquid::Template.parse("{{ ary }}").render({ "ary" => ["foo", 123, ["nested", "ary"], nil, 0.5] })
|
126
|
+
assert_equal("foo123nestedary0.5", output)
|
100
127
|
end
|
101
128
|
|
102
|
-
def
|
103
|
-
|
104
|
-
|
129
|
+
def test_write_nil
|
130
|
+
output = Liquid::Template.parse("{{ obj }}").render({ "obj" => nil })
|
131
|
+
assert_equal("", output)
|
105
132
|
end
|
106
133
|
|
107
|
-
|
108
|
-
|
134
|
+
class StringConvertible
|
135
|
+
def initialize(as_string)
|
136
|
+
@as_string = as_string
|
137
|
+
end
|
138
|
+
|
139
|
+
def to_s
|
140
|
+
@as_string
|
141
|
+
end
|
142
|
+
|
143
|
+
def to_liquid
|
144
|
+
self
|
145
|
+
end
|
146
|
+
end
|
147
|
+
|
148
|
+
def test_write_to_s_convertible_object
|
149
|
+
output = Liquid::Template.parse("{{ obj }}").render!({ "obj" => StringConvertible.new("foo") })
|
150
|
+
assert_equal("foo", output)
|
151
|
+
end
|
152
|
+
|
153
|
+
def test_write_object_with_broken_to_s
|
154
|
+
template = Liquid::Template.parse("{{ obj }}")
|
155
|
+
exc = assert_raises(TypeError) do
|
156
|
+
template.render!({ "obj" => StringConvertible.new(123) })
|
157
|
+
end
|
158
|
+
assert_equal(
|
159
|
+
"VariableTest::StringConvertible#to_s returned a non-String convertible value of type Integer",
|
160
|
+
exc.message
|
161
|
+
)
|
162
|
+
end
|
163
|
+
|
164
|
+
class DerivedString < String
|
165
|
+
def to_s
|
166
|
+
self
|
167
|
+
end
|
168
|
+
end
|
169
|
+
|
170
|
+
def test_write_derived_string
|
171
|
+
output = Liquid::Template.parse("{{ obj }}").render!({ "obj" => DerivedString.new("bar") })
|
172
|
+
assert_equal("bar", output)
|
173
|
+
end
|
174
|
+
|
175
|
+
def test_filter_without_args
|
176
|
+
output = Liquid::Template.parse("{{ var | upcase }}").render({ "var" => "Hello" })
|
177
|
+
assert_equal("HELLO", output)
|
178
|
+
end
|
179
|
+
|
180
|
+
def test_filter_with_const_arg
|
181
|
+
output = Liquid::Template.parse("{{ x | plus: 2 }}").render({ "x" => 3 })
|
182
|
+
assert_equal("5", output)
|
183
|
+
end
|
184
|
+
|
185
|
+
def test_filter_with_variable_arg
|
186
|
+
output = Liquid::Template.parse("{{ x | plus: y }}").render({ "x" => 10, "y" => 123 })
|
187
|
+
assert_equal("133", output)
|
188
|
+
end
|
189
|
+
|
190
|
+
def test_filter_with_variable_arg_after_const_arg
|
191
|
+
output = Liquid::Template.parse("{{ ary | slice: 1, 2 }}").render({ "ary" => [1, 2, 3, 4] })
|
192
|
+
assert_equal("23", output)
|
193
|
+
end
|
194
|
+
|
195
|
+
def test_filter_with_const_keyword_arg
|
196
|
+
output = Liquid::Template.parse("{{ value | default: 'None' }}").render({ "value" => false })
|
197
|
+
assert_equal("None", output)
|
198
|
+
|
199
|
+
output = Liquid::Template.parse("{{ value | default: 'None', allow_false: true }}").render({ "value" => false })
|
200
|
+
assert_equal("false", output)
|
201
|
+
end
|
202
|
+
|
203
|
+
def test_filter_with_variable_keyword_arg
|
204
|
+
template = Liquid::Template.parse("{{ value | default: 'None', allow_false: false_allowed }}")
|
205
|
+
|
206
|
+
assert_equal("None", template.render({ "value" => false, "false_allowed" => false }))
|
207
|
+
assert_equal("false", template.render({ "value" => false, "false_allowed" => true }))
|
208
|
+
end
|
209
|
+
|
210
|
+
def test_filter_error
|
211
|
+
output = Liquid::Template.parse("before ({{ ary | concat: 2 }}) after").render({ "ary" => [1] })
|
212
|
+
assert_equal("before (Liquid error: concat filter requires an array argument) after", output)
|
213
|
+
end
|
214
|
+
|
215
|
+
def test_render_variable_object
|
216
|
+
variable = Liquid::Variable.new("ary | concat: ary2", Liquid::ParseContext.new)
|
217
|
+
assert_instance_of(Liquid::C::VariableExpression, variable.name)
|
218
|
+
|
219
|
+
context = Liquid::Context.new("ary" => [1], "ary2" => [2])
|
220
|
+
assert_equal([1, 2], variable.render(context))
|
221
|
+
|
222
|
+
context["ary2"] = 2
|
223
|
+
exc = assert_raises(Liquid::ArgumentError) do
|
224
|
+
variable.render(context)
|
225
|
+
end
|
226
|
+
assert_equal("Liquid error: concat filter requires an array argument", exc.message)
|
227
|
+
end
|
228
|
+
|
229
|
+
def test_filter_argument_error_translation
|
230
|
+
variable = Liquid::Variable.new("'some words' | split", Liquid::ParseContext.new)
|
231
|
+
context = Liquid::Context.new
|
232
|
+
exc = assert_raises(Liquid::ArgumentError) { variable.render(context) }
|
233
|
+
assert_equal("Liquid error: wrong number of arguments (given 1, expected 2)", exc.message)
|
234
|
+
end
|
235
|
+
|
236
|
+
class IntegerDrop < Liquid::Drop
|
237
|
+
def initialize(value)
|
238
|
+
super()
|
239
|
+
@value = value.to_i
|
240
|
+
end
|
241
|
+
|
242
|
+
def to_liquid_value
|
243
|
+
@value
|
244
|
+
end
|
245
|
+
end
|
246
|
+
|
247
|
+
def test_to_liquid_value_on_variable_lookup
|
248
|
+
context = {
|
249
|
+
"number" => IntegerDrop.new("1"),
|
250
|
+
"list" => [1, 2, 3, 4, 5],
|
251
|
+
}
|
252
|
+
|
253
|
+
output = variable_strict_parse("list[number]").render!(context)
|
254
|
+
assert_equal("2", output)
|
255
|
+
end
|
256
|
+
|
257
|
+
def test_encoding_error_message_with_multi_byte_characters
|
258
|
+
# 2 byte character
|
259
|
+
exc = assert_raises(Liquid::SyntaxError) do
|
260
|
+
variable_strict_parse("\u00A0")
|
261
|
+
end
|
262
|
+
assert_equal(
|
263
|
+
"Liquid syntax error: Unexpected character \u00A0 in \"{{\u00a0}}\"",
|
264
|
+
exc.message
|
265
|
+
)
|
266
|
+
|
267
|
+
# 3 byte character
|
268
|
+
exc = assert_raises(Liquid::SyntaxError) do
|
269
|
+
variable_strict_parse("\u3042")
|
270
|
+
end
|
271
|
+
assert_equal(
|
272
|
+
"Liquid syntax error: Unexpected character \u3042 in \"{{\u3042}}\"",
|
273
|
+
exc.message
|
274
|
+
)
|
275
|
+
|
276
|
+
# 4 byte character
|
277
|
+
exc = assert_raises(Liquid::SyntaxError) do
|
278
|
+
variable_strict_parse("\u{1F600}")
|
279
|
+
end
|
280
|
+
assert_equal(
|
281
|
+
"Liquid syntax error: Unexpected character \u{1F600} in \"{{\u{1F600}}}\"",
|
282
|
+
exc.message
|
283
|
+
)
|
284
|
+
end
|
285
|
+
|
286
|
+
def test_invalid_utf8_sequence
|
287
|
+
# 2 byte character with 1 byte missing
|
288
|
+
exc = assert_raises(ArgumentError) do
|
289
|
+
variable_strict_parse("\xC0")
|
290
|
+
end
|
291
|
+
assert_equal("invalid byte sequence in UTF-8", exc.message)
|
292
|
+
|
293
|
+
# 3 byte character with 1 byte missing
|
294
|
+
exc = assert_raises(ArgumentError) do
|
295
|
+
variable_strict_parse("\xE0\x01")
|
296
|
+
end
|
297
|
+
assert_equal("invalid byte sequence in UTF-8", exc.message)
|
298
|
+
|
299
|
+
# 3 byte character with 2 byte missing
|
300
|
+
exc = assert_raises(ArgumentError) do
|
301
|
+
variable_strict_parse("\xE0")
|
302
|
+
end
|
303
|
+
assert_equal("invalid byte sequence in UTF-8", exc.message)
|
304
|
+
|
305
|
+
# 4 byte character with 1 byte missing
|
306
|
+
exc = assert_raises(ArgumentError) do
|
307
|
+
variable_strict_parse("\xF0\x01\x01")
|
308
|
+
end
|
309
|
+
assert_equal("invalid byte sequence in UTF-8", exc.message)
|
310
|
+
|
311
|
+
# 4 byte character with 2 byte missing
|
312
|
+
exc = assert_raises(ArgumentError) do
|
313
|
+
variable_strict_parse("\xF0\x01")
|
314
|
+
end
|
315
|
+
assert_equal("invalid byte sequence in UTF-8", exc.message)
|
316
|
+
|
317
|
+
# 4 byte character with 3 byte missing
|
318
|
+
exc = assert_raises(ArgumentError) do
|
319
|
+
variable_strict_parse("\xF0")
|
320
|
+
end
|
321
|
+
assert_equal("invalid byte sequence in UTF-8", exc.message)
|
322
|
+
end
|
323
|
+
|
324
|
+
private
|
325
|
+
|
326
|
+
def variable_strict_parse(markup)
|
327
|
+
Liquid::Template.parse("{{#{markup}}}", error_mode: :strict)
|
109
328
|
end
|
110
329
|
end
|