require_relative '../helper' require_relative "assertions" require "json" require "fluent/config/error" require "fluent/config/basic_parser" require "fluent/config/literal_parser" require "fluent/config/v1_parser" require 'fluent/config/parser' module Fluent::Config module V1TestHelper def root(*elements) if elements.first.is_a?(Fluent::Config::Element) attrs = {} else attrs = elements.shift || {} end Fluent::Config::Element.new('ROOT', '', attrs, elements) end def e(name, arg='', attrs={}, elements=[]) Fluent::Config::Element.new(name, arg, attrs, elements) end end class AllTypes include Fluent::Configurable config_param :param_string, :string config_param :param_enum, :enum, list: [:foo, :bar, :baz] config_param :param_integer, :integer config_param :param_float, :float config_param :param_size, :size config_param :param_bool, :bool config_param :param_time, :time config_param :param_hash, :hash config_param :param_array, :array end class TestV1Parser < ::Test::Unit::TestCase def read_config(path) path = File.expand_path(path) data = File.read(path) Fluent::Config::V1Parser.parse(data, File.basename(path), File.dirname(path)) end def parse_text(text) basepath = File.expand_path(File.dirname(__FILE__) + '/../../') Fluent::Config::V1Parser.parse(text, '(test)', basepath, nil) end include V1TestHelper extend V1TestHelper sub_test_case 'attribute parsing' do test "parses attributes" do assert_text_parsed_as(e('ROOT', '', {"k1"=>"v1", "k2"=>"v2"}), %[ k1 v1 k2 v2 ]) end test "allows attribute without value" do assert_text_parsed_as(e('ROOT', '', {"k1"=>"", "k2"=>"v2"}), %[ k1 k2 v2 ]) end test "parses attribute key always string" do assert_text_parsed_as(e('ROOT', '', {"1" => "1"}), "1 1") end data("_.%$!," => "_.%$!,", "/=~-~@\`:?" => "/=~-~@\`:?", "()*{}.[]" => "()*{}.[]") test "parses a value with symbols" do |v| assert_text_parsed_as(e('ROOT', '', {"k" => v}), "k #{v}") end test "ignores spacing around value" do assert_text_parsed_as(e('ROOT', '', {"k1" => "a"}), " k1 a ") end test "allows spaces in value" do assert_text_parsed_as(e('ROOT', '', {"k1" => "a b c"}), "k1 a b c") end test "parses value into empty string if only key exists" do # value parser parses empty string as true for bool type assert_text_parsed_as(e('ROOT', '', {"k1" => ""}), "k1\n") assert_text_parsed_as(e('ROOT', '', {"k1" => ""}), "k1") end sub_test_case 'non-quoted string' do test "remains text starting with '#'" do assert_text_parsed_as(e('ROOT', '', {"k1" => "#not_comment"}), " k1 #not_comment") end test "remains text just after '#'" do assert_text_parsed_as(e('ROOT', '', {"k1" => "a#not_comment"}), " k1 a#not_comment") end test "remove text after ` #` (comment)" do assert_text_parsed_as(e('ROOT', '', {"k1" => "a"}), " k1 a #comment") end test "does not require escaping backslash" do assert_text_parsed_as(e('ROOT', '', {"k1" => "\\\\"}), " k1 \\\\") assert_text_parsed_as(e('ROOT', '', {"k1" => "\\"}), " k1 \\") end test "remains backslash in front of a normal character" do assert_text_parsed_as(e('ROOT', '', {"k1" => '\['}), " k1 \\[") end test "does not accept escape characters" do assert_text_parsed_as(e('ROOT', '', {"k1" => '\n'}), " k1 \\n") end end sub_test_case 'double quoted string' do test "allows # in value" do assert_text_parsed_as(e('ROOT', '', {"k1" => "a#comment"}), ' k1 "a#comment"') end test "rejects characters after double quoted string" do assert_parse_error(' k1 "a" 1') end test "requires escaping backslash" do assert_text_parsed_as(e('ROOT', '', {"k1" => "\\"}), ' k1 "\\\\"') assert_parse_error(' k1 "\\"') end test "requires escaping double quote" do assert_text_parsed_as(e('ROOT', '', {"k1" => '"'}), ' k1 "\\""') assert_parse_error(' k1 """') assert_parse_error(' k1 ""\'') end test "removes backslash in front of a normal character" do assert_text_parsed_as(e('ROOT', '', {"k1" => '['}), ' k1 "\\["') end test "accepts escape characters" do assert_text_parsed_as(e('ROOT', '', {"k1" => "\n"}), ' k1 "\\n"') end test "support multiline string" do assert_text_parsed_as(e('ROOT', '', {"k1" => %[line1 line2] }), %[k1 "line1 line2"] ) assert_text_parsed_as(e('ROOT', '', {"k1" => %[line1 line2] }), %[k1 "line1\\ line2"] ) assert_text_parsed_as(e('ROOT', '', {"k1" => %[line1 line2 line3] }), %[k1 "line1 line2 line3"] ) assert_text_parsed_as(e('ROOT', '', {"k1" => %[line1 line2 line3] }), %[k1 "line1 line2\\ line3"] ) end end sub_test_case 'single quoted string' do test "allows # in value" do assert_text_parsed_as(e('ROOT', '', {"k1" => "a#comment"}), " k1 'a#comment'") end test "rejects characters after single quoted string" do assert_parse_error(" k1 'a' 1") end test "requires escaping backslash" do assert_text_parsed_as(e('ROOT', '', {"k1" => "\\"}), " k1 '\\\\'") assert_parse_error(" k1 '\\'") end test "requires escaping single quote" do assert_text_parsed_as(e('ROOT', '', {"k1" => "'"}), " k1 '\\''") assert_parse_error(" k1 '''") end test "remains backslash in front of a normal character" do assert_text_parsed_as(e('ROOT', '', {"k1" => '\\['}), " k1 '\\['") end test "does not accept escape characters" do assert_text_parsed_as(e('ROOT', '', {"k1" => "\\n"}), " k1 '\\n'") end end data( "in match" => %[ @k v ], "in source" => %[ @k v ], "in filter" => %[ @k v ], "in top-level" => ' @k v ' ) def test_rejects_at_prefix_in_the_parameter_name(data) assert_parse_error(data) end data( "in nested" => %[ @k v ] ) def test_not_reject_at_prefix_in_the_parameter_name(data) assert_nothing_raised { parse_text(data) } end end sub_test_case 'element parsing' do data( 'root' => [root, ""], "accepts empty element" => [root(e("test")), %[ ]], "accepts argument and attributes" => [root(e("test", 'var', {'key'=>"val"})), %[ key val ]], "accepts nested elements" => [root( e("test", 'var', {'key'=>'1'}, [ e('nested1'), e('nested2') ])), %[ key 1 ]], "accepts multiline json values" => [root(e("test", 'var', {'key'=>"[\"a\",\"b\",\"c\",\"d\"]"})), %[ key ["a", "b", "c", "d"] ]], "parses empty element argument to nil" => [root(e("test", '')), %[ ]], "ignores spacing around element argument" => [root(e("test", "a")), %[ ]], "accepts spacing inside element argument (for multiple tags)" => [root(e("test", "a.** b.**")), %[ ]]) def test_parse_element(data) expected, target = data assert_text_parsed_as(expected, target) end [ "**", "*.*", "1", "_.%$!", "/", "()*{}.[]", ].each do |arg| test "parses symbol element argument:#{arg}" do assert_text_parsed_as(root(e("test", arg)), %[ ]) end end data( "considers comments in element argument" => %[ ], "requires line_end after begin tag" => %[ ], "requires line_end after end tag" => %[ ]) def test_parse_error(data) assert_parse_error(data) end end # port from test_config.rb sub_test_case '@include parsing' do TMP_DIR = File.dirname(__FILE__) + "/tmp/v1_config#{ENV['TEST_ENV_NUMBER']}" def write_config(path, data) FileUtils.mkdir_p(File.dirname(path)) File.open(path, "w") { |f| f.write data } end def prepare_config write_config "#{TMP_DIR}/config_test_1.conf", %[ k1 root_config include dir/config_test_2.conf # @include #{TMP_DIR}/config_test_4.conf include file://#{TMP_DIR}/config_test_5.conf @include config.d/*.conf ] write_config "#{TMP_DIR}/dir/config_test_2.conf", %[ k2 relative_path_include @include ../config_test_3.conf ] write_config "#{TMP_DIR}/config_test_3.conf", %[ k3 relative_include_in_included_file ] write_config "#{TMP_DIR}/config_test_4.conf", %[ k4 absolute_path_include ] write_config "#{TMP_DIR}/config_test_5.conf", %[ k5 uri_include ] write_config "#{TMP_DIR}/config.d/config_test_6.conf", %[ k6 wildcard_include_1 include normal_parameter ] write_config "#{TMP_DIR}/config.d/config_test_7.conf", %[ k7 wildcard_include_2 ] write_config "#{TMP_DIR}/config.d/config_test_8.conf", %[ @include ../dir/config_test_9.conf ] write_config "#{TMP_DIR}/dir/config_test_9.conf", %[ k9 embeded nested nested_value include hoge ] write_config "#{TMP_DIR}/config.d/00_config_test_8.conf", %[ k8 wildcard_include_3 include normal_parameter ] end test 'parses @include / include correctly' do prepare_config c = read_config("#{TMP_DIR}/config_test_1.conf") assert_equal('root_config', c['k1']) assert_equal('relative_path_include', c['k2']) assert_equal('relative_include_in_included_file', c['k3']) assert_equal('absolute_path_include', c['k4']) assert_equal('uri_include', c['k5']) assert_equal('wildcard_include_1', c['k6']) assert_equal('wildcard_include_2', c['k7']) assert_equal('wildcard_include_3', c['k8']) assert_equal([ 'k1', 'k2', 'k3', 'k4', 'k5', 'k8', # Because of the file name this comes first. 'k6', 'k7', ], c.keys) elem1 = c.elements.find { |e| e.name == 'elem1' } assert(elem1) assert_equal('name', elem1.arg) assert_equal('normal_parameter', elem1['include']) elem2 = c.elements.find { |e| e.name == 'elem2' } assert(elem2) assert_equal('name', elem2.arg) assert_equal('embeded', elem2['k9']) assert_not_include(elem2, 'include') elem3 = elem2.elements.find { |e| e.name == 'elem3' } assert(elem3) assert_equal('nested_value', elem3['nested']) assert_equal('hoge', elem3['include']) end # TODO: Add uri based include spec end sub_test_case '#to_s' do test 'parses dumpped configuration' do original = %q!a\\\n\r\f\b'"z! expected = %q!a\\\n\r\f\b'"z! conf = parse_text(%[k1 #{original}]) assert_equal(expected, conf['k1']) # escape check conf2 = parse_text(conf.to_s) # use dumpped configuration to check unescape assert_equal(expected, conf2.elements.first['k1']) end test 'all types' do conf = parse_text(%[ param_string "value" param_enum foo param_integer 999 param_float 55.55 param_size 4k param_bool true param_time 10m param_hash { "key1": "value1", "key2": 2 } param_array ["value1", "value2", 100] ]) target = AllTypes.new.configure(conf) assert_equal(conf.to_s, target.config.to_s) expected = < param_string "value" param_enum foo param_integer 999 param_float 55.55 param_size 4k param_bool true param_time 10m param_hash {"key1":"value1","key2":2} param_array ["value1","value2",100] DUMP assert_equal(expected, conf.to_s) end end end class TestV0Parser < ::Test::Unit::TestCase def parse_text(text) basepath = File.expand_path(File.dirname(__FILE__) + '/../../') Fluent::Config::Parser.parse(StringIO.new(text), '(test)', basepath) end sub_test_case "Fluent::Config::Element#to_s" do test 'all types' do conf = parse_text(%[ param_string value param_enum foo param_integer 999 param_float 55.55 param_size 4k param_bool true param_time 10m param_hash { "key1": "value1", "key2": 2 } param_array ["value1", "value2", 100] ]) target = AllTypes.new.configure(conf) assert_equal(conf.to_s, target.config.to_s) expected = < param_string value param_enum foo param_integer 999 param_float 55.55 param_size 4k param_bool true param_time 10m param_hash { "key1": "value1", "key2": 2 } param_array ["value1", "value2", 100] DUMP assert_equal(expected, conf.to_s) end end end end