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
data/lib/liquid/c.rb CHANGED
@@ -1,95 +1,273 @@
1
- require 'liquid/c/version'
2
- require 'liquid'
3
- require 'liquid_c'
1
+ # frozen_string_literal: true
2
+
3
+ require "liquid/c/version"
4
+ require "liquid"
5
+ require "liquid_c"
6
+ require "liquid/c/compile_ext"
7
+
8
+ Liquid::C::BlockBody.class_eval do
9
+ def render(context)
10
+ render_to_output_buffer(context, +"")
11
+ end
12
+ end
13
+
14
+ module Liquid
15
+ BlockBody.class_eval do
16
+ def self.c_rescue_render_node(context, output, line_number, exc, blank_tag)
17
+ # There seems to be a MRI ruby bug with how the rb_rescue C function,
18
+ # where $! gets set for its rescue callback, but it doesn't stay set
19
+ # after a nested exception is raised and handled as is the case in
20
+ # Liquid::Context#internal_error. This isn't a problem for plain ruby code,
21
+ # so use a ruby rescue block to have setup $! properly.
22
+ raise(exc)
23
+ rescue => exc
24
+ rescue_render_node(context, output, line_number, exc, blank_tag)
25
+ end
26
+ end
27
+ end
4
28
 
5
29
  module Liquid
6
30
  module C
7
- @enabled = true
31
+ # Placeholder for variables in the Liquid::C::BlockBody#nodelist.
32
+ class VariablePlaceholder
33
+ class << self
34
+ private :new
35
+ end
36
+ end
8
37
 
9
- class << self
10
- attr_accessor :enabled
38
+ class Tokenizer
39
+ MAX_SOURCE_BYTE_SIZE = (1 << 24) - 1
11
40
  end
12
41
  end
13
42
  end
14
43
 
15
- Liquid::Tokenizer.class_eval do
16
- def self.new(source, line_numbers = false)
17
- if Liquid::C.enabled
18
- Liquid::C::Tokenizer.new(source.to_s, line_numbers)
44
+ Liquid::Raw.class_eval do
45
+ alias_method :ruby_parse, :parse
46
+
47
+ def parse(tokenizer)
48
+ if parse_context.liquid_c_nodes_disabled?
49
+ ruby_parse(tokenizer)
19
50
  else
20
- super
51
+ c_parse(tokenizer)
21
52
  end
22
53
  end
23
54
  end
24
55
 
25
- Liquid::BlockBody.class_eval do
26
- alias_method :ruby_parse, :parse
56
+ Liquid::ParseContext.class_eval do
57
+ class << self
58
+ attr_accessor :liquid_c_nodes_disabled
59
+ end
60
+ self.liquid_c_nodes_disabled = false
61
+
62
+ alias_method :ruby_new_block_body, :new_block_body
27
63
 
28
- def parse(tokens, options)
29
- if Liquid::C.enabled && !options[:profile]
30
- c_parse(tokens, options) { |t, m| yield t, m }
64
+ def new_block_body
65
+ if liquid_c_nodes_disabled?
66
+ ruby_new_block_body
31
67
  else
32
- ruby_parse(tokens, options) { |t, m| yield t, m }
68
+ Liquid::C::BlockBody.new(self)
69
+ end
70
+ end
71
+
72
+ alias_method :ruby_new_tokenizer, :new_tokenizer
73
+ def new_tokenizer(source, start_line_number: nil, for_liquid_tag: false)
74
+ unless liquid_c_nodes_disabled?
75
+ source = source.to_s.to_str
76
+ if source.bytesize <= Liquid::C::Tokenizer::MAX_SOURCE_BYTE_SIZE
77
+ source = source.encode(Encoding::UTF_8)
78
+ return Liquid::C::Tokenizer.new(source, start_line_number || 0, for_liquid_tag)
79
+ else
80
+ @liquid_c_nodes_disabled = true
81
+ end
82
+ end
83
+
84
+ ruby_new_tokenizer(source, start_line_number: start_line_number, for_liquid_tag: for_liquid_tag)
85
+ end
86
+
87
+ def parse_expression(markup)
88
+ if liquid_c_nodes_disabled?
89
+ Liquid::Expression.parse(markup)
90
+ else
91
+ Liquid::C::Expression.lax_parse(markup)
92
+ end
93
+ end
94
+
95
+ # @api private
96
+ def liquid_c_nodes_disabled?
97
+ # Liquid::Profiler exposes the internal parse tree that we don't want to build when
98
+ # parsing with liquid-c, so consider liquid-c to be disabled when using it.
99
+ # Also, some templates are parsed before the profiler is running, on which case we
100
+ # provide the `disable_liquid_c_nodes` option to enable the Ruby AST to be produced
101
+ # so the profiler can use it on future runs.
102
+ return @liquid_c_nodes_disabled if defined?(@liquid_c_nodes_disabled)
103
+ @liquid_c_nodes_disabled = !Liquid::C.enabled || @template_options[:profile] ||
104
+ @template_options[:disable_liquid_c_nodes] || self.class.liquid_c_nodes_disabled
105
+ end
106
+ end
107
+
108
+ module Liquid
109
+ module C
110
+ module DocumentClassPatch
111
+ def parse(tokenizer, parse_context)
112
+ if tokenizer.is_a?(Liquid::C::Tokenizer)
113
+ if parse_context[:bug_compatible_whitespace_trimming]
114
+ # Temporary to test rollout of the fix for this bug
115
+ tokenizer.bug_compatible_whitespace_trimming!
116
+ end
117
+
118
+ begin
119
+ parse_context.start_liquid_c_parsing
120
+ super
121
+ ensure
122
+ parse_context.cleanup_liquid_c_parsing
123
+ end
124
+ else
125
+ super
126
+ end
127
+ end
33
128
  end
129
+ Liquid::Document.singleton_class.prepend(DocumentClassPatch)
34
130
  end
35
131
  end
36
132
 
37
133
  Liquid::Variable.class_eval do
38
- alias_method :ruby_lax_parse, :lax_parse
39
- alias_method :ruby_strict_parse, :strict_parse
134
+ class << self
135
+ # @api private
136
+ def call_variable_fallback_stats_callback(parse_context)
137
+ callbacks = parse_context[:stats_callbacks]
138
+ callbacks[:variable_fallback]&.call if callbacks
139
+ end
40
140
 
41
- def lax_parse(markup)
42
- stats = options[:stats_callbacks]
43
- stats[:variable_parse].call if stats
141
+ private
44
142
 
45
- if Liquid::C.enabled
143
+ # helper method for C code
144
+ def rescue_strict_parse_syntax_error(error, markup, parse_context)
145
+ error.line_number = parse_context.line_number
146
+ error.markup_context = "in \"{{#{markup}}}\""
147
+ case parse_context.error_mode
148
+ when :strict
149
+ raise
150
+ when :warn
151
+ parse_context.warnings << error
152
+ end
153
+ call_variable_fallback_stats_callback(parse_context)
154
+ lax_parse(markup, parse_context) # Avoid redundant strict parse
155
+ end
156
+
157
+ def lax_parse(markup, parse_context)
158
+ old_error_mode = parse_context.error_mode
46
159
  begin
47
- return strict_parse(markup)
48
- rescue Liquid::SyntaxError
49
- stats[:variable_fallback].call if stats
160
+ set_error_mode(parse_context, :lax)
161
+ new(markup, parse_context)
162
+ ensure
163
+ set_error_mode(parse_context, old_error_mode)
50
164
  end
51
165
  end
52
166
 
53
- ruby_lax_parse(markup)
167
+ def set_error_mode(parse_context, mode)
168
+ parse_context.instance_variable_set(:@error_mode, mode)
169
+ end
54
170
  end
55
171
 
172
+ alias_method :ruby_strict_parse, :strict_parse
173
+
56
174
  def strict_parse(markup)
57
- if Liquid::C.enabled
58
- @name = Liquid::Variable.c_strict_parse(markup, @filters = [])
59
- else
175
+ if parse_context.liquid_c_nodes_disabled?
60
176
  ruby_strict_parse(markup)
177
+ else
178
+ c_strict_parse(markup)
61
179
  end
62
180
  end
63
181
  end
64
182
 
65
- Liquid::VariableLookup.class_eval do
66
- alias_method :ruby_initialize, :initialize
183
+ Liquid::StrainerTemplate.class_eval do
184
+ class << self
185
+ private
67
186
 
68
- def initialize(markup, name = nil, lookups = nil, command_flags = nil)
69
- if Liquid::C.enabled && markup == false
70
- @name = name
71
- @lookups = lookups
72
- @command_flags = command_flags
73
- else
74
- ruby_initialize(markup)
187
+ def filter_methods_hash
188
+ @filter_methods_hash ||= {}.tap do |hash|
189
+ filter_methods.each do |method_name|
190
+ hash[method_name.to_sym] = true
191
+ end
192
+ end
193
+ end
194
+
195
+ # Convert wrong number of argument error into a liquid exception to
196
+ # treat it as an error in the template, not an internal error.
197
+ def arg_exc_to_liquid_exc(argument_error)
198
+ raise Liquid::ArgumentError, argument_error.message, argument_error.backtrace
199
+ rescue Liquid::ArgumentError => liquid_exc
200
+ liquid_exc
75
201
  end
76
202
  end
77
203
  end
78
204
 
79
- Liquid::Expression.class_eval do
205
+ Liquid::C::Expression.class_eval do
80
206
  class << self
81
- alias_method :ruby_parse, :parse
207
+ def lax_parse(markup)
208
+ strict_parse(markup)
209
+ rescue Liquid::SyntaxError
210
+ Liquid::Expression.parse(markup)
211
+ end
82
212
 
83
- def parse(markup)
84
- return nil unless markup
213
+ # Default to strict parsing, since Liquid::C::Expression.parse should only really
214
+ # be used with constant expressions. Otherwise, prefer parse_context.parse_expression.
215
+ alias_method :parse, :strict_parse
216
+ end
217
+ end
218
+
219
+ Liquid::Context.class_eval do
220
+ alias_method :ruby_parse_evaluate, :[]
221
+ alias_method :ruby_evaluate, :evaluate
222
+ alias_method :ruby_find_variable, :find_variable
223
+ alias_method :ruby_strict_variables=, :strict_variables=
224
+
225
+ # This isn't entered often by Ruby (most calls stay in C via VariableLookup#evaluate)
226
+ # so the wrapper method isn't costly.
227
+ def c_find_variable_kwarg(key, raise_on_not_found: true)
228
+ c_find_variable(key, raise_on_not_found)
229
+ end
230
+
231
+ def c_parse_evaluate(expression)
232
+ c_evaluate(Liquid::C::Expression.lax_parse(expression))
233
+ end
234
+ end
235
+
236
+ Liquid::ResourceLimits.class_eval do
237
+ def self.new(limits)
238
+ if Liquid::C.enabled
239
+ Liquid::C::ResourceLimits.new(
240
+ limits[:render_length_limit],
241
+ limits[:render_score_limit],
242
+ limits[:assign_score_limit]
243
+ )
244
+ else
245
+ super
246
+ end
247
+ end
248
+ end
85
249
 
86
- if Liquid::C.enabled
87
- begin
88
- return c_parse(markup)
89
- rescue Liquid::SyntaxError
250
+ module Liquid
251
+ module C
252
+ class << self
253
+ attr_reader :enabled
254
+
255
+ def enabled=(value)
256
+ @enabled = value
257
+ if value
258
+ Liquid::Context.send(:alias_method, :[], :c_parse_evaluate)
259
+ Liquid::Context.send(:alias_method, :evaluate, :c_evaluate)
260
+ Liquid::Context.send(:alias_method, :find_variable, :c_find_variable_kwarg)
261
+ Liquid::Context.send(:alias_method, :strict_variables=, :c_strict_variables=)
262
+ else
263
+ Liquid::Context.send(:alias_method, :[], :ruby_parse_evaluate)
264
+ Liquid::Context.send(:alias_method, :evaluate, :ruby_evaluate)
265
+ Liquid::Context.send(:alias_method, :find_variable, :ruby_find_variable)
266
+ Liquid::Context.send(:alias_method, :strict_variables=, :ruby_strict_variables=)
90
267
  end
91
268
  end
92
- ruby_parse(markup)
93
269
  end
270
+
271
+ self.enabled = true
94
272
  end
95
273
  end
data/liquid-c.gemspec CHANGED
@@ -1,7 +1,11 @@
1
1
  # coding: utf-8
2
- lib = File.expand_path('../lib', __FILE__)
2
+ # frozen_string_literal: true
3
+
4
+ # rubocop:disable Gemspec/RubyVersionGlobalsUsage
5
+
6
+ lib = File.expand_path("../lib", __FILE__)
3
7
  $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
- require 'liquid/c/version'
8
+ require "liquid/c/version"
5
9
 
6
10
  Gem::Specification.new do |spec|
7
11
  spec.name = "liquid-c"
@@ -12,19 +16,21 @@ Gem::Specification.new do |spec|
12
16
  spec.homepage = "https://github.com/shopify/liquid-c"
13
17
  spec.license = "MIT"
14
18
 
15
- spec.extensions = ['ext/liquid_c/extconf.rb']
16
- spec.files = `git ls-files -z`.split("\x0")
19
+ spec.extensions = ["ext/liquid_c/extconf.rb"]
20
+ spec.files = %x(git ls-files -z).split("\x0")
17
21
  spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
22
  spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
23
  spec.require_paths = ["lib"]
20
24
 
25
+ spec.required_ruby_version = ">= 2.5.0"
26
+
21
27
  spec.metadata["allowed_push_host"] = "https://rubygems.org"
22
28
 
23
- spec.add_dependency 'liquid', '>= 3.0.0'
29
+ spec.add_dependency("liquid", ">= 5.0.1")
24
30
 
25
- spec.add_development_dependency "bundler", ">= 1.5"
26
- spec.add_development_dependency "rake"
27
- spec.add_development_dependency 'rake-compiler'
28
- spec.add_development_dependency 'minitest'
29
- spec.add_development_dependency 'stackprof' if Gem::Version.new(RUBY_VERSION) >= Gem::Version.new("2.1.0")
31
+ spec.add_development_dependency("bundler", ">= 1.5") # has bugfix for segfaulting deploys
32
+ spec.add_development_dependency("minitest")
33
+ spec.add_development_dependency("rake")
34
+ spec.add_development_dependency("rake-compiler")
35
+ spec.add_development_dependency("stackprof") if Gem::Version.new(RUBY_VERSION) >= Gem::Version.new("2.1.0")
30
36
  end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "liquid"
4
+ require "liquid/c"
5
+ liquid_lib_dir = $LOAD_PATH.detect { |p| File.exist?(File.join(p, "liquid.rb")) }
6
+ require File.join(File.dirname(liquid_lib_dir), "performance/theme_runner")
7
+
8
+ TASK_NAMES = ["run", "compile", "render"]
9
+ task_name = ARGV.first || "run"
10
+ unless TASK_NAMES.include?(task_name)
11
+ raise "Unsupported task '#{task_name}' (must be one of #{TASK_NAMES})"
12
+ end
13
+ task = ThemeRunner.new.method(task_name)
14
+
15
+ runner_id = fork do
16
+ end_time = Time.now + 5.0
17
+ task.call until Time.now >= end_time
18
+ end
19
+
20
+ profiler_pid = spawn("instruments -t 'Time Profiler' -p #{runner_id}")
21
+
22
+ Process.waitpid(runner_id)
23
+ Process.waitpid(profiler_pid)
data/performance.rb CHANGED
@@ -1,6 +1,8 @@
1
- require 'liquid'
2
- require 'liquid/c' if ARGV.shift == "c"
3
- liquid_lib_dir = $LOAD_PATH.detect{ |p| File.exists?(File.join(p, 'liquid.rb')) }
1
+ # frozen_string_literal: true
4
2
 
5
- script = ARGV.shift or abort("unspecified performance script")
3
+ require "liquid"
4
+ require "liquid/c" if ARGV.shift == "c"
5
+ liquid_lib_dir = $LOAD_PATH.detect { |p| File.exist?(File.join(p, "liquid.rb")) }
6
+
7
+ (script = ARGV.shift) || abort("unspecified performance script")
6
8
  require File.join(File.dirname(liquid_lib_dir), "performance/#{script}")
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ ext_task = Rake::ExtensionTask.new("liquid_c")
4
+
5
+ # For MacOS, generate debug information that ruby can read
6
+ dsymutil = RbConfig::CONFIG["dsymutil"]
7
+ unless dsymutil.to_s.empty?
8
+ ext_lib_path = "lib/#{ext_task.binary}"
9
+ dsym_path = "#{ext_lib_path}.dSYM"
10
+
11
+ file dsym_path => [ext_lib_path] do
12
+ sh dsymutil, ext_lib_path
13
+ end
14
+ Rake::Task["compile"].enhance([dsym_path])
15
+ end
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: true
2
+
3
+ namespace :test do
4
+ integration_test_config = lambda do |t|
5
+ t.libs << "lib"
6
+ t.test_files = ["test/integration_test.rb"]
7
+ end
8
+
9
+ desc "run test suite with default parser"
10
+ Rake::TestTask.new(integration: :compile, &integration_test_config)
11
+
12
+ namespace :integration do
13
+ define_env_integration_tests = lambda do |task_name|
14
+ rake_task = Rake::Task[task_name]
15
+
16
+ [
17
+ [:lax, { "LIQUID_PARSER_MODE" => "lax" }],
18
+ [:strict, { "LIQUID_PARSER_MODE" => "strict" }],
19
+ [:without_vm, { "LIQUID_C_DISABLE_VM" => "true" }],
20
+ ].each do |name, env_vars|
21
+ task(name) do
22
+ old_env_values = ENV.to_hash.slice(*env_vars.keys)
23
+ begin
24
+ env_vars.each { |key, value| ENV[key] = value }
25
+ rake_task.invoke
26
+ ensure
27
+ old_env_values.each { |key, value| ENV[key] = value }
28
+ rake_task.reenable
29
+ end
30
+ end
31
+ end
32
+
33
+ task(all: [:lax, :strict, :without_vm])
34
+ end
35
+
36
+ define_env_integration_tests.call("test:integration")
37
+
38
+ RubyMemcheck::TestTask.new(valgrind: :compile, &integration_test_config)
39
+ namespace :valgrind do
40
+ define_env_integration_tests.call("test:integration:valgrind")
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: true
2
+
3
+ namespace :benchmark do
4
+ desc "Run the liquid benchmark with lax parsing"
5
+ task :run do
6
+ ruby "./performance.rb c benchmark lax"
7
+ end
8
+
9
+ desc "Run the liquid benchmark with strict parsing"
10
+ task :strict do
11
+ ruby "./performance.rb c benchmark strict"
12
+ end
13
+ end
14
+
15
+ namespace :c_profile do
16
+ [:run, :compile, :render].each do |task_name|
17
+ task(task_name) do
18
+ ruby "./performance/c_profile.rb #{task_name}"
19
+ end
20
+ end
21
+ end
22
+
23
+ namespace :profile do
24
+ desc "Run the liquid profile/performance coverage"
25
+ task :run do
26
+ ruby "./performance.rb c profile lax"
27
+ end
28
+
29
+ desc "Run the liquid profile/performance coverage with strict parsing"
30
+ task :strict do
31
+ ruby "./performance.rb c profile strict"
32
+ end
33
+ end
34
+
35
+ namespace :compare do
36
+ ["lax", "warn", "strict"].each do |type|
37
+ desc "Compare Liquid to Liquid-C in #{type} mode"
38
+ task type.to_sym do
39
+ ruby "./performance.rb bare benchmark #{type}"
40
+ ruby "./performance.rb c benchmark #{type}"
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,6 @@
1
+ # frozen_string_literal: true
2
+
3
+ task :rubocop do
4
+ require "rubocop/rake_task"
5
+ RuboCop::RakeTask.new
6
+ end
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ namespace :test do
4
+ unit_test_config = lambda do |t|
5
+ t.libs << "lib" << "test"
6
+ t.test_files = FileList["test/unit/**/*_test.rb"]
7
+ end
8
+
9
+ Rake::TestTask.new(unit: :compile, &unit_test_config)
10
+
11
+ namespace :unit do
12
+ RubyMemcheck::TestTask.new(valgrind: :compile, &unit_test_config)
13
+ end
14
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ at_exit { GC.start }
4
+
5
+ require_relative "liquid_test_helper"
6
+
7
+ test_files = Dir[File.join(LIQUID_TEST_DIR, "integration/**/*_test.rb")]
8
+ test_files << File.join(LIQUID_TEST_DIR, "unit/tokenizer_unit_test.rb")
9
+ test_files.each do |test_file|
10
+ require test_file
11
+ end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ # This can be used to setup for running tests from the liquid test suite.
4
+ # For example, you could run a single liquid test as follows:
5
+ # $ ruby -r./test/liquid_test_helper `bundle info liquid --path`/test/integration/template_test.rb
6
+
7
+ require "bundler/setup"
8
+
9
+ liquid_lib_dir = $LOAD_PATH.detect { |p| File.exist?(File.join(p, "liquid.rb")) }
10
+ LIQUID_TEST_DIR = File.join(File.dirname(liquid_lib_dir), "test")
11
+ $LOAD_PATH << LIQUID_TEST_DIR << File.expand_path("../lib", __dir__)
12
+
13
+ require "test_helper"
14
+ require "liquid/c"
15
+
16
+ if ENV["LIQUID_C_DISABLE_VM"]
17
+ puts "-- Liquid-C VM Disabled"
18
+ Liquid::ParseContext.liquid_c_nodes_disabled = true
19
+ end
20
+
21
+ GC.stress = true if ENV["GC_STRESS"]
data/test/test_helper.rb CHANGED
@@ -1,2 +1,21 @@
1
- require 'minitest/autorun'
2
- require 'liquid/c'
1
+ # frozen_string_literal: true
2
+
3
+ require "minitest/autorun"
4
+ require "liquid/c"
5
+
6
+ if GC.respond_to?(:verify_compaction_references)
7
+ # This method was added in Ruby 3.0.0. Calling it this way asks the GC to
8
+ # move objects around, helping to find object movement bugs.
9
+ begin
10
+ GC.verify_compaction_references(double_heap: true, toward: :empty)
11
+ rescue NotImplementedError
12
+ puts "W: GC compaction not suppported by platform"
13
+ end
14
+ end
15
+
16
+ # Enable auto-compaction in the GC if supported.
17
+ if GC.respond_to?(:auto_compact=)
18
+ GC.auto_compact = true
19
+ end
20
+
21
+ GC.stress = true if ENV["GC_STRESS"]