New Upstream Snapshot - ruby-liquid

Ready changes

Summary

Merged new upstream version: 5.4.0+git20230118.1.daf93a8 (was: 5.4.0).

Resulting package

Built on 2023-01-22T03:37 (took 4m33s)

The resulting binary packages can be installed (if you have the apt repository enabled) by running one of:

apt install -t fresh-snapshots ruby-liquid

Lintian Result

Diff

diff --git a/.github/probots.yml b/.github/probots.yml
deleted file mode 100644
index 1491d27..0000000
--- a/.github/probots.yml
+++ /dev/null
@@ -1,2 +0,0 @@
-enabled:
-  - cla
diff --git a/.github/workflows/liquid.yml b/.github/workflows/liquid.yml
deleted file mode 100644
index 749eee0..0000000
--- a/.github/workflows/liquid.yml
+++ /dev/null
@@ -1,39 +0,0 @@
-name: Liquid
-on: [push, pull_request]
-jobs:
-  test:
-    runs-on: ubuntu-latest
-    strategy:
-      matrix:
-        entry:
-          - { ruby: 2.7, allowed-failure: false } # minimum supported
-          - { ruby: 3.1, allowed-failure: false } # latest
-          - { ruby: ruby-head, allowed-failure: true }
-    name: test (${{ matrix.entry.ruby }})
-    steps:
-      - uses: actions/checkout@v3
-      - uses: ruby/setup-ruby@v1
-        with:
-          ruby-version: ${{ matrix.entry.ruby }}
-      - uses: actions/cache@v1
-        with:
-          path: vendor/bundle
-          key: ${{ runner.os }}-gems-${{ hashFiles('Gemfile') }}
-          restore-keys: ${{ runner.os }}-gems-
-      - run: bundle install --jobs=3 --retry=3 --path=vendor/bundle
-      - run: bundle exec rake
-        continue-on-error: ${{ matrix.entry.allowed-failure }}
-  memory_profile:
-    runs-on: ubuntu-latest
-    steps:
-      - uses: actions/checkout@v3
-      - uses: ruby/setup-ruby@v1
-        with:
-          ruby-version: 2.7
-      - uses: actions/cache@v1
-        with:
-          path: vendor/bundle
-          key: ${{ runner.os }}-gems-${{ hashFiles('Gemfile') }}
-          restore-keys: ${{ runner.os }}-gems-
-      - run: bundle install --jobs=3 --retry=3 --path=vendor/bundle
-      - run: bundle exec rake memory_profile:run
diff --git a/.gitignore b/.gitignore
deleted file mode 100644
index 90bf6dc..0000000
--- a/.gitignore
+++ /dev/null
@@ -1,10 +0,0 @@
-*~
-*.gem
-*.swp
-pkg
-*.rbc
-.rvmrc
-.ruby-version
-Gemfile.lock
-.bundle
-.byebug_history
diff --git a/debian/changelog b/debian/changelog
index 34d435c..816a2fd 100644
--- a/debian/changelog
+++ b/debian/changelog
@@ -1,3 +1,9 @@
+ruby-liquid (5.4.0+git20230118.1.daf93a8-1) UNRELEASED; urgency=low
+
+  * New upstream snapshot.
+
+ -- Debian Janitor <janitor@jelmer.uk>  Sun, 22 Jan 2023 03:34:43 -0000
+
 ruby-liquid (5.4.0-3) unstable; urgency=medium
 
   * Team upload
diff --git a/debian/patches/0001-context_test-disable-test_interrupt_avoids_object_al.patch b/debian/patches/0001-context_test-disable-test_interrupt_avoids_object_al.patch
index 552b0cf..1546a28 100644
--- a/debian/patches/0001-context_test-disable-test_interrupt_avoids_object_al.patch
+++ b/debian/patches/0001-context_test-disable-test_interrupt_avoids_object_al.patch
@@ -10,11 +10,11 @@ Forwarded: not-needed
  test/integration/context_test.rb | 1 +
  1 file changed, 1 insertion(+)
 
-diff --git a/test/integration/context_test.rb b/test/integration/context_test.rb
-index 832e3e3..4eeaee4 100644
---- a/test/integration/context_test.rb
-+++ b/test/integration/context_test.rb
-@@ -635,6 +635,7 @@ class ContextTest < Minitest::Test
+Index: ruby-liquid.git/test/integration/context_test.rb
+===================================================================
+--- ruby-liquid.git.orig/test/integration/context_test.rb
++++ ruby-liquid.git/test/integration/context_test.rb
+@@ -624,6 +624,7 @@ class ContextTest < Minitest::Test
    private
  
    def assert_no_object_allocations
diff --git a/debian/patches/0002-Bring-back-Registers-each.patch b/debian/patches/0002-Bring-back-Registers-each.patch
index 316115f..a9e3764 100644
--- a/debian/patches/0002-Bring-back-Registers-each.patch
+++ b/debian/patches/0002-Bring-back-Registers-each.patch
@@ -29,10 +29,10 @@ Source: https://github.com/Shopify/liquid/pull/1663
  test/unit/registers_unit_test.rb | 24 ++++++++++++++++++++++++
  2 files changed, 34 insertions(+)
 
-diff --git a/lib/liquid/registers.rb b/lib/liquid/registers.rb
-index 0b65d86..6b100fc 100644
---- a/lib/liquid/registers.rb
-+++ b/lib/liquid/registers.rb
+Index: ruby-liquid.git/lib/liquid/registers.rb
+===================================================================
+--- ruby-liquid.git.orig/lib/liquid/registers.rb
++++ ruby-liquid.git/lib/liquid/registers.rb
 @@ -27,6 +27,16 @@ module Liquid
  
      UNDEFINED = Object.new
@@ -50,10 +50,10 @@ index 0b65d86..6b100fc 100644
      def fetch(key, default = UNDEFINED, &block)
        if @changes.key?(key)
          @changes.fetch(key)
-diff --git a/test/unit/registers_unit_test.rb b/test/unit/registers_unit_test.rb
-index caa718e..512bde3 100644
---- a/test/unit/registers_unit_test.rb
-+++ b/test/unit/registers_unit_test.rb
+Index: ruby-liquid.git/test/unit/registers_unit_test.rb
+===================================================================
+--- ruby-liquid.git.orig/test/unit/registers_unit_test.rb
++++ ruby-liquid.git/test/unit/registers_unit_test.rb
 @@ -36,6 +36,30 @@ class RegistersUnitTest < Minitest::Test
      assert_nil(static_register.delete(:d))
    end
diff --git a/lib/liquid/block_body.rb b/lib/liquid/block_body.rb
index a0d35a7..4674c5f 100644
--- a/lib/liquid/block_body.rb
+++ b/lib/liquid/block_body.rb
@@ -109,14 +109,22 @@ module Liquid
       end
     end
 
-    private def parse_for_document(tokenizer, parse_context)
+    private def handle_invalid_tag_token(token, parse_context)
+      if token.end_with?('%}')
+        yield token, token
+      else
+        BlockBody.raise_missing_tag_terminator(token, parse_context)
+      end
+    end
+
+    private def parse_for_document(tokenizer, parse_context, &block)
       while (token = tokenizer.shift)
         next if token.empty?
         case
         when token.start_with?(TAGSTART)
           whitespace_handler(token, parse_context)
           unless token =~ FullToken
-            BlockBody.raise_missing_tag_terminator(token, parse_context)
+            return handle_invalid_tag_token(token, parse_context, &block)
           end
           tag_name = Regexp.last_match(2)
           markup   = Regexp.last_match(4)
diff --git a/lib/liquid/context.rb b/lib/liquid/context.rb
index eb6ddf9..36d3868 100644
--- a/lib/liquid/context.rb
+++ b/lib/liquid/context.rb
@@ -26,7 +26,7 @@ module Liquid
       @environments = [environments]
       @environments.flatten!
 
-      @static_environments = [static_environments].flat_map(&:freeze).freeze
+      @static_environments = [static_environments].flatten(1).freeze
       @scopes              = [(outer_scope || {})]
       @registers           = registers.is_a?(Registers) ? registers : Registers.new(registers)
       @errors              = []
diff --git a/lib/liquid/drop.rb b/lib/liquid/drop.rb
index d4d8950..b990630 100644
--- a/lib/liquid/drop.rb
+++ b/lib/liquid/drop.rb
@@ -25,6 +25,10 @@ module Liquid
   class Drop
     attr_writer :context
 
+    def initialize
+      @context = nil
+    end
+
     # Catch all for the method
     def liquid_method_missing(method)
       return nil unless @context&.strict_variables
diff --git a/lib/liquid/forloop_drop.rb b/lib/liquid/forloop_drop.rb
index 9592faa..1dfa2f3 100644
--- a/lib/liquid/forloop_drop.rb
+++ b/lib/liquid/forloop_drop.rb
@@ -5,7 +5,7 @@ module Liquid
   # @liquid_type object
   # @liquid_name forloop
   # @liquid_summary
-  #   Information about a parent [`for` loop](/api/liquid/tags#for).
+  #   Information about a parent [`for` loop](/api/liquid/tags/for).
   class ForloopDrop < Drop
     def initialize(name, length, parentloop)
       @name       = name
@@ -30,10 +30,7 @@ module Liquid
     # @liquid_return [forloop]
     attr_reader :parentloop
 
-    def name
-      Usage.increment('forloop_drop_name')
-      @name
-    end
+    attr_reader :name
 
     # @liquid_public_docs
     # @liquid_summary
diff --git a/lib/liquid/lexer.rb b/lib/liquid/lexer.rb
index f620581..4ce2bc7 100644
--- a/lib/liquid/lexer.rb
+++ b/lib/liquid/lexer.rb
@@ -18,6 +18,7 @@ module Liquid
     IDENTIFIER            = /[a-zA-Z_][\w-]*\??/
     SINGLE_STRING_LITERAL = /'[^\']*'/
     DOUBLE_STRING_LITERAL = /"[^\"]*"/
+    STRING_LITERAL        = Regexp.union(SINGLE_STRING_LITERAL, DOUBLE_STRING_LITERAL)
     NUMBER_LITERAL        = /-?\d+(\.\d+)?/
     DOTDOT                = /\.\./
     COMPARISON_OPERATOR   = /==|!=|<>|<=?|>=?|contains(?=\s)/
@@ -35,9 +36,7 @@ module Liquid
         break if @ss.eos?
         tok      = if (t = @ss.scan(COMPARISON_OPERATOR))
           [:comparison, t]
-        elsif (t = @ss.scan(SINGLE_STRING_LITERAL))
-          [:string, t]
-        elsif (t = @ss.scan(DOUBLE_STRING_LITERAL))
+        elsif (t = @ss.scan(STRING_LITERAL))
           [:string, t]
         elsif (t = @ss.scan(NUMBER_LITERAL))
           [:number, t]
diff --git a/lib/liquid/range_lookup.rb b/lib/liquid/range_lookup.rb
index 7e159be..fd208a6 100644
--- a/lib/liquid/range_lookup.rb
+++ b/lib/liquid/range_lookup.rb
@@ -8,7 +8,17 @@ module Liquid
       if start_obj.respond_to?(:evaluate) || end_obj.respond_to?(:evaluate)
         new(start_obj, end_obj)
       else
-        start_obj.to_i..end_obj.to_i
+        begin
+          start_obj.to_i..end_obj.to_i
+        rescue NoMethodError
+          invalid_expr = start_markup unless start_obj.respond_to?(:to_i)
+          invalid_expr ||= end_markup unless end_obj.respond_to?(:to_i)
+          if invalid_expr
+            raise Liquid::SyntaxError, "Invalid expression type '#{invalid_expr}' in range expression"
+          end
+
+          raise
+        end
       end
     end
 
diff --git a/lib/liquid/standardfilters.rb b/lib/liquid/standardfilters.rb
index ed7136a..7450a93 100644
--- a/lib/liquid/standardfilters.rb
+++ b/lib/liquid/standardfilters.rb
@@ -6,7 +6,14 @@ require 'bigdecimal'
 
 module Liquid
   module StandardFilters
-    MAX_INT = (1 << 31) - 1
+    MAX_I32 = (1 << 31) - 1
+    private_constant :MAX_I32
+
+    MIN_I64 = -(1 << 63)
+    MAX_I64 = (1 << 63) - 1
+    I64_RANGE = MIN_I64..MAX_I64
+    private_constant :MIN_I64, :MAX_I64, :I64_RANGE
+
     HTML_ESCAPE = {
       '&' => '&amp;',
       '>' => '&gt;',
@@ -73,7 +80,7 @@ module Liquid
     # @liquid_type filter
     # @liquid_category string
     # @liquid_summary
-    #   Escapes a string.
+    #   Escapes special characters in HTML, such as `<>`, `'`, and `&`, and converts characters into escape sequences. The filter doesn't effect characters within the string that don’t have a corresponding escape sequence.".
     # @liquid_syntax string | escape
     # @liquid_return [string]
     def escape(input)
@@ -186,10 +193,19 @@ module Liquid
       offset = Utils.to_integer(offset)
       length = length ? Utils.to_integer(length) : 1
 
-      if input.is_a?(Array)
-        input.slice(offset, length) || []
-      else
-        input.to_s.slice(offset, length) || ''
+      begin
+        if input.is_a?(Array)
+          input.slice(offset, length) || []
+        else
+          input.to_s.slice(offset, length) || ''
+        end
+      rescue RangeError
+        if I64_RANGE.cover?(length) && I64_RANGE.cover?(offset)
+          raise # unexpected error
+        end
+        offset = offset.clamp(I64_RANGE)
+        length = length.clamp(I64_RANGE)
+        retry
       end
     end
 
@@ -239,9 +255,9 @@ module Liquid
       wordlist = begin
         input.split(" ", words + 1)
       rescue RangeError
-        raise if words + 1 < MAX_INT
-        # e.g. integer #{words} too big to convert to `int'
-        raise Liquid::ArgumentError, "integer #{words} too big for truncatewords"
+        # integer too big for String#split, but we can semantically assume no truncation is needed
+        return input if words + 1 > MAX_I32
+        raise # unexpected error
       end
       return input if wordlist.length <= words
 
@@ -599,7 +615,7 @@ module Liquid
     # @liquid_description
     #   > Note:
     #   > The `concat` filter won't filter out duplicates. If you want to remove duplicates, then you need to use the
-    #   > [`uniq` filter](/api/liquid/filters#uniq).
+    #   > [`uniq` filter](/api/liquid/filters/uniq).
     # @liquid_syntax array | concat: array
     # @liquid_return [array[untyped]]
     def concat(input, array)
@@ -741,7 +757,7 @@ module Liquid
     # @liquid_type filter
     # @liquid_category math
     # @liquid_summary
-    #   Divides a number by a given number.
+    #   Divides a number by a given number. The `divided_by` filter produces a result of the same type as the divisor. This means if you divide by an integer, the result will be an integer, and if you divide by a float, the result will be a float.
     # @liquid_syntax number | divided_by: number
     # @liquid_return [number]
     def divided_by(input, operand)
diff --git a/lib/liquid/tablerowloop_drop.rb b/lib/liquid/tablerowloop_drop.rb
index b24e734..554a00e 100644
--- a/lib/liquid/tablerowloop_drop.rb
+++ b/lib/liquid/tablerowloop_drop.rb
@@ -5,7 +5,7 @@ module Liquid
   # @liquid_type object
   # @liquid_name tablerowloop
   # @liquid_summary
-  #   Information about a parent [`tablerow` loop](/api/liquid/tags#tablerow).
+  #   Information about a parent [`tablerow` loop](/api/liquid/tags/tablerow).
   class TablerowloopDrop < Drop
     def initialize(length, cols)
       @length = length
diff --git a/lib/liquid/tag.rb b/lib/liquid/tag.rb
index 085c0ef..2f7bc30 100644
--- a/lib/liquid/tag.rb
+++ b/lib/liquid/tag.rb
@@ -14,12 +14,18 @@ module Liquid
       end
 
       def disable_tags(*tag_names)
-        @disabled_tags ||= []
-        @disabled_tags.concat(tag_names)
+        tag_names += disabled_tags
+        define_singleton_method(:disabled_tags) { tag_names }
         prepend(Disabler)
       end
 
       private :new
+
+      protected
+
+      def disabled_tags
+        []
+      end
     end
 
     def initialize(tag_name, markup, parse_context)
diff --git a/lib/liquid/tag/disabler.rb b/lib/liquid/tag/disabler.rb
index 210a177..852205a 100644
--- a/lib/liquid/tag/disabler.rb
+++ b/lib/liquid/tag/disabler.rb
@@ -3,14 +3,6 @@
 module Liquid
   class Tag
     module Disabler
-      module ClassMethods
-        attr_reader :disabled_tags
-      end
-
-      def self.prepended(base)
-        base.extend(ClassMethods)
-      end
-
       def render_to_output_buffer(context, output)
         context.with_disabled_tags(self.class.disabled_tags) do
           super
diff --git a/lib/liquid/tags/break.rb b/lib/liquid/tags/break.rb
index ebdf117..70bc9bd 100644
--- a/lib/liquid/tags/break.rb
+++ b/lib/liquid/tags/break.rb
@@ -15,7 +15,7 @@ module Liquid
   # @liquid_category iteration
   # @liquid_name break
   # @liquid_summary
-  #   Stops a [`for` loop](/api/liquid/tags#for) from iterating.
+  #   Stops a [`for` loop](/api/liquid/tags/for) from iterating.
   # @liquid_syntax
   #   {% break %}
   class Break < Tag
diff --git a/lib/liquid/tags/comment.rb b/lib/liquid/tags/comment.rb
index 51cea5c..9922ee0 100644
--- a/lib/liquid/tags/comment.rb
+++ b/lib/liquid/tags/comment.rb
@@ -8,7 +8,7 @@ module Liquid
   # @liquid_summary
   #   Prevents an expression from being rendered or output.
   # @liquid_description
-  #   Any text inside `comment` tags won't be output, and any Liquid code won't be rendered.
+  #   Any text inside `comment` tags won't be output, and any Liquid code will be parsed, but not executed.
   # @liquid_syntax
   #   {% comment %}
   #     content
diff --git a/lib/liquid/tags/continue.rb b/lib/liquid/tags/continue.rb
index 2435899..aea0a72 100644
--- a/lib/liquid/tags/continue.rb
+++ b/lib/liquid/tags/continue.rb
@@ -6,7 +6,7 @@ module Liquid
   # @liquid_category iteration
   # @liquid_name continue
   # @liquid_summary
-  #   Causes a [`for` loop](/api/liquid/tags#for) to skip to the next iteration.
+  #   Causes a [`for` loop](/api/liquid/tags/for) to skip to the next iteration.
   # @liquid_syntax
   #   {% continue %}
   class Continue < Tag
diff --git a/lib/liquid/tags/cycle.rb b/lib/liquid/tags/cycle.rb
index 3790d06..19aee5a 100644
--- a/lib/liquid/tags/cycle.rb
+++ b/lib/liquid/tags/cycle.rb
@@ -6,7 +6,7 @@ module Liquid
   # @liquid_category iteration
   # @liquid_name cycle
   # @liquid_summary
-  #   Loops through a group of strings and outputs them one at a time for each iteration of a [`for` loop](/api/liquid/tags#for).
+  #   Loops through a group of strings and outputs them one at a time for each iteration of a [`for` loop](/api/liquid/tags/for).
   # @liquid_description
   #   The `cycle` tag must be used inside a `for` loop.
   #
diff --git a/lib/liquid/tags/decrement.rb b/lib/liquid/tags/decrement.rb
index f1e67e2..3846e16 100644
--- a/lib/liquid/tags/decrement.rb
+++ b/lib/liquid/tags/decrement.rb
@@ -12,22 +12,25 @@ module Liquid
   #   or [section](/themes/architecture/sections) file that they're created in. However, the variable is shared across
   #   [snippets](/themes/architecture#snippets) included in the file.
   #
-  #   Similarly, variables that are created with `decrement` are independent from those created with [`assign`](/api/liquid/tags#assign)
-  #   and [`capture`](/api/liquid/tags#capture). However, `decrement` and [`increment`](/api/liquid/tags#increment) share
+  #   Similarly, variables that are created with `decrement` are independent from those created with [`assign`](/api/liquid/tags/assign)
+  #   and [`capture`](/api/liquid/tags/capture). However, `decrement` and [`increment`](/api/liquid/tags/increment) share
   #   variables.
   # @liquid_syntax
   #   {% decrement variable_name %}
   # @liquid_syntax_keyword variable_name The name of the variable being decremented.
   class Decrement < Tag
+    attr_reader :variable_name
+
     def initialize(tag_name, markup, options)
       super
-      @variable = markup.strip
+      @variable_name = markup.strip
     end
 
     def render_to_output_buffer(context, output)
-      value = context.environments.first[@variable] ||= 0
+      counter_environment = context.environments.first
+      value = counter_environment[@variable_name] || 0
       value -= 1
-      context.environments.first[@variable] = value
+      counter_environment[@variable_name] = value
       output << value.to_s
       output
     end
diff --git a/lib/liquid/tags/echo.rb b/lib/liquid/tags/echo.rb
index 9d3a60c..607b137 100644
--- a/lib/liquid/tags/echo.rb
+++ b/lib/liquid/tags/echo.rb
@@ -9,7 +9,7 @@ module Liquid
   #   Outputs an expression.
   # @liquid_description
   #   Using the `echo` tag is the same as wrapping an expression in curly brackets (`{{` and `}}`). However, unlike the curly
-  #   bracket method, you can use the `echo` tag inside [`liquid` tags](/api/liquid/tags#liquid).
+  #   bracket method, you can use the `echo` tag inside [`liquid` tags](/api/liquid/tags/liquid).
   #
   #   > Tip:
   #   > You can use [filters](/api/liquid/filters) on expressions inside `echo` tags.
diff --git a/lib/liquid/tags/for.rb b/lib/liquid/tags/for.rb
index ff8c7fc..e02b658 100644
--- a/lib/liquid/tags/for.rb
+++ b/lib/liquid/tags/for.rb
@@ -9,10 +9,10 @@ module Liquid
   #   Renders an expression for every item in an array.
   # @liquid_description
   #   You can do a maximum of 50 iterations with a `for` loop. If you need to iterate over more than 50 items, then use the
-  #   [`paginate` tag](/api/liquid/tags#paginate) to split the items over multiple pages.
+  #   [`paginate` tag](/api/liquid/tags/paginate) to split the items over multiple pages.
   #
   #   > Tip:
-  #   > Every `for` loop has an associated [`forloop` object](/api/liquid/objects#forloop) with information about the loop.
+  #   > Every `for` loop has an associated [`forloop` object](/api/liquid/objects/forloop) with information about the loop.
   # @liquid_syntax
   #   {% for variable in array %}
   #     expression
@@ -98,11 +98,12 @@ module Liquid
       @name     = "#{@variable_name}-#{collection_name}"
       @reversed = p.id?('reversed')
 
-      while p.look(:id) && p.look(:colon, 1)
+      while p.look(:comma) || p.look(:id)
+        p.consume?(:comma)
         unless (attribute = p.id?('limit') || p.id?('offset'))
           raise SyntaxError, options[:locale].t("errors.syntax.for_invalid_attribute")
         end
-        p.consume
+        p.consume(:colon)
         set_attribute(attribute, p.expression)
       end
       p.consume(:end_of_string)
@@ -177,7 +178,6 @@ module Liquid
       case key
       when 'offset'
         @from = if expr == 'continue'
-          Usage.increment('for_offset_continue')
           :continue
         else
           parse_expression(expr)
diff --git a/lib/liquid/tags/include.rb b/lib/liquid/tags/include.rb
index f8fa053..0664f5c 100644
--- a/lib/liquid/tags/include.rb
+++ b/lib/liquid/tags/include.rb
@@ -8,7 +8,7 @@ module Liquid
   # @liquid_summary
   #   Renders a [snippet](/themes/architecture#snippets).
   # @liquid_description
-  #   Inside the snippet, you can access and alter variables that are [created](/api/liquid/tags#variable-tags) outside of the
+  #   Inside the snippet, you can access and alter variables that are [created](/api/liquid/tags/variable-tags) outside of the
   #   snippet.
   # @liquid_syntax
   #   {% include 'filename' %}
@@ -16,7 +16,7 @@ module Liquid
   # @liquid_deprecated
   #   Deprecated because the way that variables are handled reduces performance and makes code harder to both read and maintain.
   #
-  #   The `include` tag has been replaced by [`render`](/api/liquid/tags#render).
+  #   The `include` tag has been replaced by [`render`](/api/liquid/tags/render).
   class Include < Tag
     prepend Tag::Disableable
 
@@ -52,7 +52,7 @@ module Liquid
 
     def render_to_output_buffer(context, output)
       template_name = context.evaluate(@template_name_expr)
-      raise ArgumentError, options[:locale].t("errors.argument.include") unless template_name
+      raise ArgumentError, options[:locale].t("errors.argument.include") unless template_name.is_a?(String)
 
       partial = PartialCache.load(
         template_name,
diff --git a/lib/liquid/tags/increment.rb b/lib/liquid/tags/increment.rb
index f3edd3b..67ca24f 100644
--- a/lib/liquid/tags/increment.rb
+++ b/lib/liquid/tags/increment.rb
@@ -12,21 +12,24 @@ module Liquid
   #   or [section](/themes/architecture/sections) file that they're created in. However, the variable is shared across
   #   [snippets](/themes/architecture#snippets) included in the file.
   #
-  #   Similarly, variables that are created with `increment` are independent from those created with [`assign`](/api/liquid/tags#assign)
-  #   and [`capture`](/api/liquid/tags#capture). However, `increment` and [`decrement`](/api/liquid/tags#decrement) share
+  #   Similarly, variables that are created with `increment` are independent from those created with [`assign`](/api/liquid/tags/assign)
+  #   and [`capture`](/api/liquid/tags/capture). However, `increment` and [`decrement`](/api/liquid/tags/decrement) share
   #   variables.
   # @liquid_syntax
   #   {% increment variable_name %}
   # @liquid_syntax_keyword variable_name The name of the variable being incremented.
   class Increment < Tag
+    attr_reader :variable_name
+
     def initialize(tag_name, markup, options)
       super
-      @variable = markup.strip
+      @variable_name = markup.strip
     end
 
     def render_to_output_buffer(context, output)
-      value = context.environments.first[@variable] ||= 0
-      context.environments.first[@variable] = value + 1
+      counter_environment = context.environments.first
+      value = counter_environment[@variable_name] || 0
+      counter_environment[@variable_name] = value + 1
 
       output << value.to_s
       output
diff --git a/lib/liquid/tags/inline_comment.rb b/lib/liquid/tags/inline_comment.rb
index e2cbc13..493cfdd 100644
--- a/lib/liquid/tags/inline_comment.rb
+++ b/lib/liquid/tags/inline_comment.rb
@@ -1,19 +1,6 @@
 # frozen_string_literal: true
 
 module Liquid
-  # @liquid_public_docs
-  # @liquid_type tag
-  # @liquid_category syntax
-  # @liquid_name inline_comment
-  # @liquid_summary
-  #   Prevents an expression from being rendered or output.
-  # @liquid_description
-  #   Any text inside an `inline_comment` tag won't be rendered or output.
-  #
-  #   You can create multi-line inline comments. However, each line must begin with a `#`.
-  # @liquid_syntax
-  #   {% # content %}
-  # @liquid_syntax_keyword content The content of the comment.
   class InlineComment < Tag
     def initialize(tag_name, markup, options)
       super
diff --git a/lib/liquid/tags/render.rb b/lib/liquid/tags/render.rb
index d9a2103..ed03353 100644
--- a/lib/liquid/tags/render.rb
+++ b/lib/liquid/tags/render.rb
@@ -8,19 +8,19 @@ module Liquid
   # @liquid_summary
   #   Renders a [snippet](/themes/architecture#snippets) or [app block](/themes/architecture/sections/section-schema#render-app-blocks).
   # @liquid_description
-  #   Inside snippets and app blocks, you can't directly access variables that are [created](/api/liquid/tags#variable-tags) outside
-  #   of the snippet or app block. However, you can [specify variables as parameters](/api/liquid/tags#render-passing-variables-to-snippets)
+  #   Inside snippets and app blocks, you can't directly access variables that are [created](/api/liquid/tags/variable-tags) outside
+  #   of the snippet or app block. However, you can [specify variables as parameters](/api/liquid/tags/render#render-passing-variables-to-a-snippet)
   #   to pass outside variables to snippets.
   #
   #   While you can't directly access created variables, you can access global objects, as well as any objects that are
   #   directly accessible outside the snippet or app block. For example, a snippet or app block inside the [product template](/themes/architecture/templates/product)
-  #   can access the [`product` object](/api/liquid/objects#product), and a snippet or app block inside a [section](/themes/architecture/sections)
-  #   can access the [`section` object](/api/liquid/objects#section).
+  #   can access the [`product` object](/api/liquid/objects/product), and a snippet or app block inside a [section](/themes/architecture/sections)
+  #   can access the [`section` object](/api/liquid/objects/section).
   #
   #   Outside a snippet or app block, you can't access variables created inside the snippet or app block.
   #
   #   > Note:
-  #   > When you render a snippet using the `render` tag, you can't use the [`include` tag](/api/liquid/tags#include)
+  #   > When you render a snippet using the `render` tag, you can't use the [`include` tag](/api/liquid/tags/include)
   #   > inside the snippet.
   # @liquid_syntax
   #   {% render 'filename' %}
@@ -31,7 +31,7 @@ module Liquid
 
     disable_tags "include"
 
-    attr_reader :template_name_expr, :variable_name_expr, :attributes
+    attr_reader :template_name_expr, :variable_name_expr, :attributes, :alias_name
 
     def initialize(tag_name, markup, options)
       super
@@ -45,7 +45,7 @@ module Liquid
       @alias_name = Regexp.last_match(6)
       @variable_name_expr = variable_name ? parse_expression(variable_name) : nil
       @template_name_expr = parse_expression(template_name)
-      @for = (with_or_for == FOR)
+      @is_for_loop = (with_or_for == FOR)
 
       @attributes = {}
       markup.scan(TagAttributes) do |key, value|
@@ -53,6 +53,10 @@ module Liquid
       end
     end
 
+    def for_loop?
+      @is_for_loop
+    end
+
     def render_to_output_buffer(context, output)
       render_tag(context, output)
     end
@@ -85,7 +89,7 @@ module Liquid
       }
 
       variable = @variable_name_expr ? context.evaluate(@variable_name_expr) : nil
-      if @for && variable.respond_to?(:each) && variable.respond_to?(:count)
+      if @is_for_loop && variable.respond_to?(:each) && variable.respond_to?(:count)
         forloop = Liquid::ForloopDrop.new(template_name, variable.count, nil)
         variable.each { |var| render_partial_func.call(var, forloop) }
       else
diff --git a/lib/liquid/tags/table_row.rb b/lib/liquid/tags/table_row.rb
index 6ca528c..d52f268 100644
--- a/lib/liquid/tags/table_row.rb
+++ b/lib/liquid/tags/table_row.rb
@@ -11,7 +11,7 @@ module Liquid
   #   The `tablerow` tag must be wrapped in HTML `<table>` and `</table>` tags.
   #
   #   > Tip:
-  #   > Every `tablerow` loop has an associated [`tablerowloop` object](/api/liquid/objects#tablerowloop) with information about the loop.
+  #   > Every `tablerow` loop has an associated [`tablerowloop` object](/api/liquid/objects/tablerowloop) with information about the loop.
   # @liquid_syntax
   #   {% tablerow variable in array %}
   #     expression
@@ -45,13 +45,13 @@ module Liquid
     def render_to_output_buffer(context, output)
       (collection = context.evaluate(@collection_name)) || (return '')
 
-      from = @attributes.key?('offset') ? context.evaluate(@attributes['offset']).to_i : 0
-      to   = @attributes.key?('limit')  ? from + context.evaluate(@attributes['limit']).to_i : nil
+      from = @attributes.key?('offset') ? to_integer(context.evaluate(@attributes['offset'])) : 0
+      to = @attributes.key?('limit') ? from + to_integer(context.evaluate(@attributes['limit'])) : nil
 
       collection = Utils.slice_collection(collection, from, to)
       length     = collection.length
 
-      cols = context.evaluate(@attributes['cols']).to_i
+      cols = @attributes.key?('cols') ? to_integer(context.evaluate(@attributes['cols'])) : length
 
       output << "<tr class=\"row1\">\n"
       context.stack do
@@ -82,6 +82,14 @@ module Liquid
         super + @node.attributes.values + [@node.collection_name]
       end
     end
+
+    private
+
+    def to_integer(value)
+      value.to_i
+    rescue NoMethodError
+      raise Liquid::ArgumentError, "invalid integer"
+    end
   end
 
   Template.register_tag('tablerow', TableRow)
diff --git a/lib/liquid/tags/unless.rb b/lib/liquid/tags/unless.rb
index 30c65b0..56b9228 100644
--- a/lib/liquid/tags/unless.rb
+++ b/lib/liquid/tags/unless.rb
@@ -11,7 +11,7 @@ module Liquid
   #   Renders an expression unless a specific condition is `true`.
   # @liquid_description
   #   > Tip:
-  #   > Similar to the [`if` tag](/api/liquid/tags#if), you can use `elsif` to add more conditions to an `unless` tag.
+  #   > Similar to the [`if` tag](/api/liquid/tags/if), you can use `elsif` to add more conditions to an `unless` tag.
   # @liquid_syntax
   #   {% unless condition %}
   #     expression
diff --git a/lib/liquid/tokenizer.rb b/lib/liquid/tokenizer.rb
index d3bda7b..740f859 100644
--- a/lib/liquid/tokenizer.rb
+++ b/lib/liquid/tokenizer.rb
@@ -8,11 +8,15 @@ module Liquid
       @source         = source.to_s.to_str
       @line_number    = line_number || (line_numbers ? 1 : nil)
       @for_liquid_tag = for_liquid_tag
+      @offset         = 0
       @tokens         = tokenize
     end
 
     def shift
-      (token = @tokens.shift) || return
+      token = @tokens[@offset]
+      return nil unless token
+
+      @offset += 1
 
       if @line_number
         @line_number += @for_liquid_tag ? 1 : token.count("\n")
@@ -31,7 +35,9 @@ module Liquid
       tokens = @source.split(TemplateParser)
 
       # removes the rogue empty element at the beginning of the array
-      tokens.shift if tokens[0]&.empty?
+      if tokens[0]&.empty?
+        @offset += 1
+      end
 
       tokens
     end
diff --git a/lib/liquid/utils.rb b/lib/liquid/utils.rb
index 38a406e..4ec7d81 100644
--- a/lib/liquid/utils.rb
+++ b/lib/liquid/utils.rb
@@ -16,7 +16,11 @@ module Liquid
 
       # Maintains Ruby 1.8.7 String#each behaviour on 1.9
       if collection.is_a?(String)
-        return collection.empty? ? [] : [collection]
+        return [] if collection.empty?
+        if from > 0 || to == 0
+          Usage.increment("string_slice_bug")
+        end
+        return [collection]
       end
       return [] unless collection.respond_to?(:each)
 
diff --git a/test/integration/assign_test.rb b/test/integration/assign_test.rb
index e761a08..ba795fb 100644
--- a/test/integration/assign_test.rb
+++ b/test/integration/assign_test.rb
@@ -6,51 +6,42 @@ class AssignTest < Minitest::Test
   include Liquid
 
   def test_assign_with_hyphen_in_variable_name
-    template_source = <<-END_TEMPLATE
-    {% assign this-thing = 'Print this-thing' %}
-    {{ this-thing }}
+    template_source = <<~END_TEMPLATE
+      {% assign this-thing = 'Print this-thing' -%}
+      {{ this-thing -}}
     END_TEMPLATE
-    template        = Template.parse(template_source)
-    rendered        = template.render!
-    assert_equal("Print this-thing", rendered.strip)
+    assert_template_result("Print this-thing", template_source)
   end
 
   def test_assigned_variable
     assert_template_result('.foo.',
       '{% assign foo = values %}.{{ foo[0] }}.',
-      'values' => %w(foo bar baz))
+      { 'values' => %w(foo bar baz) })
 
     assert_template_result('.bar.',
       '{% assign foo = values %}.{{ foo[1] }}.',
-      'values' => %w(foo bar baz))
+      { 'values' => %w(foo bar baz) })
   end
 
   def test_assign_with_filter
     assert_template_result('.bar.',
       '{% assign foo = values | split: "," %}.{{ foo[1] }}.',
-      'values' => "foo,bar,baz")
+      { 'values' => "foo,bar,baz" })
   end
 
   def test_assign_syntax_error
-    assert_match_syntax_error(/assign/,
-      '{% assign foo not values %}.',
-      'values' => "foo,bar,baz")
+    assert_match_syntax_error(/assign/, '{% assign foo not values %}.')
   end
 
   def test_assign_uses_error_mode
-    with_error_mode(:strict) do
-      assert_raises(SyntaxError) do
-        Template.parse("{% assign foo = ('X' | downcase) %}")
-      end
-    end
-    with_error_mode(:lax) do
-      assert(Template.parse("{% assign foo = ('X' | downcase) %}"))
-    end
+    assert_match_syntax_error("Expected dotdot but found pipe in ",
+      "{% assign foo = ('X' | downcase) %}", error_mode: :strict)
+    assert_template_result("", "{% assign foo = ('X' | downcase) %}", error_mode: :lax)
   end
 
   def test_expression_with_whitespace_in_square_brackets
     source = "{% assign r = a[ 'b' ] %}{{ r }}"
-    assert_template_result('result', source, 'a' => { 'b' => 'result' })
+    assert_template_result('result', source, { 'a' => { 'b' => 'result' } })
   end
 
   def test_assign_score_exceeding_resource_limit
diff --git a/test/integration/blank_test.rb b/test/integration/blank_test.rb
index e3a82c2..be64545 100644
--- a/test/integration/blank_test.rb
+++ b/test/integration/blank_test.rb
@@ -9,12 +9,6 @@ class FoobarTag < Liquid::Tag
   end
 end
 
-class BlankTestFileSystem
-  def read_template_file(template_path)
-    template_path
-  end
-end
-
 class BlankTest < Minitest::Test
   include Liquid
   N = 10
@@ -33,7 +27,7 @@ class BlankTest < Minitest::Test
 
   def test_new_tags_are_not_blank_by_default
     with_custom_tag('foobar', FoobarTag) do
-      assert_template_result(" " * N, wrap_in_for("{% foobar %}"))
+      assert_equal(" " * N, Liquid::Template.parse(wrap_in_for("{% foobar %}")).render!)
     end
   end
 
@@ -95,10 +89,12 @@ class BlankTest < Minitest::Test
   end
 
   def test_include_is_blank
-    Liquid::Template.file_system = BlankTestFileSystem.new
-    assert_template_result("foobar" * (N + 1), wrap("{% include 'foobar' %}"))
-    assert_template_result(" foobar " * (N + 1), wrap("{% include ' foobar ' %}"))
-    assert_template_result("   " * (N + 1), wrap(" {% include ' ' %} "))
+    assert_template_result("foobar" * (N + 1), wrap("{% include 'foobar' %}"),
+      partials: { 'foobar' => 'foobar' })
+    assert_template_result(" foobar " * (N + 1), wrap("{% include ' foobar ' %}"),
+      partials: { ' foobar ' => ' foobar ' })
+    assert_template_result("   " * (N + 1), wrap(" {% include ' ' %} "),
+      partials: { ' ' => ' ' })
   end
 
   def test_case_is_blank
diff --git a/test/integration/block_test.rb b/test/integration/block_test.rb
index eeca8fa..1d3c78c 100644
--- a/test/integration/block_test.rb
+++ b/test/integration/block_test.rb
@@ -6,10 +6,8 @@ class BlockTest < Minitest::Test
   include Liquid
 
   def test_unexpected_end_tag
-    exc = assert_raises(SyntaxError) do
-      Template.parse("{% if true %}{% endunless %}")
-    end
-    assert_equal(exc.message, "Liquid syntax error: 'endunless' is not a valid delimiter for if tags. use endif")
+    source = '{% if true %}{% endunless %}'
+    assert_match_syntax_error("Liquid syntax error (line 1): 'endunless' is not a valid delimiter for if tags. use endif", source)
   end
 
   def test_with_custom_tag
diff --git a/test/integration/capture_test.rb b/test/integration/capture_test.rb
index c2dc526..7399393 100644
--- a/test/integration/capture_test.rb
+++ b/test/integration/capture_test.rb
@@ -10,44 +10,38 @@ class CaptureTest < Minitest::Test
   end
 
   def test_capture_with_hyphen_in_variable_name
-    template_source = <<-END_TEMPLATE
-    {% capture this-thing %}Print this-thing{% endcapture %}
-    {{ this-thing }}
+    template_source = <<~END_TEMPLATE
+      {% capture this-thing %}Print this-thing{% endcapture -%}
+      {{ this-thing -}}
     END_TEMPLATE
-    template        = Template.parse(template_source)
-    rendered        = template.render!
-    assert_equal("Print this-thing", rendered.strip)
+    assert_template_result("Print this-thing", template_source)
   end
 
   def test_capture_to_variable_from_outer_scope_if_existing
-    template_source = <<-END_TEMPLATE
-    {% assign var = '' %}
-    {% if true %}
-    {% capture var %}first-block-string{% endcapture %}
-    {% endif %}
-    {% if true %}
-    {% capture var %}test-string{% endcapture %}
-    {% endif %}
-    {{var}}
+    template_source = <<~END_TEMPLATE
+      {% assign var = '' -%}
+      {% if true -%}
+        {% capture var %}first-block-string{% endcapture -%}
+      {% endif -%}
+      {% if true -%}
+        {% capture var %}test-string{% endcapture -%}
+      {% endif -%}
+      {{var-}}
     END_TEMPLATE
-    template        = Template.parse(template_source)
-    rendered        = template.render!
-    assert_equal("test-string", rendered.gsub(/\s/, ''))
+    assert_template_result("test-string", template_source)
   end
 
   def test_assigning_from_capture
-    template_source = <<-END_TEMPLATE
-    {% assign first = '' %}
-    {% assign second = '' %}
-    {% for number in (1..3) %}
-    {% capture first %}{{number}}{% endcapture %}
-    {% assign second = first %}
-    {% endfor %}
-    {{ first }}-{{ second }}
+    template_source = <<~END_TEMPLATE
+      {% assign first = '' -%}
+      {% assign second = '' -%}
+      {% for number in (1..3) -%}
+        {% capture first %}{{number}}{% endcapture -%}
+        {% assign second = first -%}
+      {% endfor -%}
+      {{ first }}-{{ second -}}
     END_TEMPLATE
-    template        = Template.parse(template_source)
-    rendered        = template.render!
-    assert_equal("3-3", rendered.gsub(/\s/, ''))
+    assert_template_result("3-3", template_source)
   end
 
   def test_increment_assign_score_by_bytes_not_characters
diff --git a/test/integration/context_test.rb b/test/integration/context_test.rb
index 832e3e3..698aaa4 100644
--- a/test/integration/context_test.rb
+++ b/test/integration/context_test.rb
@@ -102,7 +102,7 @@ class ContextTest < Minitest::Test
   end
 
   def test_variables_not_existing
-    assert_nil(@context['does_not_exist'])
+    assert_template_result("true", "{% if does_not_exist == nil %}true{% endif %}")
   end
 
   def test_scoping
@@ -121,22 +121,18 @@ class ContextTest < Minitest::Test
   end
 
   def test_length_query
-    @context['numbers'] = [1, 2, 3, 4]
+    assert_template_result("true", "{% if numbers.size == 4 %}true{% endif %}",
+      { "numbers" => [1, 2, 3, 4] })
 
-    assert_equal(4, @context['numbers.size'])
+    assert_template_result("true", "{% if numbers.size == 4 %}true{% endif %}",
+      { "numbers" => { 1 => 1, 2 => 2, 3 => 3, 4 => 4 } })
 
-    @context['numbers'] = { 1 => 1, 2 => 2, 3 => 3, 4 => 4 }
-
-    assert_equal(4, @context['numbers.size'])
-
-    @context['numbers'] = { 1 => 1, 2 => 2, 3 => 3, 4 => 4, 'size' => 1000 }
-
-    assert_equal(1000, @context['numbers.size'])
+    assert_template_result("true", "{% if numbers.size == 1000 %}true{% endif %}",
+      { "numbers" => { 1 => 1, 2 => 2, 3 => 3, 4 => 4, 'size' => 1000 } })
   end
 
   def test_hyphenated_variable
-    @context['oh-my'] = 'godz'
-    assert_equal('godz', @context['oh-my'])
+    assert_template_result("godz", "{{ oh-my }}", { "oh-my" => 'godz' })
   end
 
   def test_add_filter
@@ -188,24 +184,24 @@ class ContextTest < Minitest::Test
   end
 
   def test_hierachical_data
-    @context['hash'] = { "name" => 'tobi' }
-    assert_equal('tobi', @context['hash.name'])
-    assert_equal('tobi', @context['hash["name"]'])
+    assigns = { 'hash' => { "name" => 'tobi' } }
+    assert_template_result("tobi", "{{ hash.name }}", assigns)
+    assert_template_result("tobi", '{{ hash["name"] }}', assigns)
   end
 
   def test_keywords
-    assert_equal(true, @context['true'])
-    assert_equal(false, @context['false'])
+    assert_template_result("pass", "{% if true == expect %}pass{% endif %}", { "expect" => true })
+    assert_template_result("pass", "{% if false == expect %}pass{% endif %}", { "expect" => false })
   end
 
   def test_digits
-    assert_equal(100, @context['100'])
-    assert_equal(100.00, @context['100.00'])
+    assert_template_result("pass", "{% if 100 == expect %}pass{% endif %}", { "expect" => 100 })
+    assert_template_result("pass", "{% if 100.00 == expect %}pass{% endif %}", { "expect" => 100.00 })
   end
 
   def test_strings
-    assert_equal("hello!", @context['"hello!"'])
-    assert_equal("hello!", @context["'hello!'"])
+    assert_template_result("hello!", '{{ "hello!" }}')
+    assert_template_result("hello!", "{{ 'hello!' }}")
   end
 
   def test_merge
@@ -217,99 +213,90 @@ class ContextTest < Minitest::Test
   end
 
   def test_array_notation
-    @context['test'] = [1, 2, 3, 4, 5]
-
-    assert_equal(1, @context['test[0]'])
-    assert_equal(2, @context['test[1]'])
-    assert_equal(3, @context['test[2]'])
-    assert_equal(4, @context['test[3]'])
-    assert_equal(5, @context['test[4]'])
+    assigns = { "test" => ["a", "b"] }
+    assert_template_result("a", "{{ test[0] }}", assigns)
+    assert_template_result("b", "{{ test[1] }}", assigns)
+    assert_template_result("pass", "{% if test[2] == nil %}pass{% endif %}", assigns)
   end
 
   def test_recoursive_array_notation
-    @context['test'] = { 'test' => [1, 2, 3, 4, 5] }
-
-    assert_equal(1, @context['test.test[0]'])
-
-    @context['test'] = [{ 'test' => 'worked' }]
+    assigns = { "test" => { 'test' => [1, 2, 3, 4, 5] } }
+    assert_template_result("1", "{{ test.test[0] }}", assigns)
 
-    assert_equal('worked', @context['test[0].test'])
+    assigns = { "test" => [{ 'test' => 'worked' }] }
+    assert_template_result("worked", "{{ test[0].test }}", assigns)
   end
 
   def test_hash_to_array_transition
-    @context['colors'] = {
+    assigns = { 'colors' => {
       'Blue' => ['003366', '336699', '6699CC', '99CCFF'],
       'Green' => ['003300', '336633', '669966', '99CC99'],
       'Yellow' => ['CC9900', 'FFCC00', 'FFFF99', 'FFFFCC'],
       'Red' => ['660000', '993333', 'CC6666', 'FF9999'],
-    }
+    } }
 
-    assert_equal('003366', @context['colors.Blue[0]'])
-    assert_equal('FF9999', @context['colors.Red[3]'])
+    assert_template_result("003366", "{{ colors.Blue[0] }}", assigns)
+    assert_template_result("FF9999", "{{ colors.Red[3] }}", assigns)
   end
 
   def test_try_first
-    @context['test'] = [1, 2, 3, 4, 5]
-
-    assert_equal(1, @context['test.first'])
-    assert_equal(5, @context['test.last'])
-
-    @context['test'] = { 'test' => [1, 2, 3, 4, 5] }
+    assigns = { 'test' => [1, 2, 3, 4, 5] }
+    assert_template_result("1", "{{ test.first }}", assigns)
+    assert_template_result("pass", "{% if test.last == 5 %}pass{% endif %}", assigns)
 
-    assert_equal(1, @context['test.test.first'])
-    assert_equal(5, @context['test.test.last'])
+    assigns = { "test" => { "test" => [1, 2, 3, 4, 5] } }
+    assert_template_result("1", "{{ test.test.first }}", assigns)
+    assert_template_result("5", "{{ test.test.last }}", assigns)
 
-    @context['test'] = [1]
-    assert_equal(1, @context['test.first'])
-    assert_equal(1, @context['test.last'])
+    assigns = { "test" => [1] }
+    assert_template_result("1", "{{ test.first }}", assigns)
+    assert_template_result("1", "{{ test.last }}", assigns)
   end
 
   def test_access_hashes_with_hash_notation
-    @context['products'] = { 'count' => 5, 'tags' => ['deepsnow', 'freestyle'] }
-    @context['product']  = { 'variants' => [{ 'title' => 'draft151cm' }, { 'title' => 'element151cm' }] }
+    assigns = { 'products' => { 'count' => 5, 'tags' => ['deepsnow', 'freestyle'] } }
+    assert_template_result("5", '{{ products["count"] }}', assigns)
+    assert_template_result("deepsnow", '{{ products["tags"][0] }}', assigns)
+    assert_template_result("deepsnow", '{{ products["tags"].first }}', assigns)
 
-    assert_equal(5, @context['products["count"]'])
-    assert_equal('deepsnow', @context['products["tags"][0]'])
-    assert_equal('deepsnow', @context['products["tags"].first'])
-    assert_equal('draft151cm', @context['product["variants"][0]["title"]'])
-    assert_equal('element151cm', @context['product["variants"][1]["title"]'])
-    assert_equal('draft151cm', @context['product["variants"][0]["title"]'])
-    assert_equal('element151cm', @context['product["variants"].last["title"]'])
+    assigns = { 'product' => { 'variants' => [{ 'title' => 'draft151cm' }, { 'title' => 'element151cm' }] } }
+    assert_template_result("draft151cm", '{{ product["variants"][0]["title"] }}', assigns)
+    assert_template_result("element151cm", '{{ product["variants"][1]["title"] }}', assigns)
+    assert_template_result("draft151cm", '{{ product["variants"][0]["title"] }}', assigns)
+    assert_template_result("element151cm", '{{ product["variants"].last["title"] }}', assigns)
   end
 
   def test_access_variable_with_hash_notation
-    @context['foo'] = 'baz'
-    @context['bar'] = 'foo'
-
-    assert_equal('baz', @context['["foo"]'])
-    assert_equal('baz', @context['[bar]'])
+    assert_template_result('baz', '{{ ["foo"] }}', { "foo" => "baz" })
+    assert_template_result('baz', '{{ [bar] }}', { 'foo' => 'baz', 'bar' => 'foo' })
   end
 
   def test_access_hashes_with_hash_access_variables
-    @context['var']      = 'tags'
-    @context['nested']   = { 'var' => 'tags' }
-    @context['products'] = { 'count' => 5, 'tags' => ['deepsnow', 'freestyle'] }
+    assigns = {
+      'var' => 'tags',
+      'nested' => { 'var' => 'tags' },
+      'products' => { 'count' => 5, 'tags' => ['deepsnow', 'freestyle'] },
+    }
 
-    assert_equal('deepsnow', @context['products[var].first'])
-    assert_equal('freestyle', @context['products[nested.var].last'])
+    assert_template_result('deepsnow', '{{ products[var].first }}', assigns)
+    assert_template_result('freestyle', '{{ products[nested.var].last }}', assigns)
   end
 
   def test_hash_notation_only_for_hash_access
-    @context['array'] = [1, 2, 3, 4, 5]
-    @context['hash']  = { 'first' => 'Hello' }
+    assigns = { "array" => [1, 2, 3, 4, 5] }
+    assert_template_result("1", "{{ array.first }}", assigns)
+    assert_template_result("pass", '{% if array["first"] == nil %}pass{% endif %}', assigns)
 
-    assert_equal(1, @context['array.first'])
-    assert_nil(@context['array["first"]'])
-    assert_equal('Hello', @context['hash["first"]'])
+    assert_template_result("Hello", '{{ hash["first"] }}', { "hash" => { "first" => "Hello" } })
   end
 
   def test_first_can_appear_in_middle_of_callchain
-    @context['product'] = { 'variants' => [{ 'title' => 'draft151cm' }, { 'title' => 'element151cm' }] }
+    assigns = { "product" => { 'variants' => [{ 'title' => 'draft151cm' }, { 'title' => 'element151cm' }] } }
 
-    assert_equal('draft151cm', @context['product.variants[0].title'])
-    assert_equal('element151cm', @context['product.variants[1].title'])
-    assert_equal('draft151cm', @context['product.variants.first.title'])
-    assert_equal('element151cm', @context['product.variants.last.title'])
+    assert_template_result('draft151cm', '{{ product.variants[0].title }}', assigns)
+    assert_template_result('element151cm', '{{ product.variants[1].title }}', assigns)
+    assert_template_result('draft151cm', '{{ product.variants.first.title }}', assigns)
+    assert_template_result('element151cm', '{{ product.variants.last.title }}', assigns)
   end
 
   def test_cents
@@ -351,10 +338,12 @@ class ContextTest < Minitest::Test
   end
 
   def test_ranges
-    @context.merge("test" => '5')
-    assert_equal((1..5), @context['(1..5)'])
-    assert_equal((1..5), @context['(1..test)'])
-    assert_equal((5..5), @context['(test..test)'])
+    assert_template_result("1..5", '{{ (1..5) }}')
+    assert_template_result("pass", '{% if (1..5) == expect %}pass{% endif %}', { "expect" => (1..5) })
+
+    assigns = { "test" => '5' }
+    assert_template_result("1..5", "{{ (1..test) }}", assigns)
+    assert_template_result("5..5", "{{ (test..test) }}", assigns)
   end
 
   def test_cents_through_drop_nestedly
diff --git a/test/integration/document_test.rb b/test/integration/document_test.rb
index 4198c01..c013253 100644
--- a/test/integration/document_test.rb
+++ b/test/integration/document_test.rb
@@ -6,16 +6,12 @@ class DocumentTest < Minitest::Test
   include Liquid
 
   def test_unexpected_outer_tag
-    exc = assert_raises(SyntaxError) do
-      Template.parse("{% else %}")
-    end
-    assert_equal(exc.message, "Liquid syntax error: Unexpected outer 'else' tag")
+    source = "{% else %}"
+    assert_match_syntax_error("Liquid syntax error (line 1): Unexpected outer 'else' tag", source)
   end
 
   def test_unknown_tag
-    exc = assert_raises(SyntaxError) do
-      Template.parse("{% foo %}")
-    end
-    assert_equal(exc.message, "Liquid syntax error: Unknown tag 'foo'")
+    source = "{% foo %}"
+    assert_match_syntax_error("Liquid syntax error (line 1): Unknown tag 'foo'", source)
   end
 end
diff --git a/test/integration/error_handling_test.rb b/test/integration/error_handling_test.rb
index 8eeb6ce..b7ee624 100644
--- a/test/integration/error_handling_test.rb
+++ b/test/integration/error_handling_test.rb
@@ -63,9 +63,7 @@ class ErrorHandlingTest < Minitest::Test
   end
 
   def test_missing_endtag_parse_time_error
-    assert_raises(Liquid::SyntaxError) do
-      Liquid::Template.parse(' {% for a in b %} ... ')
-    end
+    assert_match_syntax_error(/: 'for' tag was never closed\z/, ' {% for a in b %} ... ')
   end
 
   def test_unrecognized_operator
@@ -84,33 +82,26 @@ class ErrorHandlingTest < Minitest::Test
   end
 
   def test_with_line_numbers_adds_numbers_to_parser_errors
-    err = assert_raises(SyntaxError) do
-      Liquid::Template.parse('
-          foobar
-
-          {% "cat" | foobar %}
+    source = <<~LIQUID
+      foobar
 
-          bla
-        ',
-        line_numbers: true)
-    end
+      {% "cat" | foobar %}
 
-    assert_match(/Liquid syntax error \(line 4\)/, err.message)
+      bla
+    LIQUID
+    assert_match_syntax_error(/Liquid syntax error \(line 3\)/, source)
   end
 
   def test_with_line_numbers_adds_numbers_to_parser_errors_with_whitespace_trim
-    err = assert_raises(SyntaxError) do
-      Liquid::Template.parse('
-          foobar
+    source = <<~LIQUID
+      foobar
 
-          {%- "cat" | foobar -%}
+      {%- "cat" | foobar -%}
 
-          bla
-        ',
-        line_numbers: true)
-    end
+      bla
+    LIQUID
 
-    assert_match(/Liquid syntax error \(line 4\)/, err.message)
+    assert_match_syntax_error(/Liquid syntax error \(line 3\)/, source)
   end
 
   def test_parsing_warn_with_line_numbers_adds_numbers_to_lexer_errors
@@ -145,20 +136,17 @@ class ErrorHandlingTest < Minitest::Test
   end
 
   def test_syntax_errors_in_nested_blocks_have_correct_line_number
-    err = assert_raises(SyntaxError) do
-      Liquid::Template.parse('
-          foobar
+    source = <<~LIQUID
+      foobar
 
-          {% if 1 != 2 %}
-            {% foo %}
-          {% endif %}
+      {% if 1 != 2 %}
+        {% foo %}
+      {% endif %}
 
-          bla
-                ',
-        line_numbers: true)
-    end
+      bla
+    LIQUID
 
-    assert_equal("Liquid syntax error (line 5): Unknown tag 'foo'", err.message)
+    assert_match_syntax_error("Liquid syntax error (line 4): Unknown tag 'foo'", source)
   end
 
   def test_strict_error_messages
diff --git a/test/integration/expression_test.rb b/test/integration/expression_test.rb
index 91a0494..4773448 100644
--- a/test/integration/expression_test.rb
+++ b/test/integration/expression_test.rb
@@ -4,43 +4,46 @@ require 'test_helper'
 
 class ExpressionTest < Minitest::Test
   def test_keyword_literals
-    assert_equal(true, parse_and_eval("true"))
-    assert_equal(true, parse_and_eval(" true "))
+    assert_template_result("true", "{{ true }}")
+    assert_expression_result(true, "true")
   end
 
   def test_string
-    assert_equal("single quoted", parse_and_eval("'single quoted'"))
-    assert_equal("double quoted", parse_and_eval('"double quoted"'))
-    assert_equal("spaced", parse_and_eval(" 'spaced' "))
-    assert_equal("spaced2", parse_and_eval(' "spaced2" '))
+    assert_template_result("single quoted", "{{'single quoted'}}")
+    assert_template_result("double quoted", '{{"double quoted"}}')
+    assert_template_result("spaced", "{{ 'spaced' }}")
+    assert_template_result("spaced2", "{{ 'spaced2' }}")
   end
 
   def test_int
-    assert_equal(123, parse_and_eval("123"))
-    assert_equal(456, parse_and_eval(" 456 "))
-    assert_equal(12, parse_and_eval("012"))
+    assert_template_result("456", "{{ 456 }}")
+    assert_expression_result(123, "123")
+    assert_expression_result(12, "012")
   end
 
   def test_float
-    assert_equal(1.5, parse_and_eval("1.5"))
-    assert_equal(2.5, parse_and_eval(" 2.5 "))
+    assert_template_result("2.5", "{{ 2.5 }}")
+    assert_expression_result(1.5, "1.5")
   end
 
   def test_range
-    assert_equal(1..2, parse_and_eval("(1..2)"))
-    assert_equal(3..4, parse_and_eval(" ( 3 .. 4 ) "))
+    assert_template_result("3..4", "{{ ( 3 .. 4 ) }}")
+    assert_expression_result(1..2, "(1..2)")
+
+    assert_match_syntax_error(
+      "Liquid syntax error (line 1): Invalid expression type 'false' in range expression",
+      "{{ (false..true) }}"
+    )
+    assert_match_syntax_error(
+      "Liquid syntax error (line 1): Invalid expression type '(1..2)' in range expression",
+      "{{ ((1..2)..3) }}"
+    )
   end
 
   private
 
-  def parse_and_eval(markup, **assigns)
-    if Liquid::Template.error_mode == :strict
-      p = Liquid::Parser.new(markup)
-      markup = p.expression
-      p.consume(:end_of_string)
-    end
-    expression = Liquid::Expression.parse(markup)
-    context = Liquid::Context.new(assigns)
-    context.evaluate(expression)
+  def assert_expression_result(expect, markup, **assigns)
+    liquid = "{% if expect == #{markup} %}pass{% else %}got {{ #{markup} }}{% endif %}"
+    assert_template_result("pass", liquid, { "expect" => expect, **assigns })
   end
 end
diff --git a/test/integration/filter_test.rb b/test/integration/filter_test.rb
index 6413d2d..80d652d 100644
--- a/test/integration/filter_test.rb
+++ b/test/integration/filter_test.rb
@@ -59,84 +59,66 @@ class FiltersTest < Minitest::Test
   end
 
   def test_size
-    @context['var'] = 'abcd'
-    @context.add_filters(MoneyFilter)
-
-    assert_equal('4', Template.parse("{{var | size}}").render(@context))
+    assert_template_result("4", "{{var | size}}", { "var" => 'abcd' })
   end
 
   def test_join
-    @context['var'] = [1, 2, 3, 4]
-
-    assert_equal("1 2 3 4", Template.parse("{{var | join}}").render(@context))
+    assert_template_result("1 2 3 4", "{{var | join}}", { "var" => [1, 2, 3, 4] })
   end
 
   def test_sort
-    @context['value']   = 3
-    @context['numbers'] = [2, 1, 4, 3]
-    @context['words']   = ['expected', 'as', 'alphabetic']
-    @context['arrays']  = ['flower', 'are']
-    @context['case_sensitive'] = ['sensitive', 'Expected', 'case']
-
-    assert_equal('1 2 3 4', Template.parse("{{numbers | sort | join}}").render(@context))
-    assert_equal('alphabetic as expected', Template.parse("{{words | sort | join}}").render(@context))
-    assert_equal('3', Template.parse("{{value | sort}}").render(@context))
-    assert_equal('are flower', Template.parse("{{arrays | sort | join}}").render(@context))
-    assert_equal('Expected case sensitive', Template.parse("{{case_sensitive | sort | join}}").render(@context))
+    assert_template_result("1 2 3 4", "{{numbers | sort | join}}", { "numbers" => [2, 1, 4, 3] })
+    assert_template_result("alphabetic as expected", "{{words | sort | join}}",
+      { "words" => ['expected', 'as', 'alphabetic'] })
+    assert_template_result("3", "{{value | sort}}", { "value" => 3 })
+    assert_template_result('are flower', "{{arrays | sort | join}}", { 'arrays' => ['flower', 'are'] })
+    assert_template_result("Expected case sensitive", "{{case_sensitive | sort | join}}",
+      { "case_sensitive" => ["sensitive", "Expected", "case"] })
   end
 
   def test_sort_natural
-    @context['words']   = ['case', 'Assert', 'Insensitive']
-    @context['hashes']  = [{ 'a' => 'A' }, { 'a' => 'b' }, { 'a' => 'C' }]
-    @context['objects'] = [TestObject.new('A'), TestObject.new('b'), TestObject.new('C')]
-
     # Test strings
-    assert_equal('Assert case Insensitive', Template.parse("{{words | sort_natural | join}}").render(@context))
+    assert_template_result("Assert case Insensitive", "{{words | sort_natural | join}}",
+      { "words" => ["case", "Assert", "Insensitive"] })
 
     # Test hashes
-    assert_equal('A b C', Template.parse("{{hashes | sort_natural: 'a' | map: 'a' | join}}").render(@context))
+    assert_template_result("A b C", "{{hashes | sort_natural: 'a' | map: 'a' | join}}",
+      { "hashes" => [{ "a" => "A" }, { "a" => "b" }, { "a" => "C" }] })
 
     # Test objects
+    @context['objects'] = [TestObject.new('A'), TestObject.new('b'), TestObject.new('C')]
     assert_equal('A b C', Template.parse("{{objects | sort_natural: 'a' | map: 'a' | join}}").render(@context))
   end
 
   def test_compact
-    @context['words']   = ['a', nil, 'b', nil, 'c']
-    @context['hashes']  = [{ 'a' => 'A' }, { 'a' => nil }, { 'a' => 'C' }]
-    @context['objects'] = [TestObject.new('A'), TestObject.new(nil), TestObject.new('C')]
-
     # Test strings
-    assert_equal('a b c', Template.parse("{{words | compact | join}}").render(@context))
+    assert_template_result("a b c", "{{words | compact | join}}",
+      { "words" => ['a', nil, 'b', nil, 'c'] })
 
     # Test hashes
-    assert_equal('A C', Template.parse("{{hashes | compact: 'a' | map: 'a' | join}}").render(@context))
+    assert_template_result("A C", "{{hashes | compact: 'a' | map: 'a' | join}}",
+      { "hashes" => [{ "a" => "A" }, { "a" => nil }, { "a" => "C" }] })
 
     # Test objects
+    @context['objects'] = [TestObject.new('A'), TestObject.new(nil), TestObject.new('C')]
     assert_equal('A C', Template.parse("{{objects | compact: 'a' | map: 'a' | join}}").render(@context))
   end
 
   def test_strip_html
-    @context['var'] = "<b>bla blub</a>"
-
-    assert_equal("bla blub", Template.parse("{{ var | strip_html }}").render(@context))
+    assert_template_result("bla blub", "{{ var | strip_html }}", { "var" => "<b>bla blub</a>" })
   end
 
   def test_strip_html_ignore_comments_with_html
-    @context['var'] = "<!-- split and some <ul> tag --><b>bla blub</a>"
-
-    assert_equal("bla blub", Template.parse("{{ var | strip_html }}").render(@context))
+    assert_template_result("bla blub", "{{ var | strip_html }}",
+      { "var" => "<!-- split and some <ul> tag --><b>bla blub</a>" })
   end
 
   def test_capitalize
-    @context['var'] = "blub"
-
-    assert_equal("Blub", Template.parse("{{ var | capitalize }}").render(@context))
+    assert_template_result("Blub", "{{ var | capitalize }}", { "var" => "blub" })
   end
 
   def test_nonexistent_filter_is_ignored
-    @context['var'] = 1000
-
-    assert_equal('1000', Template.parse("{{ var | xyzzy }}").render(@context))
+    assert_template_result("1000", "{{ var | xyzzy }}", { "var" => 1000 })
   end
 
   def test_filter_with_keyword_arguments
diff --git a/test/integration/output_test.rb b/test/integration/output_test.rb
index 3e1cf28..c6f18a9 100644
--- a/test/integration/output_test.rb
+++ b/test/integration/output_test.rb
@@ -33,31 +33,25 @@ class OutputTest < Minitest::Test
 
   def setup
     @assigns = {
-      'best_cars' => 'bmw',
       'car' => { 'bmw' => 'good', 'gm' => 'bad' },
     }
   end
 
   def test_variable
-    text = %( {{best_cars}} )
-
-    expected = %( bmw )
-    assert_equal(expected, Template.parse(text).render!(@assigns))
+    assert_template_result(" bmw ", " {{best_cars}} ", { "best_cars" => "bmw" })
   end
 
   def test_variable_traversing_with_two_brackets
-    text = %({{ site.data.menu[include.menu][include.locale] }})
-    assert_equal("it works!", Template.parse(text).render!(
+    source = "{{ site.data.menu[include.menu][include.locale] }}"
+    assert_template_result("it works!", source, {
       "site" => { "data" => { "menu" => { "foo" => { "bar" => "it works!" } } } },
-      "include" => { "menu" => "foo", "locale" => "bar" }
-    ))
+      "include" => { "menu" => "foo", "locale" => "bar" },
+    })
   end
 
   def test_variable_traversing
-    text = %( {{car.bmw}} {{car.gm}} {{car.bmw}} )
-
-    expected = %( good bad good )
-    assert_equal(expected, Template.parse(text).render!(@assigns))
+    source = " {{car.bmw}} {{car.gm}} {{car.bmw}} "
+    assert_template_result(" good bad good ", source, @assigns)
   end
 
   def test_variable_piping
@@ -110,10 +104,11 @@ class OutputTest < Minitest::Test
   end
 
   def test_multiple_pipings
+    assigns = { 'best_cars' => 'bmw' }
     text     = %( {{ best_cars | cite_funny | paragraph }} )
     expected = %( <p>LOL: bmw</p> )
 
-    assert_equal(expected, Template.parse(text).render!(@assigns, filters: [FunnyFilter]))
+    assert_equal(expected, Template.parse(text).render!(assigns, filters: [FunnyFilter]))
   end
 
   def test_link_to
diff --git a/test/integration/parsing_quirks_test.rb b/test/integration/parsing_quirks_test.rb
index e3f2431..744936c 100644
--- a/test/integration/parsing_quirks_test.rb
+++ b/test/integration/parsing_quirks_test.rb
@@ -129,6 +129,6 @@ class ParsingQuirksTest < Minitest::Test
   end
 
   def test_contains_in_id
-    assert_template_result(' YES ', '{% if containsallshipments == true %} YES {% endif %}', 'containsallshipments' => true)
+    assert_template_result(' YES ', '{% if containsallshipments == true %} YES {% endif %}', { 'containsallshipments' => true })
   end
 end # ParsingQuirksTest
diff --git a/test/integration/standard_filter_test.rb b/test/integration/standard_filter_test.rb
index f413e6f..b633024 100644
--- a/test/integration/standard_filter_test.rb
+++ b/test/integration/standard_filter_test.rb
@@ -109,6 +109,10 @@ class StandardFiltersTest < Minitest::Test
     assert_raises(Liquid::ArgumentError) do
       @filters.slice('foobar', 0, "")
     end
+    assert_equal("", @filters.slice("foobar", 0, -(1 << 64)))
+    assert_equal("foobar", @filters.slice("foobar", 0, 1 << 63))
+    assert_equal("", @filters.slice("foobar", 1 << 63, 6))
+    assert_equal("", @filters.slice("foobar", -(1 << 63), 6))
   end
 
   def test_slice_on_arrays
@@ -123,6 +127,10 @@ class StandardFiltersTest < Minitest::Test
     assert_equal(%w(r), @filters.slice(input, -1))
     assert_equal(%w(), @filters.slice(input, 100, 10))
     assert_equal(%w(), @filters.slice(input, -100, 10))
+    assert_equal([], @filters.slice(input, 0, -(1 << 64)))
+    assert_equal(input, @filters.slice(input, 0, 1 << 63))
+    assert_equal([], @filters.slice(input, 1 << 63, 6))
+    assert_equal([], @filters.slice(input, -(1 << 63), 6))
   end
 
   def test_truncate
@@ -132,6 +140,8 @@ class StandardFiltersTest < Minitest::Test
     assert_equal('1234567890', @filters.truncate('1234567890'))
     assert_equal("测试...", @filters.truncate("测试测试测试测试", 5))
     assert_equal('12341', @filters.truncate("1234567890", 5, 1))
+    assert_equal("foobar", @filters.truncate("foobar", 1 << 63))
+    assert_equal("...", @filters.truncate("foobar", -(1 << 63)))
   end
 
   def test_split
@@ -227,10 +237,8 @@ class StandardFiltersTest < Minitest::Test
     assert_equal('one two three...', @filters.truncatewords("one  two\tthree\nfour", 3))
     assert_equal('one two...', @filters.truncatewords("one two three four", 2))
     assert_equal('one...', @filters.truncatewords("one two three four", 0))
-    exception = assert_raises(Liquid::ArgumentError) do
-      @filters.truncatewords("one two three four", 1 << 31)
-    end
-    assert_equal("Liquid error: integer #{1 << 31} too big for truncatewords", exception.message)
+    assert_equal('one two three four', @filters.truncatewords("one two three four", 1 << 31))
+    assert_equal('one...', @filters.truncatewords("one two three four", -(1 << 32)))
   end
 
   def test_strip_html
@@ -426,7 +434,7 @@ class StandardFiltersTest < Minitest::Test
   def test_map
     assert_equal([1, 2, 3, 4], @filters.map([{ "a" => 1 }, { "a" => 2 }, { "a" => 3 }, { "a" => 4 }], 'a'))
     assert_template_result('abc', "{{ ary | map:'foo' | map:'bar' }}",
-      'ary' => [{ 'foo' => { 'bar' => 'a' } }, { 'foo' => { 'bar' => 'b' } }, { 'foo' => { 'bar' => 'c' } }])
+      { 'ary' => [{ 'foo' => { 'bar' => 'a' } }, { 'foo' => { 'bar' => 'b' } }, { 'foo' => { 'bar' => 'c' } }] })
   end
 
   def test_map_doesnt_call_arbitrary_stuff
@@ -436,7 +444,7 @@ class StandardFiltersTest < Minitest::Test
 
   def test_map_calls_to_liquid
     t = TestThing.new
-    assert_template_result("woot: 1", '{{ foo | map: "whatever" }}', "foo" => [t])
+    assert_template_result("woot: 1", '{{ foo | map: "whatever" }}', { "foo" => [t] })
   end
 
   def test_map_calls_context=
@@ -451,13 +459,13 @@ class StandardFiltersTest < Minitest::Test
 
   def test_map_on_hashes
     assert_template_result("4217", '{{ thing | map: "foo" | map: "bar" }}',
-      "thing" => { "foo" => [{ "bar" => 42 }, { "bar" => 17 }] })
+      { "thing" => { "foo" => [{ "bar" => 42 }, { "bar" => 17 }] } })
   end
 
   def test_legacy_map_on_hashes_with_dynamic_key
     template = "{% assign key = 'foo' %}{{ thing | map: key | map: 'bar' }}"
     hash     = { "foo" => { "bar" => 42 } }
-    assert_template_result("42", template, "thing" => hash)
+    assert_template_result("42", template, { "thing" => hash })
   end
 
   def test_sort_calls_to_liquid
@@ -469,8 +477,8 @@ class StandardFiltersTest < Minitest::Test
   def test_map_over_proc
     drop  = TestDrop.new(value: "testfoo")
     p     = proc { drop }
-    templ = '{{ procs | map: "value" }}'
-    assert_template_result("testfoo", templ, "procs" => [p])
+    output = Liquid::Template.parse('{{ procs | map: "value" }}').render!({ "procs" => [p] })
+    assert_equal("testfoo", output)
   end
 
   def test_map_over_drops_returning_procs
@@ -482,12 +490,13 @@ class StandardFiltersTest < Minitest::Test
         "proc" => -> { "bar" },
       },
     ]
-    templ = '{{ drops | map: "proc" }}'
-    assert_template_result("foobar", templ, "drops" => drops)
+    output = Liquid::Template.parse('{{ drops | map: "proc" }}').render!({ "drops" => drops })
+    assert_equal("foobar", output)
   end
 
   def test_map_works_on_enumerables
-    assert_template_result("123", '{{ foo | map: "foo" }}', "foo" => TestEnumerable.new)
+    output = Liquid::Template.parse('{{ foo | map: "foo" }}').render!({ "foo" => TestEnumerable.new })
+    assert_equal("123", output)
   end
 
   def test_map_returns_empty_on_2d_input_array
@@ -514,16 +523,16 @@ class StandardFiltersTest < Minitest::Test
   end
 
   def test_sort_works_on_enumerables
-    assert_template_result("213", '{{ foo | sort: "bar" | map: "foo" }}', "foo" => TestEnumerable.new)
+    assert_template_result("213", '{{ foo | sort: "bar" | map: "foo" }}', { "foo" => TestEnumerable.new })
   end
 
   def test_first_and_last_call_to_liquid
-    assert_template_result('foobar', '{{ foo | first }}', 'foo' => [ThingWithToLiquid.new])
-    assert_template_result('foobar', '{{ foo | last }}', 'foo' => [ThingWithToLiquid.new])
+    assert_template_result('foobar', '{{ foo | first }}', { 'foo' => [ThingWithToLiquid.new] })
+    assert_template_result('foobar', '{{ foo | last }}', { 'foo' => [ThingWithToLiquid.new] })
   end
 
   def test_truncate_calls_to_liquid
-    assert_template_result("wo...", '{{ foo | truncate: 5 }}', "foo" => TestThing.new)
+    assert_template_result("wo...", '{{ foo | truncate: 5 }}', { "foo" => TestThing.new })
   end
 
   def test_date
@@ -597,42 +606,42 @@ class StandardFiltersTest < Minitest::Test
   end
 
   def test_strip
-    assert_template_result('ab c', "{{ source | strip }}", 'source' => " ab c  ")
-    assert_template_result('ab c', "{{ source | strip }}", 'source' => " \tab c  \n \t")
+    assert_template_result('ab c', "{{ source | strip }}", { 'source' => " ab c  " })
+    assert_template_result('ab c', "{{ source | strip }}", { 'source' => " \tab c  \n \t" })
   end
 
   def test_lstrip
-    assert_template_result('ab c  ', "{{ source | lstrip }}", 'source' => " ab c  ")
-    assert_template_result("ab c  \n \t", "{{ source | lstrip }}", 'source' => " \tab c  \n \t")
+    assert_template_result('ab c  ', "{{ source | lstrip }}", { 'source' => " ab c  " })
+    assert_template_result("ab c  \n \t", "{{ source | lstrip }}", { 'source' => " \tab c  \n \t" })
   end
 
   def test_rstrip
-    assert_template_result(" ab c", "{{ source | rstrip }}", 'source' => " ab c  ")
-    assert_template_result(" \tab c", "{{ source | rstrip }}", 'source' => " \tab c  \n \t")
+    assert_template_result(" ab c", "{{ source | rstrip }}", { 'source' => " ab c  " })
+    assert_template_result(" \tab c", "{{ source | rstrip }}", { 'source' => " \tab c  \n \t" })
   end
 
   def test_strip_newlines
-    assert_template_result('abc', "{{ source | strip_newlines }}", 'source' => "a\nb\nc")
-    assert_template_result('abc', "{{ source | strip_newlines }}", 'source' => "a\r\nb\nc")
+    assert_template_result('abc', "{{ source | strip_newlines }}", { 'source' => "a\nb\nc" })
+    assert_template_result('abc', "{{ source | strip_newlines }}", { 'source' => "a\r\nb\nc" })
   end
 
   def test_newlines_to_br
-    assert_template_result("a<br />\nb<br />\nc", "{{ source | newline_to_br }}", 'source' => "a\nb\nc")
-    assert_template_result("a<br />\nb<br />\nc", "{{ source | newline_to_br }}", 'source' => "a\r\nb\nc")
+    assert_template_result("a<br />\nb<br />\nc", "{{ source | newline_to_br }}", { 'source' => "a\nb\nc" })
+    assert_template_result("a<br />\nb<br />\nc", "{{ source | newline_to_br }}", { 'source' => "a\r\nb\nc" })
   end
 
   def test_plus
     assert_template_result("2", "{{ 1 | plus:1 }}")
     assert_template_result("2.0", "{{ '1' | plus:'1.0' }}")
 
-    assert_template_result("5", "{{ price | plus:'2' }}", 'price' => NumberLikeThing.new(3))
+    assert_template_result("5", "{{ price | plus:'2' }}", { 'price' => NumberLikeThing.new(3) })
   end
 
   def test_minus
-    assert_template_result("4", "{{ input | minus:operand }}", 'input' => 5, 'operand' => 1)
+    assert_template_result("4", "{{ input | minus:operand }}", { 'input' => 5, 'operand' => 1 })
     assert_template_result("2.3", "{{ '4.3' | minus:'2' }}")
 
-    assert_template_result("5", "{{ price | minus:'2' }}", 'price' => NumberLikeThing.new(7))
+    assert_template_result("5", "{{ price | minus:'2' }}", { 'price' => NumberLikeThing.new(7) })
   end
 
   def test_abs
@@ -655,7 +664,7 @@ class StandardFiltersTest < Minitest::Test
     assert_template_result("7.25", "{{ 0.0725 | times:100 }}")
     assert_template_result("-7.25", '{{ "-0.0725" | times:100 }}')
     assert_template_result("7.25", '{{ "-0.0725" | times: -100 }}')
-    assert_template_result("4", "{{ price | times:2 }}", 'price' => NumberLikeThing.new(2))
+    assert_template_result("4", "{{ price | times:2 }}", { 'price' => NumberLikeThing.new(2) })
   end
 
   def test_divided_by
@@ -670,7 +679,7 @@ class StandardFiltersTest < Minitest::Test
       assert_template_result("4", "{{ 1 | modulo: 0 }}")
     end
 
-    assert_template_result("5", "{{ price | divided_by:2 }}", 'price' => NumberLikeThing.new(10))
+    assert_template_result("5", "{{ price | divided_by:2 }}", { 'price' => NumberLikeThing.new(10) })
   end
 
   def test_modulo
@@ -679,39 +688,39 @@ class StandardFiltersTest < Minitest::Test
       assert_template_result("4", "{{ 1 | modulo: 0 }}")
     end
 
-    assert_template_result("1", "{{ price | modulo:2 }}", 'price' => NumberLikeThing.new(3))
+    assert_template_result("1", "{{ price | modulo:2 }}", { 'price' => NumberLikeThing.new(3) })
   end
 
   def test_round
-    assert_template_result("5", "{{ input | round }}", 'input' => 4.6)
+    assert_template_result("5", "{{ input | round }}", { 'input' => 4.6 })
     assert_template_result("4", "{{ '4.3' | round }}")
-    assert_template_result("4.56", "{{ input | round: 2 }}", 'input' => 4.5612)
+    assert_template_result("4.56", "{{ input | round: 2 }}", { 'input' => 4.5612 })
     assert_raises(Liquid::FloatDomainError) do
       assert_template_result("4", "{{ 1.0 | divided_by: 0.0 | round }}")
     end
 
-    assert_template_result("5", "{{ price | round }}", 'price' => NumberLikeThing.new(4.6))
-    assert_template_result("4", "{{ price | round }}", 'price' => NumberLikeThing.new(4.3))
+    assert_template_result("5", "{{ price | round }}", { 'price' => NumberLikeThing.new(4.6) })
+    assert_template_result("4", "{{ price | round }}", { 'price' => NumberLikeThing.new(4.3) })
   end
 
   def test_ceil
-    assert_template_result("5", "{{ input | ceil }}", 'input' => 4.6)
+    assert_template_result("5", "{{ input | ceil }}", { 'input' => 4.6 })
     assert_template_result("5", "{{ '4.3' | ceil }}")
     assert_raises(Liquid::FloatDomainError) do
       assert_template_result("4", "{{ 1.0 | divided_by: 0.0 | ceil }}")
     end
 
-    assert_template_result("5", "{{ price | ceil }}", 'price' => NumberLikeThing.new(4.6))
+    assert_template_result("5", "{{ price | ceil }}", { 'price' => NumberLikeThing.new(4.6) })
   end
 
   def test_floor
-    assert_template_result("4", "{{ input | floor }}", 'input' => 4.6)
+    assert_template_result("4", "{{ input | floor }}", { 'input' => 4.6 })
     assert_template_result("4", "{{ '4.3' | floor }}")
     assert_raises(Liquid::FloatDomainError) do
       assert_template_result("4", "{{ 1.0 | divided_by: 0.0 | floor }}")
     end
 
-    assert_template_result("5", "{{ price | floor }}", 'price' => NumberLikeThing.new(5.4))
+    assert_template_result("5", "{{ price | floor }}", { 'price' => NumberLikeThing.new(5.4) })
   end
 
   def test_at_most
@@ -720,9 +729,9 @@ class StandardFiltersTest < Minitest::Test
     assert_template_result("5", "{{ 5 | at_most:6 }}")
 
     assert_template_result("4.5", "{{ 4.5 | at_most:5 }}")
-    assert_template_result("5", "{{ width | at_most:5 }}", 'width' => NumberLikeThing.new(6))
-    assert_template_result("4", "{{ width | at_most:5 }}", 'width' => NumberLikeThing.new(4))
-    assert_template_result("4", "{{ 5 | at_most: width }}", 'width' => NumberLikeThing.new(4))
+    assert_template_result("5", "{{ width | at_most:5 }}", { 'width' => NumberLikeThing.new(6) })
+    assert_template_result("4", "{{ width | at_most:5 }}", { 'width' => NumberLikeThing.new(4) })
+    assert_template_result("4", "{{ 5 | at_most: width }}", { 'width' => NumberLikeThing.new(4) })
   end
 
   def test_at_least
@@ -731,9 +740,9 @@ class StandardFiltersTest < Minitest::Test
     assert_template_result("6", "{{ 5 | at_least:6 }}")
 
     assert_template_result("5", "{{ 4.5 | at_least:5 }}")
-    assert_template_result("6", "{{ width | at_least:5 }}", 'width' => NumberLikeThing.new(6))
-    assert_template_result("5", "{{ width | at_least:5 }}", 'width' => NumberLikeThing.new(4))
-    assert_template_result("6", "{{ 5 | at_least: width }}", 'width' => NumberLikeThing.new(6))
+    assert_template_result("6", "{{ width | at_least:5 }}", { 'width' => NumberLikeThing.new(6) })
+    assert_template_result("5", "{{ width | at_least:5 }}", { 'width' => NumberLikeThing.new(4) })
+    assert_template_result("6", "{{ 5 | at_least: width }}", { 'width' => NumberLikeThing.new(6) })
   end
 
   def test_append
@@ -766,8 +775,8 @@ class StandardFiltersTest < Minitest::Test
     assert_equal("bar", @filters.default([], "bar"))
     assert_equal("bar", @filters.default({}, "bar"))
     assert_template_result('bar', "{{ false | default: 'bar' }}")
-    assert_template_result('bar', "{{ drop | default: 'bar' }}", 'drop' => BooleanDrop.new(false))
-    assert_template_result('Yay', "{{ drop | default: 'bar' }}", 'drop' => BooleanDrop.new(true))
+    assert_template_result('bar', "{{ drop | default: 'bar' }}", { 'drop' => BooleanDrop.new(false) })
+    assert_template_result('Yay', "{{ drop | default: 'bar' }}", { 'drop' => BooleanDrop.new(true) })
   end
 
   def test_default_handle_false
@@ -778,8 +787,8 @@ class StandardFiltersTest < Minitest::Test
     assert_equal("bar", @filters.default([], "bar", "allow_false" => true))
     assert_equal("bar", @filters.default({}, "bar", "allow_false" => true))
     assert_template_result('false', "{{ false | default: 'bar', allow_false: true }}")
-    assert_template_result('Nay', "{{ drop | default: 'bar', allow_false: true }}", 'drop' => BooleanDrop.new(false))
-    assert_template_result('Yay', "{{ drop | default: 'bar', allow_false: true }}", 'drop' => BooleanDrop.new(true))
+    assert_template_result('Nay', "{{ drop | default: 'bar', allow_false: true }}", { 'drop' => BooleanDrop.new(false) })
+    assert_template_result('Yay', "{{ drop | default: 'bar', allow_false: true }}", { 'drop' => BooleanDrop.new(true) })
   end
 
   def test_cannot_access_private_methods
diff --git a/test/integration/tags/break_tag_test.rb b/test/integration/tags/break_tag_test.rb
index a67a8b5..0a02d58 100644
--- a/test/integration/tags/break_tag_test.rb
+++ b/test/integration/tags/break_tag_test.rb
@@ -9,8 +9,8 @@ class BreakTagTest < Minitest::Test
   # block
   def test_break_with_no_block
     assigns  = { 'i' => 1 }
-    markup   = '{% break %}'
-    expected = ''
+    markup   = 'before{% break %}after'
+    expected = 'before'
 
     assert_template_result(expected, markup, assigns)
   end
diff --git a/test/integration/tags/echo_test.rb b/test/integration/tags/echo_test.rb
index c64932e..dfe290e 100644
--- a/test/integration/tags/echo_test.rb
+++ b/test/integration/tags/echo_test.rb
@@ -6,7 +6,7 @@ class EchoTest < Minitest::Test
   include Liquid
 
   def test_echo_outputs_its_input
-    assert_template_result('BAR', <<~LIQUID, 'variable-name' => 'bar')
+    assert_template_result('BAR', <<~LIQUID, { 'variable-name' => 'bar' })
       {%- echo variable-name | upcase -%}
     LIQUID
   end
diff --git a/test/integration/tags/for_tag_test.rb b/test/integration/tags/for_tag_test.rb
index f19d630..b36a5ba 100644
--- a/test/integration/tags/for_tag_test.rb
+++ b/test/integration/tags/for_tag_test.rb
@@ -12,10 +12,10 @@ class ForTagTest < Minitest::Test
   include Liquid
 
   def test_for
-    assert_template_result(' yo  yo  yo  yo ', '{%for item in array%} yo {%endfor%}', 'array' => [1, 2, 3, 4])
-    assert_template_result('yoyo', '{%for item in array%}yo{%endfor%}', 'array' => [1, 2])
-    assert_template_result(' yo ', '{%for item in array%} yo {%endfor%}', 'array' => [1])
-    assert_template_result('', '{%for item in array%}{%endfor%}', 'array' => [1, 2])
+    assert_template_result(' yo  yo  yo  yo ', '{%for item in array%} yo {%endfor%}', { 'array' => [1, 2, 3, 4] })
+    assert_template_result('yoyo', '{%for item in array%}yo{%endfor%}', { 'array' => [1, 2] })
+    assert_template_result(' yo ', '{%for item in array%} yo {%endfor%}', { 'array' => [1] })
+    assert_template_result('', '{%for item in array%}{%endfor%}', { 'array' => [1, 2] })
     expected = <<HERE
 
   yo
@@ -30,7 +30,7 @@ HERE
         yo
       {%endfor%}
     HERE
-    assert_template_result(expected, template, 'array' => [1, 2, 3])
+    assert_template_result(expected, template, { 'array' => [1, 2, 3] })
   end
 
   def test_for_reversed
@@ -45,30 +45,30 @@ HERE
       Template.parse('{% for i in (a..2) %}{% endfor %}').render!("a" => [1, 2])
     end
 
-    assert_template_result(' 0  1  2  3 ', '{% for item in (a..3) %} {{item}} {% endfor %}', "a" => "invalid integer")
+    assert_template_result(' 0  1  2  3 ', '{% for item in (a..3) %} {{item}} {% endfor %}', { "a" => "invalid integer" })
   end
 
   def test_for_with_variable_range
-    assert_template_result(' 1  2  3 ', '{%for item in (1..foobar) %} {{item}} {%endfor%}', "foobar" => 3)
+    assert_template_result(' 1  2  3 ', '{%for item in (1..foobar) %} {{item}} {%endfor%}', { "foobar" => 3 })
   end
 
   def test_for_with_hash_value_range
     foobar = { "value" => 3 }
-    assert_template_result(' 1  2  3 ', '{%for item in (1..foobar.value) %} {{item}} {%endfor%}', "foobar" => foobar)
+    assert_template_result(' 1  2  3 ', '{%for item in (1..foobar.value) %} {{item}} {%endfor%}', { "foobar" => foobar })
   end
 
   def test_for_with_drop_value_range
     foobar = ThingWithValue.new
-    assert_template_result(' 1  2  3 ', '{%for item in (1..foobar.value) %} {{item}} {%endfor%}', "foobar" => foobar)
+    assert_template_result(' 1  2  3 ', '{%for item in (1..foobar.value) %} {{item}} {%endfor%}', { "foobar" => foobar })
   end
 
   def test_for_with_variable
-    assert_template_result(' 1  2  3 ', '{%for item in array%} {{item}} {%endfor%}', 'array' => [1, 2, 3])
-    assert_template_result('123', '{%for item in array%}{{item}}{%endfor%}', 'array' => [1, 2, 3])
-    assert_template_result('123', '{% for item in array %}{{item}}{% endfor %}', 'array' => [1, 2, 3])
-    assert_template_result('abcd', '{%for item in array%}{{item}}{%endfor%}', 'array' => ['a', 'b', 'c', 'd'])
-    assert_template_result('a b c', '{%for item in array%}{{item}}{%endfor%}', 'array' => ['a', ' ', 'b', ' ', 'c'])
-    assert_template_result('abc', '{%for item in array%}{{item}}{%endfor%}', 'array' => ['a', '', 'b', '', 'c'])
+    assert_template_result(' 1  2  3 ', '{%for item in array%} {{item}} {%endfor%}', { 'array' => [1, 2, 3] })
+    assert_template_result('123', '{%for item in array%}{{item}}{%endfor%}', { 'array' => [1, 2, 3] })
+    assert_template_result('123', '{% for item in array %}{{item}}{% endfor %}', { 'array' => [1, 2, 3] })
+    assert_template_result('abcd', '{%for item in array%}{{item}}{%endfor%}', { 'array' => ['a', 'b', 'c', 'd'] })
+    assert_template_result('a b c', '{%for item in array%}{{item}}{%endfor%}', { 'array' => ['a', ' ', 'b', ' ', 'c'] })
+    assert_template_result('abc', '{%for item in array%}{{item}}{%endfor%}', { 'array' => ['a', '', 'b', '', 'c'] })
   end
 
   def test_for_helpers
@@ -92,9 +92,9 @@ HERE
   end
 
   def test_for_else
-    assert_template_result('+++', '{%for item in array%}+{%else%}-{%endfor%}', 'array' => [1, 2, 3])
-    assert_template_result('-',   '{%for item in array%}+{%else%}-{%endfor%}', 'array' => [])
-    assert_template_result('-',   '{%for item in array%}+{%else%}-{%endfor%}', 'array' => nil)
+    assert_template_result('+++', '{%for item in array%}+{%else%}-{%endfor%}', { 'array' => [1, 2, 3] })
+    assert_template_result('-',   '{%for item in array%}+{%else%}-{%endfor%}', { 'array' => [] })
+    assert_template_result('-',   '{%for item in array%}+{%else%}-{%endfor%}', { 'array' => nil })
   end
 
   def test_limiting
@@ -103,6 +103,7 @@ HERE
     assert_template_result('1234', '{%for i in array limit:4 %}{{ i }}{%endfor%}', assigns)
     assert_template_result('3456', '{%for i in array limit:4 offset:2 %}{{ i }}{%endfor%}', assigns)
     assert_template_result('3456', '{%for i in array limit: 4 offset: 2 %}{{ i }}{%endfor%}', assigns)
+    assert_template_result('3456', '{%for i in array, limit: 4, offset: 2 %}{{ i }}{%endfor%}', assigns)
   end
 
   def test_limiting_with_invalid_limit
@@ -263,6 +264,19 @@ HERE
     assert_template_result(expected, markup, assigns)
   end
 
+  def test_for_with_break_after_nested_loop
+    source = <<~LIQUID.chomp
+      {% for i in (1..2) -%}
+        {% for j in (1..2) -%}
+          {{ i }}-{{ j }},
+        {%- endfor -%}
+        {% break -%}
+      {% endfor -%}
+      after
+    LIQUID
+    assert_template_result("1-1,1-2,after", source)
+  end
+
   def test_for_with_continue
     assigns = { 'array' => { 'items' => [1, 2, 3, 4, 5] } }
 
@@ -313,11 +327,11 @@ HERE
 
     assert_template_result('test string',
       '{%for val in string%}{{val}}{%endfor%}',
-      'string' => "test string")
+      { 'string' => "test string" })
 
     assert_template_result('test string',
       '{%for val in string limit:1%}{{val}}{%endfor%}',
-      'string' => "test string")
+      { 'string' => "test string" })
 
     assert_template_result('val-string-1-1-0-1-0-true-true-test string',
       '{%for val in string%}' \
@@ -330,7 +344,7 @@ HERE
       '{{forloop.first}}-' \
       '{{forloop.last}}-' \
       '{{val}}{%endfor%}',
-      'string' => "test string")
+      { 'string' => "test string" })
   end
 
   def test_for_parentloop_references_parent_loop
@@ -338,7 +352,7 @@ HERE
       '{% for inner in outer %}{% for k in inner %}' \
       '{{ forloop.parentloop.index }}.{{ forloop.index }} ' \
       '{% endfor %}{% endfor %}',
-      'outer' => [[1, 1, 1], [1, 1, 1]])
+      { 'outer' => [[1, 1, 1], [1, 1, 1]] })
   end
 
   def test_for_parentloop_nil_when_not_present
@@ -346,7 +360,7 @@ HERE
       '{% for inner in outer %}' \
       '{{ forloop.parentloop.index }}.{{ forloop.index }} ' \
       '{% endfor %}',
-      'outer' => [[1, 1, 1], [1, 1, 1]])
+      { 'outer' => [[1, 1, 1], [1, 1, 1]] })
   end
 
   def test_inner_for_over_empty_input
@@ -354,7 +368,7 @@ HERE
   end
 
   def test_blank_string_not_iterable
-    assert_template_result('', "{% for char in characters %}I WILL NOT BE OUTPUT{% endfor %}", 'characters' => '')
+    assert_template_result('', "{% for char in characters %}I WILL NOT BE OUTPUT{% endfor %}", { 'characters' => '' })
   end
 
   def test_bad_variable_naming_in_for_loop
@@ -437,30 +451,4 @@ HERE
 
     assert(context.registers[:for_stack].empty?)
   end
-
-  def test_instrument_for_offset_continue
-    assert_usage_increment('for_offset_continue') do
-      Template.parse('{% for item in items offset:continue %}{{item}}{% endfor %}')
-    end
-
-    assert_usage_increment('for_offset_continue', times: 0) do
-      Template.parse('{% for item in items offset:2 %}{{item}}{% endfor %}')
-    end
-  end
-
-  def test_instrument_forloop_drop_name
-    assigns = { 'items' => [1, 2, 3, 4, 5] }
-
-    assert_usage_increment('forloop_drop_name', times: 5) do
-      Template.parse('{% for item in items %}{{forloop.name}}{% endfor %}').render!(assigns)
-    end
-
-    assert_usage_increment('forloop_drop_name', times: 0) do
-      Template.parse('{% for item in items %}{{forloop.index}}{% endfor %}').render!(assigns)
-    end
-
-    assert_usage_increment('forloop_drop_name', times: 0) do
-      Template.parse('{% for item in items %}{{item}}{% endfor %}').render!(assigns)
-    end
-  end
 end
diff --git a/test/integration/tags/if_else_tag_test.rb b/test/integration/tags/if_else_tag_test.rb
index 503d912..b550ea9 100644
--- a/test/integration/tags/if_else_tag_test.rb
+++ b/test/integration/tags/if_else_tag_test.rb
@@ -24,23 +24,23 @@ class IfElseTagTest < Minitest::Test
   end
 
   def test_if_boolean
-    assert_template_result(' YES ', '{% if var %} YES {% endif %}', 'var' => true)
+    assert_template_result(' YES ', '{% if var %} YES {% endif %}', { 'var' => true })
   end
 
   def test_if_or
-    assert_template_result(' YES ', '{% if a or b %} YES {% endif %}', 'a' => true, 'b' => true)
-    assert_template_result(' YES ', '{% if a or b %} YES {% endif %}', 'a' => true, 'b' => false)
-    assert_template_result(' YES ', '{% if a or b %} YES {% endif %}', 'a' => false, 'b' => true)
-    assert_template_result('',      '{% if a or b %} YES {% endif %}', 'a' => false, 'b' => false)
+    assert_template_result(' YES ', '{% if a or b %} YES {% endif %}', { 'a' => true, 'b' => true })
+    assert_template_result(' YES ', '{% if a or b %} YES {% endif %}', { 'a' => true, 'b' => false })
+    assert_template_result(' YES ', '{% if a or b %} YES {% endif %}', { 'a' => false, 'b' => true })
+    assert_template_result('',      '{% if a or b %} YES {% endif %}', { 'a' => false, 'b' => false })
 
-    assert_template_result(' YES ', '{% if a or b or c %} YES {% endif %}', 'a' => false, 'b' => false, 'c' => true)
-    assert_template_result('',      '{% if a or b or c %} YES {% endif %}', 'a' => false, 'b' => false, 'c' => false)
+    assert_template_result(' YES ', '{% if a or b or c %} YES {% endif %}', { 'a' => false, 'b' => false, 'c' => true })
+    assert_template_result('',      '{% if a or b or c %} YES {% endif %}', { 'a' => false, 'b' => false, 'c' => false })
   end
 
   def test_if_or_with_operators
-    assert_template_result(' YES ', '{% if a == true or b == true %} YES {% endif %}', 'a' => true, 'b' => true)
-    assert_template_result(' YES ', '{% if a == true or b == false %} YES {% endif %}', 'a' => true, 'b' => true)
-    assert_template_result('', '{% if a == false or b == false %} YES {% endif %}', 'a' => true, 'b' => true)
+    assert_template_result(' YES ', '{% if a == true or b == true %} YES {% endif %}', { 'a' => true, 'b' => true })
+    assert_template_result(' YES ', '{% if a == true or b == false %} YES {% endif %}', { 'a' => true, 'b' => true })
+    assert_template_result('', '{% if a == false or b == false %} YES {% endif %}', { 'a' => true, 'b' => true })
   end
 
   def test_comparison_of_strings_containing_and_or_or
@@ -66,40 +66,40 @@ class IfElseTagTest < Minitest::Test
   end
 
   def test_hash_miss_generates_false
-    assert_template_result('', '{% if foo.bar %} NO {% endif %}', 'foo' => {})
+    assert_template_result('', '{% if foo.bar %} NO {% endif %}', { 'foo' => {} })
   end
 
   def test_if_from_variable
-    assert_template_result('', '{% if var %} NO {% endif %}', 'var' => false)
-    assert_template_result('', '{% if var %} NO {% endif %}', 'var' => nil)
-    assert_template_result('', '{% if foo.bar %} NO {% endif %}', 'foo' => { 'bar' => false })
-    assert_template_result('', '{% if foo.bar %} NO {% endif %}', 'foo' => {})
-    assert_template_result('', '{% if foo.bar %} NO {% endif %}', 'foo' => nil)
-    assert_template_result('', '{% if foo.bar %} NO {% endif %}', 'foo' => true)
-
-    assert_template_result(' YES ', '{% if var %} YES {% endif %}', 'var' => "text")
-    assert_template_result(' YES ', '{% if var %} YES {% endif %}', 'var' => true)
-    assert_template_result(' YES ', '{% if var %} YES {% endif %}', 'var' => 1)
-    assert_template_result(' YES ', '{% if var %} YES {% endif %}', 'var' => {})
-    assert_template_result(' YES ', '{% if var %} YES {% endif %}', 'var' => [])
+    assert_template_result('', '{% if var %} NO {% endif %}', { 'var' => false })
+    assert_template_result('', '{% if var %} NO {% endif %}', { 'var' => nil })
+    assert_template_result('', '{% if foo.bar %} NO {% endif %}', { 'foo' => { 'bar' => false } })
+    assert_template_result('', '{% if foo.bar %} NO {% endif %}', { 'foo' => {} })
+    assert_template_result('', '{% if foo.bar %} NO {% endif %}', { 'foo' => nil })
+    assert_template_result('', '{% if foo.bar %} NO {% endif %}', { 'foo' => true })
+
+    assert_template_result(' YES ', '{% if var %} YES {% endif %}', { 'var' => "text" })
+    assert_template_result(' YES ', '{% if var %} YES {% endif %}', { 'var' => true })
+    assert_template_result(' YES ', '{% if var %} YES {% endif %}', { 'var' => 1 })
+    assert_template_result(' YES ', '{% if var %} YES {% endif %}', { 'var' => {} })
+    assert_template_result(' YES ', '{% if var %} YES {% endif %}', { 'var' => [] })
     assert_template_result(' YES ', '{% if "foo" %} YES {% endif %}')
-    assert_template_result(' YES ', '{% if foo.bar %} YES {% endif %}', 'foo' => { 'bar' => true })
-    assert_template_result(' YES ', '{% if foo.bar %} YES {% endif %}', 'foo' => { 'bar' => "text" })
-    assert_template_result(' YES ', '{% if foo.bar %} YES {% endif %}', 'foo' => { 'bar' => 1 })
-    assert_template_result(' YES ', '{% if foo.bar %} YES {% endif %}', 'foo' => { 'bar' => {} })
-    assert_template_result(' YES ', '{% if foo.bar %} YES {% endif %}', 'foo' => { 'bar' => [] })
+    assert_template_result(' YES ', '{% if foo.bar %} YES {% endif %}', { 'foo' => { 'bar' => true } })
+    assert_template_result(' YES ', '{% if foo.bar %} YES {% endif %}', { 'foo' => { 'bar' => "text" } })
+    assert_template_result(' YES ', '{% if foo.bar %} YES {% endif %}', { 'foo' => { 'bar' => 1 } })
+    assert_template_result(' YES ', '{% if foo.bar %} YES {% endif %}', { 'foo' => { 'bar' => {} } })
+    assert_template_result(' YES ', '{% if foo.bar %} YES {% endif %}', { 'foo' => { 'bar' => [] } })
 
-    assert_template_result(' YES ', '{% if var %} NO {% else %} YES {% endif %}', 'var' => false)
-    assert_template_result(' YES ', '{% if var %} NO {% else %} YES {% endif %}', 'var' => nil)
-    assert_template_result(' YES ', '{% if var %} YES {% else %} NO {% endif %}', 'var' => true)
-    assert_template_result(' YES ', '{% if "foo" %} YES {% else %} NO {% endif %}', 'var' => "text")
+    assert_template_result(' YES ', '{% if var %} NO {% else %} YES {% endif %}', { 'var' => false })
+    assert_template_result(' YES ', '{% if var %} NO {% else %} YES {% endif %}', { 'var' => nil })
+    assert_template_result(' YES ', '{% if var %} YES {% else %} NO {% endif %}', { 'var' => true })
+    assert_template_result(' YES ', '{% if "foo" %} YES {% else %} NO {% endif %}', { 'var' => "text" })
 
-    assert_template_result(' YES ', '{% if foo.bar %} NO {% else %} YES {% endif %}', 'foo' => { 'bar' => false })
-    assert_template_result(' YES ', '{% if foo.bar %} YES {% else %} NO {% endif %}', 'foo' => { 'bar' => true })
-    assert_template_result(' YES ', '{% if foo.bar %} YES {% else %} NO {% endif %}', 'foo' => { 'bar' => "text" })
-    assert_template_result(' YES ', '{% if foo.bar %} NO {% else %} YES {% endif %}', 'foo' => { 'notbar' => true })
-    assert_template_result(' YES ', '{% if foo.bar %} NO {% else %} YES {% endif %}', 'foo' => {})
-    assert_template_result(' YES ', '{% if foo.bar %} NO {% else %} YES {% endif %}', 'notfoo' => { 'bar' => true })
+    assert_template_result(' YES ', '{% if foo.bar %} NO {% else %} YES {% endif %}', { 'foo' => { 'bar' => false } })
+    assert_template_result(' YES ', '{% if foo.bar %} YES {% else %} NO {% endif %}', { 'foo' => { 'bar' => true } })
+    assert_template_result(' YES ', '{% if foo.bar %} YES {% else %} NO {% endif %}', { 'foo' => { 'bar' => "text" } })
+    assert_template_result(' YES ', '{% if foo.bar %} NO {% else %} YES {% endif %}', { 'foo' => { 'notbar' => true } })
+    assert_template_result(' YES ', '{% if foo.bar %} NO {% else %} YES {% endif %}', { 'foo' => {} })
+    assert_template_result(' YES ', '{% if foo.bar %} NO {% else %} YES {% endif %}', { 'notfoo' => { 'bar' => true } })
   end
 
   def test_nested_if
@@ -184,7 +184,7 @@ class IfElseTagTest < Minitest::Test
     tests.each do |vals, expected|
       a, b, c = vals
       assigns = { 'a' => a, 'b' => b, 'c' => c }
-      assert_template_result(expected.to_s, tpl, assigns, assigns.to_s)
+      assert_template_result(expected.to_s, tpl, assigns, message: assigns.to_s)
     end
   end
 end
diff --git a/test/integration/tags/include_tag_test.rb b/test/integration/tags/include_tag_test.rb
index 73f4875..91f1aea 100644
--- a/test/integration/tags/include_tag_test.rb
+++ b/test/integration/tags/include_tag_test.rb
@@ -3,44 +3,13 @@
 require 'test_helper'
 
 class TestFileSystem
-  def read_template_file(template_path)
-    case template_path
-    when "product"
-      "Product: {{ product.title }} "
-
-    when "product_alias"
-      "Product: {{ product.title }} "
-
-    when "locale_variables"
-      "Locale: {{echo1}} {{echo2}}"
-
-    when "variant"
-      "Variant: {{ variant.title }}"
-
-    when "nested_template"
-      "{% include 'header' %} {% include 'body' %} {% include 'footer' %}"
-
-    when "body"
-      "body {% include 'body_detail' %}"
-
-    when "nested_product_template"
-      "Product: {{ nested_product_template.title }} {%include 'details'%} "
+  PARTIALS = {
+    "nested_template" => "{% include 'header' %} {% include 'body' %} {% include 'footer' %}",
+    "body" => "body {% include 'body_detail' %}",
+  }
 
-    when "recursively_nested_template"
-      "-{% include 'recursively_nested_template' %}"
-
-    when "pick_a_source"
-      "from TestFileSystem"
-
-    when 'assignments'
-      "{% assign foo = 'bar' %}"
-
-    when 'break'
-      "{% break %}"
-
-    else
-      template_path
-    end
+  def read_template_file(template_path)
+    PARTIALS[template_path] || template_path
   end
 end
 
@@ -81,7 +50,11 @@ class IncludeTagTest < Minitest::Test
   include Liquid
 
   def setup
-    Liquid::Template.file_system = TestFileSystem.new
+    @default_file_system = Liquid::Template.file_system
+  end
+
+  def teardown
+    Liquid::Template.file_system = @default_file_system
   end
 
   def test_include_tag_looks_for_file_system_in_registers_first
@@ -91,60 +64,86 @@ class IncludeTagTest < Minitest::Test
 
   def test_include_tag_with
     assert_template_result("Product: Draft 151cm ",
-      "{% include 'product' with products[0] %}", "products" => [{ 'title' => 'Draft 151cm' }, { 'title' => 'Element 155cm' }])
+      "{% include 'product' with products[0] %}",
+      { "products" => [{ 'title' => 'Draft 151cm' }, { 'title' => 'Element 155cm' }] },
+      partials: { "product" => "Product: {{ product.title }} " })
   end
 
   def test_include_tag_with_alias
     assert_template_result("Product: Draft 151cm ",
-      "{% include 'product_alias' with products[0] as product %}", "products" => [{ 'title' => 'Draft 151cm' }, { 'title' => 'Element 155cm' }])
+      "{% include 'product_alias' with products[0] as product %}",
+      { "products" => [{ 'title' => 'Draft 151cm' }, { 'title' => 'Element 155cm' }] },
+      partials: { "product_alias" => "Product: {{ product.title }} " })
   end
 
   def test_include_tag_for_alias
     assert_template_result("Product: Draft 151cm Product: Element 155cm ",
-      "{% include 'product_alias' for products as product %}", "products" => [{ 'title' => 'Draft 151cm' }, { 'title' => 'Element 155cm' }])
+      "{% include 'product_alias' for products as product %}",
+      { "products" => [{ 'title' => 'Draft 151cm' }, { 'title' => 'Element 155cm' }] },
+      partials: { "product_alias" => "Product: {{ product.title }} " })
   end
 
   def test_include_tag_with_default_name
     assert_template_result("Product: Draft 151cm ",
-      "{% include 'product' %}", "product" => { 'title' => 'Draft 151cm' })
+      "{% include 'product' %}", { "product" => { 'title' => 'Draft 151cm' } },
+      partials: { "product" => "Product: {{ product.title }} " })
   end
 
   def test_include_tag_for
     assert_template_result("Product: Draft 151cm Product: Element 155cm ",
-      "{% include 'product' for products %}", "products" => [{ 'title' => 'Draft 151cm' }, { 'title' => 'Element 155cm' }])
+      "{% include 'product' for products %}",
+      { "products" => [{ 'title' => 'Draft 151cm' }, { 'title' => 'Element 155cm' }] },
+      partials: { "product" => "Product: {{ product.title }} " })
   end
 
   def test_include_tag_with_local_variables
-    assert_template_result("Locale: test123 ", "{% include 'locale_variables' echo1: 'test123' %}")
+    assert_template_result("Locale: test123 ", "{% include 'locale_variables' echo1: 'test123' %}",
+      partials: { "locale_variables" => "Locale: {{echo1}} {{echo2}}" })
   end
 
   def test_include_tag_with_multiple_local_variables
     assert_template_result("Locale: test123 test321",
-      "{% include 'locale_variables' echo1: 'test123', echo2: 'test321' %}")
+      "{% include 'locale_variables' echo1: 'test123', echo2: 'test321' %}",
+      partials: { "locale_variables" => "Locale: {{echo1}} {{echo2}}" })
   end
 
   def test_include_tag_with_multiple_local_variables_from_context
     assert_template_result("Locale: test123 test321",
       "{% include 'locale_variables' echo1: echo1, echo2: more_echos.echo2 %}",
-      'echo1' => 'test123', 'more_echos' => { "echo2" => 'test321' })
+      { 'echo1' => 'test123', 'more_echos' => { "echo2" => 'test321' } },
+      partials: { "locale_variables" => "Locale: {{echo1}} {{echo2}}" })
   end
 
   def test_included_templates_assigns_variables
-    assert_template_result("bar", "{% include 'assignments' %}{{ foo }}")
+    assert_template_result("bar", "{% include 'assignments' %}{{ foo }}",
+      partials: { 'assignments' => "{% assign foo = 'bar' %}" })
   end
 
   def test_nested_include_tag
-    assert_template_result("body body_detail", "{% include 'body' %}")
+    partials = { "body" => "body {% include 'body_detail' %}", "body_detail" => "body_detail" }
+    assert_template_result("body body_detail", "{% include 'body' %}", partials: partials)
 
-    assert_template_result("header body body_detail footer", "{% include 'nested_template' %}")
+    partials = partials.merge({
+      "nested_template" => "{% include 'header' %} {% include 'body' %} {% include 'footer' %}",
+      "header" => "header",
+      "footer" => "footer",
+    })
+    assert_template_result("header body body_detail footer", "{% include 'nested_template' %}", partials: partials)
   end
 
   def test_nested_include_with_variable
+    partials = {
+      "nested_product_template" => "Product: {{ nested_product_template.title }} {%include 'details'%} ",
+      "details" => "details",
+    }
+
     assert_template_result("Product: Draft 151cm details ",
-      "{% include 'nested_product_template' with product %}", "product" => { "title" => 'Draft 151cm' })
+      "{% include 'nested_product_template' with product %}", { "product" => { "title" => 'Draft 151cm' } },
+      partials: partials)
 
     assert_template_result("Product: Draft 151cm details Product: Element 155cm details ",
-      "{% include 'nested_product_template' for products %}", "products" => [{ "title" => 'Draft 151cm' }, { "title" => 'Element 155cm' }])
+      "{% include 'nested_product_template' for products %}", { "products" => [{ "title" => 'Draft 151cm' }, { "title" => 'Element 155cm' }] },
+      partials: partials)
   end
 
   def test_recursively_included_template_does_not_produce_endless_loop
@@ -162,11 +161,15 @@ class IncludeTagTest < Minitest::Test
   end
 
   def test_dynamically_choosen_template
-    assert_template_result("Test123", "{% include template %}", "template" => 'Test123')
-    assert_template_result("Test321", "{% include template %}", "template" => 'Test321')
+    assert_template_result("Test123", "{% include template %}", { "template" => 'Test123' },
+      partials: { "Test123" => "Test123" })
+
+    assert_template_result("Test321", "{% include template %}", { "template" => 'Test321' },
+      partials: { "Test321" => "Test321" })
 
     assert_template_result("Product: Draft 151cm ", "{% include template for product %}",
-      "template" => 'product', 'product' => { 'title' => 'Draft 151cm' })
+      { "template" => 'product', 'product' => { 'title' => 'Draft 151cm' } },
+      partials: { "product" => "Product: {{ product.title }} " })
   end
 
   def test_include_tag_caches_second_read_of_same_partial
@@ -188,7 +191,8 @@ class IncludeTagTest < Minitest::Test
   end
 
   def test_include_tag_within_if_statement
-    assert_template_result("foo_if_true", "{% if true %}{% include 'foo_if_true' %}{% endif %}")
+    assert_template_result("foo_if_true", "{% if true %}{% include 'foo_if_true' %}{% endif %}",
+      partials: { "foo_if_true" => "foo_if_true" })
   end
 
   def test_custom_include_tag
@@ -222,6 +226,7 @@ class IncludeTagTest < Minitest::Test
   end
 
   def test_passing_options_to_included_templates
+    Liquid::Template.file_system = TestFileSystem.new
     assert_raises(Liquid::SyntaxError) do
       Template.parse("{% include template %}", error_mode: :strict).render!("template" => '{{ "X" || downcase }}')
     end
@@ -237,25 +242,35 @@ class IncludeTagTest < Minitest::Test
   end
 
   def test_render_raise_argument_error_when_template_is_undefined
-    assert_raises(Liquid::ArgumentError) do
-      template = Liquid::Template.parse('{% include undefined_variable %}')
-      template.render!
-    end
-    assert_raises(Liquid::ArgumentError) do
-      template = Liquid::Template.parse('{% include nil %}')
-      template.render!
-    end
+    assert_template_result("Liquid error (line 1): Argument error in tag 'include' - Illegal template name",
+      "{% include undefined_variable %}", render_errors: true)
+
+    assert_template_result("Liquid error (line 1): Argument error in tag 'include' - Illegal template name",
+      "{% include nil %}", render_errors: true)
+  end
+
+  def test_render_raise_argument_error_when_template_is_not_a_string
+    assert_template_result("Liquid error (line 1): Argument error in tag 'include' - Illegal template name",
+      "{% include 123 %}", render_errors: true)
   end
 
   def test_including_via_variable_value
-    assert_template_result("from TestFileSystem", "{% assign page = 'pick_a_source' %}{% include page %}")
+    assert_template_result("from TestFileSystem", "{% assign page = 'pick_a_source' %}{% include page %}",
+      partials: { "pick_a_source" => "from TestFileSystem" })
+
+    partials = { "product" => "Product: {{ product.title }} " }
 
-    assert_template_result("Product: Draft 151cm ", "{% assign page = 'product' %}{% include page %}", "product" => { 'title' => 'Draft 151cm' })
+    assert_template_result("Product: Draft 151cm ", "{% assign page = 'product' %}{% include page %}",
+      { "product" => { 'title' => 'Draft 151cm' } },
+      partials: partials)
 
-    assert_template_result("Product: Draft 151cm ", "{% assign page = 'product' %}{% include page for foo %}", "foo" => { 'title' => 'Draft 151cm' })
+    assert_template_result("Product: Draft 151cm ", "{% assign page = 'product' %}{% include page for foo %}",
+      { "foo" => { 'title' => 'Draft 151cm' } },
+      partials: partials)
   end
 
   def test_including_with_strict_variables
+    Liquid::Template.file_system = StubFileSystem.new({ "simple" => "simple" })
     template = Liquid::Template.parse("{% include 'simple' %}", error_mode: :warn)
     template.render(nil, strict_variables: true)
 
@@ -264,6 +279,7 @@ class IncludeTagTest < Minitest::Test
 
   def test_break_through_include
     assert_template_result("1", "{% for i in (1..3) %}{{ i }}{% break %}{{ i }}{% endfor %}")
-    assert_template_result("1", "{% for i in (1..3) %}{{ i }}{% include 'break' %}{{ i }}{% endfor %}")
+    assert_template_result("1", "{% for i in (1..3) %}{{ i }}{% include 'break' %}{{ i }}{% endfor %}",
+      partials: { 'break' => "{% break %}" })
   end
 end # IncludeTagTest
diff --git a/test/integration/tags/increment_tag_test.rb b/test/integration/tags/increment_tag_test.rb
index d561a1b..666d2d5 100644
--- a/test/integration/tags/increment_tag_test.rb
+++ b/test/integration/tags/increment_tag_test.rb
@@ -6,20 +6,21 @@ class IncrementTagTest < Minitest::Test
   include Liquid
 
   def test_inc
-    assert_template_result('0', '{%increment port %}', {})
-    assert_template_result('0 1', '{%increment port %} {%increment port%}', {})
+    assert_template_result('0 1', '{%increment port %} {{ port }}')
+    assert_template_result(' 0 1 2', '{{port}} {%increment port %} {%increment port%} {{port}}')
     assert_template_result('0 0 1 2 1',
       '{%increment port %} {%increment starboard%} ' \
       '{%increment port %} {%increment port%} ' \
-      '{%increment starboard %}', {})
+      '{%increment starboard %}')
   end
 
   def test_dec
-    assert_template_result('9', '{%decrement port %}', 'port' => 10)
-    assert_template_result('-1 -2', '{%decrement port %} {%decrement port%}', {})
-    assert_template_result('1 5 2 2 5',
+    assert_template_result('-1 -1', '{%decrement port %} {{ port }}', { 'port' => 10 })
+    assert_template_result(' -1 -2 -2', '{{port}} {%decrement port %} {%decrement port%} {{port}}')
+    assert_template_result('0 1 2 0 3 1 1 3',
+      '{%increment starboard %} {%increment starboard%} {%increment starboard%} ' \
       '{%increment port %} {%increment starboard%} ' \
       '{%increment port %} {%decrement port%} ' \
-      '{%decrement starboard %}', 'port' => 1, 'starboard' => 5)
+      '{%decrement starboard %}')
   end
 end
diff --git a/test/integration/tags/liquid_tag_test.rb b/test/integration/tags/liquid_tag_test.rb
index b8eb881..17ce001 100644
--- a/test/integration/tags/liquid_tag_test.rb
+++ b/test/integration/tags/liquid_tag_test.rb
@@ -6,13 +6,13 @@ class LiquidTagTest < Minitest::Test
   include Liquid
 
   def test_liquid_tag
-    assert_template_result('1 2 3', <<~LIQUID, 'array' => [1, 2, 3])
+    assert_template_result('1 2 3', <<~LIQUID, { 'array' => [1, 2, 3] })
       {%- liquid
         echo array | join: " "
       -%}
     LIQUID
 
-    assert_template_result('1 2 3', <<~LIQUID, 'array' => [1, 2, 3])
+    assert_template_result('1 2 3', <<~LIQUID, { 'array' => [1, 2, 3] })
       {%- liquid
         for value in array
           echo value
@@ -23,7 +23,7 @@ class LiquidTagTest < Minitest::Test
       -%}
     LIQUID
 
-    assert_template_result('4 8 12 6', <<~LIQUID, 'array' => [1, 2, 3])
+    assert_template_result('4 8 12 6', <<~LIQUID, { 'array' => [1, 2, 3] })
       {%- liquid
         for value in array
           assign double_value = value | times: 2
diff --git a/test/integration/tags/render_tag_test.rb b/test/integration/tags/render_tag_test.rb
index 1af22b9..28dbb0d 100644
--- a/test/integration/tags/render_tag_test.rb
+++ b/test/integration/tags/render_tag_test.rb
@@ -6,53 +6,52 @@ class RenderTagTest < Minitest::Test
   include Liquid
 
   def test_render_with_no_arguments
-    Liquid::Template.file_system = StubFileSystem.new('source' => 'rendered content')
-    assert_template_result('rendered content', '{% render "source" %}')
+    assert_template_result('rendered content', '{% render "source" %}',
+      partials: { 'source' => 'rendered content' })
   end
 
   def test_render_tag_looks_for_file_system_in_registers_first
-    file_system = StubFileSystem.new('pick_a_source' => 'from register file system')
-    assert_equal('from register file system',
-      Template.parse('{% render "pick_a_source" %}').render!({}, registers: { file_system: file_system }))
+    assert_template_result('from register file system', '{% render "pick_a_source" %}',
+      partials: { 'pick_a_source' => 'from register file system' })
   end
 
   def test_render_passes_named_arguments_into_inner_scope
-    Liquid::Template.file_system = StubFileSystem.new('product' => '{{ inner_product.title }}')
     assert_template_result('My Product', '{% render "product", inner_product: outer_product %}',
-      'outer_product' => { 'title' => 'My Product' })
+      { 'outer_product' => { 'title' => 'My Product' } },
+      partials: { 'product' => '{{ inner_product.title }}' })
   end
 
   def test_render_accepts_literals_as_arguments
-    Liquid::Template.file_system = StubFileSystem.new('snippet' => '{{ price }}')
-    assert_template_result('123', '{% render "snippet", price: 123 %}')
+    assert_template_result('123', '{% render "snippet", price: 123 %}',
+      partials: { 'snippet' => '{{ price }}' })
   end
 
   def test_render_accepts_multiple_named_arguments
-    Liquid::Template.file_system = StubFileSystem.new('snippet' => '{{ one }} {{ two }}')
-    assert_template_result('1 2', '{% render "snippet", one: 1, two: 2 %}')
+    assert_template_result('1 2', '{% render "snippet", one: 1, two: 2 %}',
+      partials: { 'snippet' => '{{ one }} {{ two }}' })
   end
 
   def test_render_does_not_inherit_parent_scope_variables
-    Liquid::Template.file_system = StubFileSystem.new('snippet' => '{{ outer_variable }}')
-    assert_template_result('', '{% assign outer_variable = "should not be visible" %}{% render "snippet" %}')
+    assert_template_result('', '{% assign outer_variable = "should not be visible" %}{% render "snippet" %}',
+      partials: { 'snippet' => '{{ outer_variable }}' })
   end
 
   def test_render_does_not_inherit_variable_with_same_name_as_snippet
-    Liquid::Template.file_system = StubFileSystem.new('snippet' => '{{ snippet }}')
-    assert_template_result('', "{% assign snippet = 'should not be visible' %}{% render 'snippet' %}")
+    assert_template_result('', "{% assign snippet = 'should not be visible' %}{% render 'snippet' %}",
+      partials: { 'snippet' => '{{ snippet }}' })
   end
 
   def test_render_does_not_mutate_parent_scope
-    Liquid::Template.file_system = StubFileSystem.new('snippet' => '{% assign inner = 1 %}')
-    assert_template_result('', "{% render 'snippet' %}{{ inner }}")
+    assert_template_result('', "{% render 'snippet' %}{{ inner }}",
+      partials: { 'snippet' => '{% assign inner = 1 %}' })
   end
 
   def test_nested_render_tag
-    Liquid::Template.file_system = StubFileSystem.new(
-      'one' => "one {% render 'two' %}",
-      'two' => 'two'
-    )
-    assert_template_result('one two', "{% render 'one' %}")
+    assert_template_result('one two', "{% render 'one' %}",
+      partials: {
+        'one' => "one {% render 'two' %}",
+        'two' => 'two',
+      })
   end
 
   def test_recursively_rendered_template_does_not_produce_endless_loop
@@ -73,11 +72,7 @@ class RenderTagTest < Minitest::Test
   end
 
   def test_dynamically_choosen_templates_are_not_allowed
-    Liquid::Template.file_system = StubFileSystem.new('snippet' => 'should not be rendered')
-
-    assert_raises(Liquid::SyntaxError) do
-      Liquid::Template.parse("{% assign name = 'snippet' %}{% render name %}")
-    end
+    assert_syntax_error("{% assign name = 'snippet' %}{% render name %}")
   end
 
   def test_include_tag_caches_second_read_of_same_partial
@@ -101,113 +96,114 @@ class RenderTagTest < Minitest::Test
   end
 
   def test_render_tag_within_if_statement
-    Liquid::Template.file_system = StubFileSystem.new('snippet' => 'my message')
-    assert_template_result('my message', '{% if true %}{% render "snippet" %}{% endif %}')
+    assert_template_result('my message', '{% if true %}{% render "snippet" %}{% endif %}',
+      partials: { 'snippet' => 'my message' })
   end
 
   def test_break_through_render
-    Liquid::Template.file_system = StubFileSystem.new('break' => '{% break %}')
-    assert_template_result('1', '{% for i in (1..3) %}{{ i }}{% break %}{{ i }}{% endfor %}')
-    assert_template_result('112233', '{% for i in (1..3) %}{{ i }}{% render "break" %}{{ i }}{% endfor %}')
+    options = { partials: { 'break' => '{% break %}' } }
+    assert_template_result('1', '{% for i in (1..3) %}{{ i }}{% break %}{{ i }}{% endfor %}', **options)
+    assert_template_result('112233', '{% for i in (1..3) %}{{ i }}{% render "break" %}{{ i }}{% endfor %}', **options)
   end
 
   def test_increment_is_isolated_between_renders
-    Liquid::Template.file_system = StubFileSystem.new('incr' => '{% increment %}')
-    assert_template_result('010', '{% increment %}{% increment %}{% render "incr" %}')
+    assert_template_result('010', '{% increment %}{% increment %}{% render "incr" %}',
+      partials: { 'incr' => '{% increment %}' })
   end
 
   def test_decrement_is_isolated_between_renders
-    Liquid::Template.file_system = StubFileSystem.new('decr' => '{% decrement %}')
-    assert_template_result('-1-2-1', '{% decrement %}{% decrement %}{% render "decr" %}')
+    assert_template_result('-1-2-1', '{% decrement %}{% decrement %}{% render "decr" %}',
+      partials: { 'decr' => '{% decrement %}' })
   end
 
   def test_includes_will_not_render_inside_render_tag
-    Liquid::Template.file_system = StubFileSystem.new(
-      'foo' => 'bar',
-      'test_include' => '{% include "foo" %}'
+    assert_template_result(
+      'Liquid error (test_include line 1): include usage is not allowed in this context',
+      '{% render "test_include" %}',
+      render_errors: true,
+      partials: {
+        'foo' => 'bar',
+        'test_include' => '{% include "foo" %}',
+      }
     )
-
-    exc = assert_raises(Liquid::DisabledError) do
-      Liquid::Template.parse('{% render "test_include" %}').render!
-    end
-    assert_equal('Liquid error: include usage is not allowed in this context', exc.message)
   end
 
   def test_includes_will_not_render_inside_nested_sibling_tags
-    Liquid::Template.file_system = StubFileSystem.new(
-      'foo' => 'bar',
-      'nested_render_with_sibling_include' => '{% render "test_include" %}{% include "foo" %}',
-      'test_include' => '{% include "foo" %}'
+    assert_template_result(
+      "Liquid error (test_include line 1): include usage is not allowed in this context" \
+        "Liquid error (nested_render_with_sibling_include line 1): include usage is not allowed in this context",
+      '{% render "nested_render_with_sibling_include" %}',
+      partials: {
+        'foo' => 'bar',
+        'nested_render_with_sibling_include' => '{% render "test_include" %}{% include "foo" %}',
+        'test_include' => '{% include "foo" %}',
+      },
+      render_errors: true
     )
-
-    output = Liquid::Template.parse('{% render "nested_render_with_sibling_include" %}').render
-    assert_equal('Liquid error: include usage is not allowed in this contextLiquid error: include usage is not allowed in this context', output)
   end
 
   def test_render_tag_with
-    Liquid::Template.file_system = StubFileSystem.new(
-      'product' => "Product: {{ product.title }} ",
-      'product_alias' => "Product: {{ product.title }} ",
-    )
-
     assert_template_result("Product: Draft 151cm ",
-      "{% render 'product' with products[0] %}", "products" => [{ 'title' => 'Draft 151cm' }, { 'title' => 'Element 155cm' }])
+      "{% render 'product' with products[0] %}",
+      { "products" => [{ 'title' => 'Draft 151cm' }, { 'title' => 'Element 155cm' }] },
+      partials: {
+        'product' => "Product: {{ product.title }} ",
+        'product_alias' => "Product: {{ product.title }} ",
+      })
   end
 
   def test_render_tag_with_alias
-    Liquid::Template.file_system = StubFileSystem.new(
-      'product' => "Product: {{ product.title }} ",
-      'product_alias' => "Product: {{ product.title }} ",
-    )
-
     assert_template_result("Product: Draft 151cm ",
-      "{% render 'product_alias' with products[0] as product %}", "products" => [{ 'title' => 'Draft 151cm' }, { 'title' => 'Element 155cm' }])
+      "{% render 'product_alias' with products[0] as product %}",
+      { "products" => [{ 'title' => 'Draft 151cm' }, { 'title' => 'Element 155cm' }] },
+      partials: {
+        'product' => "Product: {{ product.title }} ",
+        'product_alias' => "Product: {{ product.title }} ",
+      })
   end
 
   def test_render_tag_for_alias
-    Liquid::Template.file_system = StubFileSystem.new(
-      'product' => "Product: {{ product.title }} ",
-      'product_alias' => "Product: {{ product.title }} ",
-    )
-
     assert_template_result("Product: Draft 151cm Product: Element 155cm ",
-      "{% render 'product_alias' for products as product %}", "products" => [{ 'title' => 'Draft 151cm' }, { 'title' => 'Element 155cm' }])
+      "{% render 'product_alias' for products as product %}",
+      { "products" => [{ 'title' => 'Draft 151cm' }, { 'title' => 'Element 155cm' }] },
+      partials: {
+        'product' => "Product: {{ product.title }} ",
+        'product_alias' => "Product: {{ product.title }} ",
+      })
   end
 
   def test_render_tag_for
-    Liquid::Template.file_system = StubFileSystem.new(
-      'product' => "Product: {{ product.title }} ",
-      'product_alias' => "Product: {{ product.title }} ",
-    )
-
     assert_template_result("Product: Draft 151cm Product: Element 155cm ",
-      "{% render 'product' for products %}", "products" => [{ 'title' => 'Draft 151cm' }, { 'title' => 'Element 155cm' }])
+      "{% render 'product' for products %}",
+      { "products" => [{ 'title' => 'Draft 151cm' }, { 'title' => 'Element 155cm' }] },
+      partials: {
+        'product' => "Product: {{ product.title }} ",
+        'product_alias' => "Product: {{ product.title }} ",
+      })
   end
 
   def test_render_tag_forloop
-    Liquid::Template.file_system = StubFileSystem.new(
-      'product' => "Product: {{ product.title }} {% if forloop.first %}first{% endif %} {% if forloop.last %}last{% endif %} index:{{ forloop.index }} ",
-    )
-
     assert_template_result("Product: Draft 151cm first  index:1 Product: Element 155cm  last index:2 ",
-      "{% render 'product' for products %}", "products" => [{ 'title' => 'Draft 151cm' }, { 'title' => 'Element 155cm' }])
+      "{% render 'product' for products %}",
+      { "products" => [{ 'title' => 'Draft 151cm' }, { 'title' => 'Element 155cm' }] },
+      partials: {
+        'product' => "Product: {{ product.title }} {% if forloop.first %}first{% endif %} {% if forloop.last %}last{% endif %} index:{{ forloop.index }} ",
+      })
   end
 
   def test_render_tag_for_drop
-    Liquid::Template.file_system = StubFileSystem.new(
-      'loop' => "{{ value.foo }}",
-    )
-
     assert_template_result("123",
-      "{% render 'loop' for loop as value %}", "loop" => TestEnumerable.new)
+      "{% render 'loop' for loop as value %}", { "loop" => TestEnumerable.new },
+      partials: {
+        'loop' => "{{ value.foo }}",
+      })
   end
 
   def test_render_tag_with_drop
-    Liquid::Template.file_system = StubFileSystem.new(
-      'loop' => "{{ value }}",
-    )
-
     assert_template_result("TestEnumerable",
-      "{% render 'loop' with loop as value %}", "loop" => TestEnumerable.new)
+      "{% render 'loop' with loop as value %}", { "loop" => TestEnumerable.new },
+      partials: {
+        'loop' => "{{ value }}",
+      })
   end
 end
diff --git a/test/integration/tags/standard_tag_test.rb b/test/integration/tags/standard_tag_test.rb
index ea7dca1..5ff425a 100644
--- a/test/integration/tags/standard_tag_test.rb
+++ b/test/integration/tags/standard_tag_test.rb
@@ -36,6 +36,8 @@ class StandardTagTest < Minitest::Test
     assert_template_result('', '{%comment%}{% endif %}{%endcomment%}')
     assert_template_result('', '{% comment %}{% endwhatever %}{% endcomment %}')
     assert_template_result('', '{% comment %}{% raw %} {{%%%%}}  }} { {% endcomment %} {% comment {% endraw %} {% endcomment %}')
+    assert_template_result('', '{% comment %}{% " %}{% endcomment %}')
+    assert_template_result('', '{% comment %}{%%}{% endcomment %}')
 
     assert_template_result('foobar', 'foo{%comment%}comment{%endcomment%}bar')
     assert_template_result('foobar', 'foo{% comment %}comment{% endcomment %}bar')
@@ -71,7 +73,7 @@ class StandardTagTest < Minitest::Test
     assert_raises(SyntaxError) do
       assert_template_result('content foo content foo ',
         '{{ var2 }}{% capture %}{{ var }} foo {% endcapture %}{{ var2 }}{{ var2 }}',
-        'var' => 'content')
+        { 'var' => 'content' })
     end
   end
 
@@ -120,38 +122,38 @@ class StandardTagTest < Minitest::Test
   end
 
   def test_case_on_size
-    assert_template_result('',  '{% case a.size %}{% when 1 %}1{% when 2 %}2{% endcase %}', 'a' => [])
-    assert_template_result('1', '{% case a.size %}{% when 1 %}1{% when 2 %}2{% endcase %}', 'a' => [1])
-    assert_template_result('2', '{% case a.size %}{% when 1 %}1{% when 2 %}2{% endcase %}', 'a' => [1, 1])
-    assert_template_result('',  '{% case a.size %}{% when 1 %}1{% when 2 %}2{% endcase %}', 'a' => [1, 1, 1])
-    assert_template_result('',  '{% case a.size %}{% when 1 %}1{% when 2 %}2{% endcase %}', 'a' => [1, 1, 1, 1])
-    assert_template_result('',  '{% case a.size %}{% when 1 %}1{% when 2 %}2{% endcase %}', 'a' => [1, 1, 1, 1, 1])
+    assert_template_result('',  '{% case a.size %}{% when 1 %}1{% when 2 %}2{% endcase %}', { 'a' => [] })
+    assert_template_result('1', '{% case a.size %}{% when 1 %}1{% when 2 %}2{% endcase %}', { 'a' => [1] })
+    assert_template_result('2', '{% case a.size %}{% when 1 %}1{% when 2 %}2{% endcase %}', { 'a' => [1, 1] })
+    assert_template_result('',  '{% case a.size %}{% when 1 %}1{% when 2 %}2{% endcase %}', { 'a' => [1, 1, 1] })
+    assert_template_result('',  '{% case a.size %}{% when 1 %}1{% when 2 %}2{% endcase %}', { 'a' => [1, 1, 1, 1] })
+    assert_template_result('',  '{% case a.size %}{% when 1 %}1{% when 2 %}2{% endcase %}', { 'a' => [1, 1, 1, 1, 1] })
   end
 
   def test_case_on_size_with_else
     assert_template_result('else',
       '{% case a.size %}{% when 1 %}1{% when 2 %}2{% else %}else{% endcase %}',
-      'a' => [])
+      { 'a' => [] })
 
     assert_template_result('1',
       '{% case a.size %}{% when 1 %}1{% when 2 %}2{% else %}else{% endcase %}',
-      'a' => [1])
+      { 'a' => [1] })
 
     assert_template_result('2',
       '{% case a.size %}{% when 1 %}1{% when 2 %}2{% else %}else{% endcase %}',
-      'a' => [1, 1])
+      { 'a' => [1, 1] })
 
     assert_template_result('else',
       '{% case a.size %}{% when 1 %}1{% when 2 %}2{% else %}else{% endcase %}',
-      'a' => [1, 1, 1])
+      { 'a' => [1, 1, 1] })
 
     assert_template_result('else',
       '{% case a.size %}{% when 1 %}1{% when 2 %}2{% else %}else{% endcase %}',
-      'a' => [1, 1, 1, 1])
+      { 'a' => [1, 1, 1, 1] })
 
     assert_template_result('else',
       '{% case a.size %}{% when 1 %}1{% when 2 %}2{% else %}else{% endcase %}',
-      'a' => [1, 1, 1, 1, 1])
+      { 'a' => [1, 1, 1, 1, 1] })
   end
 
   def test_case_on_length_with_else
@@ -174,48 +176,47 @@ class StandardTagTest < Minitest::Test
 
   def test_assign_from_case
     # Example from the shopify forums
-    code     = "{% case collection.handle %}{% when 'menswear-jackets' %}{% assign ptitle = 'menswear' %}{% when 'menswear-t-shirts' %}{% assign ptitle = 'menswear' %}{% else %}{% assign ptitle = 'womenswear' %}{% endcase %}{{ ptitle }}"
-    template = Liquid::Template.parse(code)
-    assert_equal("menswear",   template.render!("collection" => { 'handle' => 'menswear-jackets' }))
-    assert_equal("menswear",   template.render!("collection" => { 'handle' => 'menswear-t-shirts' }))
-    assert_equal("womenswear", template.render!("collection" => { 'handle' => 'x' }))
-    assert_equal("womenswear", template.render!("collection" => { 'handle' => 'y' }))
-    assert_equal("womenswear", template.render!("collection" => { 'handle' => 'z' }))
+    code = "{% case collection.handle %}{% when 'menswear-jackets' %}{% assign ptitle = 'menswear' %}{% when 'menswear-t-shirts' %}{% assign ptitle = 'menswear' %}{% else %}{% assign ptitle = 'womenswear' %}{% endcase %}{{ ptitle }}"
+    assert_template_result("menswear",   code, { "collection" => { 'handle' => 'menswear-jackets' } })
+    assert_template_result("menswear",   code, { "collection" => { 'handle' => 'menswear-t-shirts' } })
+    assert_template_result("womenswear", code, { "collection" => { 'handle' => 'x' } })
+    assert_template_result("womenswear", code, { "collection" => { 'handle' => 'y' } })
+    assert_template_result("womenswear", code, { "collection" => { 'handle' => 'z' } })
   end
 
   def test_case_when_or
     code = '{% case condition %}{% when 1 or 2 or 3 %} its 1 or 2 or 3 {% when 4 %} its 4 {% endcase %}'
-    assert_template_result(' its 1 or 2 or 3 ', code, 'condition' => 1)
-    assert_template_result(' its 1 or 2 or 3 ', code, 'condition' => 2)
-    assert_template_result(' its 1 or 2 or 3 ', code, 'condition' => 3)
-    assert_template_result(' its 4 ', code, 'condition' => 4)
-    assert_template_result('', code, 'condition' => 5)
+    assert_template_result(' its 1 or 2 or 3 ', code, { 'condition' => 1 })
+    assert_template_result(' its 1 or 2 or 3 ', code, { 'condition' => 2 })
+    assert_template_result(' its 1 or 2 or 3 ', code, { 'condition' => 3 })
+    assert_template_result(' its 4 ', code, { 'condition' => 4 })
+    assert_template_result('', code, { 'condition' => 5 })
 
     code = '{% case condition %}{% when 1 or "string" or null %} its 1 or 2 or 3 {% when 4 %} its 4 {% endcase %}'
-    assert_template_result(' its 1 or 2 or 3 ', code, 'condition' => 1)
-    assert_template_result(' its 1 or 2 or 3 ', code, 'condition' => 'string')
-    assert_template_result(' its 1 or 2 or 3 ', code, 'condition' => nil)
-    assert_template_result('', code, 'condition' => 'something else')
+    assert_template_result(' its 1 or 2 or 3 ', code, { 'condition' => 1 })
+    assert_template_result(' its 1 or 2 or 3 ', code, { 'condition' => 'string' })
+    assert_template_result(' its 1 or 2 or 3 ', code, { 'condition' => nil })
+    assert_template_result('', code, { 'condition' => 'something else' })
   end
 
   def test_case_when_comma
     code = '{% case condition %}{% when 1, 2, 3 %} its 1 or 2 or 3 {% when 4 %} its 4 {% endcase %}'
-    assert_template_result(' its 1 or 2 or 3 ', code, 'condition' => 1)
-    assert_template_result(' its 1 or 2 or 3 ', code, 'condition' => 2)
-    assert_template_result(' its 1 or 2 or 3 ', code, 'condition' => 3)
-    assert_template_result(' its 4 ', code, 'condition' => 4)
-    assert_template_result('', code, 'condition' => 5)
+    assert_template_result(' its 1 or 2 or 3 ', code, { 'condition' => 1 })
+    assert_template_result(' its 1 or 2 or 3 ', code, { 'condition' => 2 })
+    assert_template_result(' its 1 or 2 or 3 ', code, { 'condition' => 3 })
+    assert_template_result(' its 4 ', code, { 'condition' => 4 })
+    assert_template_result('', code, { 'condition' => 5 })
 
     code = '{% case condition %}{% when 1, "string", null %} its 1 or 2 or 3 {% when 4 %} its 4 {% endcase %}'
-    assert_template_result(' its 1 or 2 or 3 ', code, 'condition' => 1)
-    assert_template_result(' its 1 or 2 or 3 ', code, 'condition' => 'string')
-    assert_template_result(' its 1 or 2 or 3 ', code, 'condition' => nil)
-    assert_template_result('', code, 'condition' => 'something else')
+    assert_template_result(' its 1 or 2 or 3 ', code, { 'condition' => 1 })
+    assert_template_result(' its 1 or 2 or 3 ', code, { 'condition' => 'string' })
+    assert_template_result(' its 1 or 2 or 3 ', code, { 'condition' => nil })
+    assert_template_result('', code, { 'condition' => 'something else' })
   end
 
   def test_case_when_comma_and_blank_body
     code = '{% case condition %}{% when 1, 2 %} {% assign r = "result" %} {% endcase %}{{ r }}'
-    assert_template_result('result', code, 'condition' => 2)
+    assert_template_result('result', code, { 'condition' => 2 })
   end
 
   def test_assign
diff --git a/test/integration/tags/statements_test.rb b/test/integration/tags/statements_test.rb
index d1c55c9..f8cd3ab 100644
--- a/test/integration/tags/statements_test.rb
+++ b/test/integration/tags/statements_test.rb
@@ -60,22 +60,22 @@ class StatementsTest < Minitest::Test
 
   def test_var_strings_equal
     text = ' {% if var == "hello there!" %} true {% else %} false {% endif %} '
-    assert_template_result('  true  ', text, 'var' => 'hello there!')
+    assert_template_result('  true  ', text, { 'var' => 'hello there!' })
   end
 
   def test_var_strings_are_not_equal
     text = ' {% if "hello there!" == var %} true {% else %} false {% endif %} '
-    assert_template_result('  true  ', text, 'var' => 'hello there!')
+    assert_template_result('  true  ', text, { 'var' => 'hello there!' })
   end
 
   def test_var_and_long_string_are_equal
     text = " {% if var == 'hello there!' %} true {% else %} false {% endif %} "
-    assert_template_result('  true  ', text, 'var' => 'hello there!')
+    assert_template_result('  true  ', text, { 'var' => 'hello there!' })
   end
 
   def test_var_and_long_string_are_equal_backwards
     text = " {% if 'hello there!' == var %} true {% else %} false {% endif %} "
-    assert_template_result('  true  ', text, 'var' => 'hello there!')
+    assert_template_result('  true  ', text, { 'var' => 'hello there!' })
   end
 
   # def test_is_nil
@@ -87,27 +87,27 @@ class StatementsTest < Minitest::Test
 
   def test_is_collection_empty
     text = ' {% if array == empty %} true {% else %} false {% endif %} '
-    assert_template_result('  true  ', text, 'array' => [])
+    assert_template_result('  true  ', text, { 'array' => [] })
   end
 
   def test_is_not_collection_empty
     text = ' {% if array == empty %} true {% else %} false {% endif %} '
-    assert_template_result('  false  ', text, 'array' => [1, 2, 3])
+    assert_template_result('  false  ', text, { 'array' => [1, 2, 3] })
   end
 
   def test_nil
     text = ' {% if var == nil %} true {% else %} false {% endif %} '
-    assert_template_result('  true  ', text, 'var' => nil)
+    assert_template_result('  true  ', text, { 'var' => nil })
 
     text = ' {% if var == null %} true {% else %} false {% endif %} '
-    assert_template_result('  true  ', text, 'var' => nil)
+    assert_template_result('  true  ', text, { 'var' => nil })
   end
 
   def test_not_nil
     text = ' {% if var != nil %} true {% else %} false {% endif %} '
-    assert_template_result('  true  ', text, 'var' => 1)
+    assert_template_result('  true  ', text, { 'var' => 1 })
 
     text = ' {% if var != null %} true {% else %} false {% endif %} '
-    assert_template_result('  true  ', text, 'var' => 1)
+    assert_template_result('  true  ', text, { 'var' => 1 })
   end
 end # StatementsTest
diff --git a/test/integration/tags/table_row_test.rb b/test/integration/tags/table_row_test.rb
index 71df4f3..5a7d1db 100644
--- a/test/integration/tags/table_row_test.rb
+++ b/test/integration/tags/table_row_test.rb
@@ -20,47 +20,161 @@ class TableRowTest < Minitest::Test
   def test_table_row
     assert_template_result("<tr class=\"row1\">\n<td class=\"col1\"> 1 </td><td class=\"col2\"> 2 </td><td class=\"col3\"> 3 </td></tr>\n<tr class=\"row2\"><td class=\"col1\"> 4 </td><td class=\"col2\"> 5 </td><td class=\"col3\"> 6 </td></tr>\n",
       '{% tablerow n in numbers cols:3%} {{n}} {% endtablerow %}',
-      'numbers' => [1, 2, 3, 4, 5, 6])
+      { 'numbers' => [1, 2, 3, 4, 5, 6] })
 
     assert_template_result("<tr class=\"row1\">\n</tr>\n",
       '{% tablerow n in numbers cols:3%} {{n}} {% endtablerow %}',
-      'numbers' => [])
+      { 'numbers' => [] })
   end
 
   def test_table_row_with_different_cols
     assert_template_result("<tr class=\"row1\">\n<td class=\"col1\"> 1 </td><td class=\"col2\"> 2 </td><td class=\"col3\"> 3 </td><td class=\"col4\"> 4 </td><td class=\"col5\"> 5 </td></tr>\n<tr class=\"row2\"><td class=\"col1\"> 6 </td></tr>\n",
       '{% tablerow n in numbers cols:5%} {{n}} {% endtablerow %}',
-      'numbers' => [1, 2, 3, 4, 5, 6])
+      { 'numbers' => [1, 2, 3, 4, 5, 6] })
   end
 
   def test_table_col_counter
     assert_template_result("<tr class=\"row1\">\n<td class=\"col1\">1</td><td class=\"col2\">2</td></tr>\n<tr class=\"row2\"><td class=\"col1\">1</td><td class=\"col2\">2</td></tr>\n<tr class=\"row3\"><td class=\"col1\">1</td><td class=\"col2\">2</td></tr>\n",
       '{% tablerow n in numbers cols:2%}{{tablerowloop.col}}{% endtablerow %}',
-      'numbers' => [1, 2, 3, 4, 5, 6])
+      { 'numbers' => [1, 2, 3, 4, 5, 6] })
   end
 
   def test_quoted_fragment
     assert_template_result("<tr class=\"row1\">\n<td class=\"col1\"> 1 </td><td class=\"col2\"> 2 </td><td class=\"col3\"> 3 </td></tr>\n<tr class=\"row2\"><td class=\"col1\"> 4 </td><td class=\"col2\"> 5 </td><td class=\"col3\"> 6 </td></tr>\n",
       "{% tablerow n in collections.frontpage cols:3%} {{n}} {% endtablerow %}",
-      'collections' => { 'frontpage' => [1, 2, 3, 4, 5, 6] })
+      { 'collections' => { 'frontpage' => [1, 2, 3, 4, 5, 6] } })
     assert_template_result("<tr class=\"row1\">\n<td class=\"col1\"> 1 </td><td class=\"col2\"> 2 </td><td class=\"col3\"> 3 </td></tr>\n<tr class=\"row2\"><td class=\"col1\"> 4 </td><td class=\"col2\"> 5 </td><td class=\"col3\"> 6 </td></tr>\n",
       "{% tablerow n in collections['frontpage'] cols:3%} {{n}} {% endtablerow %}",
-      'collections' => { 'frontpage' => [1, 2, 3, 4, 5, 6] })
+      { 'collections' => { 'frontpage' => [1, 2, 3, 4, 5, 6] } })
   end
 
   def test_enumerable_drop
     assert_template_result("<tr class=\"row1\">\n<td class=\"col1\"> 1 </td><td class=\"col2\"> 2 </td><td class=\"col3\"> 3 </td></tr>\n<tr class=\"row2\"><td class=\"col1\"> 4 </td><td class=\"col2\"> 5 </td><td class=\"col3\"> 6 </td></tr>\n",
       '{% tablerow n in numbers cols:3%} {{n}} {% endtablerow %}',
-      'numbers' => ArrayDrop.new([1, 2, 3, 4, 5, 6]))
+      { 'numbers' => ArrayDrop.new([1, 2, 3, 4, 5, 6]) })
   end
 
   def test_offset_and_limit
     assert_template_result("<tr class=\"row1\">\n<td class=\"col1\"> 1 </td><td class=\"col2\"> 2 </td><td class=\"col3\"> 3 </td></tr>\n<tr class=\"row2\"><td class=\"col1\"> 4 </td><td class=\"col2\"> 5 </td><td class=\"col3\"> 6 </td></tr>\n",
       '{% tablerow n in numbers cols:3 offset:1 limit:6%} {{n}} {% endtablerow %}',
-      'numbers' => [0, 1, 2, 3, 4, 5, 6, 7])
+      { 'numbers' => [0, 1, 2, 3, 4, 5, 6, 7] })
   end
 
   def test_blank_string_not_iterable
-    assert_template_result("<tr class=\"row1\">\n</tr>\n", "{% tablerow char in characters cols:3 %}I WILL NOT BE OUTPUT{% endtablerow %}", 'characters' => '')
+    assert_template_result("<tr class=\"row1\">\n</tr>\n",
+      "{% tablerow char in characters cols:3 %}I WILL NOT BE OUTPUT{% endtablerow %}",
+      { 'characters' => '' })
+  end
+
+  def test_cols_nil_constant_same_as_evaluated_nil_expression
+    expect = "<tr class=\"row1\">\n" \
+      "<td class=\"col1\">false</td>" \
+      "<td class=\"col2\">false</td>" \
+      "</tr>\n"
+
+    assert_template_result(expect,
+      "{% tablerow i in (1..2) cols:nil %}{{ tablerowloop.col_last }}{% endtablerow %}")
+
+    assert_template_result(expect,
+      "{% tablerow i in (1..2) cols:var %}{{ tablerowloop.col_last }}{% endtablerow %}",
+      { "var" => nil })
+  end
+
+  def test_nil_limit_is_treated_as_zero
+    expect = "<tr class=\"row1\">\n" \
+      "</tr>\n"
+
+    assert_template_result(expect,
+      "{% tablerow i in (1..2) limit:nil %}{{ i }}{% endtablerow %}")
+
+    assert_template_result(expect,
+      "{% tablerow i in (1..2) limit:var %}{{ i }}{% endtablerow %}",
+      { "var" => nil })
+  end
+
+  def test_nil_offset_is_treated_as_zero
+    expect = "<tr class=\"row1\">\n" \
+      "<td class=\"col1\">1:false</td>" \
+      "<td class=\"col2\">2:true</td>" \
+      "</tr>\n"
+
+    assert_template_result(expect,
+      "{% tablerow i in (1..2) offset:nil %}{{ i }}:{{ tablerowloop.col_last }}{% endtablerow %}")
+
+    assert_template_result(expect,
+      "{% tablerow i in (1..2) offset:var %}{{ i }}:{{ tablerowloop.col_last }}{% endtablerow %}",
+      { "var" => nil })
+  end
+
+  def test_tablerow_loop_drop_attributes
+    template = <<~LIQUID.chomp
+      {% tablerow i in (1...2) %}
+      col: {{ tablerowloop.col }}
+      col0: {{ tablerowloop.col0 }}
+      col_first: {{ tablerowloop.col_first }}
+      col_last: {{ tablerowloop.col_last }}
+      first: {{ tablerowloop.first }}
+      index: {{ tablerowloop.index }}
+      index0: {{ tablerowloop.index0 }}
+      last: {{ tablerowloop.last }}
+      length: {{ tablerowloop.length }}
+      rindex: {{ tablerowloop.rindex }}
+      rindex0: {{ tablerowloop.rindex0 }}
+      row: {{ tablerowloop.row }}
+      {% endtablerow %}
+    LIQUID
+
+    expected_output = <<~OUTPUT
+      <tr class="row1">
+      <td class="col1">
+      col: 1
+      col0: 0
+      col_first: true
+      col_last: false
+      first: true
+      index: 1
+      index0: 0
+      last: false
+      length: 2
+      rindex: 2
+      rindex0: 1
+      row: 1
+      </td><td class="col2">
+      col: 2
+      col0: 1
+      col_first: false
+      col_last: true
+      first: false
+      index: 2
+      index0: 1
+      last: true
+      length: 2
+      rindex: 1
+      rindex0: 0
+      row: 1
+      </td></tr>
+    OUTPUT
+
+    assert_template_result(expected_output, template)
+  end
+
+  def test_table_row_renders_correct_error_message_for_invalid_parameters
+    assert_template_result(
+      "Liquid error (line 1): invalid integer",
+      '{% tablerow n in (1...10) limit:true %} {{n}} {% endtablerow %}',
+      render_errors: true,
+    )
+
+    assert_template_result(
+      "Liquid error (line 1): invalid integer",
+      '{% tablerow n in (1...10) offset:true %} {{n}} {% endtablerow %}',
+      render_errors: true,
+    )
+
+    assert_template_result(
+      "Liquid error (line 1): invalid integer",
+      '{% tablerow n in (1...10) cols:true %} {{n}} {% endtablerow %}',
+      render_errors: true,
+    )
   end
 end
diff --git a/test/integration/tags/unless_else_tag_test.rb b/test/integration/tags/unless_else_tag_test.rb
index f5c9c7f..174fb79 100644
--- a/test/integration/tags/unless_else_tag_test.rb
+++ b/test/integration/tags/unless_else_tag_test.rb
@@ -19,10 +19,10 @@ class UnlessElseTagTest < Minitest::Test
   end
 
   def test_unless_in_loop
-    assert_template_result('23', '{% for i in choices %}{% unless i %}{{ forloop.index }}{% endunless %}{% endfor %}', 'choices' => [1, nil, false])
+    assert_template_result('23', '{% for i in choices %}{% unless i %}{{ forloop.index }}{% endunless %}{% endfor %}', { 'choices' => [1, nil, false] })
   end
 
   def test_unless_else_in_loop
-    assert_template_result(' TRUE  2  3 ', '{% for i in choices %}{% unless i %} {{ forloop.index }} {% else %} TRUE {% endunless %}{% endfor %}', 'choices' => [1, nil, false])
+    assert_template_result(' TRUE  2  3 ', '{% for i in choices %}{% unless i %} {{ forloop.index }} {% else %} TRUE {% endunless %}{% endfor %}', { 'choices' => [1, nil, false] })
   end
 end # UnlessElseTest
diff --git a/test/integration/template_test.rb b/test/integration/template_test.rb
index 9790639..d3f6356 100644
--- a/test/integration/template_test.rb
+++ b/test/integration/template_test.rb
@@ -315,13 +315,11 @@ class TemplateTest < Minitest::Test
   end
 
   def test_using_range_literal_works_as_expected
-    t = Template.parse("{% assign foo = (x..y) %}{{ foo }}")
-    result = t.render('x' => 1, 'y' => 5)
-    assert_equal('1..5', result)
+    source = "{% assign foo = (x..y) %}{{ foo }}"
+    assert_template_result("1..5", source, { "x" => 1, "y" => 5 })
 
-    t = Template.parse("{% assign nums = (x..y) %}{% for num in nums %}{{ num }}{% endfor %}")
-    result = t.render('x' => 1, 'y' => 5)
-    assert_equal('12345', result)
+    source = "{% assign nums = (x..y) %}{% for num in nums %}{{ num }}{% endfor %}"
+    assert_template_result("12345", source, { "x" => 1, "y" => 5 })
   end
 
   def test_source_string_subclass
diff --git a/test/integration/trim_mode_test.rb b/test/integration/trim_mode_test.rb
index 4f555b6..a0a5908 100644
--- a/test/integration/trim_mode_test.rb
+++ b/test/integration/trim_mode_test.rb
@@ -530,14 +530,9 @@ class TrimModeTest < Minitest::Test
   end
 
   def test_pre_trim_blank_preceding_text
-    template = Liquid::Template.parse("\n{%- raw %}{% endraw %}")
-    assert_equal("", template.render)
-
-    template = Liquid::Template.parse("\n{%- if true %}{% endif %}")
-    assert_equal("", template.render)
-
-    template = Liquid::Template.parse("{{ 'B' }} \n{%- if true %}C{% endif %}")
-    assert_equal("BC", template.render)
+    assert_template_result("", "\n{%- raw %}{% endraw %}")
+    assert_template_result("", "\n{%- if true %}{% endif %}")
+    assert_template_result("BC", "{{ 'B' }} \n{%- if true %}C{% endif %}")
   end
 
   def test_bug_compatible_pre_trim
diff --git a/test/integration/variable_test.rb b/test/integration/variable_test.rb
index 5c87550..c007f1f 100644
--- a/test/integration/variable_test.rb
+++ b/test/integration/variable_test.rb
@@ -6,81 +6,81 @@ class VariableTest < Minitest::Test
   include Liquid
 
   def test_simple_variable
-    template = Template.parse(%({{test}}))
-    assert_equal('worked', template.render!('test' => 'worked'))
-    assert_equal('worked wonderfully', template.render!('test' => 'worked wonderfully'))
+    assert_template_result('worked', "{{test}}", { 'test' => 'worked' })
+    assert_template_result('worked wonderfully', "{{test}}", { 'test' => 'worked wonderfully' })
   end
 
   def test_variable_render_calls_to_liquid
-    assert_template_result('foobar', '{{ foo }}', 'foo' => ThingWithToLiquid.new)
+    assert_template_result('foobar', '{{ foo }}', { 'foo' => ThingWithToLiquid.new })
   end
 
   def test_variable_lookup_calls_to_liquid_value
-    assert_template_result('1', '{{ foo }}', 'foo' => IntegerDrop.new('1'))
-    assert_template_result('2', '{{ list[foo] }}', 'foo' => IntegerDrop.new('1'), 'list' => [1, 2, 3])
-    assert_template_result('one', '{{ list[foo] }}', 'foo' => IntegerDrop.new('1'), 'list' => { 1 => 'one' })
-    assert_template_result('Yay', '{{ foo }}', 'foo' => BooleanDrop.new(true))
-    assert_template_result('YAY', '{{ foo | upcase }}', 'foo' => BooleanDrop.new(true))
+    assert_template_result('1', '{{ foo }}', { 'foo' => IntegerDrop.new('1') })
+    assert_template_result('2', '{{ list[foo] }}', { 'foo' => IntegerDrop.new('1'), 'list' => [1, 2, 3] })
+    assert_template_result('one', '{{ list[foo] }}', { 'foo' => IntegerDrop.new('1'), 'list' => { 1 => 'one' } })
+    assert_template_result('Yay', '{{ foo }}', { 'foo' => BooleanDrop.new(true) })
+    assert_template_result('YAY', '{{ foo | upcase }}', { 'foo' => BooleanDrop.new(true) })
   end
 
   def test_if_tag_calls_to_liquid_value
-    assert_template_result('one', '{% if foo == 1 %}one{% endif %}', 'foo' => IntegerDrop.new('1'))
-    assert_template_result('one', '{% if 0 < foo %}one{% endif %}', 'foo' => IntegerDrop.new('1'))
-    assert_template_result('one', '{% if foo > 0 %}one{% endif %}', 'foo' => IntegerDrop.new('1'))
-    assert_template_result('true', '{% if foo == true %}true{% endif %}', 'foo' => BooleanDrop.new(true))
-    assert_template_result('true', '{% if foo %}true{% endif %}', 'foo' => BooleanDrop.new(true))
+    assert_template_result('one', '{% if foo == 1 %}one{% endif %}', { 'foo' => IntegerDrop.new('1') })
+    assert_template_result('one', '{% if foo == eqv %}one{% endif %}', { 'foo' => IntegerDrop.new(1), 'eqv' => IntegerDrop.new(1) })
+    assert_template_result('one', '{% if 0 < foo %}one{% endif %}', { 'foo' => IntegerDrop.new('1') })
+    assert_template_result('one', '{% if foo > 0 %}one{% endif %}', { 'foo' => IntegerDrop.new('1') })
+    assert_template_result('one', '{% if b > a %}one{% endif %}', { 'b' => IntegerDrop.new(1), 'a' => IntegerDrop.new(0) })
+    assert_template_result('true', '{% if foo == true %}true{% endif %}', { 'foo' => BooleanDrop.new(true) })
+    assert_template_result('true', '{% if foo %}true{% endif %}', { 'foo' => BooleanDrop.new(true) })
 
-    assert_template_result('', '{% if foo %}true{% endif %}', 'foo' => BooleanDrop.new(false))
-    assert_template_result('', '{% if foo == true %}True{% endif %}', 'foo' => BooleanDrop.new(false))
+    assert_template_result('', '{% if foo %}true{% endif %}', { 'foo' => BooleanDrop.new(false) })
+    assert_template_result('', '{% if foo == true %}True{% endif %}', { 'foo' => BooleanDrop.new(false) })
+
+    assert_template_result('one', '{% if a contains x %}one{% endif %}', { 'a' => [1], 'x' => IntegerDrop.new(1) })
   end
 
   def test_unless_tag_calls_to_liquid_value
-    assert_template_result('', '{% unless foo %}true{% endunless %}', 'foo' => BooleanDrop.new(true))
+    assert_template_result('', '{% unless foo %}true{% endunless %}', { 'foo' => BooleanDrop.new(true) })
+    assert_template_result('true', '{% unless foo %}true{% endunless %}', { 'foo' => BooleanDrop.new(false) })
   end
 
   def test_case_tag_calls_to_liquid_value
-    assert_template_result('One', '{% case foo %}{% when 1 %}One{% endcase %}', 'foo' => IntegerDrop.new('1'))
+    assert_template_result('One', '{% case foo %}{% when 1 %}One{% endcase %}', { 'foo' => IntegerDrop.new('1') })
   end
 
   def test_simple_with_whitespaces
-    template = Template.parse(%(  {{ test }}  ))
-    assert_equal('  worked  ', template.render!('test' => 'worked'))
-    assert_equal('  worked wonderfully  ', template.render!('test' => 'worked wonderfully'))
+    assert_template_result("  worked  ", "  {{ test }}  ", { "test" => "worked" })
+    assert_template_result("  worked wonderfully  ", "  {{ test }}  ", { "test" => "worked wonderfully" })
   end
 
   def test_expression_with_whitespace_in_square_brackets
-    assert_template_result('result', "{{ a[ 'b' ] }}", 'a' => { 'b' => 'result' })
-    assert_template_result('result', "{{ a[ [ 'b' ] ] }}", 'b' => 'c', 'a' => { 'c' => 'result' })
+    assert_template_result('result', "{{ a[ 'b' ] }}", { 'a' => { 'b' => 'result' } })
+    assert_template_result('result', "{{ a[ [ 'b' ] ] }}", { 'b' => 'c', 'a' => { 'c' => 'result' } })
   end
 
   def test_ignore_unknown
-    template = Template.parse(%({{ test }}))
-    assert_equal('', template.render!)
+    assert_template_result("", "{{ test }}")
   end
 
   def test_using_blank_as_variable_name
-    template = Template.parse("{% assign foo = blank %}{{ foo }}")
-    assert_equal('', template.render!)
+    assert_template_result("", "{% assign foo = blank %}{{ foo }}")
   end
 
   def test_using_empty_as_variable_name
-    template = Template.parse("{% assign foo = empty %}{{ foo }}")
-    assert_equal('', template.render!)
+    assert_template_result("", "{% assign foo = empty %}{{ foo }}")
   end
 
   def test_hash_scoping
-    assert_template_result('worked', "{{ test.test }}", 'test' => { 'test' => 'worked' })
-    assert_template_result('worked', "{{ test . test }}", 'test' => { 'test' => 'worked' })
+    assert_template_result('worked', "{{ test.test }}", { 'test' => { 'test' => 'worked' } })
+    assert_template_result('worked', "{{ test . test }}", { 'test' => { 'test' => 'worked' } })
   end
 
   def test_false_renders_as_false
-    assert_equal('false', Template.parse("{{ foo }}").render!('foo' => false))
-    assert_equal('false', Template.parse("{{ false }}").render!)
+    assert_template_result("false", "{{ foo }}", { 'foo' => false })
+    assert_template_result("false", "{{ false }}")
   end
 
   def test_nil_renders_as_empty_string
-    assert_equal('', Template.parse("{{ nil }}").render!)
-    assert_equal('cat', Template.parse("{{ nil | append: 'cat' }}").render!)
+    assert_template_result("", "{{ nil }}")
+    assert_template_result("cat", "{{ nil | append: 'cat' }}")
   end
 
   def test_preset_assigns
@@ -121,18 +121,18 @@ class VariableTest < Minitest::Test
   end
 
   def test_multiline_variable
-    assert_equal('worked', Template.parse("{{\ntest\n}}").render!('test' => 'worked'))
+    assert_template_result("worked", "{{\ntest\n}}", { "test" => "worked" })
   end
 
   def test_render_symbol
-    assert_template_result('bar', '{{ foo }}', 'foo' => :bar)
+    assert_template_result('bar', '{{ foo }}', { 'foo' => :bar })
   end
 
   def test_dynamic_find_var
-    assert_template_result('bar', '{{ [key] }}', 'key' => 'foo', 'foo' => 'bar')
+    assert_template_result('bar', '{{ [key] }}', { 'key' => 'foo', 'foo' => 'bar' })
   end
 
   def test_raw_value_variable
-    assert_template_result('bar', '{{ [key] }}', 'key' => 'foo', 'foo' => 'bar')
+    assert_template_result('bar', '{{ [key] }}', { 'key' => 'foo', 'foo' => 'bar' })
   end
 end
diff --git a/test/test_helper.rb b/test/test_helper.rb
index 874dd8f..49b1cc7 100755
--- a/test/test_helper.rb
+++ b/test/test_helper.rb
@@ -37,23 +37,29 @@ module Minitest
   module Assertions
     include Liquid
 
-    def assert_template_result(expected, template, assigns = {}, message = nil)
-      assert_equal(expected, Template.parse(template, line_numbers: true).render!(assigns), message)
+    def assert_template_result(
+      expected, template, assigns = {},
+      message: nil, partials: nil, error_mode: nil, render_errors: false
+    )
+      template = Liquid::Template.parse(template, line_numbers: true, error_mode: error_mode&.to_sym)
+      file_system = StubFileSystem.new(partials || {})
+      registers = Liquid::Registers.new(file_system: file_system)
+      context = Liquid::Context.build(static_environments: assigns, rethrow_errors: !render_errors, registers: registers)
+      output = template.render(context)
+      assert_equal(expected, output, message)
     end
 
-    def assert_template_result_matches(expected, template, assigns = {}, message = nil)
-      return assert_template_result(expected, template, assigns, message) unless expected.is_a?(Regexp)
-
-      assert_match(expected, Template.parse(template, line_numbers: true).render!(assigns), message)
-    end
-
-    def assert_match_syntax_error(match, template, assigns = {})
+    def assert_match_syntax_error(match, template, error_mode: nil)
       exception = assert_raises(Liquid::SyntaxError) do
-        Template.parse(template, line_numbers: true).render(assigns)
+        Template.parse(template, line_numbers: true, error_mode: error_mode&.to_sym).render
       end
       assert_match(match, exception.message)
     end
 
+    def assert_syntax_error(template, error_mode: nil)
+      assert_match_syntax_error("", template, error_mode: error_mode)
+    end
+
     def assert_usage_increment(name, times: 1)
       old_method = Liquid::Usage.method(:increment)
       calls = 0
@@ -125,10 +131,6 @@ class IntegerDrop < Liquid::Drop
     @value = value.to_i
   end
 
-  def ==(other)
-    @value == other
-  end
-
   def to_s
     @value.to_s
   end
@@ -144,10 +146,6 @@ class BooleanDrop < Liquid::Drop
     @value = value
   end
 
-  def ==(other)
-    @value == other
-  end
-
   def to_liquid_value
     @value
   end
diff --git a/test/unit/tags/for_tag_unit_test.rb b/test/unit/tags/for_tag_unit_test.rb
index 5a52c71..cbb68fc 100644
--- a/test/unit/tags/for_tag_unit_test.rb
+++ b/test/unit/tags/for_tag_unit_test.rb
@@ -12,4 +12,34 @@ class ForTagUnitTest < Minitest::Test
     template = Liquid::Template.parse('{% for item in items %}FOR{% else %}ELSE{% endfor %}')
     assert_equal(['FOR', 'ELSE'], template.root.nodelist[0].nodelist.map(&:nodelist).flatten)
   end
+
+  def test_for_string_slice_bug_usage
+    template = Liquid::Template.parse("{% for x in str, offset: 1 %}{{ x }},{% endfor %}")
+    assert_usage("string_slice_bug") do
+      assert_equal("abc,", template.render({ "str" => "abc" }))
+    end
+  end
+
+  def test_for_string_0_limit_usage
+    template = Liquid::Template.parse("{% for x in str, limit: 0 %}{{ x }},{% endfor %}")
+    assert_usage("string_slice_bug") do
+      assert_equal("abc,", template.render({ "str" => "abc" }))
+    end
+  end
+
+  def test_for_string_no_slice_usage
+    template = Liquid::Template.parse("{% for x in str, offset: 0, limit: 1 %}{{ x }},{% endfor %}")
+    assert_usage("string_slice_bug", times: 0) do
+      assert_equal("abc,", template.render({ "str" => "abc" }))
+    end
+  end
+
+  private
+
+  def assert_usage(name, times: 1, &block)
+    count = 0
+    result = Liquid::Usage.stub(:increment, ->(n) { count += 1 if n == name }, &block)
+    assert_equal(times, count)
+    result
+  end
 end

Debdiff

File lists identical (after any substitutions)

No differences were encountered in the control files

More details

Full run details