Back to Repositories

Testing Time Formatting Implementation in Fluentd

This test suite thoroughly validates the TimeFormatter functionality in Fluentd, focusing on various time format conversions and timezone handling. The tests ensure accurate timestamp formatting across different time zones, formats, and configurations.

Test Coverage Overview

The test suite provides comprehensive coverage of time formatting operations in Fluentd.

Key areas tested include:
  • UTC and local time conversions
  • Various timezone format handling (HH:MM, HHMM, HH)
  • Daylight Saving Time (DST) handling
  • Subsecond precision formatting
  • Event time and integer time compatibility

Implementation Analysis

The testing approach uses Test::Unit framework with modular test organization through TestLists module. The implementation employs systematic verification of time formatting across different scenarios, using helper methods and timezone manipulation utilities.

Technical patterns include:
  • Shared test cases between EventTime and Integer time formats
  • Sub-test case organization for different time types
  • Configuration parameter testing through DummyForTimeFormatter

Technical Details

Testing tools and configuration:
  • Test::Unit as the primary testing framework
  • Fluent::EventTime for precise timestamp handling
  • TimeMixin::Formatter for configuration testing
  • Custom time format strings (%Y%m%d %H%M%z)
  • Timezone manipulation helpers

Best Practices Demonstrated

The test suite exemplifies several testing best practices in Ruby testing.

Notable practices include:
  • Modular test organization using shared test lists
  • Comprehensive edge case coverage
  • Consistent setup and teardown patterns
  • Clear test naming conventions
  • Isolation of timezone-dependent tests

fluent/fluentd

test/test_time_formatter.rb

            
require_relative 'helper'
require 'fluent/test'
require 'fluent/time'

class TimeFormatterTest < ::Test::Unit::TestCase
  setup do
    @fmt ="%Y%m%d %H%M%z"  # YYYYMMDD HHMM[+-]HHMM
  end

  def format(format, localtime, timezone)
    formatter = Fluent::TimeFormatter.new(format, localtime, timezone)
    formatter.format(@time)
  end

  module TestLists
    def test_default_utc_nil
      assert_equal("2014-09-27T00:00:00Z", format(nil, false, nil))
    end

    def test_default_utc_pHH_MM
      assert_equal("2014-09-27T01:30:00+01:30", format(nil, false, "+01:30"))
    end

    def test_default_utc_nHH_MM
      assert_equal("2014-09-26T22:30:00-01:30", format(nil, false, "-01:30"))
    end

    def test_default_utc_pHHMM
      assert_equal("2014-09-27T02:30:00+02:30", format(nil, false, "+0230"))
    end

    def test_default_utc_nHHMM
      assert_equal("2014-09-26T21:30:00-02:30", format(nil, false, "-0230"))
    end

    def test_default_utc_pHH
      assert_equal("2014-09-27T03:00:00+03:00", format(nil, false, "+03"))
    end

    def test_default_utc_nHH
      assert_equal("2014-09-26T21:00:00-03:00", format(nil, false, "-03"))
    end

    def test_default_utc_timezone_1
      # Asia/Tokyo (+09:00) does not have daylight saving time.
      assert_equal("2014-09-27T09:00:00+09:00", format(nil, false, "Asia/Tokyo"))
    end

    def test_default_utc_timezone_2
      # Pacific/Honolulu (-10:00) does not have daylight saving time.
      assert_equal("2014-09-26T14:00:00-10:00", format(nil, false, "Pacific/Honolulu"))
    end

    def test_default_utc_timezone_3
      # America/Argentina/Buenos_Aires (-03:00) does not have daylight saving time.
      assert_equal("2014-09-26T21:00:00-03:00", format(nil, false, "America/Argentina/Buenos_Aires"))
    end

    def test_default_utc_timezone_4
      # Europe/Paris has daylight saving time. Its UTC offset is +01:00 and its
      # UTC offset in DST is +02:00. In September, Europe/Paris is in DST.
      assert_equal("2014-09-27T02:00:00+02:00", format(nil, false, "Europe/Paris"))
    end

    def test_default_utc_timezone_5
      # Europe/Paris has daylight saving time. Its UTC offset is +01:00 and its
      # UTC offset in DST is +02:00. In January, Europe/Paris is not in DST.
      @time = Time.new(2014, 1, 24, 0, 0, 0, 0).to_i
      assert_equal("2014-01-24T01:00:00+01:00", format(nil, false, "Europe/Paris"))
    end

    def test_default_utc_invalid
      assert_equal("2014-09-27T00:00:00Z", format(nil, false, "Invalid"))
    end

    def test_default_localtime_nil_1
      with_timezone("UTC-04") do
        assert_equal("2014-09-27T04:00:00+04:00", format(nil, true, nil))
      end
    end

    def test_default_localtime_nil_2
      with_timezone("UTC+05") do
        assert_equal("2014-09-26T19:00:00-05:00", format(nil, true, nil))
      end
    end

    def test_default_localtime_timezone
      # 'timezone' takes precedence over 'localtime'.
      with_timezone("UTC-06") do
        assert_equal("2014-09-27T07:00:00+07:00", format(nil, true, "+07"))
      end
    end

    def test_specific_utc_nil
      assert_equal("20140927 0000+0000", format(@fmt, false, nil))
    end

    def test_specific_utc_pHH_MM
      assert_equal("20140927 0830+0830", format(@fmt, false, "+08:30"))
    end

    def test_specific_utc_nHH_MM
      assert_equal("20140926 1430-0930", format(@fmt, false, "-09:30"))
    end

    def test_specific_utc_pHHMM
      assert_equal("20140927 1030+1030", format(@fmt, false, "+1030"))
    end

    def test_specific_utc_nHHMM
      assert_equal("20140926 1230-1130", format(@fmt, false, "-1130"))
    end

    def test_specific_utc_pHH
      assert_equal("20140927 1200+1200", format(@fmt, false, "+12"))
    end

    def test_specific_utc_nHH
      assert_equal("20140926 1100-1300", format(@fmt, false, "-13"))
    end

    def test_specific_utc_timezone_1
      # Europe/Moscow (+04:00) does not have daylight saving time.
      assert_equal("20140927 0400+0400", format(@fmt, false, "Europe/Moscow"))
    end

    def test_specific_utc_timezone_2
      # Pacific/Galapagos (-06:00) does not have daylight saving time.
      assert_equal("20140926 1800-0600", format(@fmt, false, "Pacific/Galapagos"))
    end

    def test_specific_utc_timezone_3
      # America/Argentina/Buenos_Aires (-03:00) does not have daylight saving time.
      assert_equal("20140926 2100-0300", format(@fmt, false, "America/Argentina/Buenos_Aires"))
    end

    def test_specific_utc_timezone_4
      # America/Los_Angeles has daylight saving time. Its UTC offset is -08:00 and its
      # UTC offset in DST is -07:00. In September, America/Los_Angeles is in DST.
      assert_equal("20140926 1700-0700", format(@fmt, false, "America/Los_Angeles"))
    end

    def test_specific_utc_timezone_5
      # America/Los_Angeles has daylight saving time. Its UTC offset is -08:00 and its
      # UTC offset in DST is -07:00. In January, America/Los_Angeles is not in DST.
      @time = Time.new(2014, 1, 24, 0, 0, 0, 0).to_i
      assert_equal("20140123 1600-0800", format(@fmt, false, "America/Los_Angeles"))
    end

    def test_specific_utc_invalid
      assert_equal("20140927 0000+0000", format(@fmt, false, "Invalid"))
    end

    def test_specific_localtime_nil_1
      with_timezone("UTC-07") do
        assert_equal("20140927 0700+0700", format(@fmt, true, nil))
      end
    end

    def test_specific_localtime_nil_2
      with_timezone("UTC+08") do
        assert_equal("20140926 1600-0800", format(@fmt, true, nil))
      end
    end

    def test_specific_localtime_timezone
      # 'timezone' takes precedence over 'localtime'.
      with_timezone("UTC-09") do
        assert_equal("20140926 1400-1000", format(@fmt, true, "-10"))
      end
    end
  end

  sub_test_case 'Fluent::EventTime time' do
    setup do
      @time = Fluent::EventTime.from_time(Time.new(2014, 9, 27, 0, 0, 0, 0))
    end

    include TestLists
  end

  # for v0.12 compatibility
  sub_test_case 'Integer time' do
    setup do
      @time = Time.new(2014, 9, 27, 0, 0, 0, 0).to_i
    end

    include TestLists
  end

  def test_format_with_subsec
    time = Time.new(2014, 9, 27, 0, 0, 0, 0).to_i
    time = Fluent::EventTime.new(time)
    formatter = Fluent::TimeFormatter.new("%Y%m%d %H%M.%N", false, nil)
    assert_equal("20140927 0000.000000000", formatter.format(time))
  end

  sub_test_case 'TimeMixin::Formatter' do
    class DummyForTimeFormatter
      include Fluent::Configurable
      include Fluent::TimeMixin::Formatter
    end

    test 'provides configuration parameters for TimeFormatter with default values for localtime' do
      str = with_timezone("UTC+07") do
        i = DummyForTimeFormatter.new
        i.configure(config_element('format'))

        assert_nil   i.time_format
        assert_true  i.localtime
        assert_false i.utc
        assert_nil   i.timezone

        fmt = i.time_formatter_create
        fmt.format(event_time("2016-09-02 18:42:31.012345678 UTC", format: '%Y-%m-%d %H:%M:%S.%N %z'))
      end
      assert_equal "2016-09-02T11:42:31-07:00", str
    end

    test 'provides configuration parameters for TimeFormatter, configurable for any time format' do
      str = with_timezone("UTC+07") do
        i = DummyForTimeFormatter.new
        i.configure(config_element('format', '', {'time_format' => '%Y-%m-%d %H:%M:%S.%N %z'}))

        fmt = i.time_formatter_create
        fmt.format(event_time("2016-09-02 18:42:31.012345678 UTC", format: '%Y-%m-%d %H:%M:%S.%N %z'))
      end
      assert_equal "2016-09-02 11:42:31.012345678 -0700", str
    end

    test 'provides configuration parameters for TimeFormatter, configurable for UTC' do
      str = with_timezone("UTC+07") do
        i = DummyForTimeFormatter.new
        i.configure(config_element('format', '', {'time_format' => '%Y-%m-%d %H:%M:%S.%N %z', 'utc' => 'true'}))

        fmt = i.time_formatter_create
        fmt.format(event_time("2016-09-02 18:42:31.012345678 UTC", format: '%Y-%m-%d %H:%M:%S.%N %z'))
      end
      assert_equal "2016-09-02 18:42:31.012345678 +0000", str
    end

    test 'provides configuration parameters for TimeFormatter, configurable for any timezone' do
      str = with_timezone("UTC+07") do
        i = DummyForTimeFormatter.new
        i.configure(config_element('format', '', {'time_format' => '%Y-%m-%d %H:%M:%S.%N %z', 'timezone' => '+0900'}))

        fmt = i.time_formatter_create
        fmt.format(event_time("2016-09-02 18:42:31.012345678 UTC", format: '%Y-%m-%d %H:%M:%S.%N %z'))
      end
      assert_equal "2016-09-03 03:42:31.012345678 +0900", str
    end

    test '#time_formatter_create returns TimeFormatter with specified time format and timezone' do
      str = with_timezone("UTC+07") do
        i = DummyForTimeFormatter.new
        i.configure(config_element('format', '', {'time_format' => '%Y-%m-%d %H:%M:%S.%N %z', 'timezone' => '+0900'}))

        fmt = i.time_formatter_create(format: '%m/%d/%Y %H-%M-%S %N', timezone: '+0000')
        fmt.format(event_time("2016-09-02 18:42:31.012345678 UTC", format: '%Y-%m-%d %H:%M:%S.%N %z'))
      end
      assert_equal "09/02/2016 18-42-31 012345678", str
    end

    test '#time_formatter_create returns TimeFormatter with localtime besides any configuration parameters' do
      str = with_timezone("UTC+07") do
        i = DummyForTimeFormatter.new
        i.configure(config_element('format', '', {'time_format' => '%Y-%m-%d %H:%M:%S.%N %z', 'utc' => 'true'}))

        fmt = i.time_formatter_create(format: '%m/%d/%Y %H-%M-%S %N', force_localtime: true)
        fmt.format(event_time("2016-09-02 18:42:31.012345678 UTC", format: '%Y-%m-%d %H:%M:%S.%N %z'))
      end
      assert_equal "09/02/2016 11-42-31 012345678", str

      str = with_timezone("UTC+07") do
        i = DummyForTimeFormatter.new
        i.configure(config_element('format', '', {'time_format' => '%Y-%m-%d %H:%M:%S.%N %z', 'timezone' => '+0900'}))

        fmt = i.time_formatter_create(format: '%m/%d/%Y %H-%M-%S %N', force_localtime: true)
        fmt.format(event_time("2016-09-02 18:42:31.012345678 UTC", format: '%Y-%m-%d %H:%M:%S.%N %z'))
      end
      assert_equal "09/02/2016 11-42-31 012345678", str
    end
  end

  test '#time_formatter_create returns NumericTimeFormatter to format time as unixtime when time_type unixtime specified' do
    i = DummyForTimeFormatter.new
    i.configure(config_element('format', '', {'time_type' => 'unixtime'}))
    fmt = i.time_formatter_create
    time = event_time("2016-10-03 20:08:30.123456789 +0100", format: '%Y-%m-%d %H:%M:%S.%N %z')
    assert_equal "#{time.sec}", fmt.format(time)
  end

  test '#time_formatter_create returns NumericTimeFormatter to format time as float when time_type float specified' do
    i = DummyForTimeFormatter.new
    i.configure(config_element('format', '', {'time_type' => 'float'}))
    fmt = i.time_formatter_create
    time = event_time("2016-10-03 20:08:30.123456789 +0100", format: '%Y-%m-%d %H:%M:%S.%N %z')
    assert_equal "#{time.sec}.#{time.nsec}", fmt.format(time)
  end
end