Back to Repositories

Testing Plugin Lifecycle Method Inheritance in Fluentd

This test suite validates the proper implementation of superclass method calls in Fluentd plugins, specifically focusing on Input, Output, and Filter components. It ensures compatibility and proper lifecycle management for both well-implemented and incorrectly implemented plugins.

Test Coverage Overview

The test suite provides comprehensive coverage of plugin lifecycle methods including configure, start, before_shutdown, and shutdown.

Key areas tested include:
  • Proper superclass method calls in good implementations
  • Forced superclass method calls in bad implementations
  • Logging behavior for missing super calls
  • State verification through configured?, started?, before_shutdown? and shutdown? flags

Implementation Analysis

The testing approach uses dummy plugin implementations to validate both correct and incorrect inheritance patterns. It employs Test::Unit framework with sub_test_cases to organize related tests logically.

The implementation specifically tests:
  • DummyGood* classes that properly call super
  • DummyBad* classes that omit super calls
  • Automatic super call injection for compatibility

Technical Details

Testing infrastructure includes:
  • Test::Unit as the testing framework
  • Fluent::Test setup for environment initialization
  • Custom plugin classes inheriting from Fluent::Input, Output, and Filter
  • config_element() for configuration testing
  • Log capture and assertion mechanisms

Best Practices Demonstrated

The test suite exemplifies several testing best practices:

  • Thorough lifecycle method testing
  • Clear test case organization using sub_test_case
  • Explicit state verification after each operation
  • Comprehensive error logging validation
  • Separation of concerns between good and bad implementations

fluent/fluentd

test/compat/test_calls_super.rb

            
require_relative '../helper'

# these are Fluent::Compat::* in fact
require 'fluent/input'
require 'fluent/output'
require 'fluent/filter'

class CompatCallsSuperTest < Test::Unit::TestCase
  class DummyGoodInput < Fluent::Input
    def configure(conf); super; end
    def start; super; end
    def before_shutdown; super; end
    def shutdown; super; end
  end
  class DummyBadInput < Fluent::Input
    def configure(conf); super; end
    def start; end
    def before_shutdown; end
    def shutdown; end
  end
  class DummyGoodOutput < Fluent::Output
    def configure(conf); super; end
    def start; super; end
    def before_shutdown; super; end
    def shutdown; super; end
  end
  class DummyBadOutput < Fluent::Output
    def configure(conf); super; end
    def start; end
    def before_shutdown; end
    def shutdown; end
  end
  class DummyGoodFilter < Fluent::Filter
    def configure(conf); super; end
    def filter(tag, time, record); end
    def start; super; end
    def before_shutdown; super; end
    def shutdown; super; end
  end
  class DummyBadFilter < Fluent::Filter
    def configure(conf); super; end
    def filter(tag, time, record); end
    def start; end
    def before_shutdown; end
    def shutdown; end
  end

  setup do
    Fluent::Test.setup
  end

  sub_test_case 'old API plugin which calls super properly' do
    test 'Input#start, #before_shutdown and #shutdown calls all superclass methods properly' do
      i = DummyGoodInput.new
      i.configure(config_element())
      assert i.configured?

      i.start
      assert i.started?

      i.before_shutdown
      assert i.before_shutdown?

      i.shutdown
      assert i.shutdown?

      assert i.log.out.logs.empty?
    end

    test 'Output#start, #before_shutdown and #shutdown calls all superclass methods properly' do
      i = DummyGoodOutput.new
      i.configure(config_element())
      assert i.configured?

      i.start
      assert i.started?

      i.before_shutdown
      assert i.before_shutdown?

      i.shutdown
      assert i.shutdown?

      assert i.log.out.logs.empty?
    end

    test 'Filter#start, #before_shutdown and #shutdown calls all superclass methods properly' do
      i = DummyGoodFilter.new
      i.configure(config_element())
      assert i.configured?

      i.start
      assert i.started?

      i.before_shutdown
      assert i.before_shutdown?

      i.shutdown
      assert i.shutdown?

      assert i.log.out.logs.empty?
    end
  end

  sub_test_case 'old API plugin which does not call super' do
    test 'Input#start, #before_shutdown and #shutdown calls superclass methods forcedly with logs' do
      i = DummyBadInput.new
      i.configure(config_element())
      assert i.configured?

      i.start
      assert i.started?

      i.before_shutdown
      assert i.before_shutdown?

      i.shutdown
      assert i.shutdown?

      logs = i.log.out.logs
      assert{ logs.any?{|l| l.include?("[warn]: super was not called in #start: called it forcedly plugin=CompatCallsSuperTest::DummyBadInput") } }
      assert{ logs.any?{|l| l.include?("[warn]: super was not called in #before_shutdown: calling it forcedly plugin=CompatCallsSuperTest::DummyBadInput") } }
      assert{ logs.any?{|l| l.include?("[warn]: super was not called in #shutdown: calling it forcedly plugin=CompatCallsSuperTest::DummyBadInput") } }
    end

    test 'Output#start, #before_shutdown and #shutdown calls superclass methods forcedly with logs' do
      i = DummyBadOutput.new
      i.configure(config_element())
      assert i.configured?

      i.start
      assert i.started?

      i.before_shutdown
      assert i.before_shutdown?

      i.shutdown
      assert i.shutdown?

      logs = i.log.out.logs
      assert{ logs.any?{|l| l.include?("[warn]: super was not called in #start: called it forcedly plugin=CompatCallsSuperTest::DummyBadOutput") } }
      assert{ logs.any?{|l| l.include?("[warn]: super was not called in #before_shutdown: calling it forcedly plugin=CompatCallsSuperTest::DummyBadOutput") } }
      assert{ logs.any?{|l| l.include?("[warn]: super was not called in #shutdown: calling it forcedly plugin=CompatCallsSuperTest::DummyBadOutput") } }
    end

    test 'Filter#start, #before_shutdown and #shutdown calls superclass methods forcedly with logs' do
      i = DummyBadFilter.new
      i.configure(config_element())
      assert i.configured?

      i.start
      assert i.started?

      i.before_shutdown
      assert i.before_shutdown?

      i.shutdown
      assert i.shutdown?

      logs = i.log.out.logs
      assert{ logs.any?{|l| l.include?("[warn]: super was not called in #start: called it forcedly plugin=CompatCallsSuperTest::DummyBadFilter") } }
      assert{ logs.any?{|l| l.include?("[warn]: super was not called in #before_shutdown: calling it forcedly plugin=CompatCallsSuperTest::DummyBadFilter") } }
      assert{ logs.any?{|l| l.include?("[warn]: super was not called in #shutdown: calling it forcedly plugin=CompatCallsSuperTest::DummyBadFilter") } }
    end
  end
end