require 'helper' require 'fluent/config/types' class TestConfigTypes < ::Test::Unit::TestCase include Fluent sub_test_case 'Config.size_value' do data("2k" => [2048, "2k"], "2K" => [2048, "2K"], "3m" => [3145728, "3m"], "3M" => [3145728, "3M"], "4g" => [4294967296, "4g"], "4G" => [4294967296, "4G"], "5t" => [5497558138880, "5t"], "5T" => [5497558138880, "5T"], "6" => [6, "6"]) test 'normal case' do |(expected, val)| assert_equal(expected, Config.size_value(val)) assert_equal(expected, Config.size_value(val, { strict: true })) end data("integer" => [6, 6], "hoge" => [0, "hoge"], "empty" => [0, ""]) test 'not assumed case' do |(expected, val)| assert_equal(expected, Config.size_value(val)) end test 'nil' do assert_equal(nil, Config.size_value(nil)) end data("integer" => [6, 6], "hoge" => [Fluent::ConfigError.new('name1: invalid value for Integer(): "hoge"'), "hoge"], "empty" => [Fluent::ConfigError.new('name1: invalid value for Integer(): ""'), ""]) test 'not assumed case with strict' do |(expected, val)| if expected.kind_of? Exception assert_raise(expected) do Config.size_value(val, { strict: true }, "name1") end else assert_equal(expected, Config.size_value(val, { strict: true }, "name1")) end end test 'nil with strict' do assert_equal(nil, Config.size_value(nil, { strict: true })) end end sub_test_case 'Config.time_value' do data("10s" => [10, "10s"], "10sec" => [10, "10sec"], "2m" => [120, "2m"], "3h" => [10800, "3h"], "4d" => [345600, "4d"]) test 'normal case' do |(expected, val)| assert_equal(expected, Config.time_value(val)) assert_equal(expected, Config.time_value(val, { strict: true })) end data("integer" => [4.0, 4], "float" => [0.4, 0.4], "hoge" => [0.0, "hoge"], "empty" => [0.0, ""]) test 'not assumed case' do |(expected, val)| assert_equal(expected, Config.time_value(val)) end test 'nil' do assert_equal(nil, Config.time_value(nil)) end data("integer" => [6, 6], "hoge" => [Fluent::ConfigError.new('name1: invalid value for Float(): "hoge"'), "hoge"], "empty" => [Fluent::ConfigError.new('name1: invalid value for Float(): ""'), ""]) test 'not assumed case with strict' do |(expected, val)| if expected.kind_of? Exception assert_raise(expected) do Config.time_value(val, { strict: true }, "name1") end else assert_equal(expected, Config.time_value(val, { strict: true }, "name1")) end end test 'nil with strict' do assert_equal(nil, Config.time_value(nil, { strict: true })) end end sub_test_case 'Config.bool_value' do data("true" => [true, "true"], "yes" => [true, "yes"], "empty" => [true, ""], "false" => [false, "false"], "no" => [false, "no"]) test 'normal case' do |(expected, val)| assert_equal(expected, Config.bool_value(val)) end data("true" => [true, true], "false" => [false, false], "hoge" => [nil, "hoge"], "nil" => [nil, nil], "integer" => [nil, 10]) test 'not assumed case' do |(expected, val)| assert_equal(expected, Config.bool_value(val)) end data("true" => [true, true], "false" => [false, false], "hoge" => [Fluent::ConfigError.new("name1: invalid bool value: hoge"), "hoge"], "nil" => [nil, nil], "integer" => [Fluent::ConfigError.new("name1: invalid bool value: 10"), 10]) test 'not assumed case with strict' do |(expected, val)| if expected.kind_of? Exception assert_raise(expected) do Config.bool_value(val, { strict: true }, "name1") end else assert_equal(expected, Config.bool_value(val, { strict: true }, "name1")) end end end sub_test_case 'Config.regexp_value' do data("empty" => [//, "//"], "plain" => [/regexp/, "/regexp/"], "zero width" => [/^$/, "/^$/"], "character classes" => [/[a-z]/, "/[a-z]/"], "meta charactersx" => [/.+.*?\d\w\s\S/, '/.+.*?\d\w\s\S/']) test 'normal case' do |(expected, str)| assert_equal(expected, Config.regexp_value(str)) end data("empty" => [//, ""], "plain" => [/regexp/, "regexp"], "zero width" => [/^$/, "^$"], "character classes" => [/[a-z]/, "[a-z]"], "meta charactersx" => [/.+.*?\d\w\s\S/, '.+.*?\d\w\s\S']) test 'w/o slashes' do |(expected, str)| assert_equal(expected, Config.regexp_value(str)) end data("missing right slash" => "/regexp", "too many options" => "/regexp/imx",) test 'invalid regexp' do |(str)| assert_raise(Fluent::ConfigError.new("invalid regexp: missing right slash: #{str}")) do Config.regexp_value(str) end end test 'nil' do assert_equal nil, Config.regexp_value(nil) end end sub_test_case 'type converters for config_param definitions' do data("test" => ['test', 'test'], "1" => ['1', '1'], "spaces" => [' ', ' ']) test 'string' do |(expected, val)| assert_equal expected, Config::STRING_TYPE.call(val, {}) assert_equal Encoding::UTF_8, Config::STRING_TYPE.call(val, {}).encoding end test 'string nil' do assert_equal nil, Config::STRING_TYPE.call(nil, {}) end data('latin' => 'Märch', 'ascii' => 'ascii', 'space' => ' ', 'number' => '1', 'Hiragana' => 'あいうえお') test 'string w/ binary' do |str| actual = Config::STRING_TYPE.call(str.b, {}) assert_equal str, actual assert_equal Encoding::UTF_8, actual.encoding end data('starts_with_semicolon' => [:conor, ':conor'], 'simple_string' => [:conor, 'conor'], 'empty_string' => [nil, '']) test 'symbol' do |(expected, val)| assert_equal Config::SYMBOL_TYPE.call(val, {}), expected end data("val" => [:val, 'val'], "v" => [:v, 'v'], "value" => [:value, 'value']) test 'enum' do |(expected, val)| assert_equal expected, Config::ENUM_TYPE.call(val, {list: [:val, :value, :v]}) end test 'enum: pick unknown choice' do assert_raises(Fluent::ConfigError.new("valid options are val,value,v but got x")) do Config::ENUM_TYPE.call('x', {list: [:val, :value, :v]}) end end data("empty list" => {}, "string list" => {list: ["val", "value", "v"]}) test 'enum: invalid choices' do | list | assert_raises(RuntimeError.new("Plugin BUG: config type 'enum' requires :list of symbols")) do Config::ENUM_TYPE.call('val', list) end end test 'enum: nil' do assert_equal nil, Config::ENUM_TYPE.call(nil) end data("1" => [1, '1'], "1.0" => [1, '1.0'], "1_000" => [1000, '1_000'], "1x" => [1, '1x']) test 'integer' do |(expected, val)| assert_equal expected, Config::INTEGER_TYPE.call(val, {}) end data("integer" => [6, 6], "hoge" => [0, "hoge"], "empty" => [0, ""]) test 'integer: not assumed case' do |(expected, val)| assert_equal expected, Config::INTEGER_TYPE.call(val, {}) end test 'integer: nil' do assert_equal nil, Config::INTEGER_TYPE.call(nil, {}) end data("integer" => [6, 6], "hoge" => [Fluent::ConfigError.new('name1: invalid value for Integer(): "hoge"'), "hoge"], "empty" => [Fluent::ConfigError.new('name1: invalid value for Integer(): ""'), ""]) test 'integer: not assumed case with strict' do |(expected, val)| if expected.kind_of? Exception assert_raise(expected) do Config::INTEGER_TYPE.call(val, { strict: true }, "name1") end else assert_equal expected, Config::INTEGER_TYPE.call(val, { strict: true }, "name1") end end test 'integer: nil with strict' do assert_equal nil, Config::INTEGER_TYPE.call(nil, { strict: true }) end data("1" => [1.0, '1'], "1.0" => [1.0, '1.0'], "1.00" => [1.0, '1.00'], "1e0" => [1.0, '1e0']) test 'float' do |(expected, val)| assert_equal expected, Config::FLOAT_TYPE.call(val, {}) end data("integer" => [6, 6], "hoge" => [0, "hoge"], "empty" => [0, ""]) test 'float: not assumed case' do |(expected, val)| assert_equal expected, Config::FLOAT_TYPE.call(val, {}) end test 'float: nil' do assert_equal nil, Config::FLOAT_TYPE.call(nil, {}) end data("integer" => [6, 6], "hoge" => [Fluent::ConfigError.new('name1: invalid value for Float(): "hoge"'), "hoge"], "empty" => [Fluent::ConfigError.new('name1: invalid value for Float(): ""'), ""]) test 'float: not assumed case with strict' do |(expected, val)| if expected.kind_of? Exception assert_raise(expected) do Config::FLOAT_TYPE.call(val, { strict: true }, "name1") end else assert_equal expected, Config::FLOAT_TYPE.call(val, { strict: true }, "name1") end end test 'float: nil with strict' do assert_equal nil, Config::FLOAT_TYPE.call(nil, { strict: true }) end data("1000" => [1000, '1000'], "1k" => [1024, '1k'], "1m" => [1024*1024, '1m']) test 'size' do |(expected, val)| assert_equal expected, Config::SIZE_TYPE.call(val, {}) end data("true" => [true, 'true'], "yes" => [true, 'yes'], "no" => [false, 'no'], "false" => [false, 'false'], "TRUE" => [nil, 'TRUE'], "True" => [nil, 'True'], "Yes" => [nil, 'Yes'], "No" => [nil, 'No'], "empty" => [true, ''], "unexpected_string" => [nil, 'unexpected_string']) test 'bool' do |(expected, val)| assert_equal expected, Config::BOOL_TYPE.call(val, {}) end data("0" => [0, '0'], "1" => [1.0, '1'], "1.01" => [1.01, '1.01'], "1s" => [1, '1s'], "1," => [60, '1m'], "1h" => [3600, '1h'], "1d" => [86400, '1d']) test 'time' do |(expected, val)| assert_equal expected, Config::TIME_TYPE.call(val, {}) end data("empty" => [//, "//"], "plain" => [/regexp/, "/regexp/"], "zero width" => [/^$/, "/^$/"], "character classes" => [/[a-z]/, "/[a-z]/"], "meta charactersx" => [/.+.*?\d\w\s\S/, '/.+.*?\d\w\s\S/']) test 'regexp' do |(expected, str)| assert_equal(expected, Config::REGEXP_TYPE.call(str, {})) end data("string and integer" => [{"x"=>"v","k"=>1}, '{"x":"v","k":1}', {}], "strings" => [{"x"=>"v","k"=>"1"}, 'x:v,k:1', {}], "w/ space" => [{"x"=>"v","k"=>"1"}, 'x:v, k:1', {}], "heading space" => [{"x"=>"v","k"=>"1"}, ' x:v, k:1 ', {}], "trailing space" => [{"x"=>"v","k"=>"1"}, 'x:v , k:1 ', {}], "multiple colons" => [{"x"=>"v:v","k"=>"1"}, 'x:v:v, k:1', {}], "symbolize keys" => [{x: "v", k: 1}, '{"x":"v","k":1}', {symbolize_keys: true}], "value_type: :string" => [{x: "v", k: "1"}, 'x:v,k:1', {symbolize_keys: true, value_type: :string}], "value_type: :string 2" => [{x: "v", k: "1"}, '{"x":"v","k":1}', {symbolize_keys: true, value_type: :string}], "value_type: :integer" => [{x: 0, k: 1}, 'x:0,k:1', {symbolize_keys: true, value_type: :integer}], "time 1" => [{"x"=>1,"y"=>60,"z"=>3600}, '{"x":"1s","y":"1m","z":"1h"}', {value_type: :time}], "time 2" => [{"x"=>1,"y"=>60,"z"=>3600}, 'x:1s,y:1m,z:1h', {value_type: :time}]) test 'hash' do |(expected, val, opts)| assert_equal(expected, Config::HASH_TYPE.call(val, opts)) end test 'hash w/ unknown type' do assert_raise(RuntimeError.new("unknown type in REFORMAT: foo")) do Config::HASH_TYPE.call("x:1,y:2", {value_type: :foo}) end end test 'hash w/ strict option' do assert_raise(Fluent::ConfigError.new('y: invalid value for Integer(): "hoge"')) do Config::HASH_TYPE.call("x:1,y:hoge", {value_type: :integer, strict: true}) end end data('latin' => ['3:Märch', {"3"=>"Märch"}], 'ascii' => ['ascii:ascii', {"ascii"=>"ascii"}], 'number' => ['number:1', {"number"=>"1"}], 'Hiragana' => ['hiragana:あいうえお', {"hiragana"=>"あいうえお"}]) test 'hash w/ binary' do |(target, expected)| assert_equal(expected, Config::HASH_TYPE.call(target.b, { value_type: :string })) end test 'hash w/ nil' do assert_equal(nil, Config::HASH_TYPE.call(nil)) end data("strings and integer" => [["1","2",1], '["1","2",1]', {}], "number strings" => [["1","2","1"], '1,2,1', {}], "alphabets" => [["a","b","c"], '["a","b","c"]', {}], "alphabets w/o quote" => [["a","b","c"], 'a,b,c', {}], "w/ spaces" => [["a","b","c"], 'a, b, c', {}], "w/ space before comma" => [["a","b","c"], 'a , b , c', {}], "comma or space w/ qupte" => [["a a","b,b"," c "], '["a a","b,b"," c "]', {}], "space in a value w/o qupte" => [["a a","b","c"], 'a a,b,c', {}], "integers" => [[1,2,1], '[1,2,1]', {}], "value_type: :integer w/ quote" => [[1,2,1], '["1","2","1"]', {value_type: :integer}], "value_type: :integer w/o quote" => [[1,2,1], '1,2,1', {value_type: :integer}]) test 'array' do |(expected, val, opts)| assert_equal(expected, Config::ARRAY_TYPE.call(val, opts)) end data('["1","2"]' => [["1","2"], '["1","2"]'], '["3"]' => [["3"], '["3"]']) test 'array w/ default values' do |(expected, val)| array_options = { default: [], } assert_equal(expected, Config::ARRAY_TYPE.call(val, array_options)) end test 'array w/ unknown type' do assert_raise(RuntimeError.new("unknown type in REFORMAT: foo")) do Config::ARRAY_TYPE.call("1,2", {value_type: :foo}) end end test 'array w/ strict option' do assert_raise(Fluent::ConfigError.new(': invalid value for Integer(): "hoge"')) do Config::ARRAY_TYPE.call("1,hoge", {value_type: :integer, strict: true}, "name1") end end test 'array w/ nil' do assert_equal(nil, Config::ARRAY_TYPE.call(nil)) end end end