Back to Repositories

Testing Clock Time Management Implementation in Fluentd

This test suite validates the Clock functionality in Fluentd, focusing on time manipulation and clock freezing capabilities. It ensures reliable time-based operations and clock state management across different scenarios.

Test Coverage Overview

The test suite provides comprehensive coverage of Fluentd’s Clock implementation.

Key areas tested include:
  • Basic clock operations and incremental time tracking
  • Clock freezing functionality with and without arguments
  • Nested clock freeze operations
  • Time manipulation using absolute timestamps
  • Integration with Timecop for time control

Implementation Analysis

The testing approach employs Ruby’s Test::Unit framework with sub_test_case organization for logical grouping. Tests utilize sleep operations to verify time progression, explicit assertions for clock value validation, and block-based testing patterns for managing clock state transitions.

Framework features leveraged include:
  • Teardown hooks for clock state cleanup
  • Block-based test organization
  • Exception handling verification
  • Floating-point time comparisons

Technical Details

Testing tools and configuration:
  • Test::Unit as the primary testing framework
  • Timecop for time freezing verification
  • Helper module integration
  • Floating-point precision handling for time comparisons
  • Error margin considerations for threading schedules

Best Practices Demonstrated

The test suite exemplifies several testing best practices.

Notable implementations include:
  • Proper test isolation through teardown procedures
  • Comprehensive edge case coverage
  • Clear test case organization
  • Robust error handling verification
  • Precise time-based assertions with appropriate margins

fluent/fluentd

test/test_clock.rb

            
require_relative 'helper'
require 'fluent/clock'

require 'timecop'

class ClockTest < ::Test::Unit::TestCase
  teardown do
    Fluent::Clock.return # call it always not to affect other tests
  end

  sub_test_case 'without any pre-operation' do
    test 'clock can provides incremental floating point number based on second' do
      c1 = Fluent::Clock.now
      assert_kind_of Float, c1
      sleep 1.1
      c2 = Fluent::Clock.now
      assert{ c2 >= c1 + 1.0 && c2 < c1 + 9.0 } # if clock returns deci-second (fantastic!), c2 should be larger than c1 + 10
    end

    test 'clock value will proceed even if timecop freezes Time' do
      Timecop.freeze(Time.now) do
        c1 = Fluent::Clock.now
        assert_kind_of Float, c1
        sleep 1.1
        c2 = Fluent::Clock.now
        assert{ c2 >= c1 + 1.0 && c2 < c1 + 9.0 }
      end
    end
  end

  sub_test_case 'using #freeze without any arguments' do
    test 'Clock.freeze without arguments freezes clock with current clock value' do
      c0 = Fluent::Clock.now
      Fluent::Clock.freeze
      c1 = Fluent::Clock.now
      Fluent::Clock.return
      c2 = Fluent::Clock.now
      assert{ c0 <= c1 && c1 <= c2 }
    end

    test 'Clock.return raises an error if it is called in block' do
      assert_raise RuntimeError.new("invalid return while running code in blocks") do
        Fluent::Clock.freeze do
          Fluent::Clock.return
        end
      end
    end
  end

  sub_test_case 'using #freeze with clock value' do
    test 'Clock.now always returns frozen time until #return called' do
      c0 = Fluent::Clock.now
      Fluent::Clock.freeze(c0)
      assert_equal c0, Fluent::Clock.now
      sleep 0.5
      assert_equal c0, Fluent::Clock.now
      sleep 0.6
      assert_equal c0, Fluent::Clock.now

      Fluent::Clock.return
      c1 = Fluent::Clock.now
      assert{ c1 >= c0 + 1.0 }
    end

    test 'Clock.now returns frozen time in the block argument of #freeze' do
      c0 = Fluent::Clock.now
      Fluent::Clock.freeze(c0) do
        assert_equal c0, Fluent::Clock.now
        sleep 0.5
        assert_equal c0, Fluent::Clock.now
        sleep 0.6
        assert_equal c0, Fluent::Clock.now
      end
      c1 = Fluent::Clock.now
      assert{ c1 >= c0 + 1.0 }
    end

    test 'Clock.now returns unfrozen value after jumping out from block by raising errors' do
      c0 = Fluent::Clock.now
      rescued_error = nil
      begin
        Fluent::Clock.freeze(c0) do
          assert_equal c0, Fluent::Clock.now
          sleep 0.5
          assert_equal c0, Fluent::Clock.now
          sleep 0.6
          assert_equal c0, Fluent::Clock.now
          raise "bye!"
        end
      rescue => e
        rescued_error = e
      end
      assert rescued_error # ensure to rescue an error
      c1 = Fluent::Clock.now
      assert{ c1 >= c0 + 1.0 }
    end

    test 'Clock.return cancels all Clock.freeze effects by just once' do
      c0 = Fluent::Clock.now
      sleep 0.1
      c1 = Fluent::Clock.now
      sleep 0.1
      c2 = Fluent::Clock.now
      Fluent::Clock.freeze(c0)
      sleep 0.1
      assert_equal c0, Fluent::Clock.now
      Fluent::Clock.freeze(c1)
      sleep 0.1
      assert_equal c1, Fluent::Clock.now
      Fluent::Clock.freeze(c2)
      sleep 0.1
      assert_equal c2, Fluent::Clock.now

      Fluent::Clock.return
      assert{ Fluent::Clock.now > c2 }
    end

    test 'Clock.freeze allows nested blocks by itself' do
      c0 = Fluent::Clock.now
      sleep 0.1
      c1 = Fluent::Clock.now
      sleep 0.1
      c2 = Fluent::Clock.now
      Fluent::Clock.freeze(c0) do
        sleep 0.1
        assert_equal c0, Fluent::Clock.now
        Fluent::Clock.freeze(c1) do
          sleep 0.1
          assert_equal c1, Fluent::Clock.now
          Fluent::Clock.freeze(c2) do
            sleep 0.1
            assert_equal c2, Fluent::Clock.now
          end
          assert_equal c1, Fluent::Clock.now
        end
        assert_equal c0, Fluent::Clock.now
      end
      assert{ Fluent::Clock.now > c0 }
    end
  end

  sub_test_case 'using #freeze with Time argument' do
    test 'Clock.freeze returns the clock value which should be produced when the time is at the specified time' do
      c0 = Fluent::Clock.now
      t0 = Time.now
      t1 = t0 - 30
      assert_kind_of Time, t1
      t2 = t0 + 30
      assert_kind_of Time, t2

      # 31 is for error of floating point value
      Fluent::Clock.freeze(t1) do
        c1 = Fluent::Clock.now
        assert{ c1 >= c0 - 31 && c1 <= c0 - 31 + 10 } # +10 is for threading schedule error
      end

      # 29 is for error of floating point value
      Fluent::Clock.freeze(t2) do
        c2 = Fluent::Clock.now
        assert{ c2 >= c0 + 29 && c2 <= c0 + 29 + 10 } # +10 is for threading schedule error
      end
    end
  end
end