liquid-c 4.0.1 → 4.1.0

Sign up to get free protection for your applications and to get access to all the features.
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"]