Testing Counter Store Operations in Fluentd
This test suite validates the functionality of Fluentd’s Counter Store component, which manages counter operations and value persistence. The tests cover initialization, value manipulation, and reset behaviors with time-based operations.
Test Coverage Overview
Implementation Analysis
Technical Details
Best Practices Demonstrated
fluent/fluentd
test/counter/test_store.rb
require_relative '../helper'
require 'fluent/counter/store'
require 'fluent/time'
require 'timecop'
class CounterStoreTest < ::Test::Unit::TestCase
setup do
@name = 'key_name'
@scope = "server\tworker\tplugin"
# timecop isn't compatible with EventTime
t = Time.parse('2016-09-22 16:59:59 +0900')
Timecop.freeze(t)
@now = Fluent::EventTime.now
end
teardown do
Timecop.return
end
def extract_value_from_counter(counter, key)
store = counter.instance_variable_get(:@storage).instance_variable_get(:@store)
store[key]
end
def travel(sec)
# Since Timecop.travel() causes test failures on Windows/AppVeyor by inducing
# rounding errors to Time.now, we need to use Timecop.freeze() instead.
Timecop.freeze(Time.now + sec)
end
sub_test_case 'init' do
setup do
@reset_interval = 10
@store = Fluent::Counter::Store.new
@data = { 'name' => @name, 'reset_interval' => @reset_interval }
@key = Fluent::Counter::Store.gen_key(@scope, @name)
end
test 'create new value in the counter' do
v = @store.init(@key, @data)
assert_equal @name, v['name']
assert_equal @reset_interval, v['reset_interval']
v2 = extract_value_from_counter(@store, @key)
v2 = @store.send(:build_response, v2)
assert_equal v, v2
end
test 'raise an error when a passed key already exists' do
@store.init(@key, @data)
assert_raise Fluent::Counter::InvalidParams do
@store.init(@key, @data)
end
end
test 'return a value when passed key already exists and a ignore option is true' do
v = @store.init(@key, @data)
v1 = extract_value_from_counter(@store, @key)
v1 = @store.send(:build_response, v1)
v2 = @store.init(@key, @data, ignore: true)
assert_equal v, v2
assert_equal v1, v2
end
end
sub_test_case 'get' do
setup do
@store = Fluent::Counter::Store.new
data = { 'name' => @name, 'reset_interval' => 10 }
@key = Fluent::Counter::Store.gen_key(@scope, @name)
@store.init(@key, data)
end
test 'return a value from the counter' do
v = extract_value_from_counter(@store, @key)
expected = @store.send(:build_response, v)
assert_equal expected, @store.get(@key)
end
test 'return a raw value from the counter when raw option is true' do
v = extract_value_from_counter(@store, @key)
assert_equal v, @store.get(@key, raw: true)
end
test "return nil when a passed key doesn't exist" do
assert_equal nil, @store.get('unknown_key')
end
test "raise a error when a passed key doesn't exist and raise_error option is true" do
assert_raise Fluent::Counter::UnknownKey do
@store.get('unknown_key', raise_error: true)
end
end
end
sub_test_case 'key?' do
setup do
@store = Fluent::Counter::Store.new
data = { 'name' => @name, 'reset_interval' => 10 }
@key = Fluent::Counter::Store.gen_key(@scope, @name)
@store.init(@key, data)
end
test 'return true when passed key exists' do
assert_true @store.key?(@key)
end
test "return false when passed key doesn't exist" do
assert_true [email protected]?('unknown_key')
end
end
sub_test_case 'delete' do
setup do
@store = Fluent::Counter::Store.new
data = { 'name' => @name, 'reset_interval' => 10 }
@key = Fluent::Counter::Store.gen_key(@scope, @name)
@init_value = @store.init(@key, data)
end
test 'delete a value from the counter' do
v = @store.delete(@key)
assert_equal @init_value, v
assert_nil extract_value_from_counter(@store, @key)
end
test "raise an error when passed key doesn't exist" do
assert_raise Fluent::Counter::UnknownKey do
@store.delete('unknown_key')
end
end
end
sub_test_case 'inc' do
setup do
@store = Fluent::Counter::Store.new
@init_data = { 'name' => @name, 'reset_interval' => 10 }
@travel_sec = 10
end
data(
positive: 10,
negative: -10
)
test 'increment or decrement a value in the counter' do |value|
key = Fluent::Counter::Store.gen_key(@scope, @name)
@store.init(key, @init_data)
travel(@travel_sec)
v = @store.inc(key, { 'value' => value })
assert_equal value, v['total']
assert_equal value, v['current']
assert_equal @now, v['last_reset_at'] # last_reset_at doesn't change
v1 = extract_value_from_counter(@store, key)
v1 = @store.send(:build_response, v1)
assert_equal v, v1
end
test "raise an error when passed key doesn't exist" do
assert_raise Fluent::Counter::UnknownKey do
@store.inc('unknown_key', { 'value' => 1 })
end
end
test 'raise an error when a type of passed value is incompatible with a stored value' do
key1 = Fluent::Counter::Store.gen_key(@scope, @name)
key2 = Fluent::Counter::Store.gen_key(@scope, 'name2')
key3 = Fluent::Counter::Store.gen_key(@scope, 'name3')
v1 = @store.init(key1, @init_data.merge('type' => 'integer'))
v2 = @store.init(key2, @init_data.merge('type' => 'float'))
v3 = @store.init(key3, @init_data.merge('type' => 'numeric'))
assert_equal 'integer', v1['type']
assert_equal 'float', v2['type']
assert_equal 'numeric', v3['type']
assert_raise Fluent::Counter::InvalidParams do
@store.inc(key1, { 'value' => 1.1 })
end
assert_raise Fluent::Counter::InvalidParams do
@store.inc(key2, { 'value' => 1 })
end
assert_nothing_raised do
@store.inc(key3, { 'value' => 1 })
@store.inc(key3, { 'value' => 1.0 })
end
end
end
sub_test_case 'reset' do
setup do
@store = Fluent::Counter::Store.new
@travel_sec = 10
@inc_value = 10
@key = Fluent::Counter::Store.gen_key(@scope, @name)
@store.init(@key, { 'name' => @name, 'reset_interval' => 10 })
@store.inc(@key, { 'value' => 10 })
end
test 'reset a value in the counter' do
travel(@travel_sec)
v = @store.reset(@key)
assert_equal @travel_sec, v['elapsed_time']
assert_true v['success']
counter = v['counter_data']
assert_equal @name, counter['name']
assert_equal @inc_value, counter['total']
assert_equal @inc_value, counter['current']
assert_equal 'numeric', counter['type']
assert_equal @now, counter['last_reset_at']
assert_equal 10, counter['reset_interval']
v1 = extract_value_from_counter(@store, @key)
assert_equal 0, v1['current']
assert_true v1['current'].is_a?(Integer)
assert_equal @inc_value, v1['total']
assert_equal (@now + @travel_sec), Fluent::EventTime.new(*v1['last_reset_at'])
assert_equal (@now + @travel_sec), Fluent::EventTime.new(*v1['last_modified_at'])
end
test 'reset a value after `reset_interval` passed' do
first_travel_sec = 5
travel(first_travel_sec) # jump time less than reset_interval
v = @store.reset(@key)
assert_equal false, v['success']
assert_equal first_travel_sec, v['elapsed_time']
store = extract_value_from_counter(@store, @key)
assert_equal 10, store['current']
assert_equal @now, Fluent::EventTime.new(*store['last_reset_at'])
# time is passed greater than reset_interval
travel(@travel_sec)
v = @store.reset(@key)
assert_true v['success']
assert_equal @travel_sec + first_travel_sec, v['elapsed_time']
v1 = extract_value_from_counter(@store, @key)
assert_equal 0, v1['current']
assert_equal (@now + @travel_sec + first_travel_sec), Fluent::EventTime.new(*v1['last_reset_at'])
assert_equal (@now + @travel_sec + first_travel_sec), Fluent::EventTime.new(*v1['last_modified_at'])
end
test "raise an error when passed key doesn't exist" do
assert_raise Fluent::Counter::UnknownKey do
@store.reset('unknown_key')
end
end
end
end