fluentd 0.10.62 → 0.12.0.pre.1

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.

Files changed (140) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +1 -2
  3. data/.travis.yml +0 -4
  4. data/ChangeLog +0 -72
  5. data/Gemfile +0 -6
  6. data/Rakefile +12 -3
  7. data/example/in_http.conf +14 -0
  8. data/example/in_syslog.conf +15 -0
  9. data/example/in_tail.conf +14 -0
  10. data/example/in_tcp.conf +13 -0
  11. data/example/in_udp.conf +13 -0
  12. data/example/out_copy.conf +20 -0
  13. data/example/out_file.conf +13 -0
  14. data/example/out_forward.conf +30 -0
  15. data/fluent.conf +2 -12
  16. data/fluentd.gemspec +8 -11
  17. data/lib/fluent/agent.rb +180 -0
  18. data/lib/fluent/buffer.rb +6 -12
  19. data/lib/fluent/command/cat.rb +1 -3
  20. data/lib/fluent/command/debug.rb +1 -3
  21. data/lib/fluent/command/fluentd.rb +0 -10
  22. data/lib/fluent/config.rb +9 -3
  23. data/lib/fluent/config/basic_parser.rb +1 -6
  24. data/lib/fluent/config/configure_proxy.rb +25 -61
  25. data/lib/fluent/config/dsl.rb +16 -0
  26. data/lib/fluent/config/element.rb +21 -2
  27. data/lib/fluent/config/error.rb +16 -0
  28. data/lib/fluent/config/literal_parser.rb +9 -27
  29. data/lib/fluent/config/parser.rb +16 -0
  30. data/lib/fluent/config/section.rb +16 -2
  31. data/lib/fluent/config/types.rb +16 -1
  32. data/lib/fluent/config/v1_parser.rb +4 -12
  33. data/lib/fluent/configurable.rb +16 -0
  34. data/lib/fluent/engine.rb +43 -163
  35. data/lib/fluent/env.rb +16 -1
  36. data/lib/fluent/event.rb +20 -48
  37. data/lib/fluent/event_router.rb +187 -0
  38. data/lib/fluent/filter.rb +32 -0
  39. data/lib/fluent/formatter.rb +29 -101
  40. data/lib/fluent/input.rb +6 -4
  41. data/lib/fluent/label.rb +18 -0
  42. data/lib/fluent/load.rb +1 -3
  43. data/lib/fluent/log.rb +1 -3
  44. data/lib/fluent/match.rb +12 -19
  45. data/lib/fluent/mixin.rb +9 -25
  46. data/lib/fluent/output.rb +27 -45
  47. data/lib/fluent/parser.rb +93 -99
  48. data/lib/fluent/plugin.rb +22 -48
  49. data/lib/fluent/plugin/buf_file.rb +10 -7
  50. data/lib/fluent/plugin/buf_memory.rb +2 -3
  51. data/lib/fluent/plugin/buf_zfile.rb +75 -0
  52. data/lib/fluent/plugin/exec_util.rb +16 -0
  53. data/lib/fluent/plugin/in_debug_agent.rb +2 -3
  54. data/lib/fluent/plugin/in_exec.rb +2 -9
  55. data/lib/fluent/plugin/in_forward.rb +4 -22
  56. data/lib/fluent/plugin/in_gc_stat.rb +2 -3
  57. data/lib/fluent/plugin/in_http.rb +19 -59
  58. data/lib/fluent/plugin/in_monitor_agent.rb +21 -47
  59. data/lib/fluent/plugin/in_object_space.rb +2 -3
  60. data/lib/fluent/plugin/in_status.rb +2 -3
  61. data/lib/fluent/plugin/in_stream.rb +6 -16
  62. data/lib/fluent/plugin/in_syslog.rb +8 -17
  63. data/lib/fluent/plugin/in_tail.rb +17 -24
  64. data/lib/fluent/plugin/in_tcp.rb +16 -0
  65. data/lib/fluent/plugin/in_udp.rb +16 -0
  66. data/lib/fluent/plugin/out_copy.rb +3 -4
  67. data/lib/fluent/plugin/out_exec.rb +2 -4
  68. data/lib/fluent/plugin/out_exec_filter.rb +2 -13
  69. data/lib/fluent/plugin/out_file.rb +5 -6
  70. data/lib/fluent/plugin/out_forward.rb +4 -5
  71. data/lib/fluent/plugin/out_null.rb +2 -3
  72. data/lib/fluent/plugin/out_relabel.rb +26 -0
  73. data/lib/fluent/plugin/out_roundrobin.rb +3 -4
  74. data/lib/fluent/plugin/out_stdout.rb +2 -3
  75. data/lib/fluent/plugin/out_stream.rb +2 -3
  76. data/{test/scripts → lib}/fluent/plugin/out_test.rb +2 -3
  77. data/lib/fluent/plugin/socket_util.rb +19 -10
  78. data/lib/fluent/process.rb +4 -6
  79. data/lib/fluent/registry.rb +16 -0
  80. data/lib/fluent/root_agent.rb +212 -0
  81. data/lib/fluent/status.rb +2 -3
  82. data/lib/fluent/supervisor.rb +33 -54
  83. data/lib/fluent/test.rb +16 -0
  84. data/lib/fluent/test/base.rb +3 -17
  85. data/lib/fluent/test/input_test.rb +52 -7
  86. data/lib/fluent/test/output_test.rb +4 -20
  87. data/lib/fluent/version.rb +17 -1
  88. data/spec/config/config_parser_spec.rb +314 -0
  89. data/spec/config/configurable_spec.rb +524 -0
  90. data/spec/config/configure_proxy_spec.rb +96 -0
  91. data/spec/config/dsl_spec.rb +239 -0
  92. data/spec/config/helper.rb +49 -0
  93. data/spec/config/literal_parser_spec.rb +222 -0
  94. data/spec/config/section_spec.rb +97 -0
  95. data/spec/config/system_config_spec.rb +49 -0
  96. data/test/helper.rb +0 -25
  97. data/test/plugin/test_in_exec.rb +1 -1
  98. data/test/plugin/test_in_forward.rb +2 -1
  99. data/test/plugin/test_in_gc_stat.rb +1 -1
  100. data/test/plugin/test_in_http.rb +3 -78
  101. data/test/plugin/test_in_object_space.rb +1 -1
  102. data/test/plugin/test_in_status.rb +1 -1
  103. data/test/plugin/test_in_stream.rb +2 -1
  104. data/test/plugin/test_in_syslog.rb +2 -1
  105. data/test/plugin/test_in_tail.rb +6 -11
  106. data/test/plugin/test_in_tcp.rb +2 -1
  107. data/test/plugin/test_in_udp.rb +2 -1
  108. data/test/plugin/test_out_copy.rb +1 -12
  109. data/test/plugin/test_out_exec.rb +1 -1
  110. data/test/plugin/test_out_exec_filter.rb +1 -1
  111. data/test/plugin/test_out_file.rb +7 -96
  112. data/test/plugin/test_out_forward.rb +2 -1
  113. data/test/plugin/test_out_roundrobin.rb +1 -12
  114. data/test/plugin/test_out_stdout.rb +1 -1
  115. data/test/plugin/test_out_stream.rb +2 -1
  116. data/test/scripts/fluent/plugin/formatter_known.rb +1 -4
  117. data/test/scripts/fluent/plugin/parser_known.rb +1 -2
  118. data/test/test_config.rb +1 -1
  119. data/test/test_configdsl.rb +2 -1
  120. data/test/test_formatter.rb +3 -395
  121. data/test/test_match.rb +2 -1
  122. data/test/test_mixin.rb +3 -75
  123. data/test/test_output.rb +1 -112
  124. data/test/test_parser.rb +85 -152
  125. metadata +58 -167
  126. data/example/v1_literal_example.conf +0 -36
  127. data/lib/fluent/plugin/in_dummy.rb +0 -103
  128. data/lib/fluent/timezone.rb +0 -131
  129. data/test/config/assertions.rb +0 -42
  130. data/test/config/test_config_parser.rb +0 -389
  131. data/test/config/test_configurable.rb +0 -652
  132. data/test/config/test_configure_proxy.rb +0 -99
  133. data/test/config/test_dsl.rb +0 -237
  134. data/test/config/test_literal_parser.rb +0 -295
  135. data/test/config/test_section.rb +0 -112
  136. data/test/config/test_system_config.rb +0 -99
  137. data/test/config/test_types.rb +0 -63
  138. data/test/plugin/test_in_dummy.rb +0 -95
  139. data/test/test_event.rb +0 -168
  140. data/test/test_input.rb +0 -21
data/lib/fluent/status.rb CHANGED
@@ -1,7 +1,5 @@
1
1
  #
2
- # Fluent
3
- #
4
- # Copyright (C) 2011 FURUHASHI Sadayuki
2
+ # Fluentd
5
3
  #
6
4
  # Licensed under the Apache License, Version 2.0 (the "License");
7
5
  # you may not use this file except in compliance with the License.
@@ -15,6 +13,7 @@
15
13
  # See the License for the specific language governing permissions and
16
14
  # limitations under the License.
17
15
  #
16
+
18
17
  module Fluent
19
18
  class StatusClass
20
19
  def initialize
@@ -1,7 +1,5 @@
1
1
  #
2
- # Fluent
3
- #
4
- # Copyright (C) 2011 FURUHASHI Sadayuki
2
+ # Fluentd
5
3
  #
6
4
  # Licensed under the Apache License, Version 2.0 (the "License");
7
5
  # you may not use this file except in compliance with the License.
@@ -73,11 +71,6 @@ module Fluent
73
71
  end
74
72
  self
75
73
  end
76
-
77
- def level=(level)
78
- @level = level
79
- $log.level = level
80
- end
81
74
  end
82
75
 
83
76
  def self.default_options
@@ -94,14 +87,12 @@ module Fluent
94
87
  :suppress_interval => 0,
95
88
  :suppress_repeated_stacktrace => false,
96
89
  :without_source => false,
97
- :use_v1_config => false,
98
- :supervise => true,
90
+ :use_v1_config => true,
99
91
  }
100
92
  end
101
93
 
102
94
  def initialize(opt)
103
95
  @daemonize = opt[:daemonize]
104
- @supervise = opt[:supervise]
105
96
  @config_path = opt[:config_path]
106
97
  @inline_config = opt[:inline_config]
107
98
  @use_v1_config = opt[:use_v1_config]
@@ -112,6 +103,8 @@ module Fluent
112
103
  @chgroup = opt[:chgroup]
113
104
  @chuser = opt[:chuser]
114
105
 
106
+ apply_system_config(opt)
107
+
115
108
  @log_level = opt[:log_level]
116
109
  @suppress_interval = opt[:suppress_interval]
117
110
  @suppress_config_dump = opt[:suppress_config_dump]
@@ -125,28 +118,13 @@ module Fluent
125
118
 
126
119
  def start
127
120
  @log.init
128
- read_config
129
- apply_system_config
130
121
 
131
122
  dry_run if @dry_run
132
123
  start_daemonize if @daemonize
133
- if @supervise
134
- install_supervisor_signal_handlers
135
- until @finished
136
- supervise do
137
- change_privilege
138
- init_engine
139
- install_main_process_signal_handlers
140
- run_configure
141
- finish_daemonize if @daemonize
142
- run_engine
143
- exit 0
144
- end
145
- $log.error "fluentd main process died unexpectedly. restarting." unless @finished
146
- end
147
- else
148
- $log.info "starting fluentd-#{Fluent::VERSION} without supervision"
149
- main_process do
124
+ install_supervisor_signal_handlers
125
+ until @finished
126
+ supervise do
127
+ read_config
150
128
  change_privilege
151
129
  init_engine
152
130
  install_main_process_signal_handlers
@@ -155,6 +133,7 @@ module Fluent
155
133
  run_engine
156
134
  exit 0
157
135
  end
136
+ $log.error "fluentd main process died unexpectedly. restarting." unless @finished
158
137
  end
159
138
  end
160
139
 
@@ -172,6 +151,7 @@ module Fluent
172
151
  def dry_run
173
152
  $log.info "starting fluentd-#{Fluent::VERSION} as dry run mode"
174
153
 
154
+ read_config
175
155
  change_privilege
176
156
  init_engine
177
157
  install_main_process_signal_handlers
@@ -323,16 +303,10 @@ module Fluent
323
303
  trap :HUP do
324
304
  $log.debug "fluentd supervisor process get SIGHUP"
325
305
  $log.info "restarting"
326
- # Creating new thread due to mutex can't lock
327
- # in main thread during trap context
328
- Thread.new {
329
- read_config
330
- apply_system_config
331
- if pid = @main_pid
332
- Process.kill(:TERM, pid)
333
- # don't resuce Erro::ESRSH here (invalid status)
334
- end
335
- }.run
306
+ if pid = @main_pid
307
+ Process.kill(:TERM, pid)
308
+ # don't resuce Erro::ESRSH here (invalid status)
309
+ end
336
310
  end
337
311
 
338
312
  trap :USR1 do
@@ -355,7 +329,6 @@ module Fluent
355
329
  elsif @inline_config
356
330
  @config_data << "\n" << @inline_config.gsub("\\n","\n")
357
331
  end
358
- @conf = Fluent::Config.parse(@config_data, @config_fname, @config_basedir, @use_v1_config)
359
332
  end
360
333
 
361
334
  class SystemConfig
@@ -374,31 +347,37 @@ module Fluent
374
347
  configure(conf)
375
348
  end
376
349
 
377
- def apply(supervisor)
378
- system = self
379
- supervisor.instance_eval {
380
- @log.level = @log_level = system.log_level unless system.log_level.nil?
381
- @suppress_interval = system.emit_error_log_interval unless system.emit_error_log_interval.nil?
382
- @suppress_config_dump = system.suppress_config_dump unless system.suppress_config_dump.nil?
383
- @suppress_repeated_stacktrace = system.suppress_repeated_stacktrace unless system.suppress_repeated_stacktrace.nil?
384
- @without_source = system.without_source unless system.without_source.nil?
385
- }
350
+ def to_opt
351
+ opt = {}
352
+ opt[:log_level] = @log_level unless @log_level.nil?
353
+ opt[:suppress_interval] = @emit_error_log_interval unless @emit_error_log_interval.nil?
354
+ opt[:suppress_config_dump] = @suppress_config_dump unless @suppress_config_dump.nil?
355
+ opt[:suppress_repeated_stacktrace] = @suppress_repeated_stacktrace unless @suppress_repeated_stacktrace.nil?
356
+ opt[:without_source] = @without_source unless @without_source.nil?
357
+ opt
386
358
  end
387
359
  end
388
360
 
389
361
  # TODO: this method should be moved to SystemConfig class method
390
- def apply_system_config
391
- systems = @conf.elements.select { |e|
362
+ def apply_system_config(opt)
363
+ # Create NULL file to avoid $log uninitialized problem before call @log.init
364
+ file = File.open(File::NULL)
365
+ $log = Fluent::Log.new(file, Log::LEVEL_INFO)
366
+ read_config
367
+ systems = Fluent::Config.parse(@config_data, @config_fname, @config_basedir, @use_v1_config).elements.select { |e|
392
368
  e.name == 'system'
393
369
  }
394
370
  return if systems.empty?
395
371
  raise ConfigError, "<system> is duplicated. <system> should be only one" if systems.size > 1
396
372
 
397
- SystemConfig.new(systems.first).apply(self)
373
+ opt.merge!(SystemConfig.new(systems.first).to_opt)
374
+ ensure
375
+ file.close
398
376
  end
399
377
 
400
378
  def run_configure
401
- Fluent::Engine.run_configure(@conf)
379
+ conf = Fluent::Config.parse(@config_data, @config_fname, @config_basedir, @use_v1_config)
380
+ Fluent::Engine.run_configure(conf)
402
381
  end
403
382
 
404
383
  def change_privilege
data/lib/fluent/test.rb CHANGED
@@ -1,3 +1,19 @@
1
+ #
2
+ # Fluentd
3
+ #
4
+ # Licensed under the Apache License, Version 2.0 (the "License");
5
+ # you may not use this file except in compliance with the License.
6
+ # You may obtain a copy of the License at
7
+ #
8
+ # http://www.apache.org/licenses/LICENSE-2.0
9
+ #
10
+ # Unless required by applicable law or agreed to in writing, software
11
+ # distributed under the License is distributed on an "AS IS" BASIS,
12
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ # See the License for the specific language governing permissions and
14
+ # limitations under the License.
15
+ #
16
+
1
17
  require 'test/unit'
2
18
  require 'fluent/load'
3
19
  require 'fluent/test/base'
@@ -1,7 +1,5 @@
1
1
  #
2
- # Fluent
3
- #
4
- # Copyright (C) 2011 FURUHASHI Sadayuki
2
+ # Fluentd
5
3
  #
6
4
  # Licensed under the Apache License, Version 2.0 (the "License");
7
5
  # you may not use this file except in compliance with the License.
@@ -15,22 +13,9 @@
15
13
  # See the License for the specific language governing permissions and
16
14
  # limitations under the License.
17
15
  #
16
+
18
17
  module Fluent
19
18
  module Test
20
- def self.setup
21
- Fluent.__send__(:remove_const, :Engine)
22
- engine = Fluent.const_set(:Engine, EngineClass.new).init
23
-
24
- engine.define_singleton_method(:now=) {|n|
25
- @now = n.to_i
26
- }
27
- engine.define_singleton_method(:now) {
28
- @now || super()
29
- }
30
-
31
- nil
32
- end
33
-
34
19
  class TestDriver
35
20
  include ::Test::Unit::Assertions
36
21
 
@@ -46,6 +31,7 @@ module Fluent
46
31
  else
47
32
  @instance = klass
48
33
  end
34
+ @instance.router = Engine.root_agent.event_router
49
35
  @instance.log = TestLogger.new
50
36
 
51
37
  @config = Config.new
@@ -1,7 +1,5 @@
1
1
  #
2
- # Fluent
3
- #
4
- # Copyright (C) 2011 FURUHASHI Sadayuki
2
+ # Fluentd
5
3
  #
6
4
  # Licensed under the Apache License, Version 2.0 (the "License");
7
5
  # you may not use this file except in compliance with the License.
@@ -15,6 +13,7 @@
15
13
  # See the License for the specific language governing permissions and
16
14
  # limitations under the License.
17
15
  #
16
+
18
17
  module Fluent
19
18
  class FileBuffer < BasicBuffer
20
19
  def self.clear_buffer_paths
@@ -75,19 +74,65 @@ module Fluent
75
74
  all
76
75
  end
77
76
 
77
+ def register_run_post_condition(&block)
78
+ if block
79
+ @run_post_conditions ||= []
80
+ @run_post_conditions << block
81
+ end
82
+ end
83
+
84
+ def register_run_breaking_condition(&block)
85
+ if block
86
+ @run_breaking_conditions ||= []
87
+ @run_breaking_conditions << block
88
+ end
89
+ end
90
+
91
+ def run_should_stop?
92
+ # Should stop running if post conditions are not registered.
93
+ return true unless @run_post_conditions
94
+
95
+ # Should stop running if all of the post conditions are true.
96
+ return true if @run_post_conditions.all? {|proc| proc.call }
97
+
98
+ # Should stop running if any of the breaking conditions is true.
99
+ # In this case, some post conditions may be not true.
100
+ return true if @run_breaking_conditions && @run_breaking_conditions.any? {|proc| proc.call }
101
+
102
+ false
103
+ end
104
+
78
105
  def run(&block)
79
106
  m = method(:emit_stream)
80
107
  Engine.define_singleton_method(:emit_stream) {|tag,es|
81
108
  m.call(tag, es)
82
109
  }
110
+ instance.router.define_singleton_method(:emit_stream) {|tag,es|
111
+ m.call(tag, es)
112
+ }
83
113
  super {
84
114
  block.call if block
85
115
 
86
- if @expected_emits_length || @expects
87
- max_length = @expected_emits_length || @expects.length
88
- started_at = Time.now
116
+ if @expected_emits_length || @expects || @run_post_conditions
117
+ # counters for emits and emit_streams
89
118
  i, j = 0, 0
90
- while i < max_length && Time.now <= started_at + @run_timeout
119
+
120
+ # Events of expected length will be emitted at the end.
121
+ max_length = @expected_emits_length
122
+ max_length ||= @expects.length if @expects
123
+ if max_length
124
+ register_run_post_condition do
125
+ i == max_length
126
+ end
127
+ end
128
+
129
+ # Set runnning timeout to avoid infinite loop caused by some errors.
130
+ started_at = Time.now
131
+ register_run_breaking_condition do
132
+ Time.now >= started_at + @run_timeout
133
+ end
134
+
135
+ until run_should_stop?
91
136
  if j >= @emit_streams.length
92
137
  sleep 0.01
93
138
  next
@@ -1,7 +1,5 @@
1
1
  #
2
- # Fluent
3
- #
4
- # Copyright (C) 2011 FURUHASHI Sadayuki
2
+ # Fluentd
5
3
  #
6
4
  # Licensed under the Apache License, Version 2.0 (the "License");
7
5
  # you may not use this file except in compliance with the License.
@@ -15,6 +13,7 @@
15
13
  # See the License for the specific language governing permissions and
16
14
  # limitations under the License.
17
15
  #
16
+
18
17
  module Fluent
19
18
  module Test
20
19
  class TestOutputChain
@@ -53,10 +52,6 @@ module Fluent
53
52
  @entries = []
54
53
  @expected_buffer = nil
55
54
  @tag = tag
56
-
57
- def @instance.buffer
58
- @buffer
59
- end
60
55
  end
61
56
 
62
57
  attr_accessor :tag
@@ -82,19 +77,8 @@ module Fluent
82
77
  assert_equal(@expected_buffer, buffer)
83
78
  end
84
79
 
85
- key = ''
86
- if @instance.respond_to?(:time_slicer)
87
- # this block is only for test_out_file
88
- time, record = @entries.first
89
- key = @instance.time_slicer.call(time)
90
- end
91
- chunk = @instance.buffer.new_chunk(key)
92
- chunk << buffer
93
- begin
94
- result = @instance.write(chunk)
95
- ensure
96
- chunk.purge
97
- end
80
+ chunk = MemoryBufferChunk.new('', buffer)
81
+ result = @instance.write(chunk)
98
82
  }
99
83
  result
100
84
  end
@@ -1,5 +1,21 @@
1
+ #
2
+ # Fluentd
3
+ #
4
+ # Licensed under the Apache License, Version 2.0 (the "License");
5
+ # you may not use this file except in compliance with the License.
6
+ # You may obtain a copy of the License at
7
+ #
8
+ # http://www.apache.org/licenses/LICENSE-2.0
9
+ #
10
+ # Unless required by applicable law or agreed to in writing, software
11
+ # distributed under the License is distributed on an "AS IS" BASIS,
12
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ # See the License for the specific language governing permissions and
14
+ # limitations under the License.
15
+ #
16
+
1
17
  module Fluent
2
18
 
3
- VERSION = '0.10.62'
19
+ VERSION = '0.12.0.pre.1'
4
20
 
5
21
  end
@@ -0,0 +1,314 @@
1
+ require "json"
2
+ require "config/helper"
3
+ require "fluent/config/error"
4
+ require "fluent/config/basic_parser"
5
+ require "fluent/config/literal_parser"
6
+ require "fluent/config/v1_parser"
7
+
8
+ describe Fluent::Config::V1Parser do
9
+ include_context 'config_helper'
10
+
11
+ def read_config(path)
12
+ path = File.expand_path(path)
13
+ data = File.read(path)
14
+ Fluent::Config::V1Parser.parse(data, File.basename(path), File.dirname(path))
15
+ end
16
+
17
+ def parse_text(text)
18
+ basepath = File.expand_path(File.dirname(__FILE__) + '/../../')
19
+ Fluent::Config::V1Parser.parse(text, '(test)', basepath, nil)
20
+ end
21
+
22
+ def root(*elements)
23
+ if elements.first.is_a?(Fluent::Config::Element)
24
+ attrs = {}
25
+ else
26
+ attrs = elements.shift || {}
27
+ end
28
+ Fluent::Config::Element.new('ROOT', '', attrs, elements)
29
+ end
30
+
31
+ def e(name, arg='', attrs={}, elements=[])
32
+ Fluent::Config::Element.new(name, arg, attrs, elements)
33
+ end
34
+
35
+ describe 'attribute parsing' do
36
+ it "parses attributes" do
37
+ expect(%[
38
+ k1 v1
39
+ k2 v2
40
+ ]).to be_parsed_as(e('ROOT', '', {"k1"=>"v1", "k2"=>"v2"}))
41
+ end
42
+
43
+ it "allows attribute without value" do
44
+ expect(%[
45
+ k1
46
+ k2 v2
47
+ ]).to be_parsed_as(e('ROOT', '', {"k1"=>"", "k2"=>"v2"}))
48
+ end
49
+
50
+ it "parses attribute key always string" do
51
+ expect("1 1").to be_parsed_as(e('ROOT', '', {"1" => "1"}))
52
+ end
53
+
54
+ [
55
+ "_.%$!,",
56
+ "/=~-~@\`:?",
57
+ "()*{}.[]",
58
+ ].each do |v|
59
+ it "parses a value with symbol #{v.inspect}" do
60
+ expect("k #{v}").to be_parsed_as(e('ROOT', '', {"k" => v}))
61
+ end
62
+ end
63
+
64
+ it "ignores spacing around value" do
65
+ expect(" k1 a ").to be_parsed_as(e('ROOT', '', {"k1" => "a"}))
66
+ end
67
+
68
+ it "allows spaces in value" do
69
+ expect("k1 a b c").to be_parsed_as(e('ROOT', '', {"k1" => "a b c"}))
70
+ end
71
+
72
+ it "ignores comments after value" do
73
+ expect(" k1 a#comment").to be_parsed_as(e('ROOT', '', {"k1" => "a"}))
74
+ end
75
+
76
+ it "allows # in value if quoted" do
77
+ expect(' k1 "a#comment"').to be_parsed_as(e('ROOT', '', {"k1" => "a#comment"}))
78
+ end
79
+
80
+ it "rejects characters after quoted string" do
81
+ expect(' k1 "a" 1').to be_parse_error
82
+ end
83
+
84
+ it "rejects @ prefix in parameter name" do
85
+ expect(' @k v').to be_parse_error
86
+ end
87
+ end
88
+
89
+ describe 'element parsing' do
90
+ it do
91
+ expect("").to be_parsed_as(root)
92
+ end
93
+
94
+ it "accepts empty element" do
95
+ expect(%[
96
+ <test>
97
+ </test>
98
+ ]).to be_parsed_as(
99
+ root(
100
+ e("test")
101
+ )
102
+ )
103
+ end
104
+
105
+ it "accepts argument and attributes" do
106
+ expect(%[
107
+ <test var>
108
+ key val
109
+ </test>
110
+ ]).to be_parsed_as(root(
111
+ e("test", 'var', {'key'=>"val"})
112
+ ))
113
+ end
114
+
115
+ it "accepts nested elements" do
116
+ expect(%[
117
+ <test var>
118
+ key 1
119
+ <nested1>
120
+ </nested1>
121
+ <nested2>
122
+ </nested2>
123
+ </test>
124
+ ]).to be_parsed_as(root(
125
+ e("test", 'var', {'key'=>'1'}, [
126
+ e('nested1'),
127
+ e('nested2')
128
+ ])
129
+ ))
130
+ end
131
+
132
+ it "accepts multiline json values" do
133
+ expect(%[
134
+ <test var>
135
+ key ["a",
136
+ "b", "c",
137
+ "d"]
138
+ </test>
139
+ ]).to be_parsed_as(root(
140
+ e("test", 'var', {'key'=>"[\"a\",\"b\",\"c\",\"d\"]"})
141
+ ))
142
+ end
143
+
144
+ [
145
+ "**",
146
+ "*.*",
147
+ "1",
148
+ "_.%$!",
149
+ "/",
150
+ "()*{}.[]",
151
+ ].each do |arg|
152
+ it "parses element argument #{arg.inspect}" do
153
+ expect(%[
154
+ <test #{arg}>
155
+ </test>
156
+ ]).to be_parsed_as(root(
157
+ e("test", arg)
158
+ ))
159
+ end
160
+ end
161
+
162
+ it "parses empty element argument to nil" do
163
+ expect(%[
164
+ <test >
165
+ </test>
166
+ ]).to be_parsed_as(root(
167
+ e("test", '')
168
+ ))
169
+ end
170
+
171
+ it "ignores spacing around element argument" do
172
+ expect(%[
173
+ <test a >
174
+ </test>
175
+ ]).to be_parsed_as(root(
176
+ e("test", "a")
177
+ ))
178
+ end
179
+
180
+ it "considers comments in element argument" do
181
+ expect(%[
182
+ <test #a>
183
+ </test>
184
+ ]).to be_parse_error
185
+ end
186
+
187
+ it "requires line_end after begin tag" do
188
+ expect(%[
189
+ <test></test>
190
+ ]).to be_parse_error
191
+ end
192
+
193
+ it "requires line_end after end tag" do
194
+ expect(%[
195
+ <test>
196
+ </test><test>
197
+ </test>
198
+ ]).to be_parse_error
199
+ end
200
+ end
201
+
202
+ # port from test_config.rb
203
+ describe '@include parsing' do
204
+ TMP_DIR = File.dirname(__FILE__) + "/tmp/v1_config#{ENV['TEST_ENV_NUMBER']}"
205
+
206
+ def write_config(path, data)
207
+ FileUtils.mkdir_p(File.dirname(path))
208
+ File.open(path, "w") { |f| f.write data }
209
+ end
210
+
211
+ def prepare_config
212
+ write_config "#{TMP_DIR}/config_test_1.conf", %[
213
+ k1 root_config
214
+ include dir/config_test_2.conf #
215
+ @include #{TMP_DIR}/config_test_4.conf
216
+ include file://#{TMP_DIR}/config_test_5.conf
217
+ @include config.d/*.conf
218
+ ]
219
+ write_config "#{TMP_DIR}/dir/config_test_2.conf", %[
220
+ k2 relative_path_include
221
+ @include ../config_test_3.conf
222
+ ]
223
+ write_config "#{TMP_DIR}/config_test_3.conf", %[
224
+ k3 relative_include_in_included_file
225
+ ]
226
+ write_config "#{TMP_DIR}/config_test_4.conf", %[
227
+ k4 absolute_path_include
228
+ ]
229
+ write_config "#{TMP_DIR}/config_test_5.conf", %[
230
+ k5 uri_include
231
+ ]
232
+ write_config "#{TMP_DIR}/config.d/config_test_6.conf", %[
233
+ k6 wildcard_include_1
234
+ <elem1 name>
235
+ include normal_parameter
236
+ </elem1>
237
+ ]
238
+ write_config "#{TMP_DIR}/config.d/config_test_7.conf", %[
239
+ k7 wildcard_include_2
240
+ ]
241
+ write_config "#{TMP_DIR}/config.d/config_test_8.conf", %[
242
+ <elem2 name>
243
+ @include ../dir/config_test_9.conf
244
+ </elem2>
245
+ ]
246
+ write_config "#{TMP_DIR}/dir/config_test_9.conf", %[
247
+ k9 embeded
248
+ <elem3 name>
249
+ nested nested_value
250
+ include hoge
251
+ </elem3>
252
+ ]
253
+ write_config "#{TMP_DIR}/config.d/00_config_test_8.conf", %[
254
+ k8 wildcard_include_3
255
+ <elem4 name>
256
+ include normal_parameter
257
+ </elem4>
258
+ ]
259
+ end
260
+
261
+ it 'parses @include / include correctly' do
262
+ prepare_config
263
+ c = read_config("#{TMP_DIR}/config_test_1.conf")
264
+ expect(c['k1']).to eq('root_config')
265
+ expect(c['k2']).to eq('relative_path_include')
266
+ expect(c['k3']).to eq('relative_include_in_included_file')
267
+ expect(c['k4']).to eq('absolute_path_include')
268
+ expect(c['k5']).to eq('uri_include')
269
+ expect(c['k6']).to eq('wildcard_include_1')
270
+ expect(c['k7']).to eq('wildcard_include_2')
271
+ expect(c['k8']).to eq('wildcard_include_3')
272
+ expect(c.keys).to eq([
273
+ 'k1',
274
+ 'k2',
275
+ 'k3',
276
+ 'k4',
277
+ 'k5',
278
+ 'k8', # Because of the file name this comes first.
279
+ 'k6',
280
+ 'k7',
281
+ ])
282
+
283
+ elem1 = c.elements.find { |e| e.name == 'elem1' }
284
+ expect(elem1).to be
285
+ expect(elem1.arg).to eq('name')
286
+ expect(elem1['include']).to eq('normal_parameter')
287
+
288
+ elem2 = c.elements.find { |e| e.name == 'elem2' }
289
+ expect(elem2).to be
290
+ expect(elem2.arg).to eq('name')
291
+ expect(elem2['k9']).to eq('embeded')
292
+ expect(elem2.has_key?('include')).to be(false)
293
+
294
+ elem3 = elem2.elements.find { |e| e.name == 'elem3' }
295
+ expect(elem3).to be
296
+ expect(elem3['nested']).to eq('nested_value')
297
+ expect(elem3['include']).to eq('hoge')
298
+ end
299
+
300
+ # TODO: Add uri based include spec
301
+ end
302
+
303
+ describe 'unescape parameter' do
304
+ it 'parses dumpped configuration' do
305
+ original = %q!a\\\\\n\r\f\b\\'\\"z!
306
+ expected = %!a\\\n\r\f\b'"z!
307
+
308
+ conf = parse_text(%[k1 #{original}])
309
+ expect(conf['k1']).to eq(expected) # escape check
310
+ conf2 = parse_text(conf.to_s) # use dumpped configuration to check unescape
311
+ expect(conf2.elements.first['k1']).to eq(expected)
312
+ end
313
+ end
314
+ end