require_relative 'helper'
require 'fluent/test'
require 'fluent/output'
require 'fluent/output_chain'
require 'timecop'
require 'flexmock/test_unit'
module FluentOutputTest
include Fluent
include FlexMock::TestCase
class BufferedOutputTest < ::Test::Unit::TestCase
include FluentOutputTest
class << self
def startup
$LOAD_PATH.unshift File.expand_path(File.join(File.dirname(__FILE__), 'scripts'))
require 'fluent/plugin/out_test'
require 'fluent/plugin/out_test2'
end
def shutdown
$LOAD_PATH.shift
end
end
def setup
Fluent::Test.setup
end
CONFIG = %[]
def create_driver(conf=CONFIG)
Fluent::Test::BufferedOutputTestDriver.new(Fluent::BufferedOutput) do
def write(chunk)
chunk.read
end
end.configure(conf)
end
def test_configure
# default
d = create_driver
assert_equal 'memory', d.instance.buffer_type
assert_equal 60, d.instance.flush_interval
assert_equal false, d.instance.disable_retry_limit
assert_equal 17, d.instance.retry_limit
assert_equal 1.0, d.instance.retry_wait
assert_equal nil, d.instance.max_retry_wait
assert_equal 1.0, d.instance.retry_wait
assert_equal 1, d.instance.num_threads
assert_equal 1, d.instance.queued_chunk_flush_interval
# max_retry_wait
d = create_driver(CONFIG + %[max_retry_wait 4])
assert_equal 4, d.instance.max_retry_wait
# disable_retry_limit
d = create_driver(CONFIG + %[disable_retry_limit true])
assert_equal true, d.instance.disable_retry_limit
#### retry_state cares it
# # retry_wait is converted to Float for calc_retry_wait
# d = create_driver(CONFIG + %[retry_wait 1s])
# assert_equal Float, d.instance.retry_wait.class
end
class FormatterInjectTestOutput < Fluent::Output
def initialize
super
@formatter = nil
end
end
def test_start
i = FormatterInjectTestOutput.new
i.configure(config_element('ROOT', '', {}, [config_element('inject', '', {'hostname_key' => "host"})]))
assert_nothing_raised do
i.start
end
end
def create_mock_driver(conf=CONFIG)
Fluent::Test::BufferedOutputTestDriver.new(Fluent::BufferedOutput) do
attr_accessor :submit_flush_threads
def start_mock
@started = false
start
# ensure OutputThread to start successfully
submit_flush
sleep 0.5
while !@started
submit_flush
sleep 0.5
end
end
def try_flush
@started = true
@submit_flush_threads ||= {}
@submit_flush_threads[Thread.current] ||= 0
@submit_flush_threads[Thread.current] += 1
end
def write(chunk)
chunk.read
end
end.configure(conf)
end
def test_secondary
d = Fluent::Test::BufferedOutputTestDriver.new(Fluent::BufferedOutput) do
def write(chunk)
chunk.read
end
end
mock(d.instance.log).warn("secondary type should be same with primary one",
{ primary: d.instance.class.to_s, secondary: "Fluent::Plugin::Test2Output" })
d.configure(CONFIG + %[
type test2
name c0
])
assert_not_nil d.instance.instance_variable_get(:@secondary).router
end
def test_secondary_with_no_warn_log
# ObjectBufferedOutput doesn't implemnt `custom_filter`
d = Fluent::Test::BufferedOutputTestDriver.new(Fluent::ObjectBufferedOutput)
mock(d.instance.log).warn("secondary type should be same with primary one",
{ primary: d.instance.class.to_s, secondary: "Fluent::Plugin::Test2Output" }).never
d.configure(CONFIG + %[
type test2
name c0
])
assert_not_nil d.instance.instance_variable_get(:@secondary).router
end
end
class ObjectBufferedOutputTest < ::Test::Unit::TestCase
include FluentOutputTest
def setup
Fluent::Test.setup
end
CONFIG = %[]
def create_driver(conf=CONFIG)
Fluent::Test::OutputTestDriver.new(Fluent::ObjectBufferedOutput).configure(conf, true)
end
def test_configure
# default
d = create_driver
assert_equal true, d.instance.time_as_integer
end
end
class TimeSlicedOutputTest < ::Test::Unit::TestCase
include FluentOutputTest
include FlexMock::TestCase
def setup
Fluent::Test.setup
FileUtils.rm_rf(TMP_DIR)
FileUtils.mkdir_p(TMP_DIR)
end
TMP_DIR = File.expand_path(File.dirname(__FILE__) + "/tmp/time_sliced_output")
CONFIG = %[
buffer_path #{TMP_DIR}/foo
time_slice_format %Y%m%d%H
]
class TimeSlicedOutputTestPlugin < Fluent::TimeSlicedOutput
attr_reader :written_chunk_keys, :errors_in_write
def initialize
super
@written_chunk_keys = []
@errors_in_write = []
end
def configure(conf)
super
@formatter = Fluent::Plugin.new_formatter('out_file')
@formatter.configure(conf)
end
def format(tag, time, record)
@formatter.format(tag, time, record)
end
def write(chunk)
@written_chunk_keys << chunk.key
true
rescue => e
@errors_in_write << e
end
end
def create_driver(conf=CONFIG)
Fluent::Test::TimeSlicedOutputTestDriver.new(TimeSlicedOutputTestPlugin).configure(conf, true)
end
data(:none => '',
:utc => "utc",
:localtime => 'localtime',
:timezone => 'timezone +0000')
test 'configure with timezone related parameters' do |param|
assert_nothing_raised {
create_driver(CONFIG + param)
}
end
sub_test_case "test emit" do
setup do
@time = Time.parse("2011-01-02 13:14:15 UTC")
Timecop.freeze(@time)
end
teardown do
Timecop.return
end
test "emit with invalid event" do
d = create_driver
d.instance.start
d.instance.after_start
assert_raise ArgumentError, "time must be a Fluent::EventTime (or Integer)" do
d.instance.emit_events('test', OneEventStream.new('string', 10))
end
end
test "plugin can get key of chunk in #write" do
d = create_driver
d.instance.start
d.instance.after_start
d.instance.emit_events('test', OneEventStream.new(event_time("2016-11-08 17:44:30 +0900"), {"message" => "yay"}))
d.instance.force_flush
waiting(10) do
sleep 0.1 until d.instance.written_chunk_keys.size == 1
end
assert_equal [], d.instance.errors_in_write
assert_equal ["2016110808"], d.instance.written_chunk_keys # default timezone is UTC
end
test "check formatted time compatibility with utc. Should Z, not +00:00" do
d = create_driver(CONFIG + %[
utc
include_time_key
])
time = Time.parse("2016-11-08 12:00:00 UTC").to_i
d.emit({"a" => 1}, time)
d.expect_format %[2016-11-08T12:00:00Z\ttest\t{"a":1,"time":"2016-11-08T12:00:00Z"}\n]
d.run
end
end
end
end