Testing Fluentd Configuration Management Implementation in fluent/fluentd
This test suite validates the Fluent Configurable module, which provides configuration management capabilities for Fluentd plugins. It thoroughly tests configuration parameter handling, section configuration, inheritance behavior, and configuration validation.
Test Coverage Overview
Implementation Analysis
Technical Details
Best Practices Demonstrated
fluent/fluentd
test/config/test_configurable.rb
require_relative '../helper'
require 'fluent/configurable'
require 'fluent/config/element'
require 'fluent/config/section'
module ConfigurableSpec
class Base1
include Fluent::Configurable
config_param :node, :string, default: "node"
config_param :flag1, :bool, default: false
config_param :flag2, :bool, default: true
config_param :name1, :string
config_param :name2, :string
config_param :name3, :string, default: "base1"
config_param :name4, :string, default: "base1"
config_param :opt1, :enum, list: [:foo, :bar, :baz]
config_param :opt2, :enum, list: [:foo, :bar, :baz], default: :foo
def get_all
[@node, @flag1, @flag2, @name1, @name2, @name3, @name4]
end
end
class Base1Safe < Base1
config_set_default :name1, "basex1"
config_set_default :name2, "basex2"
config_set_default :opt1, :baz
end
class Base1Nil < Base1
config_set_default :name1, nil
config_set_default :name2, nil
config_set_default :opt1, nil
config_param :name5, :string, default: nil
end
class Base2 < Base1
config_set_default :name2, "base2"
config_set_default :name4, "base2"
config_set_default :opt1, :bar
config_param :name5, :string
config_param :name6, :string, default: "base2"
config_param :opt3, :enum, list: [:a, :b]
def get_all
ary = super
ary + [@name5, @name6]
end
end
class Base3 < Base2
config_set_default :opt3, :a
config_section :node do
config_param :name, :string, default: "node"
config_param :type, :string
end
config_section :branch, required: true, multi: true do
config_argument :name, :string
config_param :size, :integer, default: 10
config_section :leaf, required: false, multi: true do
config_param :weight, :integer
config_section :worm, param_name: 'worms', multi: true do
config_param :type, :string, default: 'ladybird'
end
end
end
def get_all
ary = super
ary + [@branch]
end
end
class Base4 < Base2
config_set_default :opt3, :a
config_section :node, param_name: :nodes do
config_argument :num, :integer
config_param :name, :string, default: "node"
config_param :type, :string, default: "b4"
end
config_section :description1, required: false, multi: false do
config_argument :note, :string, default: "desc1"
config_param :text, :string
end
config_section :description2, required: true, multi: false do
config_argument :note, :string, default: "desc2"
config_param :text, :string
end
config_section :description3, required: true, multi: true do
config_argument :note, default: "desc3" do |val|
"desc3: #{val}"
end
config_param :text, :string
end
def get_all
ary = super
ary + [@nodes, @description1, @description2, @description3]
end
end
class Base4Safe < Base4
# config_section :node, param_name: :nodes do
# config_argument :num, :integer
# config_param :name, :string, default: "node"
# config_param :type, :string, default: "b4"
# end
# config_section :description1, required: false, multi: false do
# config_argument :note, :string, default: "desc1"
# config_param :text, :string
# end
# config_section :description2, required: true, multi: false do
# config_argument :note, :string, default: "desc2"
# config_param :text, :string
# end
# config_section :description3, required: true, multi: true do
# config_argument :note, default: "desc3" do |val|
# "desc3: #{val}"
# end
# config_param :text, :string
# end
config_section :node do
config_set_default :num, 0
end
config_section :description1 do
config_set_default :text, "teeeext"
end
config_section :description2 do
config_set_default :text, nil
end
config_section :description3 do
config_set_default :text, "yay"
end
end
class Init0
include Fluent::Configurable
config_section :sec1, init: true, multi: false do
config_param :name, :string, default: 'sec1'
end
config_section :sec2, init: true, multi: true do
config_param :name, :string, default: 'sec1'
end
end
class Example0
include Fluent::Configurable
config_param :stringvalue, :string
config_param :boolvalue, :bool
config_param :integervalue, :integer
config_param :sizevalue, :size
config_param :timevalue, :time
config_param :floatvalue, :float
config_param :hashvalue, :hash
config_param :arrayvalue, :array
end
class ExampleWithAlias
include Fluent::Configurable
config_param :name, :string, alias: :fullname
config_param :bool, :bool, alias: :flag
config_section :detail, required: false, multi: false, alias: "information" do
config_param :address, :string, default: "x"
end
def get_all
[@name, @detail]
end
end
class ExampleWithSecret
include Fluent::Configurable
config_param :normal_param, :string
config_param :secret_param, :string, secret: true
config_section :section do
config_param :normal_param2, :string
config_param :secret_param2, :string, secret: true
end
end
class ExampleWithDefaultHashAndArray
include Fluent::Configurable
config_param :obj1, :hash, default: {}
config_param :obj2, :array, default: []
end
class ExampleWithSkipAccessor
include Fluent::Configurable
config_param :name, :string, default: 'example7', skip_accessor: true
end
class ExampleWithCustomSection
include Fluent::Configurable
config_param :name_param, :string
config_section :normal_section do
config_param :normal_section_param, :string
end
class CustomSection
include Fluent::Configurable
config_param :custom_section_param, :string
end
class AnotherElement
include Fluent::Configurable
end
def configure(conf)
super
conf.elements.each do |e|
next if e.name != 'custom_section'
CustomSection.new.configure(e)
end
end
end
class ExampleWithIntFloat
include Fluent::Configurable
config_param :int1, :integer
config_param :float1, :float
end
module Overwrite
class Base
include Fluent::Configurable
config_param :name, :string, alias: :fullname
config_param :bool, :bool, alias: :flag
config_section :detail, required: false, multi: false, alias: "information" do
config_param :address, :string, default: "x"
end
end
class Required < Base
config_section :detail, required: true do
config_param :address, :string, default: "x"
end
end
class Multi < Base
config_section :detail, multi: true do
config_param :address, :string, default: "x"
end
end
class Alias < Base
config_section :detail, alias: "information2" do
config_param :address, :string, default: "x"
end
end
class DefaultOptions < Base
config_section :detail do
config_param :address, :string, default: "x"
end
end
class DetailAddressDefault < Base
config_section :detail do
config_param :address, :string, default: "y"
end
end
class AddParam < Base
config_section :detail do
config_param :phone_no, :string
end
end
class AddParamOverwriteAddress < Base
config_section :detail do
config_param :address, :string, default: "y"
config_param :phone_no, :string
end
end
end
module Final
# Show what is allowed in finalized sections
# InheritsFinalized < Finalized < Base
class Base
include Fluent::Configurable
config_section :appendix, multi: false, final: false do
config_param :code, :string
config_param :name, :string
config_param :address, :string, default: ""
end
end
class Finalized < Base
# to non-finalized section
# subclass can change type (code)
# add default value (name)
# change default value (address)
# add field (age)
config_section :appendix, final: true do
config_param :code, :integer
config_set_default :name, "y"
config_set_default :address, "-"
config_param :age, :integer, default: 10
end
end
class InheritsFinalized < Finalized
# to finalized section
# subclass can add default value (code)
# change default value (age)
# add field (phone_no)
config_section :appendix do
config_set_default :code, 2
config_set_default :age, 0
config_param :phone_no, :string
end
end
# Show what is allowed/prohibited for finalized sections
class FinalizedBase
include Fluent::Configurable
config_section :appendix, param_name: :apd, init: false, required: true, multi: false, alias: "options", final: true do
config_param :name, :string
end
end
class FinalizedBase2
include Fluent::Configurable
config_section :appendix, param_name: :apd, init: false, required: false, multi: false, alias: "options", final: true do
config_param :name, :string
end
end
# subclass can change init with adding default values
class OverwriteInit < FinalizedBase2
config_section :appendix, init: true do
config_set_default :name, "moris"
config_param :code, :integer, default: 0
end
end
# subclass cannot change type (name)
class Subclass < FinalizedBase
config_section :appendix do
config_param :name, :integer
end
end
# subclass cannot change param_name
class OverwriteParamName < FinalizedBase
config_section :appendix, param_name: :adx do
end
end
# subclass cannot change final (section)
class OverwriteFinal < FinalizedBase
config_section :appendix, final: false do
config_param :name, :integer
end
end
# subclass cannot change required
class OverwriteRequired < FinalizedBase
config_section :appendix, required: false do
end
end
# subclass cannot change multi
class OverwriteMulti < FinalizedBase
config_section :appendix, multi: true do
end
end
# subclass cannot change alias
class OverwriteAlias < FinalizedBase
config_section :appendix, alias: "options2" do
end
end
end
module OverwriteDefaults
class Owner
include Fluent::Configurable
config_set_default :key1, "V1"
config_section :buffer do
config_set_default :size_of_something, 1024
end
end
class SubOwner < Owner
config_section :buffer do
config_set_default :size_of_something, 2048
end
end
class NilOwner < Owner
config_section :buffer do
config_set_default :size_of_something, nil
end
end
class FlatChild
include Fluent::Configurable
attr_accessor :owner
config_param :key1, :string, default: "v1"
end
class BufferChild
include Fluent::Configurable
attr_accessor :owner
configured_in :buffer
config_param :size_of_something, :size, default: 128
end
class BufferBase
include Fluent::Configurable
end
class BufferSubclass < BufferBase
attr_accessor :owner
configured_in :buffer
config_param :size_of_something, :size, default: 512
end
class BufferSubSubclass < BufferSubclass
end
end
class UnRecommended
include Fluent::Configurable
attr_accessor :log
config_param :key1, :string, default: 'deprecated', deprecated: "key1 will be removed."
config_param :key2, :string, default: 'obsoleted', obsoleted: "key2 has been removed."
end
end
module Fluent::Config
class TestConfigurable < ::Test::Unit::TestCase
sub_test_case 'class defined without config_section' do
sub_test_case '#initialize' do
test 'create instance methods and default values by config_param and config_set_default' do
obj1 = ConfigurableSpec::Base1.new
assert_equal("node", obj1.node)
assert_false(obj1.flag1)
assert_true(obj1.flag2)
assert_nil(obj1.name1)
assert_nil(obj1.name2)
assert_equal("base1", obj1.name3)
assert_equal("base1", obj1.name4)
assert_nil(obj1.opt1)
assert_equal(:foo, obj1.opt2)
end
test 'create instance methods and default values overwritten by sub class definition' do
obj2 = ConfigurableSpec::Base2.new
assert_equal("node", obj2.node)
assert_false(obj2.flag1)
assert_true(obj2.flag2)
assert_nil(obj2.name1)
assert_equal("base2", obj2.name2)
assert_equal("base1", obj2.name3)
assert_equal("base2", obj2.name4)
assert_nil(obj2.name5)
assert_equal("base2", obj2.name6)
assert_equal(:bar, obj2.opt1)
assert_equal(:foo, obj2.opt2)
end
end
sub_test_case '#configured_section_create' do
test 'raises configuration error if required param exists but no configuration element is specified' do
obj = ConfigurableSpec::Base1.new
assert_raise(Fluent::ConfigError.new("'name1' parameter is required")) do
obj.configured_section_create(nil)
end
end
test 'creates root section with default values if name and config are specified with nil' do
obj = ConfigurableSpec::Base1Safe.new
root = obj.configured_section_create(nil)
assert_equal "node", root.node
assert_false root.flag1
assert_true root.flag2
assert_equal "basex1", root.name1
assert_equal "basex2", root.name2
assert_equal "base1", root.name3
assert_equal "base1", root.name4
assert_equal :baz, root.opt1
assert_equal :foo, root.opt2
end
test 'creates root section with default values if name is nil and config is empty element' do
obj = ConfigurableSpec::Base1Safe.new
root = obj.configured_section_create(nil, config_element())
assert_equal "node", root.node
assert_false root.flag1
assert_true root.flag2
assert_equal "basex1", root.name1
assert_equal "basex2", root.name2
assert_equal "base1", root.name3
assert_equal "base1", root.name4
assert_equal :baz, root.opt1
assert_equal :foo, root.opt2
end
test 'creates root section with specified value if name is nil and configuration element is specified' do
obj = ConfigurableSpec::Base1Safe.new
root = obj.configured_section_create(nil, config_element('match', '', {'node' => "nodename", 'flag1' => 'true', 'name1' => 'fixed1', 'opt1' => 'foo'}))
assert_equal "nodename", root.node
assert_equal "fixed1", root.name1
assert_true root.flag1
assert_equal :foo, root.opt1
assert_true root.flag2
assert_equal "basex2", root.name2
assert_equal "base1", root.name3
assert_equal "base1", root.name4
assert_equal :foo, root.opt2
end
end
sub_test_case '#configure' do
test 'returns configurable object itself' do
b2 = ConfigurableSpec::Base2.new
assert_instance_of(ConfigurableSpec::Base2, b2.configure(config_element("", "", {"name1" => "t1", "name5" => "t5", "opt3" => "a"})))
end
test 'can accept frozen string' do
b2 = ConfigurableSpec::Base2.new
assert_instance_of(ConfigurableSpec::Base2, b2.configure(config_element("", "", {"name1" => "t1".freeze, "name5" => "t5", "opt3" => "a"})))
end
test 'raise errors without any specifications for param without defaults' do
b2 = ConfigurableSpec::Base2.new
assert_raise(Fluent::ConfigError) { b2.configure(config_element("", "", {})) }
assert_raise(Fluent::ConfigError) { b2.configure(config_element("", "", {"name1" => "t1"})) }
assert_raise(Fluent::ConfigError) { b2.configure(config_element("", "", {"name5" => "t5"})) }
assert_raise(Fluent::ConfigError) { b2.configure(config_element("", "", {"name1" => "t1", "name5" => "t5"})) }
assert_nothing_raised { b2.configure(config_element("", "", {"name1" => "t1", "name5" => "t5", "opt3" => "a"})) }
assert_equal(["node", false, true, "t1", "base2", "base1", "base2", "t5", "base2"], b2.get_all)
assert_equal(:a, b2.opt3)
end
test 'can configure bool values' do
b2a = ConfigurableSpec::Base2.new
assert_nothing_raised { b2a.configure(config_element("", "", {"flag1" => "true", "flag2" => "yes", "name1" => "t1", "name5" => "t5", "opt3" => "a"})) }
assert_true(b2a.flag1)
assert_true(b2a.flag2)
b2b = ConfigurableSpec::Base2.new
assert_nothing_raised { b2b.configure(config_element("", "", {"flag1" => false, "flag2" => "no", "name1" => "t1", "name5" => "t5", "opt3" => "a"})) }
assert_false(b2b.flag1)
assert_false(b2b.flag2)
end
test 'overwrites values of defaults' do
b2 = ConfigurableSpec::Base2.new
b2.configure(config_element("", "", {"name1" => "t1", "name2" => "t2", "name3" => "t3", "name4" => "t4", "name5" => "t5", "opt1" => "foo", "opt3" => "b"}))
assert_equal("t1", b2.name1)
assert_equal("t2", b2.name2)
assert_equal("t3", b2.name3)
assert_equal("t4", b2.name4)
assert_equal("t5", b2.name5)
assert_equal("base2", b2.name6)
assert_equal(:foo, b2.opt1)
assert_equal(:b, b2.opt3)
assert_equal(["node", false, true, "t1", "t2", "t3", "t4", "t5", "base2"], b2.get_all)
end
test 'enum type rejects values which does not exist in list' do
default = config_element("", "", {"name1" => "t1", "name2" => "t2", "name3" => "t3", "name4" => "t4", "name5" => "t5", "opt1" => "foo", "opt3" => "b"})
b2 = ConfigurableSpec::Base2.new
assert_nothing_raised { b2.configure(default) }
assert_raise(Fluent::ConfigError) { b2.configure(default.merge({"opt1" => "bazz"})) }
assert_raise(Fluent::ConfigError) { b2.configure(default.merge({"opt2" => "fooooooo"})) }
assert_raise(Fluent::ConfigError) { b2.configure(default.merge({"opt3" => "c"})) }
end
sub_test_case 'default values should be duplicated before touched in plugin code' do
test 'default object should be dupped for cases configured twice' do
x6a = ConfigurableSpec::ExampleWithDefaultHashAndArray.new
assert_nothing_raised { x6a.configure(config_element("")) }
assert_equal({}, x6a.obj1)
assert_equal([], x6a.obj2)
x6b = ConfigurableSpec::ExampleWithDefaultHashAndArray.new
assert_nothing_raised { x6b.configure(config_element("")) }
assert_equal({}, x6b.obj1)
assert_equal([], x6b.obj2)
assert { x6a.obj1.object_id != x6b.obj1.object_id }
assert { x6a.obj2.object_id != x6b.obj2.object_id }
x6c = ConfigurableSpec::ExampleWithDefaultHashAndArray.new
assert_nothing_raised { x6c.configure(config_element("")) }
assert_equal({}, x6c.obj1)
assert_equal([], x6c.obj2)
x6c.obj1['k'] = 'v'
x6c.obj2 << 'v'
assert_equal({'k' => 'v'}, x6c.obj1)
assert_equal(['v'], x6c.obj2)
assert_equal({}, x6a.obj1)
assert_equal([], x6a.obj2)
end
end
test 'strict value type' do
default = config_element("", "", {"int1" => "1", "float1" => ""})
c = ConfigurableSpec::ExampleWithIntFloat.new
assert_nothing_raised { c.configure(default) }
assert_raise(Fluent::ConfigError) { c.configure(default, true) }
end
end
test 'set nil for a parameter which has no default value' do
obj = ConfigurableSpec::Base2.new
conf = config_element("", "", {"name1" => nil, "name5" => "t5", "opt3" => "a"})
assert_raise(Fluent::ConfigError.new("'name1' parameter is required but nil is specified")) do
obj.configure(conf)
end
end
test 'set nil for a parameter which has non-nil default value' do
obj = ConfigurableSpec::Base2.new
conf = config_element("", "", {"name1" => "t1", "name3" => nil, "name5" => "t5", "opt3" => "a"})
assert_raise(Fluent::ConfigError.new("'name3' parameter is required but nil is specified")) do
obj.configure(conf)
end
end
test 'set nil for a parameter whose default value is nil' do
obj = ConfigurableSpec::Base1Nil.new
conf = config_element("", "", {"name5" => nil})
obj.configure(conf)
assert_nil obj.name5
end
test 'set nil for parameters whose default values are overwritten by nil' do
obj = ConfigurableSpec::Base1Nil.new
conf = config_element("", "", {"name1" => nil, "name2" => nil, "opt1" => nil})
obj.configure(conf)
assert_nil obj.name1
assert_nil obj.name2
assert_nil obj.opt1
end
test 'set :default' do
obj = ConfigurableSpec::Base2.new
conf = config_element("", "", {"name1" => "t1", "name3" => :default, "name5" => "t5", "opt3" => "a"})
obj.configure(conf)
assert_equal "base1", obj.name3
end
test 'set :default for a parameter which has no default value' do
obj = ConfigurableSpec::Base2.new
conf = config_element("", "", {"name1" => :default, "name5" => "t5", "opt3" => "a"})
assert_raise(Fluent::ConfigError.new("'name1' doesn't have default value")) do
obj.configure(conf)
end
end
test 'set :default for a parameter which has an overwritten default value' do
obj = ConfigurableSpec::Base2.new
conf = config_element("", "", {"name1" => "t1", "name3" => "t3", "name4" => :default, "name5" => "t5", "opt3" => "a"})
obj.configure(conf)
assert_equal "base2", obj.name4
end
end
sub_test_case 'class defined with config_section' do
sub_test_case '#initialize' do
test 'create instance methods and default values as nil for params from config_section specified as non-multi' do
b4 = ConfigurableSpec::Base4.new
assert_nil(b4.description1)
assert_nil(b4.description2)
end
test 'create instance methods and default values as [] for params from config_section specified as multi' do
b4 = ConfigurableSpec::Base4.new
assert_equal([], b4.description3)
end
test 'overwrite base class definition by config_section of sub class definition' do
b3 = ConfigurableSpec::Base3.new
assert_equal([], b3.node)
end
test 'create instance methods and default values by param_name' do
b4 = ConfigurableSpec::Base4.new
assert_equal([], b4.nodes)
assert_equal("node", b4.node)
end
test 'create non-required and multi without any specifications' do
b3 = ConfigurableSpec::Base3.new
assert_false(b3.class.merged_configure_proxy.sections[:node].required?)
assert_true(b3.class.merged_configure_proxy.sections[:node].multi?)
end
end
sub_test_case '#configured_section_create' do
test 'raises configuration error if required param exists but no configuration element is specified' do
obj = ConfigurableSpec::Base4.new
assert_raise(Fluent::ConfigError.new("'<node ARG>' section requires argument")) do
obj.configured_section_create(:node)
end
assert_raise(Fluent::ConfigError.new("'text' parameter is required")) do
obj.configured_section_create(:description1)
end
end
test 'creates any defined section with default values if name is nil and config is not specified' do
obj = ConfigurableSpec::Base4Safe.new
node = obj.configured_section_create(:node)
assert_equal 0, node.num
assert_equal "node", node.name
assert_equal "b4", node.type
desc1 = obj.configured_section_create(:description1)
assert_equal "desc1", desc1.note
assert_equal "teeeext", desc1.text
end
test 'creates any defined section with default values if name is nil and config is empty element' do
obj = ConfigurableSpec::Base4Safe.new
node = obj.configured_section_create(:node, config_element())
assert_equal 0, node.num
assert_equal "node", node.name
assert_equal "b4", node.type
desc1 = obj.configured_section_create(:description1, config_element())
assert_equal "desc1", desc1.note
assert_equal "teeeext", desc1.text
end
test 'creates any defined section with specified value if name is nil and configuration element is specified' do
obj = ConfigurableSpec::Base4Safe.new
node = obj.configured_section_create(:node, config_element('node', '1', {'name' => 'node1', 'type' => 'b1'}))
assert_equal 1, node.num
assert_equal "node1", node.name
assert_equal "b1", node.type
desc1 = obj.configured_section_create(:description1, config_element('description1', 'desc one', {'text' => 't'}))
assert_equal "desc one", desc1.note
assert_equal "t", desc1.text
end
test 'creates a defined section instance even if it is defined as multi:true' do
obj = ConfigurableSpec::Base4Safe.new
desc3 = obj.configured_section_create(:description3)
assert_equal "desc3", desc3.note
assert_equal "yay", desc3.text
desc3 = obj.configured_section_create(:description3, config_element('description3', 'foo'))
assert_equal "desc3: foo", desc3.note
assert_equal "yay", desc3.text
end
end
sub_test_case '#configure' do
BASE_ATTRS = {
"name1" => "1", "name2" => "2", "name3" => "3",
"name4" => "4", "name5" => "5", "name6" => "6",
}
test 'checks required subsections' do
b3 = ConfigurableSpec::Base3.new
# branch sections required
assert_raise(Fluent::ConfigError) { b3.configure(config_element('ROOT', '', BASE_ATTRS, [])) }
# branch argument required
msg = "'<branch ARG>' section requires argument, in section branch"
#expect{ b3.configure(e('ROOT', '', BASE_ATTRS, [e('branch', '')])) }.to raise_error(Fluent::ConfigError, msg)
assert_raise(Fluent::ConfigError.new(msg)) { b3.configure(config_element('ROOT', '', BASE_ATTRS, [config_element('branch', '')])) }
# leaf is not required
assert_nothing_raised { b3.configure(config_element('ROOT', '', BASE_ATTRS, [config_element('branch', 'branch_name')])) }
# leaf weight required
msg = "'weight' parameter is required, in section branch > leaf"
branch1 = config_element('branch', 'branch_name', {size: 1}, [config_element('leaf', '10', {"weight" => 1})])
assert_nothing_raised { b3.configure(config_element('ROOT', '', BASE_ATTRS, [branch1])) }
branch2 = config_element('branch', 'branch_name', {size: 1}, [config_element('leaf', '20')])
assert_raise(Fluent::ConfigError.new(msg)) { b3.configure(config_element('ROOT', '', BASE_ATTRS, [branch1, branch2])) }
branch3 = config_element('branch', 'branch_name', {size: 1}, [config_element('leaf', '10', {"weight" => 3}), config_element('leaf', '20')])
assert_raise(Fluent::ConfigError.new(msg)) { b3.configure(config_element('ROOT', '', BASE_ATTRS, [branch3])) }
### worm not required
b4 = ConfigurableSpec::Base4.new
d1 = config_element('description1', '', {"text" => "d1"})
d2 = config_element('description2', '', {"text" => "d2"})
d3 = config_element('description3', '', {"text" => "d3"})
assert_nothing_raised { b4.configure(config_element('ROOT', '', BASE_ATTRS, [d1.dup, d2.dup, d3.dup])) }
# description1 cannot be specified 2 or more
msg = "'<description1>' section cannot be written twice or more"
assert_raise(Fluent::ConfigError.new(msg)) { b4.configure(config_element('ROOT', '', BASE_ATTRS, [d1.dup, d2.dup, d1.dup, d3.dup])) }
# description2 cannot be specified 2 or more
msg = "'<description2>' section cannot be written twice or more"
assert_raise(Fluent::ConfigError.new(msg)) { b4.configure(config_element('ROOT', '', BASE_ATTRS, [d1.dup, d2.dup, d3.dup, d2.dup])) }
# description3 can be specified 2 or more
assert_nothing_raised { b4.configure(config_element('ROOT', '', BASE_ATTRS, [d1.dup, d2.dup, d3.dup, d3.dup])) }
end
test 'constructs configuration object tree for Base3' do
conf = config_element(
'ROOT',
'',
BASE_ATTRS,
[
config_element('node', '', {"type" => "1"}), config_element('node', '', {"name" => "node2","type" => "2"}),
config_element('branch', 'b1.*', {}, []),
config_element('branch',
'b2.*',
{"size" => 5},
[
config_element('leaf', 'THIS IS IGNORED', {"weight" => 55}, []),
config_element('leaf', 'THIS IS IGNORED', {"weight" => 50}, [ config_element('worm', '', {}) ]),
config_element('leaf', 'THIS IS IGNORED', {"weight" => 50}, [ config_element('worm', '', {"type" => "w1"}), config_element('worm', '', {"type" => "w2"}) ]),
]
),
config_element('branch',
'b3.*',
{"size" => "503"},
[
config_element('leaf', 'THIS IS IGNORED', {"weight" => 55}, []),
]
)
],
)
b3 = ConfigurableSpec::Base3.new.configure(conf)
assert_not_equal("node", b3.node) # overwritten
assert_equal("1", b3.name1)
assert_equal("2", b3.name2)
assert_equal("3", b3.name3)
assert_equal("4", b3.name4)
assert_equal("5", b3.name5)
assert_equal("6", b3.name6)
assert_instance_of(Array, b3.node)
assert_equal(2, b3.node.size)
assert_equal("node", b3.node[0].name)
assert_equal("1", b3.node[0].type)
assert_equal(b3.node[0].type, b3.node[0][:type])
assert_equal("node2", b3.node[1].name)
assert_equal("2", b3.node[1].type)
assert_equal(b3.node[1].type, b3.node[1][:type])
assert_instance_of(Array, b3.branch)
assert_equal(3, b3.branch.size)
assert_equal('b1.*', b3.branch[0].name)
assert_equal(10, b3.branch[0].size)
assert_equal([], b3.branch[0].leaf)
assert_equal('b2.*', b3.branch[1].name)
assert_equal(5, b3.branch[1].size)
assert_equal(3, b3.branch[1].leaf.size)
assert_equal(b3.branch[1].leaf, b3.branch[1][:leaf])
assert_equal(55, b3.branch[1].leaf[0].weight)
assert_equal(0, b3.branch[1].leaf[0].worms.size)
assert_equal(50, b3.branch[1].leaf[1].weight)
assert_equal(1, b3.branch[1].leaf[1].worms.size)
assert_equal("ladybird", b3.branch[1].leaf[1].worms[0].type)
assert_equal(50, b3.branch[1].leaf[2].weight)
assert_equal(2, b3.branch[1].leaf[2].worms.size)
assert_equal("w1", b3.branch[1].leaf[2].worms[0].type)
assert_equal("w2", b3.branch[1].leaf[2].worms[1].type)
assert_equal('b3.*', b3.branch[2].name)
assert_equal(503, b3.branch[2].size)
assert_equal(1, b3.branch[2].leaf.size)
assert_equal(55, b3.branch[2].leaf[0].weight)
end
test 'constructs confuguration object tree for Base4' do
conf = config_element(
'ROOT',
'',
BASE_ATTRS,
[
config_element('node', '1', {"type" => "1"}), config_element('node', '2', {"name" => "node2"}),
config_element('description3', '', {"text" => "dddd3-1"}),
config_element('description2', 'd-2', {"text" => "dddd2"}),
config_element('description1', '', {"text" => "dddd1"}),
config_element('description3', 'd-3', {"text" => "dddd3-2"}),
config_element('description3', 'd-3a', {"text" => "dddd3-3"}),
config_element('node', '4', {"type" => "four"}),
],
)
b4 = ConfigurableSpec::Base4.new.configure(conf)
assert_equal("node", b4.node)
assert_equal("1", b4.name1)
assert_equal("2", b4.name2)
assert_equal("3", b4.name3)
assert_equal("4", b4.name4)
assert_equal("5", b4.name5)
assert_equal("6", b4.name6)
assert_instance_of(Array, b4.nodes)
assert_equal(3, b4.nodes.size)
assert_equal(1, b4.nodes[0].num)
assert_equal("node", b4.nodes[0].name)
assert_equal("1", b4.nodes[0].type)
assert_equal(2, b4.nodes[1].num)
assert_equal("node2", b4.nodes[1].name)
assert_equal("b4", b4.nodes[1].type)
assert_equal(4, b4.nodes[2].num)
assert_equal("node", b4.nodes[2].name)
assert_equal("four", b4.nodes[2].type)
# config_element('description3', '', {"text" => "dddd3-1"}),
# config_element('description3', 'd-3', {"text" => "dddd3-2"}),
# config_element('description3', 'd-3a', {"text" => "dddd3-3"}),
# NoMethodError: undefined method `class' for <Fluent::Config::Section {...}>:Fluent::Config::Section occurred. Should we add class method to Section?
#assert_equal('Fluent::Config::Section', b4.description1.class.name)
assert_equal("desc1", b4.description1.note)
assert_equal("dddd1", b4.description1.text)
# same with assert_equal('Fluent::Config::Section', b4.description1)
#assert_equal('Fluent::Config::Section', b4.description2)
assert_equal("d-2", b4.description2.note)
assert_equal("dddd2", b4.description2.text)
assert_instance_of(Array, b4.description3)
assert_equal(3, b4.description3.size)
assert_equal("desc3", b4.description3[0].note)
assert_equal("dddd3-1", b4.description3[0].text)
assert_equal('desc3: d-3', b4.description3[1].note)
assert_equal('dddd3-2', b4.description3[1].text)
assert_equal('desc3: d-3a', b4.description3[2].note)
assert_equal('dddd3-3', b4.description3[2].text)
end
test 'checks missing of specifications' do
conf0 = config_element('ROOT', '', {}, [])
ex01 = ConfigurableSpec::Example0.new
assert_raise(Fluent::ConfigError) { ex01.configure(conf0) }
complete = config_element('ROOT', '', {
"stringvalue" => "s1", "boolvalue" => "yes", "integervalue" => "10",
"sizevalue" => "10m", "timevalue" => "100s", "floatvalue" => "1.001",
"hashvalue" => '{"foo":1, "bar":2}',
"arrayvalue" => '[1,"ichi"]',
})
checker = lambda { |conf| ConfigurableSpec::Example0.new.configure(conf) }
assert_nothing_raised { checker.call(complete) }
assert_raise(Fluent::ConfigError) { c = complete.dup; c.delete("stringvalue"); checker.call(c) }
assert_raise(Fluent::ConfigError) { c = complete.dup; c.delete("boolvalue"); checker.call(c) }
assert_raise(Fluent::ConfigError) { c = complete.dup; c.delete("integervalue"); checker.call(c) }
assert_raise(Fluent::ConfigError) { c = complete.dup; c.delete("sizevalue"); checker.call(c) }
assert_raise(Fluent::ConfigError) { c = complete.dup; c.delete("timevalue"); checker.call(c) }
assert_raise(Fluent::ConfigError) { c = complete.dup; c.delete("floatvalue"); checker.call(c) }
assert_raise(Fluent::ConfigError) { c = complete.dup; c.delete("hashvalue"); checker.call(c) }
assert_raise(Fluent::ConfigError) { c = complete.dup; c.delete("arrayvalue"); checker.call(c) }
end
test 'generates section with default values for init:true sections' do
conf = config_element('ROOT', '', {}, [])
init0 = ConfigurableSpec::Init0.new
assert_nothing_raised { init0.configure(conf) }
assert init0.sec1
assert_equal "sec1", init0.sec1.name
assert_equal 1, init0.sec2.size
assert_equal "sec1", init0.sec2.first.name
end
test 'accepts configuration values as string representation' do
conf = config_element('ROOT', '', {
"stringvalue" => "s1", "boolvalue" => "yes", "integervalue" => "10",
"sizevalue" => "10m", "timevalue" => "10m", "floatvalue" => "1.001",
"hashvalue" => '{"foo":1, "bar":2}',
"arrayvalue" => '[1,"ichi"]',
})
ex = ConfigurableSpec::Example0.new.configure(conf)
assert_equal("s1", ex.stringvalue)
assert_true(ex.boolvalue)
assert_equal(10, ex.integervalue)
assert_equal(10 * 1024 * 1024, ex.sizevalue)
assert_equal(10 * 60, ex.timevalue)
assert_equal(1.001, ex.floatvalue)
assert_equal({"foo" => 1, "bar" => 2}, ex.hashvalue)
assert_equal([1, "ichi"], ex.arrayvalue)
end
test 'accepts configuration values as ruby value representation (especially for DSL)' do
conf = config_element('ROOT', '', {
"stringvalue" => "s1", "boolvalue" => true, "integervalue" => 10,
"sizevalue" => 10 * 1024 * 1024, "timevalue" => 10 * 60, "floatvalue" => 1.001,
"hashvalue" => {"foo" => 1, "bar" => 2},
"arrayvalue" => [1,"ichi"],
})
ex = ConfigurableSpec::Example0.new.configure(conf)
assert_equal("s1", ex.stringvalue)
assert_true(ex.boolvalue)
assert_equal(10, ex.integervalue)
assert_equal(10 * 1024 * 1024, ex.sizevalue)
assert_equal(10 * 60, ex.timevalue)
assert_equal(1.001, ex.floatvalue)
assert_equal({"foo" => 1, "bar" => 2}, ex.hashvalue)
assert_equal([1, "ichi"], ex.arrayvalue)
end
test 'gets both of true(yes) and false(no) for bool value parameter' do
conf = config_element('ROOT', '', {
"stringvalue" => "s1", "integervalue" => 10,
"sizevalue" => 10 * 1024 * 1024, "timevalue" => 10 * 60, "floatvalue" => 1.001,
"hashvalue" => {"foo" => 1, "bar" => 2},
"arrayvalue" => [1,"ichi"],
})
ex0 = ConfigurableSpec::Example0.new.configure(conf.merge({"boolvalue" => "true"}))
assert_true(ex0.boolvalue)
ex1 = ConfigurableSpec::Example0.new.configure(conf.merge({"boolvalue" => "yes"}))
assert_true(ex1.boolvalue)
ex2 = ConfigurableSpec::Example0.new.configure(conf.merge({"boolvalue" => true}))
assert_true(ex2.boolvalue)
ex3 = ConfigurableSpec::Example0.new.configure(conf.merge({"boolvalue" => "false"}))
assert_false(ex3.boolvalue)
ex4 = ConfigurableSpec::Example0.new.configure(conf.merge({"boolvalue" => "no"}))
assert_false(ex4.boolvalue)
ex5 = ConfigurableSpec::Example0.new.configure(conf.merge({"boolvalue" => false}))
assert_false(ex5.boolvalue)
end
end
sub_test_case '.config_section' do
CONF1 = config_element('ROOT', '', {
'name' => 'tagomoris',
'bool' => true,
})
CONF2 = config_element('ROOT', '', {
'name' => 'tagomoris',
'bool' => true,
},
[config_element('detail', '', { 'phone_no' => "+81-00-0000-0000" }, [])])
CONF3 = config_element('ROOT', '', {
'name' => 'tagomoris',
'bool' => true,
},
[config_element('detail', '', { 'address' => "Chiyoda Tokyo Japan" }, [])])
CONF4 = config_element('ROOT', '', {
'name' => 'tagomoris',
'bool' => true,
},
[
config_element('detail', '', {
'address' => "Chiyoda Tokyo Japan",
'phone_no' => '+81-00-0000-0000'
},
[])
])
data(conf1: CONF1,
conf2: CONF2,
conf3: CONF3,
conf4: CONF4,)
test 'base class' do |data|
assert_nothing_raised { ConfigurableSpec::Overwrite::Base.new.configure(data) }
end
test 'subclass cannot overwrite required' do
assert_raise(Fluent::ConfigError.new("BUG: subclass cannot overwrite base class's config_section: required")) do
ConfigurableSpec::Overwrite::Required.new.configure(CONF1)
end
end
test 'subclass cannot overwrite multi' do
assert_raise(Fluent::ConfigError.new("BUG: subclass cannot overwrite base class's config_section: multi")) do
ConfigurableSpec::Overwrite::Multi.new.configure(CONF1)
end
end
test 'subclass cannot overwrite alias' do
assert_raise(Fluent::ConfigError.new("BUG: subclass cannot overwrite base class's config_section: alias")) do
ConfigurableSpec::Overwrite::Alias.new.configure(CONF1)
end
end
test 'subclass uses superclass default options' do
base = ConfigurableSpec::Overwrite::Base.new.configure(CONF2)
sub = ConfigurableSpec::Overwrite::DefaultOptions.new.configure(CONF2)
detail_base = base.class.merged_configure_proxy.sections[:detail]
detail_sub = sub.class.merged_configure_proxy.sections[:detail]
detail_base_attributes = {
required: detail_base.required,
multi: detail_base.multi,
alias: detail_base.alias,
}
detail_sub_attributes = {
required: detail_sub.required,
multi: detail_sub.multi,
alias: detail_sub.alias,
}
assert_equal(detail_base_attributes, detail_sub_attributes)
end
test 'subclass can overwrite detail.address' do
base = ConfigurableSpec::Overwrite::Base.new.configure(CONF2)
target = ConfigurableSpec::Overwrite::DetailAddressDefault.new.configure(CONF2)
expected_addresses = ["x", "y"]
actual_addresses = [base.detail.address, target.detail.address]
assert_equal(expected_addresses, actual_addresses)
end
test 'subclass can add param' do
assert_raise(Fluent::ConfigError.new("'phone_no' parameter is required, in section detail")) do
ConfigurableSpec::Overwrite::AddParam.new.configure(CONF3)
end
target = ConfigurableSpec::Overwrite::AddParam.new.configure(CONF4)
expected = {
address: "Chiyoda Tokyo Japan",
phone_no: "+81-00-0000-0000"
}
actual = {
address: target.detail.address,
phone_no: target.detail.phone_no
}
assert_equal(expected, actual)
end
test 'subclass can add param with overwriting address' do
assert_raise(Fluent::ConfigError.new("'phone_no' parameter is required, in section detail")) do
ConfigurableSpec::Overwrite::AddParamOverwriteAddress.new.configure(CONF3)
end
target = ConfigurableSpec::Overwrite::AddParamOverwriteAddress.new.configure(CONF4)
expected = {
address: "Chiyoda Tokyo Japan",
phone_no: "+81-00-0000-0000"
}
actual = {
address: target.detail.address,
phone_no: target.detail.phone_no
}
assert_equal(expected, actual)
end
sub_test_case 'final' do
test 'base class has designed params and default values' do
b = ConfigurableSpec::Final::Base.new
appendix_conf = config_element('appendix', '', {"code" => "b", "name" => "base"})
b.configure(config_element('ROOT', '', {}, [appendix_conf]))
assert_equal "b", b.appendix.code
assert_equal "base", b.appendix.name
assert_equal "", b.appendix.address
end
test 'subclass can change type, add default value, change default value of parameters, and add parameters to non-finalized section' do
f = ConfigurableSpec::Final::Finalized.new
appendix_conf = config_element('appendix', '', {"code" => 1})
f.configure(config_element('ROOT', '', {}, [appendix_conf]))
assert_equal 1, f.appendix.code
assert_equal 'y', f.appendix.name
assert_equal "-", f.appendix.address
assert_equal 10, f.appendix.age
end
test 'subclass can add default value, change default value of parameters, and add parameters to finalized section' do
i = ConfigurableSpec::Final::InheritsFinalized.new
appendix_conf = config_element('appendix', '', {"phone_no" => "00-0000-0000"})
i.configure(config_element('ROOT', '', {}, [appendix_conf]))
assert_equal 2, i.appendix.code
assert_equal 0, i.appendix.age
assert_equal "00-0000-0000", i.appendix.phone_no
end
test 'finalized base class works as designed' do
b = ConfigurableSpec::Final::FinalizedBase.new
appendix_conf = config_element('options', '', {"name" => "moris"})
assert_nothing_raised do
b.configure(config_element('ROOT', '', {}, [appendix_conf]))
end
assert b.apd
assert_equal "moris", b.apd.name
end
test 'subclass can change init' do
n = ConfigurableSpec::Final::OverwriteInit.new
assert_nothing_raised do
n.configure(config_element('ROOT', ''))
end
assert n.apd
assert_equal "moris", n.apd.name
assert_equal 0, n.apd.code
end
test 'subclass cannot change parameter types in finalized sections' do
s = ConfigurableSpec::Final::Subclass.new
appendix_conf = config_element('options', '', {"name" => "1"})
assert_nothing_raised do
s.configure(config_element('ROOT', '', {}, [appendix_conf]))
end
assert_equal "1", s.apd.name
end
test 'subclass cannot change param_name of finalized section' do
assert_raise(Fluent::ConfigError.new("BUG: subclass cannot overwrite base class's config_section: param_name")) do
ConfigurableSpec::Final::OverwriteParamName.new
end
end
test 'subclass cannot change final of finalized section' do
assert_raise(Fluent::ConfigError.new("BUG: subclass cannot overwrite finalized base class's config_section")) do
ConfigurableSpec::Final::OverwriteFinal.new
end
end
test 'subclass cannot change required of finalized section' do
assert_raise(Fluent::ConfigError.new("BUG: subclass cannot overwrite base class's config_section: required")) do
ConfigurableSpec::Final::OverwriteRequired.new
end
end
test 'subclass cannot change multi of finalized section' do
assert_raise(Fluent::ConfigError.new("BUG: subclass cannot overwrite base class's config_section: multi")) do
ConfigurableSpec::Final::OverwriteMulti.new
end
end
test 'subclass cannot change alias of finalized section' do
assert_raise(Fluent::ConfigError.new("BUG: subclass cannot overwrite base class's config_section: alias")) do
ConfigurableSpec::Final::OverwriteAlias.new
end
end
end
end
end
sub_test_case 'class defined with config_param/config_section having :alias' do
sub_test_case '#initialize' do
test 'does not create methods for alias' do
ex1 = ConfigurableSpec::ExampleWithAlias.new
assert_nothing_raised { ex1.name }
assert_raise(NoMethodError) { ex1.fullname }
assert_nothing_raised { ex1.bool }
assert_raise(NoMethodError) { ex1.flag }
assert_nothing_raised { ex1.detail }
assert_raise(NoMethodError) { ex1.information}
end
end
sub_test_case '#configure' do
test 'provides accessible data for alias attribute keys' do
ex1 = ConfigurableSpec::ExampleWithAlias.new
conf = config_element('ROOT', '', {
"fullname" => "foo bar",
"bool" => false
},
[config_element('information', '', {"address" => "Mountain View 0"})])
ex1.configure(conf)
assert_equal("foo bar", ex1.name)
assert_not_nil(ex1.bool)
assert_false(ex1.bool)
assert_not_nil(ex1.detail)
assert_equal("Mountain View 0", ex1.detail.address)
end
end
end
sub_test_case 'defaults can be overwritten by owner' do
test 'for feature plugin which has flat parameters with parent' do
owner = ConfigurableSpec::OverwriteDefaults::Owner.new
child = ConfigurableSpec::OverwriteDefaults::FlatChild.new
assert_nil child.class.merged_configure_proxy.configured_in_section
child.owner = owner
child.configure(config_element('ROOT', '', {}, []))
assert_equal "V1", child.key1
end
test 'for feature plugin which has parameters in subsection of parent' do
owner = ConfigurableSpec::OverwriteDefaults::Owner.new
child = ConfigurableSpec::OverwriteDefaults::BufferChild.new
assert_equal :buffer, child.class.merged_configure_proxy.configured_in_section
child.owner = owner
child.configure(config_element('ROOT', '', {}, []))
assert_equal 1024, child.size_of_something
end
test 'even in subclass of owner' do
owner = ConfigurableSpec::OverwriteDefaults::SubOwner.new
child = ConfigurableSpec::OverwriteDefaults::BufferChild.new
assert_equal :buffer, child.class.merged_configure_proxy.configured_in_section
child.owner = owner
child.configure(config_element('ROOT', '', {}, []))
assert_equal 2048, child.size_of_something
end
test 'default values can be overwritten with nil' do
owner = ConfigurableSpec::OverwriteDefaults::NilOwner.new
child = ConfigurableSpec::OverwriteDefaults::BufferChild.new
assert_equal :buffer, child.class.merged_configure_proxy.configured_in_section
child.owner = owner
child.configure(config_element('ROOT', '', {}, []))
assert_nil child.size_of_something
end
test 'the first configured_in (in the order from base class) will be applied' do
child = ConfigurableSpec::OverwriteDefaults::BufferSubclass.new
assert_equal :buffer, child.class.merged_configure_proxy.configured_in_section
child.configure(config_element('ROOT', '', {}, []))
assert_equal 512, child.size_of_something
end
test 'the first configured_in is valid with owner classes' do
owner = ConfigurableSpec::OverwriteDefaults::Owner.new
child = ConfigurableSpec::OverwriteDefaults::BufferSubclass.new
assert_equal :buffer, child.class.merged_configure_proxy.configured_in_section
child.owner = owner
child.configure(config_element('ROOT', '', {}, []))
assert_equal 1024, child.size_of_something
end
test 'the only first configured_in is valid even in subclasses of a class with configured_in' do
child = ConfigurableSpec::OverwriteDefaults::BufferSubSubclass.new
assert_equal :buffer, child.class.merged_configure_proxy.configured_in_section
child.configure(config_element('ROOT', '', {}, []))
assert_equal 512, child.size_of_something
end
end
sub_test_case ':secret option' do
setup do
@conf = config_element('ROOT', '',
{
'normal_param' => 'normal',
'secret_param' => 'secret'
},
[config_element('section', '', {'normal_param2' => 'normal', 'secret_param2' => 'secret'} )])
@example = ConfigurableSpec::ExampleWithSecret.new
@example.configure(@conf)
end
test 'to_s hides secret config_param' do
@conf.to_s.each_line { |line|
key, value = line.strip.split(' ', 2)
assert_secret_param(key, value)
}
end
test 'config returns masked configuration' do
conf = @example.config
conf.each_pair { |key, value|
assert_secret_param(key, value)
}
conf.elements.each { |element|
element.each_pair { |key, value|
assert_secret_param(key, value)
}
}
end
def assert_secret_param(key, value)
case key
when 'normal_param', 'normal_param2'
assert_equal 'normal', value
when 'secret_param', 'secret_param2'
assert_equal 'xxxxxx', value
end
end
end
sub_test_case 'unused section' do
test 'get plugin name when found unknown section' do
conf = config_element('ROOT', '',
{
'name_param' => 'name',
},
[config_element('unknown', '', {'name_param' => 'normal'} )])
example = ConfigurableSpec::ExampleWithCustomSection.new
example.configure(conf)
conf.elements.each { |e|
assert_equal(['ROOT', nil], e.unused_in)
}
end
test 'get an empty array when the section is defined without using config_section' do
conf = config_element('ROOT', '',
{
'name_param' => 'name',
},
[config_element('custom_section', '', {'custom_section_param' => 'custom'} )])
example = ConfigurableSpec::ExampleWithCustomSection.new
example.configure(conf)
conf.elements.each { |e|
assert_equal([], e.unused_in)
}
end
test 'get an empty array when the configuration is used in another element without any sections' do
conf = config_element('ROOT', '',
{
'name_param' => 'name',
},
[config_element('normal_section', '', {'normal_section_param' => 'normal'} )])
example = ConfigurableSpec::ExampleWithCustomSection.new
example.configure(conf)
ConfigurableSpec::ExampleWithCustomSection::AnotherElement.new.configure(conf)
conf.elements.each { |e|
assert_equal([], e.unused_in)
}
end
end
sub_test_case ':skip_accessor option' do
test 'it does not create accessor methods for parameters' do
@example = ConfigurableSpec::ExampleWithSkipAccessor.new
@example.configure(config_element('ROOT'))
assert_equal 'example7', @example.instance_variable_get(:@name)
assert_raise NoMethodError do
@example.name
end
end
end
sub_test_case 'non-required options for config_param' do
test 'desc must be a string if specified' do
assert_raise ArgumentError.new("key: desc must be a String, but Symbol") do
class InvalidDescClass
include Fluent::Configurable
config_param :key, :string, default: '', desc: :invalid_description
end
end
end
test 'alias must be a symbol if specified' do
assert_raise ArgumentError.new("key: alias must be a Symbol, but String") do
class InvalidAliasClass
include Fluent::Configurable
config_param :key, :string, default: '', alias: 'yay'
end
end
end
test 'secret must be true or false if specified' do
assert_raise ArgumentError.new("key: secret must be true or false, but NilClass") do
class InvalidSecretClass
include Fluent::Configurable
config_param :key, :string, default: '', secret: nil
end
end
assert_raise ArgumentError.new("key: secret must be true or false, but String") do
class InvalidSecret2Class
include Fluent::Configurable
config_param :key, :string, default: '', secret: 'yes'
end
end
end
test 'deprecated must be a string if specified' do
assert_raise ArgumentError.new("key: deprecated must be a String, but TrueClass") do
class InvalidDeprecatedClass
include Fluent::Configurable
config_param :key, :string, default: '', deprecated: true
end
end
end
test 'obsoleted must be a string if specified' do
assert_raise ArgumentError.new("key: obsoleted must be a String, but TrueClass") do
class InvalidObsoletedClass
include Fluent::Configurable
config_param :key, :string, default: '', obsoleted: true
end
end
end
test 'value_type for hash must be a symbol' do
assert_raise ArgumentError.new("key: value_type must be a Symbol, but String") do
class InvalidValueTypeOfHashClass
include Fluent::Configurable
config_param :key, :hash, value_type: 'yay'
end
end
end
test 'value_type for array must be a symbol' do
assert_raise ArgumentError.new("key: value_type must be a Symbol, but String") do
class InvalidValueTypeOfArrayClass
include Fluent::Configurable
config_param :key, :array, value_type: 'yay'
end
end
end
test 'skip_accessor must be true or false if specified' do
assert_raise ArgumentError.new("key: skip_accessor must be true or false, but NilClass") do
class InvalidSkipAccessorClass
include Fluent::Configurable
config_param :key, :string, default: '', skip_accessor: nil
end
end
assert_raise ArgumentError.new("key: skip_accessor must be true or false, but String") do
class InvalidSkipAccessor2Class
include Fluent::Configurable
config_param :key, :string, default: '', skip_accessor: 'yes'
end
end
end
end
sub_test_case 'enum parameters' do
test 'list must be specified as an array of symbols'
end
sub_test_case 'deprecated/obsoleted parameters' do
test 'both cannot be specified at once' do
assert_raise ArgumentError.new("param1: both of deprecated and obsoleted cannot be specified at once") do
class Buggy1
include Fluent::Configurable
config_param :param1, :string, default: '', deprecated: 'yay', obsoleted: 'foo!'
end
end
end
test 'warned if deprecated parameter is configured' do
obj = ConfigurableSpec::UnRecommended.new
obj.log = Fluent::Test::TestLogger.new
obj.configure(config_element('ROOT', '', {'key1' => 'yay'}, []))
assert_equal 'yay', obj.key1
first_log = obj.log.logs.first
assert{ first_log && first_log.include?("[warn]") && first_log.include?("'key1' parameter is deprecated: key1 will be removed.") }
end
test 'error raised if obsoleted parameter is configured' do
obj = ConfigurableSpec::UnRecommended.new
obj.log = Fluent::Test::TestLogger.new
assert_raise Fluent::ObsoletedParameterError.new("'key2' parameter is already removed: key2 has been removed.") do
obj.configure(config_element('ROOT', '', {'key2' => 'yay'}, []))
end
first_log = obj.log.logs.first
assert{ first_log && first_log.include?("[error]") && first_log.include?("config error in:\n<ROOT>\n key2 yay\n</ROOT>") }
end
sub_test_case 'logger is nil' do
test 'nothing raised if deprecated parameter is configured' do
obj = ConfigurableSpec::UnRecommended.new
obj.log = nil
obj.configure(config_element('ROOT', '', {'key1' => 'yay'}, []))
assert_nil(obj.log)
end
test 'NoMethodError is not raised if obsoleted parameter is configured' do
obj = ConfigurableSpec::UnRecommended.new
obj.log = nil
assert_raise Fluent::ObsoletedParameterError.new("'key2' parameter is already removed: key2 has been removed.") do
obj.configure(config_element('ROOT', '', {'key2' => 'yay'}, []))
end
assert_nil(obj.log)
end
end
end
sub_test_case '#config_param without default values cause error if section is configured as init:true' do
setup do
@type_lookup = ->(type) { Fluent::Configurable.lookup_type(type) }
@proxy = Fluent::Config::ConfigureProxy.new(:section, type_lookup: @type_lookup)
end
test 'with simple config_param with default value' do
class InitTestClass01
include Fluent::Configurable
config_section :subsection, init: true do
config_param :param1, :integer, default: 1
end
end
c = InitTestClass01.new
c.configure(config_element('root', ''))
assert_equal 1, c.subsection.size
assert_equal 1, c.subsection.first.param1
end
test 'with simple config_param without default value' do
class InitTestClass02
include Fluent::Configurable
config_section :subsection, init: true do
config_param :param1, :integer
end
end
c = InitTestClass02.new
assert_raises ArgumentError.new("subsection: init is specified, but there're parameters without default values:param1") do
c.configure(config_element('root', ''))
end
c.configure(config_element('root', '', {}, [config_element('subsection', '', {'param1' => '1'})]))
assert_equal 1, c.subsection.size
assert_equal 1, c.subsection.first.param1
end
test 'with config_param with config_set_default' do
module InitTestModule03
include Fluent::Configurable
config_section :subsection, init: true do
config_param :param1, :integer
end
end
class InitTestClass03
include Fluent::Configurable
include InitTestModule03
config_section :subsection do
config_set_default :param1, 1
end
end
c = InitTestClass03.new
c.configure(config_element('root', ''))
assert_equal 1, c.subsection.size
assert_equal 1, c.subsection.first.param1
end
test 'with config_argument with default value' do
class InitTestClass04
include Fluent::Configurable
config_section :subsection, init: true do
config_argument :param0, :string, default: 'yay'
end
end
c = InitTestClass04.new
c.configure(config_element('root', ''))
assert_equal 1, c.subsection.size
assert_equal 'yay', c.subsection.first.param0
end
test 'with config_argument without default value' do
class InitTestClass04
include Fluent::Configurable
config_section :subsection, init: true do
config_argument :param0, :string
end
end
c = InitTestClass04.new
assert_raise ArgumentError.new("subsection: init is specified, but default value of argument is missing") do
c.configure(config_element('root', ''))
end
end
test 'with config_argument with config_set_default' do
module InitTestModule05
include Fluent::Configurable
config_section :subsection, init: true do
config_argument :param0, :string
end
end
class InitTestClass05
include Fluent::Configurable
include InitTestModule05
config_section :subsection do
config_set_default :param0, 'foo'
end
end
c = InitTestClass05.new
c.configure(config_element('root', ''))
assert_equal 1, c.subsection.size
assert_equal 'foo', c.subsection.first.param0
end
end
sub_test_case '#config_argument' do
test 'with strict_config_value' do
class TestClass01
include Fluent::Configurable
config_section :subsection do
config_argument :param1, :integer
end
end
c = TestClass01.new
subsection = config_element('subsection', "hoge", { })
assert_raise(Fluent::ConfigError.new('param1: invalid value for Integer(): "hoge"')) do
c.configure(config_element('root', '', {}, [subsection]), true)
end
end
test 'with nil' do
class TestClass02
include Fluent::Configurable
config_section :subsection do
config_argument :param1, :integer
end
end
c = TestClass02.new
subsection = config_element('subsection', nil, { })
assert_raise(Fluent::ConfigError.new("'<subsection ARG>' section requires argument, in section subsection")) do
c.configure(config_element('root', '', {}, [subsection]))
end
end
test 'with nil for an argument whose default value is nil' do
class TestClass03
include Fluent::Configurable
config_section :subsection do
config_argument :param1, :integer, default: nil
end
end
c = TestClass03.new
subsection = config_element('subsection', nil, { })
c.configure(config_element('root', '', {}, [subsection]))
assert_equal 1, c.subsection.size
assert_equal nil, c.subsection.first.param1
end
test 'with :default' do
class TestClass04
include Fluent::Configurable
config_section :subsection do
config_argument :param1, :integer, default: 3
end
end
c = TestClass04.new
subsection = config_element('subsection', :default, { })
c.configure(config_element('root', '', {}, [subsection]))
assert_equal 1, c.subsection.size
assert_equal 3, c.subsection.first.param1
end
test 'with :default for an argument which does not have default value' do
class TestClass05
include Fluent::Configurable
config_section :subsection do
config_argument :param1, :integer
end
end
c = TestClass05.new
subsection = config_element('subsection', :default, { })
assert_raise(Fluent::ConfigError.new("'param1' doesn\'t have default value")) do
c.configure(config_element('root', '', {}, [subsection]))
end
end
end
end
end