Back to Repositories

Validating Test Driver Operations in Fluentd

This test suite validates the functionality of Fluentd’s test drivers for various plugin types including input, output, filter, and formatter plugins. It ensures proper behavior of test driver operations, timeout handling, and error propagation.

Test Coverage Overview

The test suite provides comprehensive coverage of Fluentd’s test driver functionality across different plugin types.

  • Tests return value handling from test driver run blocks
  • Validates timeout behavior (both hard and soft timeouts)
  • Verifies error propagation from plugin threads
  • Covers multiple plugin types: Input, MultiOutput, Parser, Formatter, Filter

Implementation Analysis

The testing approach utilizes Ruby’s Test::Unit framework with sub-test cases for modular organization. It employs data-driven testing patterns to validate similar functionality across different plugin types.

  • Uses Timecop for time-dependent tests
  • Implements test helpers and setup methods
  • Employs dynamic class creation for plugin testing
  • Utilizes block-based test execution patterns

Technical Details

  • Testing Framework: Test::Unit
  • Helper Libraries: Timecop
  • Test Drivers: Input, Output, MultiOutput, Parser, Formatter, Filter
  • Runtime Features: Process clock monitoring, thread creation
  • Setup Requirements: Fluent::Test.setup initialization

Best Practices Demonstrated

The test suite exemplifies several testing best practices in Ruby and Fluentd plugin development.

  • Modular test organization using sub_test_case
  • Comprehensive error handling validation
  • Isolation of time-dependent tests
  • Proper cleanup and resource management
  • Clear test case naming and organization

fluent/fluentd

test/test_test_drivers.rb

            
require_relative 'helper'
require 'fluent/plugin/input'
require 'fluent/test/driver/input'
require 'fluent/plugin/output'
require 'fluent/test/driver/output'
require 'fluent/plugin/filter'
require 'fluent/test/driver/filter'
require 'fluent/plugin/multi_output'
require 'fluent/test/driver/multi_output'
require 'fluent/plugin/parser'
require 'fluent/test/driver/parser'
require 'fluent/plugin/formatter'
require 'fluent/test/driver/formatter'

require 'timecop'

class TestDriverTest < ::Test::Unit::TestCase
  def setup
    Fluent::Test.setup
  end

  sub_test_case 'plugin test driver' do
    data(
      'input plugin test driver'  => [Fluent::Test::Driver::Input, Fluent::Plugin::Input],
      'multi_output plugin test driver' => [Fluent::Test::Driver::MultiOutput, Fluent::Plugin::MultiOutput],
      'parser plugin test driver'       => [Fluent::Test::Driver::Parser, Fluent::Plugin::Parser],
      'formatter plugin test driver'    => [Fluent::Test::Driver::Formatter, Fluent::Plugin::Formatter],
    )
    test 'returns the block value as the return value of #run' do |args|
      driver_class, plugin_class = args
      d = driver_class.new(Class.new(plugin_class))
      v = d.run do
        x = 1 + 2
        y = 2 + 4
        3 || x || y
      end
      assert_equal 3, v
    end

    data(
      'input plugin test driver'  => [Fluent::Test::Driver::Input, Fluent::Plugin::Input],
      'multi_output plugin test driver' => [Fluent::Test::Driver::MultiOutput, Fluent::Plugin::MultiOutput],
      'parser plugin test driver'       => [Fluent::Test::Driver::Parser, Fluent::Plugin::Parser],
      'formatter plugin test driver'    => [Fluent::Test::Driver::Formatter, Fluent::Plugin::Formatter],
    )
    test 'raises error for hard timeout' do |args|
      driver_class, plugin_class = args
      d = driver_class.new(Class.new(plugin_class))
      assert_raise Fluent::Test::Driver::TestTimedOut do
        d.run(timeout: 0.5) do
          sleep 2
        end
      end
    end

    data(
      'input plugin test driver'  => [Fluent::Test::Driver::Input, Fluent::Plugin::Input],
      'multi_output plugin test driver' => [Fluent::Test::Driver::MultiOutput, Fluent::Plugin::MultiOutput],
      'parser plugin test driver'       => [Fluent::Test::Driver::Parser, Fluent::Plugin::Parser],
      'formatter plugin test driver'    => [Fluent::Test::Driver::Formatter, Fluent::Plugin::Formatter],
    )
    test 'can stop with soft timeout for blocks never stops, even with Timecop' do |args|
      Timecop.freeze(Time.parse("2016-11-04 18:49:00"))
      begin
        driver_class, plugin_class = args
        d = driver_class.new(Class.new(plugin_class))
        assert_nothing_raised do
          before = Process.clock_gettime(Process::CLOCK_MONOTONIC)
          d.end_if{ false }
          d.run(timeout: 1) do
            sleep 0.1 until d.stop?
          end
          after = Process.clock_gettime(Process::CLOCK_MONOTONIC)
          assert{ after >= before + 1.0 }
        end
      ensure
        Timecop.return
      end
    end

    test 'raise errors raised in threads' do
      d = Fluent::Test::Driver::Input.new(Fluent::Plugin::Input) do
        helpers :thread
        def start
          super
          thread_create(:input_thread_for_test_driver_test) do
            sleep 0.5
            raise "yaaaaaaaaaay!"
          end
        end
      end

      assert_raise RuntimeError.new("yaaaaaaaaaay!") do
        d.end_if{ false }
        d.run(timeout: 3) do
          sleep 0.1 until d.stop?
        end
      end
    end
  end

  sub_test_case 'output plugin test driver' do
    test 'returns the block value as the return value of #run' do
      d = Fluent::Test::Driver::Output.new(Fluent::Plugin::Output) do
        def prefer_buffered_processing
          false
        end
        def process(tag, es)
          # drop
        end
      end
      v = d.run do
        x = 1 + 2
        y = 2 + 4
        3 || x || y
      end
      assert_equal 3, v
    end
  end

  sub_test_case 'filter plugin test driver' do
    test 'returns the block value as the return value of #run' do
      d = Fluent::Test::Driver::Filter.new(Fluent::Plugin::Filter) do
        def filter(tag, time, record)
          record
        end
      end
      v = d.run do
        x = 1 + 2
        y = 2 + 4
        3 || x || y
      end
      assert_equal 3, v
    end
  end
end