fluentd 0.10.53 → 0.10.54

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of fluentd might be problematic. Click here for more details.

checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: b76b903de67ead21383835723d9b289f437de46e
4
- data.tar.gz: b1b787a0cc37983a878136b9731d5ad83eea40cc
3
+ metadata.gz: a362e48a2716bb0892c742fd3e1a3ba78c943c80
4
+ data.tar.gz: 4bebdc013320b9e06be09fa93b52e83e95ee814f
5
5
  SHA512:
6
- metadata.gz: 05b4ab5c735e1d6d01c059dde695faa7513180f1520b56f7078eedf9b1491fb00b9fd29aaec0f87f28784750a5c5aa210c5aaff2f5c9fe7740fad857c92b30ee
7
- data.tar.gz: 075c930dd72a40427330313e11bf4e3fe5d4399ea91a44df072cbe6fb2605b584549b9075ae55234a999fd737ff805b24f0285969574cca485f360a9b9b6ffa8
6
+ metadata.gz: a67253d1567eb3f934d2740271c3e1355a1df08b0638809e5add6b9e739f1f99aee1fa27896f9aac798c659f5dc7ce38f943654c5299a8af63ca679c3158f260
7
+ data.tar.gz: f32dd1c438f4a6f0598182d0a26d712f05cd1782a3eeebad4d32026529668e8212b4cf0269361646c440b1b3cae8e0e5b46b89cff8e2ba4e7334e42a819953af
data/.gitignore CHANGED
@@ -17,5 +17,5 @@ fluent-gem
17
17
  fluentd
18
18
  pkg/*
19
19
  test/tmp/*
20
- spec/config/tmp/*
20
+ test/config/tmp/*
21
21
  make_dist.sh
data/ChangeLog CHANGED
@@ -1,3 +1,13 @@
1
+ Release 0.10.54 - 2014/10/17
2
+
3
+ * config: Change v1's non-quoted string literal behaviour to v0's behaviour
4
+ * config: Add --use-v0-config for keeping old configuration
5
+ * config: Add single quoted literal in v1 configuration
6
+ * config: Improve error message of Array / Hash parse error
7
+ * input: Reduce shutdown time in some network related plugins when use Cool.io 1.2
8
+ * parser: Use ParserError instead of general exception classes
9
+ * Remove unused zfile buffer plugin
10
+
1
11
  Release 0.10.53 - 2014/08/21
2
12
 
3
13
  * in_tail: Fix forget to detach Closer timer object
data/Rakefile CHANGED
@@ -4,10 +4,8 @@ require "bundler/gem_tasks"
4
4
  require 'fileutils'
5
5
  require 'rake/testtask'
6
6
  require 'rake/clean'
7
- require 'rspec/core'
8
- require 'rspec/core/rake_task'
9
7
 
10
- task :test => [:base_test, :spec]
8
+ task :test => [:base_test]
11
9
 
12
10
  desc 'Run test_unit based test'
13
11
  Rake::TestTask.new(:base_test) do |t|
@@ -15,7 +13,7 @@ Rake::TestTask.new(:base_test) do |t|
15
13
  # $ bundle exec rake base_test TEST=test/test_specified_path.rb
16
14
  # $ bundle exec rake base_test TEST=test/test_*.rb
17
15
  t.libs << "test"
18
- t.test_files = (Dir["test/test_*.rb"] + Dir["test/plugin/test_*.rb"] - ["helper.rb"]).sort
16
+ t.test_files = Dir["test/**/test_*.rb"].sort
19
17
  t.verbose = true
20
18
  #t.warning = true
21
19
  end
@@ -26,14 +24,7 @@ task :parallel_test do
26
24
  FileUtils.rm_rf('./test/tmp')
27
25
  end
28
26
 
29
- desc 'Run rspec based test'
30
- RSpec::Core::RakeTask.new(:spec) do |t|
31
- t.rspec_opts = %w[-c -f progress -r ./spec/spec_helper.rb]
32
- t.pattern = 'spec/**/*_spec.rb'
33
- t.verbose = true
34
- end
35
-
36
- desc 'Run rspec with simplecov'
27
+ desc 'Run test with simplecov'
37
28
  task :coverage do |t|
38
29
  ENV['SIMPLE_COV'] = '1'
39
30
  Rake::Task["spec"].invoke
@@ -0,0 +1,36 @@
1
+ <section1>
2
+ key1 'text' # text
3
+ key2 '\'' # ' (1 char)
4
+ key3 '\\' # \ (1 char)
5
+ key4 '\t' # \t (2 char)
6
+ key5 '\[' # \[ (2 char)
7
+ key6 '\\[' # \[ (2 char)
8
+ key7 '#t' # #t (2 char)
9
+ key8 '\#{test}' # \#{test} (8 char)
10
+ key9 '#{test}' # #{test} (7 char)
11
+ key10 '\[(?<time>[^\]]*)\] (?<message>.*)' # \[(?<time>[^\]]*\] (?<message>.*)
12
+ </section1>
13
+ <section2>
14
+ key1 "text" # text
15
+ key2 "\"" # " (1 char)
16
+ key3 "\\" # \ (1 char)
17
+ key4 "\t" # TAB (1 char)
18
+ key5 "\[" # [ (1 char)
19
+ key6 "\\[" # \[ (2 char)
20
+ key7 "#t" # #t (2 char)
21
+ key8 "\#{test}" # #{test} (7 char)
22
+ key9 "#{test}" # replaced by eval('test')
23
+ key10 "\\[(?<time>[^\\]]*)\\] (?<message>.*)" # \[(?<time>[^\]]*\] (?<message>.*)
24
+ </section2>
25
+ <section3>
26
+ key1 text # text
27
+ key2 \ # \ (1 char)
28
+ key3 \\ # \\ (2 char)
29
+ key4 \t # \t (2 char)
30
+ key5 \[ # \[ (2 char)
31
+ key6 \\[ # \\[ (3 char)
32
+ key7 #t # #t (2 char)
33
+ key8 \#{test} # \#{test} (8 char)
34
+ key9 #{test} # #{test} (7 char)
35
+ key10 \[(?<time>[^\]]*)\] (?<message>.*) # \[(?<time>[^\]]*\] (?<message>.*)
36
+ </section3>
@@ -28,8 +28,8 @@ Gem::Specification.new do |gem|
28
28
  gem.add_development_dependency("rake", [">= 0.9.2"])
29
29
  gem.add_development_dependency("flexmock")
30
30
  gem.add_development_dependency("parallel_tests", [">= 0.15.3"])
31
- gem.add_development_dependency("rspec", ["~> 3.0.0"])
32
31
  gem.add_development_dependency("simplecov", ["~> 0.6.4"])
33
32
  gem.add_development_dependency("rr", [">= 1.0.0"])
34
33
  gem.add_development_dependency("timecop", [">= 0.3.0"])
34
+ gem.add_development_dependency("test-unit", ["~> 3.0.2"])
35
35
  end
@@ -84,6 +84,10 @@ op.on('--use-v1-config', "Use v1 configuration format", TrueClass) {|b|
84
84
  opts[:use_v1_config] = b
85
85
  }
86
86
 
87
+ op.on('--use-v0-config', "Use v0 configuration format (default)", TrueClass) {|b|
88
+ opts[:use_v1_config] = !b
89
+ }
90
+
87
91
  op.on('-v', '--verbose', "increase verbose level (-v: debug, -vv: trace)", TrueClass) {|b|
88
92
  if b
89
93
  opts[:log_level] = [opts[:log_level] - 1, Fluent::Log::LEVEL_TRACE].max
@@ -26,6 +26,7 @@ module Fluent
26
26
 
27
27
  LINE_END = /(?:[ \t]*(?:\#.*)?(?:\z|[\r\n]))+/
28
28
  SPACING = /(?:[ \t\r\n]|\z|\#.*?(?:\z|[\r\n]))+/
29
+ SPACING_WITHOUT_COMMENT = /(?:[ \t\r\n]|\z)+/
29
30
 
30
31
  module ClassMethods
31
32
  def symbol(string)
@@ -77,6 +78,10 @@ module Fluent
77
78
  skip(SPACING)
78
79
  end
79
80
 
81
+ def spacing_without_comment
82
+ skip(SPACING_WITHOUT_COMMENT)
83
+ end
84
+
80
85
  def parse_error!(message)
81
86
  raise ConfigParseError, "#{message} at #{error_sample}"
82
87
  end
@@ -29,6 +29,7 @@ module Fluent
29
29
  "name:#{@name}, arg:#{@arg}, " + attrs + ", " + @elements.inspect
30
30
  end
31
31
 
32
+ # This method assumes _o_ is an Element object. Should return false for nil or other object
32
33
  def ==(o)
33
34
  self.name == o.name && self.arg == o.arg &&
34
35
  self.keys.size == o.keys.size &&
@@ -86,11 +87,7 @@ module Fluent
86
87
  out << "#{indent}<#{@name} #{@arg}>\n"
87
88
  end
88
89
  each_pair { |k, v|
89
- if @v1_config
90
- out << "#{nindent}#{k} #{Element.unescape_parameter(v)}\n"
91
- else
92
- out << "#{nindent}#{k} #{v}\n"
93
- end
90
+ out << "#{nindent}#{k} #{v}\n"
94
91
  }
95
92
  @elements.each { |e|
96
93
  out << e.to_s(nest + 1)
@@ -50,7 +50,7 @@ module Fluent
50
50
  end
51
51
 
52
52
  def parse_literal(string_boundary_charset = LINE_END)
53
- spacing
53
+ spacing_without_comment
54
54
 
55
55
  value = if skip(/\[/)
56
56
  scan_json(true)
@@ -64,13 +64,15 @@ module Fluent
64
64
 
65
65
  def scan_string(string_boundary_charset = LINE_END)
66
66
  if skip(/\"/)
67
- return scan_quoted_string
67
+ return scan_double_quoted_string
68
+ elsif skip(/\'/)
69
+ return scan_single_quoted_string
68
70
  else
69
71
  return scan_nonquoted_string(string_boundary_charset)
70
72
  end
71
73
  end
72
74
 
73
- def scan_quoted_string
75
+ def scan_double_quoted_string
74
76
  string = []
75
77
  while true
76
78
  if skip(/\"/)
@@ -83,7 +85,24 @@ module Fluent
83
85
  elsif s = scan(/./)
84
86
  string << s
85
87
  else
86
- parse_error! "unexpected end of file in a quoted string"
88
+ parse_error! "unexpected end of file in a double quoted string"
89
+ end
90
+ end
91
+ end
92
+
93
+ def scan_single_quoted_string
94
+ string = []
95
+ while true
96
+ if skip(/\'/)
97
+ return string.join
98
+ elsif s = scan(/\\'/)
99
+ string << "'"
100
+ elsif s = scan(/\\\\/)
101
+ string << "\\"
102
+ elsif s = scan(/./)
103
+ string << s
104
+ else
105
+ parse_error! "unexpected end of file in a signle quoted string"
87
106
  end
88
107
  end
89
108
  end
@@ -93,8 +112,8 @@ module Fluent
93
112
 
94
113
  string = []
95
114
  while true
96
- if s = scan(/\\./)
97
- string << eval_escape_char(s[1,1])
115
+ if s = scan(/\#/)
116
+ string << '#'
98
117
  elsif s = scan(charset)
99
118
  string << s
100
119
  else
@@ -213,7 +232,7 @@ module Fluent
213
232
  end
214
233
 
215
234
  unless result
216
- parse_error! "got incomplete #{is_array ? 'array' : 'hash'} configuration"
235
+ parse_error! "got incomplete JSON #{is_array ? 'array' : 'hash'} configuration"
217
236
  end
218
237
 
219
238
  JSON.dump(result)
@@ -99,7 +99,7 @@ module Fluent
99
99
 
100
100
  else
101
101
  k = scan_string(SPACING)
102
- spacing
102
+ spacing_without_comment
103
103
  if prev_match.include?("\n") # support 'tag_mapped' like "without value" configuration
104
104
  attrs[k] = ""
105
105
  else
@@ -19,6 +19,9 @@ module Fluent
19
19
  require 'fluent/registry'
20
20
 
21
21
  class TextParser
22
+ class ParserError < StandardError
23
+ end
24
+
22
25
  class TimeParser
23
26
  def initialize(time_format)
24
27
  @cache1_key = nil
@@ -35,7 +38,7 @@ module Fluent
35
38
 
36
39
  def parse(value)
37
40
  unless value.is_a?(String)
38
- raise ArgumentError, "Value must be string: #{value}"
41
+ raise ParserError, "value must be string: #{value}"
39
42
  end
40
43
 
41
44
  if @cache1_key == value
@@ -43,7 +46,11 @@ module Fluent
43
46
  elsif @cache2_key == value
44
47
  return @cache2_time
45
48
  else
46
- time = @parser.call(value).to_i
49
+ begin
50
+ time = @parser.call(value).to_i
51
+ rescue => e
52
+ raise ParserError, "invalid time format: value = #{value}, error_class = #{e.class.name}, error = #{e.message}"
53
+ end
47
54
  @cache1_key = @cache2_key
48
55
  @cache1_time = @cache2_time
49
56
  @cache2_key = value
@@ -225,7 +232,11 @@ module Fluent
225
232
  if @time_format
226
233
  time = @mutex.synchronize { @time_parser.parse(value) }
227
234
  else
228
- time = value.to_i
235
+ begin
236
+ time = value.to_i
237
+ rescue => e
238
+ raise ParserError, "invalid time value: value = #{value}, error_class = #{e.class.name}, error = #{e.message}"
239
+ end
229
240
  end
230
241
  else
231
242
  if @estimate_current_event
@@ -104,12 +104,20 @@ module Fluent
104
104
  end
105
105
 
106
106
  def run
107
- @loop.run
107
+ if support_blocking_timeout?
108
+ @loop.run(0.5)
109
+ else
110
+ @loop.run
111
+ end
108
112
  rescue
109
113
  log.error "unexpected error", :error=>$!.to_s
110
114
  log.error_backtrace
111
115
  end
112
116
 
117
+ def support_blocking_timeout?
118
+ @loop.method(:run).arity.nonzero?
119
+ end
120
+
113
121
  def on_request(path_info, params)
114
122
  begin
115
123
  path = path_info[1..-1] # remove /
@@ -43,13 +43,22 @@ module Fluent
43
43
  #end
44
44
 
45
45
  def run
46
- @loop.run
46
+ if support_blocking_timeout?
47
+ @loop.run(0.5)
48
+ else
49
+ @loop.run
50
+ end
47
51
  rescue
48
52
  log.error "unexpected error", :error=>$!.to_s
49
53
  log.error_backtrace
50
54
  end
51
55
 
52
56
  protected
57
+
58
+ def support_blocking_timeout?
59
+ @loop.method(:run).arity.nonzero?
60
+ end
61
+
53
62
  # message Entry {
54
63
  # 1: long time
55
64
  # 2: object record
@@ -117,13 +117,22 @@ module Fluent
117
117
  end
118
118
 
119
119
  def run
120
- @loop.run
120
+ if support_blocking_timeout?
121
+ @loop.run(0.5)
122
+ else
123
+ @loop.run
124
+ end
121
125
  rescue
122
126
  log.error "unexpected error", :error=>$!.to_s
123
127
  log.error_backtrace
124
128
  end
125
129
 
126
130
  protected
131
+
132
+ def support_blocking_timeout?
133
+ @loop.method(:run).arity.nonzero?
134
+ end
135
+
127
136
  def receive_data_parser(data, addr)
128
137
  m = SYSLOG_REGEXP.match(data)
129
138
  unless m
@@ -106,7 +106,11 @@ module Fluent
106
106
  end
107
107
 
108
108
  def run
109
- @loop.run
109
+ if support_blocking_timeout?
110
+ @loop.run(0.5)
111
+ else
112
+ @loop.run
113
+ end
110
114
  rescue => e
111
115
  log.error "unexpected error", :error => e, :error_class => e.class
112
116
  log.error_backtrace
@@ -114,6 +118,10 @@ module Fluent
114
118
 
115
119
  private
116
120
 
121
+ def support_blocking_timeout?
122
+ @loop.method(:run).arity.nonzero?
123
+ end
124
+
117
125
  def on_message(msg, addr)
118
126
  @parser.parse(msg) { |time, record|
119
127
  unless time && record
@@ -1,5 +1,5 @@
1
1
  module Fluent
2
2
 
3
- VERSION = '0.10.53'
3
+ VERSION = '0.10.54'
4
4
 
5
5
  end
@@ -0,0 +1,42 @@
1
+ require 'test/unit/assertions'
2
+
3
+ module Test::Unit::Assertions
4
+ def assert_text_parsed_as(expected, actual)
5
+ msg = parse_text(actual).inspect rescue 'failed'
6
+ msg = "expected that #{actual.inspect} would be a parsed as #{expected.inspect} but got #{msg}"
7
+ assert_block(msg) {
8
+ v = parse_text(actual)
9
+ if expected.is_a?(Float)
10
+ v.is_a?(Float) && (v == obj || (v.nan? && obj.nan?) || (v - obj).abs < 0.000001)
11
+ else
12
+ v == expected
13
+ end
14
+ }
15
+ end
16
+
17
+ def assert_text_parsed_as_json(expected, actual)
18
+ msg = parse_text(actual).inspect rescue 'failed'
19
+ msg = "expected that #{actual.inspect} would be a parsed as #{expected.inspect} but got #{msg}"
20
+ assert_block(msg) {
21
+ v = JSON.parse(parse_text(actual))
22
+ v == expected
23
+ }
24
+ end
25
+
26
+ def assert_parse_error(actual)
27
+ msg = begin
28
+ parse_text(actual).inspect
29
+ rescue => e
30
+ e.inspect
31
+ end
32
+ msg = "expected that #{actual.inspect} would cause a parse error but got #{msg}"
33
+ assert_block(msg) {
34
+ begin
35
+ parse_text(actual)
36
+ false
37
+ rescue Fluent::ConfigParseError
38
+ true
39
+ end
40
+ }
41
+ end
42
+ end
@@ -0,0 +1,354 @@
1
+ require 'helper'
2
+ require "json"
3
+ require "config/assertions"
4
+ require "fluent/config/error"
5
+ require "fluent/config/basic_parser"
6
+ require "fluent/config/literal_parser"
7
+ require "fluent/config/v1_parser"
8
+
9
+ module Fluent::Config
10
+ module V1TestHelper
11
+ def root(*elements)
12
+ if elements.first.is_a?(Fluent::Config::Element)
13
+ attrs = {}
14
+ else
15
+ attrs = elements.shift || {}
16
+ end
17
+ Fluent::Config::Element.new('ROOT', '', attrs, elements)
18
+ end
19
+
20
+ def e(name, arg='', attrs={}, elements=[])
21
+ Fluent::Config::Element.new(name, arg, attrs, elements)
22
+ end
23
+ end
24
+
25
+ class TestV1Parser < ::Test::Unit::TestCase
26
+ def read_config(path)
27
+ path = File.expand_path(path)
28
+ data = File.read(path)
29
+ Fluent::Config::V1Parser.parse(data, File.basename(path), File.dirname(path))
30
+ end
31
+
32
+ def parse_text(text)
33
+ basepath = File.expand_path(File.dirname(__FILE__) + '/../../')
34
+ Fluent::Config::V1Parser.parse(text, '(test)', basepath, nil)
35
+ end
36
+
37
+ include V1TestHelper
38
+ extend V1TestHelper
39
+
40
+ sub_test_case 'attribute parsing' do
41
+ test "parses attributes" do
42
+ assert_text_parsed_as(e('ROOT', '', {"k1"=>"v1", "k2"=>"v2"}), %[
43
+ k1 v1
44
+ k2 v2
45
+ ])
46
+ end
47
+
48
+ test "allows attribute without value" do
49
+ assert_text_parsed_as(e('ROOT', '', {"k1"=>"", "k2"=>"v2"}), %[
50
+ k1
51
+ k2 v2
52
+ ])
53
+ end
54
+
55
+ test "parses attribute key always string" do
56
+ assert_text_parsed_as(e('ROOT', '', {"1" => "1"}), "1 1")
57
+ end
58
+
59
+ data("_.%$!," => "_.%$!,",
60
+ "/=~-~@\`:?" => "/=~-~@\`:?",
61
+ "()*{}.[]" => "()*{}.[]")
62
+ test "parses a value with symbols" do |v|
63
+ assert_text_parsed_as(e('ROOT', '', {"k" => v}), "k #{v}")
64
+ end
65
+
66
+ test "ignores spacing around value" do
67
+ assert_text_parsed_as(e('ROOT', '', {"k1" => "a"}), " k1 a ")
68
+ end
69
+
70
+ test "allows spaces in value" do
71
+ assert_text_parsed_as(e('ROOT', '', {"k1" => "a b c"}), "k1 a b c")
72
+ end
73
+
74
+ sub_test_case 'non-quoted string' do
75
+ test "remains text starting with '#'" do
76
+ assert_text_parsed_as(e('ROOT', '', {"k1" => "#not_comment"}), " k1 #not_comment")
77
+ end
78
+
79
+ test "remains text just after '#'" do
80
+ assert_text_parsed_as(e('ROOT', '', {"k1" => "a#not_comment"}), " k1 a#not_comment")
81
+ end
82
+
83
+ test "remove text after ` #` (comment)" do
84
+ assert_text_parsed_as(e('ROOT', '', {"k1" => "a"}), " k1 a #comment")
85
+ end
86
+
87
+ test "does not require escaping backslash" do
88
+ assert_text_parsed_as(e('ROOT', '', {"k1" => "\\\\"}), " k1 \\\\")
89
+ assert_text_parsed_as(e('ROOT', '', {"k1" => "\\"}), " k1 \\")
90
+ end
91
+
92
+ test "remains backslash in front of a normal character" do
93
+ assert_text_parsed_as(e('ROOT', '', {"k1" => '\['}), " k1 \\[")
94
+ end
95
+
96
+ test "does not accept escape characters" do
97
+ assert_text_parsed_as(e('ROOT', '', {"k1" => '\n'}), " k1 \\n")
98
+ end
99
+ end
100
+
101
+ sub_test_case 'double quoted string' do
102
+ test "allows # in value" do
103
+ assert_text_parsed_as(e('ROOT', '', {"k1" => "a#comment"}), ' k1 "a#comment"')
104
+ end
105
+
106
+ test "rejects characters after double quoted string" do
107
+ assert_parse_error(' k1 "a" 1')
108
+ end
109
+
110
+ test "requires escaping backslash" do
111
+ assert_text_parsed_as(e('ROOT', '', {"k1" => "\\"}), ' k1 "\\\\"')
112
+ assert_parse_error(' k1 "\\"')
113
+ end
114
+
115
+ test "requires escaping double quote" do
116
+ assert_text_parsed_as(e('ROOT', '', {"k1" => '"'}), ' k1 "\\""')
117
+ assert_parse_error(' k1 """')
118
+ end
119
+
120
+ test "removes backslash in front of a normal character" do
121
+ assert_text_parsed_as(e('ROOT', '', {"k1" => '['}), ' k1 "\\["')
122
+ end
123
+
124
+ test "accepts escape characters" do
125
+ assert_text_parsed_as(e('ROOT', '', {"k1" => "\n"}), ' k1 "\\n"')
126
+ end
127
+ end
128
+
129
+ sub_test_case 'single quoted string' do
130
+ test "allows # in value" do
131
+ assert_text_parsed_as(e('ROOT', '', {"k1" => "a#comment"}), " k1 'a#comment'")
132
+ end
133
+
134
+ test "rejects characters after single quoted string" do
135
+ assert_parse_error(" k1 'a' 1")
136
+ end
137
+
138
+ test "requires escaping backslash" do
139
+ assert_text_parsed_as(e('ROOT', '', {"k1" => "\\"}), " k1 '\\\\'")
140
+ assert_parse_error(" k1 '\\'")
141
+ end
142
+
143
+ test "requires escaping single quote" do
144
+ assert_text_parsed_as(e('ROOT', '', {"k1" => "'"}), " k1 '\\''")
145
+ assert_parse_error(" k1 '''")
146
+ end
147
+
148
+ test "remains backslash in front of a normal character" do
149
+ assert_text_parsed_as(e('ROOT', '', {"k1" => '\\['}), " k1 '\\['")
150
+ end
151
+
152
+ test "does not accept escape characters" do
153
+ assert_text_parsed_as(e('ROOT', '', {"k1" => "\\n"}), " k1 '\\n'")
154
+ end
155
+ end
156
+
157
+ test "rejects @ prefix in parameter name" do
158
+ assert_parse_error(' @k v')
159
+ end
160
+ end
161
+
162
+ sub_test_case 'element parsing' do
163
+ data(
164
+ 'root' => [root, ""],
165
+ "accepts empty element" => [root(e("test")), %[
166
+ <test>
167
+ </test>
168
+ ]],
169
+ "accepts argument and attributes" => [root(e("test", 'var', {'key'=>"val"})), %[
170
+ <test var>
171
+ key val
172
+ </test>
173
+ ]],
174
+ "accepts nested elements" => [root(
175
+ e("test", 'var', {'key'=>'1'}, [
176
+ e('nested1'),
177
+ e('nested2')
178
+ ])), %[
179
+ <test var>
180
+ key 1
181
+ <nested1>
182
+ </nested1>
183
+ <nested2>
184
+ </nested2>
185
+ </test>
186
+ ]],
187
+ "accepts multiline json values" => [root(e("test", 'var', {'key'=>"[\"a\",\"b\",\"c\",\"d\"]"})), %[
188
+ <test var>
189
+ key ["a",
190
+ "b", "c",
191
+ "d"]
192
+ </test>
193
+ ]],
194
+ "parses empty element argument to nil" => [root(e("test", '')), %[
195
+ <test >
196
+ </test>
197
+ ]],
198
+ "ignores spacing around element argument" => [root(e("test", "a")), %[
199
+ <test a >
200
+ </test>
201
+ ]])
202
+ def test_parse_element(data)
203
+ expected, target = data
204
+ assert_text_parsed_as(expected, target)
205
+ end
206
+
207
+ [
208
+ "**",
209
+ "*.*",
210
+ "1",
211
+ "_.%$!",
212
+ "/",
213
+ "()*{}.[]",
214
+ ].each do |arg|
215
+ test "parses symbol element argument:#{arg}" do
216
+ assert_text_parsed_as(root(e("test", arg)), %[
217
+ <test #{arg}>
218
+ </test>
219
+ ])
220
+ end
221
+ end
222
+
223
+ data(
224
+ "considers comments in element argument" => %[
225
+ <test #a>
226
+ </test>
227
+ ],
228
+ "requires line_end after begin tag" => %[
229
+ <test></test>
230
+ ],
231
+ "requires line_end after end tag" => %[
232
+ <test>
233
+ </test><test>
234
+ </test>
235
+ ])
236
+ def test_parse_error(data)
237
+ assert_parse_error(data)
238
+ end
239
+ end
240
+
241
+ # port from test_config.rb
242
+ sub_test_case '@include parsing' do
243
+ TMP_DIR = File.dirname(__FILE__) + "/tmp/v1_config#{ENV['TEST_ENV_NUMBER']}"
244
+
245
+ def write_config(path, data)
246
+ FileUtils.mkdir_p(File.dirname(path))
247
+ File.open(path, "w") { |f| f.write data }
248
+ end
249
+
250
+ def prepare_config
251
+ write_config "#{TMP_DIR}/config_test_1.conf", %[
252
+ k1 root_config
253
+ include dir/config_test_2.conf #
254
+ @include #{TMP_DIR}/config_test_4.conf
255
+ include file://#{TMP_DIR}/config_test_5.conf
256
+ @include config.d/*.conf
257
+ ]
258
+ write_config "#{TMP_DIR}/dir/config_test_2.conf", %[
259
+ k2 relative_path_include
260
+ @include ../config_test_3.conf
261
+ ]
262
+ write_config "#{TMP_DIR}/config_test_3.conf", %[
263
+ k3 relative_include_in_included_file
264
+ ]
265
+ write_config "#{TMP_DIR}/config_test_4.conf", %[
266
+ k4 absolute_path_include
267
+ ]
268
+ write_config "#{TMP_DIR}/config_test_5.conf", %[
269
+ k5 uri_include
270
+ ]
271
+ write_config "#{TMP_DIR}/config.d/config_test_6.conf", %[
272
+ k6 wildcard_include_1
273
+ <elem1 name>
274
+ include normal_parameter
275
+ </elem1>
276
+ ]
277
+ write_config "#{TMP_DIR}/config.d/config_test_7.conf", %[
278
+ k7 wildcard_include_2
279
+ ]
280
+ write_config "#{TMP_DIR}/config.d/config_test_8.conf", %[
281
+ <elem2 name>
282
+ @include ../dir/config_test_9.conf
283
+ </elem2>
284
+ ]
285
+ write_config "#{TMP_DIR}/dir/config_test_9.conf", %[
286
+ k9 embeded
287
+ <elem3 name>
288
+ nested nested_value
289
+ include hoge
290
+ </elem3>
291
+ ]
292
+ write_config "#{TMP_DIR}/config.d/00_config_test_8.conf", %[
293
+ k8 wildcard_include_3
294
+ <elem4 name>
295
+ include normal_parameter
296
+ </elem4>
297
+ ]
298
+ end
299
+
300
+ test 'parses @include / include correctly' do
301
+ prepare_config
302
+ c = read_config("#{TMP_DIR}/config_test_1.conf")
303
+ assert_equal('root_config', c['k1'])
304
+ assert_equal('relative_path_include', c['k2'])
305
+ assert_equal('relative_include_in_included_file', c['k3'])
306
+ assert_equal('absolute_path_include', c['k4'])
307
+ assert_equal('uri_include', c['k5'])
308
+ assert_equal('wildcard_include_1', c['k6'])
309
+ assert_equal('wildcard_include_2', c['k7'])
310
+ assert_equal('wildcard_include_3', c['k8'])
311
+ assert_equal([
312
+ 'k1',
313
+ 'k2',
314
+ 'k3',
315
+ 'k4',
316
+ 'k5',
317
+ 'k8', # Because of the file name this comes first.
318
+ 'k6',
319
+ 'k7',
320
+ ], c.keys)
321
+
322
+ elem1 = c.elements.find { |e| e.name == 'elem1' }
323
+ assert(elem1)
324
+ assert_equal('name', elem1.arg)
325
+ assert_equal('normal_parameter', elem1['include'])
326
+
327
+ elem2 = c.elements.find { |e| e.name == 'elem2' }
328
+ assert(elem2)
329
+ assert_equal('name', elem2.arg)
330
+ assert_equal('embeded', elem2['k9'])
331
+ assert_not_include(elem2, 'include')
332
+
333
+ elem3 = elem2.elements.find { |e| e.name == 'elem3' }
334
+ assert(elem3)
335
+ assert_equal('nested_value', elem3['nested'])
336
+ assert_equal('hoge', elem3['include'])
337
+ end
338
+
339
+ # TODO: Add uri based include spec
340
+ end
341
+
342
+ sub_test_case '#to_s' do
343
+ test 'parses dumpped configuration' do
344
+ original = %q!a\\\n\r\f\b'"z!
345
+ expected = %q!a\\\n\r\f\b'"z!
346
+
347
+ conf = parse_text(%[k1 #{original}])
348
+ assert_equal(expected, conf['k1']) # escape check
349
+ conf2 = parse_text(conf.to_s) # use dumpped configuration to check unescape
350
+ assert_equal(expected, conf2.elements.first['k1'])
351
+ end
352
+ end
353
+ end
354
+ end