liquid-c 4.0.1 → 4.1.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 (70) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/liquid.yml +24 -2
  3. data/.gitignore +4 -0
  4. data/.rubocop.yml +14 -0
  5. data/Gemfile +14 -5
  6. data/README.md +29 -5
  7. data/Rakefile +13 -62
  8. data/ext/liquid_c/block.c +488 -60
  9. data/ext/liquid_c/block.h +28 -2
  10. data/ext/liquid_c/c_buffer.c +42 -0
  11. data/ext/liquid_c/c_buffer.h +76 -0
  12. data/ext/liquid_c/context.c +233 -0
  13. data/ext/liquid_c/context.h +70 -0
  14. data/ext/liquid_c/document_body.c +89 -0
  15. data/ext/liquid_c/document_body.h +59 -0
  16. data/ext/liquid_c/expression.c +116 -0
  17. data/ext/liquid_c/expression.h +24 -0
  18. data/ext/liquid_c/extconf.rb +19 -9
  19. data/ext/liquid_c/intutil.h +22 -0
  20. data/ext/liquid_c/lexer.c +6 -2
  21. data/ext/liquid_c/lexer.h +18 -3
  22. data/ext/liquid_c/liquid.c +76 -6
  23. data/ext/liquid_c/liquid.h +24 -1
  24. data/ext/liquid_c/parse_context.c +76 -0
  25. data/ext/liquid_c/parse_context.h +13 -0
  26. data/ext/liquid_c/parser.c +141 -65
  27. data/ext/liquid_c/parser.h +4 -2
  28. data/ext/liquid_c/raw.c +110 -0
  29. data/ext/liquid_c/raw.h +6 -0
  30. data/ext/liquid_c/resource_limits.c +279 -0
  31. data/ext/liquid_c/resource_limits.h +23 -0
  32. data/ext/liquid_c/stringutil.h +44 -0
  33. data/ext/liquid_c/tokenizer.c +149 -35
  34. data/ext/liquid_c/tokenizer.h +20 -9
  35. data/ext/liquid_c/usage.c +18 -0
  36. data/ext/liquid_c/usage.h +9 -0
  37. data/ext/liquid_c/variable.c +196 -20
  38. data/ext/liquid_c/variable.h +18 -1
  39. data/ext/liquid_c/variable_lookup.c +44 -0
  40. data/ext/liquid_c/variable_lookup.h +8 -0
  41. data/ext/liquid_c/vm.c +588 -0
  42. data/ext/liquid_c/vm.h +25 -0
  43. data/ext/liquid_c/vm_assembler.c +491 -0
  44. data/ext/liquid_c/vm_assembler.h +240 -0
  45. data/ext/liquid_c/vm_assembler_pool.c +97 -0
  46. data/ext/liquid_c/vm_assembler_pool.h +27 -0
  47. data/lib/liquid/c/compile_ext.rb +44 -0
  48. data/lib/liquid/c/version.rb +3 -1
  49. data/lib/liquid/c.rb +225 -46
  50. data/liquid-c.gemspec +16 -10
  51. data/performance/c_profile.rb +23 -0
  52. data/performance.rb +6 -4
  53. data/rakelib/compile.rake +15 -0
  54. data/rakelib/integration_test.rake +43 -0
  55. data/rakelib/performance.rake +43 -0
  56. data/rakelib/rubocop.rake +6 -0
  57. data/rakelib/unit_test.rake +14 -0
  58. data/test/integration_test.rb +11 -0
  59. data/test/liquid_test_helper.rb +21 -0
  60. data/test/test_helper.rb +14 -2
  61. data/test/unit/block_test.rb +130 -0
  62. data/test/unit/context_test.rb +83 -0
  63. data/test/unit/expression_test.rb +186 -0
  64. data/test/unit/gc_stress_test.rb +28 -0
  65. data/test/unit/raw_test.rb +19 -0
  66. data/test/unit/resource_limits_test.rb +50 -0
  67. data/test/unit/tokenizer_test.rb +90 -20
  68. data/test/unit/variable_test.rb +212 -60
  69. metadata +59 -11
  70. data/test/liquid_test.rb +0 -11
data/lib/liquid/c.rb CHANGED
@@ -1,77 +1,213 @@
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
63
+
64
+ def new_block_body
65
+ if liquid_c_nodes_disabled?
66
+ ruby_new_block_body
67
+ else
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
27
83
 
28
- def parse(tokens, options)
29
- if Liquid::C.enabled && !options[:profile]
30
- c_parse(tokens, options) { |t, m| yield t, m }
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.ruby_parse(markup)
31
90
  else
32
- ruby_parse(tokens, options) { |t, m| yield t, m }
91
+ Liquid::C::Expression.lax_parse(markup)
33
92
  end
34
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
128
+ end
129
+ Liquid::Document.singleton_class.prepend(DocumentClassPatch)
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
201
+ end
202
+ end
203
+ end
204
+
205
+ Liquid::C::Expression.class_eval do
206
+ class << self
207
+ def lax_parse(markup)
208
+ strict_parse(markup)
209
+ rescue Liquid::SyntaxError
210
+ Liquid::Expression.ruby_parse(markup)
75
211
  end
76
212
  end
77
213
  end
@@ -80,16 +216,59 @@ Liquid::Expression.class_eval do
80
216
  class << self
81
217
  alias_method :ruby_parse, :parse
82
218
 
83
- def parse(markup)
84
- return nil unless markup
219
+ def c_parse(markup)
220
+ Liquid::C::Expression.lax_parse(markup)
221
+ end
222
+ end
223
+ end
224
+
225
+ Liquid::Context.class_eval do
226
+ alias_method :ruby_evaluate, :evaluate
227
+ alias_method :ruby_find_variable, :find_variable
228
+ alias_method :ruby_strict_variables=, :strict_variables=
229
+
230
+ # This isn't entered often by Ruby (most calls stay in C via VariableLookup#evaluate)
231
+ # so the wrapper method isn't costly.
232
+ def c_find_variable_kwarg(key, raise_on_not_found: true)
233
+ c_find_variable(key, raise_on_not_found)
234
+ end
235
+ end
236
+
237
+ Liquid::ResourceLimits.class_eval do
238
+ def self.new(limits)
239
+ if Liquid::C.enabled
240
+ Liquid::C::ResourceLimits.new(
241
+ limits[:render_length_limit],
242
+ limits[:render_score_limit],
243
+ limits[:assign_score_limit]
244
+ )
245
+ else
246
+ super
247
+ end
248
+ end
249
+ end
250
+
251
+ module Liquid
252
+ module C
253
+ class << self
254
+ attr_reader :enabled
85
255
 
86
- if Liquid::C.enabled
87
- begin
88
- return c_parse(markup)
89
- rescue Liquid::SyntaxError
256
+ def enabled=(value)
257
+ @enabled = value
258
+ if value
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
+ Liquid::Expression.singleton_class.send(:alias_method, :parse, :c_parse)
263
+ else
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=)
267
+ Liquid::Expression.singleton_class.send(:alias_method, :parse, :ruby_parse)
90
268
  end
91
269
  end
92
- ruby_parse(markup)
93
270
  end
271
+
272
+ self.enabled = true
94
273
  end
95
274
  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,14 @@
1
- require 'minitest/autorun'
2
- require 'liquid/c'
1
+ # frozen_string_literal: true
2
+
3
+ at_exit { GC.start }
4
+
5
+ require "minitest/autorun"
6
+ require "liquid/c"
7
+
8
+ if GC.respond_to?(:verify_compaction_references)
9
+ # This method was added in Ruby 3.0.0. Calling it this way asks the GC to
10
+ # move objects around, helping to find object movement bugs.
11
+ GC.verify_compaction_references(double_heap: true, toward: :empty)
12
+ end
13
+
14
+ GC.stress = true if ENV["GC_STRESS"]