logstash-input-file 4.1.3 → 4.1.4

Sign up to get free protection for your applications and to get access to all the features.
Files changed (48) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +10 -0
  3. data/JAR_VERSION +1 -1
  4. data/README.md +0 -3
  5. data/docs/index.asciidoc +26 -16
  6. data/lib/filewatch/bootstrap.rb +10 -21
  7. data/lib/filewatch/discoverer.rb +35 -28
  8. data/lib/filewatch/observing_base.rb +2 -1
  9. data/lib/filewatch/read_mode/handlers/base.rb +19 -6
  10. data/lib/filewatch/read_mode/handlers/read_file.rb +43 -32
  11. data/lib/filewatch/read_mode/handlers/read_zip_file.rb +8 -3
  12. data/lib/filewatch/read_mode/processor.rb +8 -8
  13. data/lib/filewatch/settings.rb +3 -3
  14. data/lib/filewatch/sincedb_collection.rb +56 -42
  15. data/lib/filewatch/sincedb_value.rb +6 -0
  16. data/lib/filewatch/stat/generic.rb +34 -0
  17. data/lib/filewatch/stat/windows_path.rb +32 -0
  18. data/lib/filewatch/tail_mode/handlers/base.rb +40 -22
  19. data/lib/filewatch/tail_mode/handlers/create.rb +1 -2
  20. data/lib/filewatch/tail_mode/handlers/create_initial.rb +2 -1
  21. data/lib/filewatch/tail_mode/handlers/delete.rb +13 -1
  22. data/lib/filewatch/tail_mode/handlers/grow.rb +5 -2
  23. data/lib/filewatch/tail_mode/handlers/shrink.rb +7 -4
  24. data/lib/filewatch/tail_mode/handlers/unignore.rb +4 -2
  25. data/lib/filewatch/tail_mode/processor.rb +147 -58
  26. data/lib/filewatch/watch.rb +15 -35
  27. data/lib/filewatch/watched_file.rb +237 -41
  28. data/lib/filewatch/watched_files_collection.rb +2 -2
  29. data/lib/filewatch/winhelper.rb +167 -25
  30. data/lib/jars/filewatch-1.0.1.jar +0 -0
  31. data/lib/logstash/inputs/file.rb +9 -2
  32. data/logstash-input-file.gemspec +9 -2
  33. data/spec/file_ext/file_ext_windows_spec.rb +36 -0
  34. data/spec/filewatch/read_mode_handlers_read_file_spec.rb +2 -2
  35. data/spec/filewatch/reading_spec.rb +100 -57
  36. data/spec/filewatch/rotate_spec.rb +451 -0
  37. data/spec/filewatch/spec_helper.rb +33 -10
  38. data/spec/filewatch/tailing_spec.rb +273 -153
  39. data/spec/filewatch/watched_file_spec.rb +3 -3
  40. data/spec/filewatch/watched_files_collection_spec.rb +3 -3
  41. data/spec/filewatch/winhelper_spec.rb +4 -5
  42. data/spec/helpers/logging_level_helper.rb +8 -0
  43. data/spec/helpers/rspec_wait_handler_helper.rb +38 -0
  44. data/spec/helpers/spec_helper.rb +7 -1
  45. data/spec/inputs/file_read_spec.rb +54 -24
  46. data/spec/inputs/file_tail_spec.rb +244 -284
  47. metadata +13 -3
  48. data/lib/jars/filewatch-1.0.0.jar +0 -0
@@ -8,9 +8,9 @@ module FileWatch
8
8
 
9
9
  context 'Given two instances of the same file' do
10
10
  it 'their sincedb_keys should equate' do
11
- wf_key1 = WatchedFile.new(pathname, pathname.stat, Settings.new).sincedb_key
11
+ wf_key1 = WatchedFile.new(pathname, PathStatClass.new(pathname), Settings.new).sincedb_key
12
12
  hash_db = { wf_key1 => 42 }
13
- wf_key2 = WatchedFile.new(pathname, pathname.stat, Settings.new).sincedb_key
13
+ wf_key2 = WatchedFile.new(pathname, PathStatClass.new(pathname), Settings.new).sincedb_key
14
14
  expect(wf_key1).to eq(wf_key2)
15
15
  expect(wf_key1).to eql(wf_key2)
16
16
  expect(wf_key1.hash).to eq(wf_key2.hash)
@@ -20,7 +20,7 @@ module FileWatch
20
20
 
21
21
  context 'Given a barrage of state changes' do
22
22
  it 'only the previous N state changes are remembered' do
23
- watched_file = WatchedFile.new(pathname, pathname.stat, Settings.new)
23
+ watched_file = WatchedFile.new(pathname, PathStatClass.new(pathname), Settings.new)
24
24
  watched_file.ignore
25
25
  watched_file.watch
26
26
  watched_file.activate
@@ -4,9 +4,9 @@ require_relative 'spec_helper'
4
4
  module FileWatch
5
5
  describe WatchedFilesCollection do
6
6
  let(:time) { Time.now }
7
- let(:stat1) { double("stat1", :size => 98, :ctime => time - 30, :mtime => time - 30, :ino => 234567, :dev_major => 3, :dev_minor => 2) }
8
- let(:stat2) { double("stat2", :size => 99, :ctime => time - 20, :mtime => time - 20, :ino => 234568, :dev_major => 3, :dev_minor => 2) }
9
- let(:stat3) { double("stat3", :size => 100, :ctime => time, :mtime => time, :ino => 234569, :dev_major => 3, :dev_minor => 2) }
7
+ let(:stat1) { double("stat1", :size => 98, :modified_at => time - 30, :identifier => nil, :inode => 234567, :inode_struct => InodeStruct.new("234567", 3, 2)) }
8
+ let(:stat2) { double("stat2", :size => 99, :modified_at => time - 20, :identifier => nil, :inode => 234568, :inode_struct => InodeStruct.new("234568", 3, 2)) }
9
+ let(:stat3) { double("stat3", :size => 100, :modified_at => time, :identifier => nil, :inode => 234569, :inode_struct => InodeStruct.new("234569", 3, 2)) }
10
10
  let(:wf1) { WatchedFile.new("/var/log/z.log", stat1, Settings.new) }
11
11
  let(:wf2) { WatchedFile.new("/var/log/m.log", stat2, Settings.new) }
12
12
  let(:wf3) { WatchedFile.new("/var/log/a.log", stat3, Settings.new) }
@@ -3,7 +3,7 @@ require "stud/temporary"
3
3
  require "fileutils"
4
4
 
5
5
  if Gem.win_platform?
6
- require "lib/filewatch/winhelper"
6
+ require "filewatch/winhelper"
7
7
 
8
8
  describe Winhelper do
9
9
  let(:path) { Stud::Temporary.file.path }
@@ -13,11 +13,10 @@ if Gem.win_platform?
13
13
  end
14
14
 
15
15
  it "return a unique file identifier" do
16
- volume_serial, file_index_low, file_index_high = Winhelper.GetWindowsUniqueFileIdentifier(path).split("").map(&:to_i)
16
+ identifier = Winhelper.identifier_from_path(path)
17
17
 
18
- expect(volume_serial).not_to eq(0)
19
- expect(file_index_low).not_to eq(0)
20
- expect(file_index_high).not_to eq(0)
18
+ expect(identifier).not_to eq("unknown")
19
+ expect(identifier.count("-")).to eq(2)
21
20
  end
22
21
  end
23
22
  end
@@ -0,0 +1,8 @@
1
+ # encoding: utf-8
2
+
3
+ ENV["LOG_AT"].tap do |level|
4
+ if !level.nil?
5
+ LogStash::Logging::Logger::configure_logging(level)
6
+ LOG_AT_HANDLED = true
7
+ end
8
+ end
@@ -0,0 +1,38 @@
1
+ # encoding: utf-8
2
+
3
+ module RSpec
4
+ module Wait
5
+ module Handler
6
+ def handle_matcher(target, *args, &block)
7
+ # there is a similar patch in the rspec-wait repo since Nov, 19 2017
8
+ # it does not look like the author is interested in the change.
9
+ # - do not use Ruby Timeout
10
+ count = RSpec.configuration.wait_timeout.fdiv(RSpec.configuration.wait_delay).ceil
11
+ failure = nil
12
+ count.times do
13
+ begin
14
+ actual = target.respond_to?(:call) ? target.call : target
15
+ super(actual, *args, &block)
16
+ failure = nil
17
+ rescue RSpec::Expectations::ExpectationNotMetError => failure
18
+ sleep RSpec.configuration.wait_delay
19
+ end
20
+ break if failure.nil?
21
+ end
22
+ raise failure unless failure.nil?
23
+ end
24
+ end
25
+
26
+ # From: https://github.com/rspec/rspec-expectations/blob/v3.0.0/lib/rspec/expectations/handler.rb#L44-L63
27
+ class PositiveHandler < RSpec::Expectations::PositiveExpectationHandler
28
+ extend Handler
29
+ end
30
+
31
+ # From: https://github.com/rspec/rspec-expectations/blob/v3.0.0/lib/rspec/expectations/handler.rb#L66-L93
32
+ class NegativeHandler < RSpec::Expectations::NegativeExpectationHandler
33
+ extend Handler
34
+ end
35
+ end
36
+ end
37
+
38
+ RSPEC_WAIT_HANDLER_PATCHED = true
@@ -18,7 +18,7 @@ module FileInput
18
18
 
19
19
  class TracerBase
20
20
  def initialize
21
- @tracer = []
21
+ @tracer = Concurrent::Array.new
22
22
  end
23
23
 
24
24
  def trace_for(symbol)
@@ -54,6 +54,9 @@ module FileInput
54
54
  end
55
55
  end
56
56
 
57
+ require_relative "rspec_wait_handler_helper" unless defined? RSPEC_WAIT_HANDLER_PATCHED
58
+ require_relative "logging_level_helper" unless defined? LOG_AT_HANDLED
59
+
57
60
  unless RSpec::Matchers.method_defined?(:receive_call_and_args)
58
61
  RSpec::Matchers.define(:receive_call_and_args) do |m, args|
59
62
  match do |actual|
@@ -66,3 +69,6 @@ unless RSpec::Matchers.method_defined?(:receive_call_and_args)
66
69
  end
67
70
  end
68
71
 
72
+ ENV["LOG_AT"].tap do |level|
73
+ LogStash::Logging::Logger::configure_logging(level) unless level.nil?
74
+ end
@@ -9,21 +9,21 @@ require "tempfile"
9
9
  require "stud/temporary"
10
10
  require "logstash/codecs/multiline"
11
11
 
12
- FILE_DELIMITER = LogStash::Environment.windows? ? "\r\n" : "\n"
13
-
14
12
  describe LogStash::Inputs::File do
15
13
  describe "'read' mode testing with input(conf) do |pipeline, queue|" do
16
14
  it "should start at the beginning of an existing file and delete the file when done" do
17
- tmpfile_path = Stud::Temporary.pathname
18
- sincedb_path = Stud::Temporary.pathname
15
+ directory = Stud::Temporary.directory
16
+ tmpfile_path = ::File.join(directory, "A.log")
17
+ sincedb_path = ::File.join(directory, "readmode_A_sincedb.txt")
18
+ path_path = ::File.join(directory, "*.log")
19
19
 
20
20
  conf = <<-CONFIG
21
21
  input {
22
22
  file {
23
- type => "blah"
24
- path => "#{tmpfile_path}"
23
+ id => "blah"
24
+ path => "#{path_path}"
25
25
  sincedb_path => "#{sincedb_path}"
26
- delimiter => "#{FILE_DELIMITER}"
26
+ delimiter => "|"
27
27
  mode => "read"
28
28
  file_completed_action => "delete"
29
29
  }
@@ -31,17 +31,49 @@ describe LogStash::Inputs::File do
31
31
  CONFIG
32
32
 
33
33
  File.open(tmpfile_path, "a") do |fd|
34
- fd.puts("hello")
35
- fd.puts("world")
34
+ fd.write("hello|world")
36
35
  fd.fsync
37
36
  end
38
37
 
39
38
  events = input(conf) do |pipeline, queue|
39
+ wait(0.5).for{File.exist?(tmpfile_path)}.to be_falsey
40
40
  2.times.collect { queue.pop }
41
41
  end
42
42
 
43
43
  expect(events.map{|e| e.get("message")}).to contain_exactly("hello", "world")
44
- expect(File.exist?(tmpfile_path)).to be_falsey
44
+ end
45
+
46
+ it "should start at the beginning of an existing file and log the file when done" do
47
+ directory = Stud::Temporary.directory
48
+ tmpfile_path = ::File.join(directory, "A.log")
49
+ sincedb_path = ::File.join(directory, "readmode_A_sincedb.txt")
50
+ path_path = ::File.join(directory, "*.log")
51
+ log_completed_path = ::File.join(directory, "A_completed.txt")
52
+
53
+ conf = <<-CONFIG
54
+ input {
55
+ file {
56
+ id => "blah"
57
+ path => "#{path_path}"
58
+ sincedb_path => "#{sincedb_path}"
59
+ delimiter => "|"
60
+ mode => "read"
61
+ file_completed_action => "log"
62
+ file_completed_log_path => "#{log_completed_path}"
63
+ }
64
+ }
65
+ CONFIG
66
+
67
+ File.open(tmpfile_path, "a") do |fd|
68
+ fd.write("hello|world")
69
+ fd.fsync
70
+ end
71
+
72
+ events = input(conf) do |pipeline, queue|
73
+ wait(0.5).for{IO.read(log_completed_path)}.to match(/A\.log/)
74
+ 2.times.collect { queue.pop }
75
+ end
76
+ expect(events.map{|e| e.get("message")}).to contain_exactly("hello", "world")
45
77
  end
46
78
  end
47
79
 
@@ -63,7 +95,6 @@ describe LogStash::Inputs::File do
63
95
  type => "blah"
64
96
  path => "#{tmpfile_path}"
65
97
  sincedb_path => "#{sincedb_path}"
66
- delimiter => "#{FILE_DELIMITER}"
67
98
  mode => "read"
68
99
  file_completed_action => "log"
69
100
  file_completed_log_path => "#{log_completed_path}"
@@ -72,12 +103,12 @@ describe LogStash::Inputs::File do
72
103
  CONFIG
73
104
 
74
105
  events = input(conf) do |pipeline, queue|
106
+ wait(0.5).for{IO.read(log_completed_path)}.to match(/#{file_path.to_s}/)
75
107
  2.times.collect { queue.pop }
76
108
  end
77
109
 
78
110
  expect(events[0].get("message")).to start_with("2010-03-12 23:51")
79
111
  expect(events[1].get("message")).to start_with("2010-03-12 23:51")
80
- expect(IO.read(log_completed_path)).to eq(file_path.to_s + "\n")
81
112
  end
82
113
 
83
114
  end
@@ -86,10 +117,11 @@ describe LogStash::Inputs::File do
86
117
  let(:file_path) { fixture_dir.join('uncompressed.log') }
87
118
 
88
119
  it "the file is read and the path is logged to the `file_completed_log_path` file" do
89
- tmpfile_path = fixture_dir.join("unc*.log")
90
- sincedb_path = Stud::Temporary.pathname
91
120
  FileInput.make_fixture_current(file_path.to_path)
92
- log_completed_path = Stud::Temporary.pathname
121
+ tmpfile_path = fixture_dir.join("unc*.log")
122
+ directory = Stud::Temporary.directory
123
+ sincedb_path = ::File.join(directory, "readmode_B_sincedb.txt")
124
+ log_completed_path = ::File.join(directory, "B_completed.txt")
93
125
 
94
126
  conf = <<-CONFIG
95
127
  input {
@@ -97,7 +129,6 @@ describe LogStash::Inputs::File do
97
129
  type => "blah"
98
130
  path => "#{tmpfile_path}"
99
131
  sincedb_path => "#{sincedb_path}"
100
- delimiter => "#{FILE_DELIMITER}"
101
132
  mode => "read"
102
133
  file_completed_action => "log"
103
134
  file_completed_log_path => "#{log_completed_path}"
@@ -106,23 +137,25 @@ describe LogStash::Inputs::File do
106
137
  CONFIG
107
138
 
108
139
  events = input(conf) do |pipeline, queue|
140
+ wait(0.5).for{IO.read(log_completed_path)}.to match(/uncompressed\.log/)
109
141
  2.times.collect { queue.pop }
110
142
  end
111
143
 
112
144
  expect(events[0].get("message")).to start_with("2010-03-12 23:51")
113
145
  expect(events[1].get("message")).to start_with("2010-03-12 23:51")
114
- expect(IO.read(log_completed_path)).to eq(file_path.to_s + "\n")
115
146
  end
116
147
  end
117
148
 
118
149
  context "for a compressed file" do
119
150
  it "the file is read" do
120
- tmpfile_path = fixture_dir.join("compressed.*.*")
121
- sincedb_path = Stud::Temporary.pathname
122
151
  file_path = fixture_dir.join('compressed.log.gz')
123
152
  file_path2 = fixture_dir.join('compressed.log.gzip')
124
153
  FileInput.make_fixture_current(file_path.to_path)
125
- log_completed_path = Stud::Temporary.pathname
154
+ FileInput.make_fixture_current(file_path2.to_path)
155
+ tmpfile_path = fixture_dir.join("compressed.*.*")
156
+ directory = Stud::Temporary.directory
157
+ sincedb_path = ::File.join(directory, "readmode_C_sincedb.txt")
158
+ log_completed_path = ::File.join(directory, "C_completed.txt")
126
159
 
127
160
  conf = <<-CONFIG
128
161
  input {
@@ -130,7 +163,6 @@ describe LogStash::Inputs::File do
130
163
  type => "blah"
131
164
  path => "#{tmpfile_path}"
132
165
  sincedb_path => "#{sincedb_path}"
133
- delimiter => "#{FILE_DELIMITER}"
134
166
  mode => "read"
135
167
  file_completed_action => "log"
136
168
  file_completed_log_path => "#{log_completed_path}"
@@ -139,6 +171,7 @@ describe LogStash::Inputs::File do
139
171
  CONFIG
140
172
 
141
173
  events = input(conf) do |pipeline, queue|
174
+ wait(0.5).for{IO.read(log_completed_path).scan(/compressed\.log\.gz(ip)?/).size}.to eq(2)
142
175
  4.times.collect { queue.pop }
143
176
  end
144
177
 
@@ -146,9 +179,6 @@ describe LogStash::Inputs::File do
146
179
  expect(events[1].get("message")).to start_with("2010-03-12 23:51")
147
180
  expect(events[2].get("message")).to start_with("2010-03-12 23:51")
148
181
  expect(events[3].get("message")).to start_with("2010-03-12 23:51")
149
- logged_completions = IO.read(log_completed_path).split
150
- expect(logged_completions.first).to match(/compressed\.log\.(gzip|gz)$/)
151
- expect(logged_completions.last).to match(/compressed\.log\.(gzip|gz)$/)
152
182
  end
153
183
  end
154
184
  end
@@ -9,7 +9,7 @@ require "logstash/codecs/multiline"
9
9
 
10
10
  # LogStash::Logging::Logger::configure_logging("DEBUG")
11
11
 
12
- TEST_FILE_DELIMITER = LogStash::Environment.windows? ? "\r\n" : "\n"
12
+ TEST_FILE_DELIMITER = $/
13
13
 
14
14
  describe LogStash::Inputs::File do
15
15
  describe "'tail' mode testing with input(conf) do |pipeline, queue|" do
@@ -22,152 +22,159 @@ describe LogStash::Inputs::File do
22
22
  end
23
23
  end
24
24
 
25
- it "should start at the beginning of an existing file" do
26
- tmpfile_path = Stud::Temporary.pathname
27
- sincedb_path = Stud::Temporary.pathname
28
-
29
- conf = <<-CONFIG
30
- input {
31
- file {
32
- type => "blah"
33
- path => "#{tmpfile_path}"
34
- start_position => "beginning"
35
- sincedb_path => "#{sincedb_path}"
36
- delimiter => "#{TEST_FILE_DELIMITER}"
25
+ let(:directory) { Stud::Temporary.directory }
26
+ let(:sincedb_dir) { Stud::Temporary.directory }
27
+ let(:tmpfile_path) { ::File.join(directory, "#{name}.txt") }
28
+ let(:sincedb_path) { ::File.join(sincedb_dir, "readmode_#{name}_sincedb.txt") }
29
+ let(:path_path) { ::File.join(directory, "*.txt") }
30
+
31
+ context "for an existing file" do
32
+ let(:name) { "A" }
33
+ it "should start at the beginning" do
34
+ conf = <<-CONFIG
35
+ input {
36
+ file {
37
+ type => "blah"
38
+ path => "#{path_path}"
39
+ start_position => "beginning"
40
+ sincedb_path => "#{sincedb_path}"
41
+ delimiter => "#{TEST_FILE_DELIMITER}"
42
+ }
37
43
  }
38
- }
39
- CONFIG
44
+ CONFIG
40
45
 
41
- File.open(tmpfile_path, "a") do |fd|
42
- fd.puts("hello")
43
- fd.puts("world")
44
- fd.fsync
45
- end
46
+ File.open(tmpfile_path, "a") do |fd|
47
+ fd.puts("hello")
48
+ fd.puts("world")
49
+ fd.fsync
50
+ end
46
51
 
47
- events = input(conf) do |pipeline, queue|
48
- 2.times.collect { queue.pop }
52
+ events = input(conf) do |pipeline, queue|
53
+ 2.times.collect { queue.pop }
54
+ end
55
+ expect(events.map{|e| e.get("message")}).to contain_exactly("hello", "world")
49
56
  end
50
- expect(events.map{|e| e.get("message")}).to contain_exactly("hello", "world")
51
57
  end
52
58
 
53
- it "should restart at the sincedb value" do
54
- tmpfile_path = Stud::Temporary.pathname
55
- sincedb_path = Stud::Temporary.pathname
56
-
57
- conf = <<-CONFIG
58
- input {
59
- file {
60
- type => "blah"
61
- path => "#{tmpfile_path}"
62
- start_position => "beginning"
63
- sincedb_path => "#{sincedb_path}"
64
- delimiter => "#{TEST_FILE_DELIMITER}"
59
+ context "running the input twice" do
60
+ let(:name) { "B" }
61
+ it "should restart at the sincedb value" do
62
+ conf = <<-CONFIG
63
+ input {
64
+ file {
65
+ type => "blah"
66
+ path => "#{path_path}"
67
+ start_position => "beginning"
68
+ sincedb_path => "#{sincedb_path}"
69
+ "file_sort_by" => "path"
70
+ delimiter => "#{TEST_FILE_DELIMITER}"
71
+ }
65
72
  }
66
- }
67
- CONFIG
73
+ CONFIG
68
74
 
69
- File.open(tmpfile_path, "w") do |fd|
70
- fd.puts("hello3")
71
- fd.puts("world3")
72
- end
75
+ File.open(tmpfile_path, "w") do |fd|
76
+ fd.puts("hello3")
77
+ fd.puts("world3")
78
+ end
73
79
 
74
- events = input(conf) do |pipeline, queue|
75
- 2.times.collect { queue.pop }
76
- end
80
+ events = input(conf) do |pipeline, queue|
81
+ 2.times.collect { queue.pop }
82
+ end
77
83
 
78
- expect(events.map{|e| e.get("message")}).to contain_exactly("hello3", "world3")
84
+ expect(events.map{|e| e.get("message")}).to contain_exactly("hello3", "world3")
79
85
 
80
- File.open(tmpfile_path, "a") do |fd|
81
- fd.puts("foo")
82
- fd.puts("bar")
83
- fd.puts("baz")
84
- fd.fsync
85
- end
86
+ File.open(tmpfile_path, "a") do |fd|
87
+ fd.puts("foo")
88
+ fd.puts("bar")
89
+ fd.puts("baz")
90
+ fd.fsync
91
+ end
86
92
 
87
- events = input(conf) do |pipeline, queue|
88
- 3.times.collect { queue.pop }
93
+ events = input(conf) do |pipeline, queue|
94
+ 3.times.collect { queue.pop }
95
+ end
96
+ messages = events.map{|e| e.get("message")}
97
+ expect(messages).to contain_exactly("foo", "bar", "baz")
89
98
  end
90
- messages = events.map{|e| e.get("message")}
91
- expect(messages).to contain_exactly("foo", "bar", "baz")
92
99
  end
93
100
 
94
- it "should not overwrite existing path and host fields" do
95
- tmpfile_path = Stud::Temporary.pathname
96
- sincedb_path = Stud::Temporary.pathname
97
-
98
- conf = <<-CONFIG
99
- input {
100
- file {
101
- type => "blah"
102
- path => "#{tmpfile_path}"
103
- start_position => "beginning"
104
- sincedb_path => "#{sincedb_path}"
105
- delimiter => "#{TEST_FILE_DELIMITER}"
106
- codec => "json"
101
+ context "when path and host fields exist" do
102
+ let(:name) { "C" }
103
+ it "should not overwrite them" do
104
+ conf = <<-CONFIG
105
+ input {
106
+ file {
107
+ type => "blah"
108
+ path => "#{path_path}"
109
+ start_position => "beginning"
110
+ sincedb_path => "#{sincedb_path}"
111
+ delimiter => "#{TEST_FILE_DELIMITER}"
112
+ codec => "json"
113
+ }
107
114
  }
108
- }
109
- CONFIG
115
+ CONFIG
110
116
 
111
- File.open(tmpfile_path, "w") do |fd|
112
- fd.puts('{"path": "my_path", "host": "my_host"}')
113
- fd.puts('{"my_field": "my_val"}')
114
- fd.fsync
115
- end
117
+ File.open(tmpfile_path, "w") do |fd|
118
+ fd.puts('{"path": "my_path", "host": "my_host"}')
119
+ fd.puts('{"my_field": "my_val"}')
120
+ fd.fsync
121
+ end
116
122
 
117
- events = input(conf) do |pipeline, queue|
118
- 2.times.collect { queue.pop }
119
- end
123
+ events = input(conf) do |pipeline, queue|
124
+ 2.times.collect { queue.pop }
125
+ end
120
126
 
121
- existing_path_index, added_path_index = "my_val" == events[0].get("my_field") ? [1,0] : [0,1]
127
+ existing_path_index, added_path_index = "my_val" == events[0].get("my_field") ? [1,0] : [0,1]
122
128
 
123
- expect(events[existing_path_index].get("path")).to eq "my_path"
124
- expect(events[existing_path_index].get("host")).to eq "my_host"
125
- expect(events[existing_path_index].get("[@metadata][host]")).to eq "#{Socket.gethostname.force_encoding(Encoding::UTF_8)}"
129
+ expect(events[existing_path_index].get("path")).to eq "my_path"
130
+ expect(events[existing_path_index].get("host")).to eq "my_host"
131
+ expect(events[existing_path_index].get("[@metadata][host]")).to eq "#{Socket.gethostname.force_encoding(Encoding::UTF_8)}"
126
132
 
127
- expect(events[added_path_index].get("path")).to eq "#{tmpfile_path}"
128
- expect(events[added_path_index].get("host")).to eq "#{Socket.gethostname.force_encoding(Encoding::UTF_8)}"
129
- expect(events[added_path_index].get("[@metadata][host]")).to eq "#{Socket.gethostname.force_encoding(Encoding::UTF_8)}"
133
+ expect(events[added_path_index].get("path")).to eq "#{tmpfile_path}"
134
+ expect(events[added_path_index].get("host")).to eq "#{Socket.gethostname.force_encoding(Encoding::UTF_8)}"
135
+ expect(events[added_path_index].get("[@metadata][host]")).to eq "#{Socket.gethostname.force_encoding(Encoding::UTF_8)}"
136
+ end
130
137
  end
131
138
 
132
- it "should read old files" do
133
- tmpfile_path = Stud::Temporary.pathname
134
-
135
- conf = <<-CONFIG
136
- input {
137
- file {
138
- type => "blah"
139
- path => "#{tmpfile_path}"
140
- start_position => "beginning"
141
- codec => "json"
139
+ context "running the input twice" do
140
+ let(:name) { "D" }
141
+ it "should read old files" do
142
+ conf = <<-CONFIG
143
+ input {
144
+ file {
145
+ type => "blah"
146
+ path => "#{path_path}"
147
+ start_position => "beginning"
148
+ codec => "json"
149
+ }
142
150
  }
143
- }
144
- CONFIG
151
+ CONFIG
145
152
 
146
- File.open(tmpfile_path, "w") do |fd|
147
- fd.puts('{"path": "my_path", "host": "my_host"}')
148
- fd.puts('{"my_field": "my_val"}')
149
- fd.fsync
150
- end
151
- # arbitrary old file (2 days)
152
- FileInput.make_file_older(tmpfile_path, 48 * 60 * 60)
153
+ File.open(tmpfile_path, "w") do |fd|
154
+ fd.puts('{"path": "my_path", "host": "my_host"}')
155
+ fd.puts('{"my_field": "my_val"}')
156
+ fd.fsync
157
+ end
158
+ # arbitrary old file (2 days)
159
+ FileInput.make_file_older(tmpfile_path, 48 * 60 * 60)
153
160
 
154
- events = input(conf) do |pipeline, queue|
155
- 2.times.collect { queue.pop }
161
+ events = input(conf) do |pipeline, queue|
162
+ 2.times.collect { queue.pop }
163
+ end
164
+ existing_path_index, added_path_index = "my_val" == events[0].get("my_field") ? [1,0] : [0,1]
165
+ expect(events[existing_path_index].get("path")).to eq "my_path"
166
+ expect(events[existing_path_index].get("host")).to eq "my_host"
167
+ expect(events[existing_path_index].get("[@metadata][host]")).to eq "#{Socket.gethostname.force_encoding(Encoding::UTF_8)}"
168
+
169
+ expect(events[added_path_index].get("path")).to eq "#{tmpfile_path}"
170
+ expect(events[added_path_index].get("host")).to eq "#{Socket.gethostname.force_encoding(Encoding::UTF_8)}"
171
+ expect(events[added_path_index].get("[@metadata][host]")).to eq "#{Socket.gethostname.force_encoding(Encoding::UTF_8)}"
156
172
  end
157
- existing_path_index, added_path_index = "my_val" == events[0].get("my_field") ? [1,0] : [0,1]
158
- expect(events[existing_path_index].get("path")).to eq "my_path"
159
- expect(events[existing_path_index].get("host")).to eq "my_host"
160
- expect(events[existing_path_index].get("[@metadata][host]")).to eq "#{Socket.gethostname.force_encoding(Encoding::UTF_8)}"
161
-
162
- expect(events[added_path_index].get("path")).to eq "#{tmpfile_path}"
163
- expect(events[added_path_index].get("host")).to eq "#{Socket.gethostname.force_encoding(Encoding::UTF_8)}"
164
- expect(events[added_path_index].get("[@metadata][host]")).to eq "#{Socket.gethostname.force_encoding(Encoding::UTF_8)}"
165
173
  end
166
174
 
167
- context "when sincedb_path is an existing directory" do
168
- let(:tmpfile_path) { Stud::Temporary.pathname }
169
- let(:sincedb_path) { Stud::Temporary.directory }
170
- subject { LogStash::Inputs::File.new("path" => tmpfile_path, "sincedb_path" => sincedb_path) }
175
+ context "when sincedb_path is a directory" do
176
+ let(:name) { "E" }
177
+ subject { LogStash::Inputs::File.new("path" => path_path, "sincedb_path" => directory) }
171
178
 
172
179
  after :each do
173
180
  FileUtils.rm_rf(sincedb_path)
@@ -180,16 +187,19 @@ describe LogStash::Inputs::File do
180
187
  end
181
188
 
182
189
  describe "testing with new, register, run and stop" do
190
+ let(:suffix) { "A" }
183
191
  let(:conf) { Hash.new }
184
192
  let(:mlconf) { Hash.new }
185
193
  let(:events) { Array.new }
186
194
  let(:mlcodec) { LogStash::Codecs::Multiline.new(mlconf) }
187
- let(:codec) { FileInput::CodecTracer.new }
188
- let(:tmpfile_path) { Stud::Temporary.pathname }
189
- let(:sincedb_path) { Stud::Temporary.pathname }
195
+ let(:tracer_codec) { FileInput::CodecTracer.new }
190
196
  let(:tmpdir_path) { Stud::Temporary.directory }
197
+ let(:tmpfile_path) { ::File.join(tmpdir_path, "#{suffix}.txt") }
198
+ let(:path_path) { ::File.join(tmpdir_path, "*.txt") }
199
+ let(:sincedb_path) { ::File.join(tmpdir_path, "sincedb-#{suffix}") }
191
200
 
192
201
  after :each do
202
+ sleep(0.1) until subject.completely_stopped?
193
203
  FileUtils.rm_rf(sincedb_path)
194
204
  end
195
205
 
@@ -204,7 +214,7 @@ describe LogStash::Inputs::File do
204
214
  end
205
215
  mlconf.update("pattern" => "^\s", "what" => "previous")
206
216
  conf.update("type" => "blah",
207
- "path" => tmpfile_path,
217
+ "path" => path_path,
208
218
  "sincedb_path" => sincedb_path,
209
219
  "stat_interval" => 0.1,
210
220
  "codec" => mlcodec,
@@ -213,16 +223,22 @@ describe LogStash::Inputs::File do
213
223
 
214
224
  it "reads the appended data only" do
215
225
  subject.register
216
- RSpec::Sequencing
217
- .run_after(0.2, "assert zero events then append two lines") do
218
- expect(events.size).to eq(0)
226
+ actions = RSpec::Sequencing
227
+ .run_after(1, "append two lines after delay") do
219
228
  File.open(tmpfile_path, "a") { |fd| fd.puts("hello"); fd.puts("world") }
220
229
  end
221
- .then_after(0.4, "quit") do
230
+ .then("wait for one event") do
231
+ wait(0.75).for{events.size}.to eq(1)
232
+ end
233
+ .then("quit") do
222
234
  subject.stop
223
235
  end
236
+ .then("wait for flushed event") do
237
+ wait(0.75).for{events.size}.to eq(2)
238
+ end
224
239
 
225
240
  subject.run(events)
241
+ actions.assert_no_errors
226
242
 
227
243
  event1 = events[0]
228
244
  expect(event1).not_to be_nil
@@ -240,218 +256,172 @@ describe LogStash::Inputs::File do
240
256
 
241
257
  context "when close_older config is specified" do
242
258
  let(:line) { "line1.1-of-a" }
243
-
259
+ let(:suffix) { "X" }
244
260
  subject { described_class.new(conf) }
245
261
 
246
262
  before do
247
263
  conf.update(
248
264
  "type" => "blah",
249
- "path" => "#{tmpdir_path}/*.log",
265
+ "path" => path_path,
250
266
  "sincedb_path" => sincedb_path,
251
267
  "stat_interval" => 0.02,
252
- "codec" => codec,
253
- "close_older" => 0.5,
268
+ "codec" => tracer_codec,
269
+ "close_older" => "100 ms",
270
+ "start_position" => "beginning",
254
271
  "delimiter" => TEST_FILE_DELIMITER)
255
272
 
256
273
  subject.register
257
274
  end
258
275
 
259
- it "having timed_out, the identity is evicted" do
260
- RSpec::Sequencing
276
+ it "having timed_out, the codec is auto flushed" do
277
+ actions = RSpec::Sequencing
261
278
  .run("create file") do
262
- File.open("#{tmpdir_path}/a.log", "wb") { |file| file.puts(line) }
279
+ File.open(tmpfile_path, "wb") { |file| file.puts(line) }
263
280
  end
264
- .then_after(0.3, "identity is mapped") do
265
- expect(codec.trace_for(:accept)).to eq([true])
266
- expect(subject.codec.identity_count).to eq(1)
281
+ .then_after(0.1, "identity is mapped") do
282
+ wait(0.75).for{subject.codec.identity_map[tmpfile_path]}.not_to be_nil, "identity is not mapped"
267
283
  end
268
- .then_after(0.3, "test for auto_flush") do
269
- expect(codec.trace_for(:auto_flush)).to eq([true])
270
- expect(subject.codec.identity_count).to eq(0)
284
+ .then("wait for auto_flush") do
285
+ wait(0.75).for{subject.codec.identity_map[tmpfile_path].codec.trace_for(:auto_flush)}.to eq([true]), "autoflush didn't"
271
286
  end
272
- .then_after(0.1, "quit") do
287
+ .then("quit") do
273
288
  subject.stop
274
289
  end
275
290
  subject.run(events)
291
+ actions.assert_no_errors
292
+ expect(subject.codec.identity_map[tmpfile_path].codec.trace_for(:accept)).to eq([true])
276
293
  end
277
294
  end
278
295
 
279
296
  context "when ignore_older config is specified" do
280
- let(:line) { "line1.1-of-a" }
281
- let(:tmp_dir_file) { "#{tmpdir_path}/a.log" }
282
-
283
- subject { described_class.new(conf) }
284
-
297
+ let(:suffix) { "Y" }
285
298
  before do
286
- File.open(tmp_dir_file, "a") do |fd|
287
- fd.puts(line)
288
- fd.fsync
289
- end
290
- FileInput.make_file_older(tmp_dir_file, 2)
291
299
  conf.update(
292
300
  "type" => "blah",
293
- "path" => "#{tmpdir_path}/*.log",
301
+ "path" => path_path,
294
302
  "sincedb_path" => sincedb_path,
295
303
  "stat_interval" => 0.02,
296
- "codec" => codec,
297
- "ignore_older" => 1,
304
+ "codec" => tracer_codec,
305
+ "ignore_older" => "500 ms",
298
306
  "delimiter" => TEST_FILE_DELIMITER)
299
-
300
- subject.register
301
- Thread.new { subject.run(events) }
302
307
  end
308
+ subject { described_class.new(conf) }
309
+ let(:line) { "line1.1-of-a" }
303
310
 
304
311
  it "the file is not read" do
305
- sleep 0.1
306
- subject.stop
307
- expect(codec).to receive_call_and_args(:accept, false)
308
- expect(codec).to receive_call_and_args(:auto_flush, false)
309
- expect(subject.codec.identity_count).to eq(0)
312
+ subject.register
313
+ RSpec::Sequencing
314
+ .run("create file") do
315
+ File.open(tmp_dir_file, "a") do |fd|
316
+ fd.puts(line)
317
+ fd.fsync
318
+ end
319
+ FileInput.make_file_older(tmp_dir_file, 2)
320
+ end
321
+ .then_after(0.5, "stop") do
322
+ subject.stop
323
+ end
324
+ subject.run(events)
325
+ expect(subject.codec.identity_map[tmpfile_path].codec.trace_for(:accept)).to be_falsey
310
326
  end
311
327
  end
312
328
 
313
329
  context "when wildcard path and a multiline codec is specified" do
314
330
  subject { described_class.new(conf) }
315
-
331
+ let(:suffix) { "J" }
332
+ let(:tmpfile_path2) { ::File.join(tmpdir_path, "K.txt") }
316
333
  before do
317
334
  mlconf.update("pattern" => "^\s", "what" => "previous")
318
335
  conf.update(
319
336
  "type" => "blah",
320
- "path" => "#{tmpdir_path}/*.log",
337
+ "path" => path_path,
338
+ "start_position" => "beginning",
321
339
  "sincedb_path" => sincedb_path,
322
340
  "stat_interval" => 0.05,
323
341
  "codec" => mlcodec,
342
+ "file_sort_by" => "path",
324
343
  "delimiter" => TEST_FILE_DELIMITER)
325
344
 
326
345
  subject.register
327
346
  end
328
347
 
329
348
  it "collects separate multiple line events from each file" do
349
+ subject
330
350
  actions = RSpec::Sequencing
331
351
  .run_after(0.1, "create files") do
332
- File.open("#{tmpdir_path}/A.log", "wb") do |fd|
333
- fd.puts("line1.1-of-a")
334
- fd.puts(" line1.2-of-a")
335
- fd.puts(" line1.3-of-a")
352
+ File.open(tmpfile_path, "wb") do |fd|
353
+ fd.puts("line1.1-of-J")
354
+ fd.puts(" line1.2-of-J")
355
+ fd.puts(" line1.3-of-J")
336
356
  end
337
- File.open("#{tmpdir_path}/z.log", "wb") do |fd|
338
- fd.puts("line1.1-of-z")
339
- fd.puts(" line1.2-of-z")
340
- fd.puts(" line1.3-of-z")
357
+ File.open(tmpfile_path2, "wb") do |fd|
358
+ fd.puts("line1.1-of-K")
359
+ fd.puts(" line1.2-of-K")
360
+ fd.puts(" line1.3-of-K")
341
361
  end
342
362
  end
343
- .then_after(0.2, "assert both files are mapped as identities and stop") do
344
- expect(subject.codec.identity_count).to eq(2)
363
+ .then("assert both files are mapped as identities and stop") do
364
+ wait(2).for {subject.codec.identity_count}.to eq(2), "both files are not mapped as identities"
345
365
  end
346
- .then_after(0.1, "stop") do
366
+ .then("stop") do
347
367
  subject.stop
348
368
  end
349
- .then_after(0.2 , "stop flushes both events") do
350
- expect(events.size).to eq(2)
351
- e1, e2 = events
352
- e1_message = e1.get("message")
353
- e2_message = e2.get("message")
354
-
355
- # can't assume File A will be read first
356
- if e1_message.start_with?('line1.1-of-z')
357
- expect(e1.get("path")).to match(/z.log/)
358
- expect(e2.get("path")).to match(/A.log/)
359
- expect(e1_message).to eq("line1.1-of-z#{TEST_FILE_DELIMITER} line1.2-of-z#{TEST_FILE_DELIMITER} line1.3-of-z")
360
- expect(e2_message).to eq("line1.1-of-a#{TEST_FILE_DELIMITER} line1.2-of-a#{TEST_FILE_DELIMITER} line1.3-of-a")
361
- else
362
- expect(e1.get("path")).to match(/A.log/)
363
- expect(e2.get("path")).to match(/z.log/)
364
- expect(e1_message).to eq("line1.1-of-a#{TEST_FILE_DELIMITER} line1.2-of-a#{TEST_FILE_DELIMITER} line1.3-of-a")
365
- expect(e2_message).to eq("line1.1-of-z#{TEST_FILE_DELIMITER} line1.2-of-z#{TEST_FILE_DELIMITER} line1.3-of-z")
366
- end
367
- end
368
369
  subject.run(events)
369
370
  # wait for actions to complete
370
- actions.value
371
+ actions.assert_no_errors
372
+ expect(events.size).to eq(2)
373
+ e1, e2 = events
374
+ e1_message = e1.get("message")
375
+ e2_message = e2.get("message")
376
+
377
+ expect(e1.get("path")).to match(/J.txt/)
378
+ expect(e2.get("path")).to match(/K.txt/)
379
+ expect(e1_message).to eq("line1.1-of-J#{TEST_FILE_DELIMITER} line1.2-of-J#{TEST_FILE_DELIMITER} line1.3-of-J")
380
+ expect(e2_message).to eq("line1.1-of-K#{TEST_FILE_DELIMITER} line1.2-of-K#{TEST_FILE_DELIMITER} line1.3-of-K")
371
381
  end
372
382
 
373
383
  context "if auto_flush is enabled on the multiline codec" do
374
384
  let(:mlconf) { { "auto_flush_interval" => 0.5 } }
375
-
385
+ let(:suffix) { "M" }
376
386
  it "an event is generated via auto_flush" do
377
387
  actions = RSpec::Sequencing
378
388
  .run_after(0.1, "create files") do
379
- File.open("#{tmpdir_path}/A.log", "wb") do |fd|
389
+ File.open(tmpfile_path, "wb") do |fd|
380
390
  fd.puts("line1.1-of-a")
381
391
  fd.puts(" line1.2-of-a")
382
392
  fd.puts(" line1.3-of-a")
383
393
  end
384
394
  end
385
- .then_after(0.75, "wait for auto_flush") do
386
- e1 = events.first
387
- e1_message = e1.get("message")
388
- expect(e1["path"]).to match(/a.log/)
389
- expect(e1_message).to eq("line1.1-of-a#{TEST_FILE_DELIMITER} line1.2-of-a#{TEST_FILE_DELIMITER} line1.3-of-a")
395
+ .then("wait for auto_flush") do
396
+ wait(2).for{events.size}.to eq(1), "events size is not 1"
390
397
  end
391
398
  .then("stop") do
392
399
  subject.stop
393
400
  end
394
401
  subject.run(events)
395
402
  # wait for actions to complete
396
- actions.value
403
+ actions.assert_no_errors
404
+ e1 = events.first
405
+ e1_message = e1.get("message")
406
+ expect(e1_message).to eq("line1.1-of-a#{TEST_FILE_DELIMITER} line1.2-of-a#{TEST_FILE_DELIMITER} line1.3-of-a")
407
+ expect(e1.get("path")).to match(/M.txt$/)
397
408
  end
398
409
  end
399
410
  end
400
411
 
401
- context "when #run is called multiple times", :unix => true do
402
- let(:file_path) { "#{tmpdir_path}/a.log" }
403
- let(:buffer) { [] }
404
- let(:run_thread_proc) do
405
- lambda { Thread.new { subject.run(buffer) } }
406
- end
407
- let(:lsof_proc) do
408
- lambda { `lsof -p #{Process.pid} | grep #{file_path}` }
409
- end
410
-
411
- subject { described_class.new(conf) }
412
-
413
- before do
414
- conf.update(
415
- "path" => tmpdir_path + "/*.log",
416
- "start_position" => "beginning",
417
- "stat_interval" => 0.1,
418
- "sincedb_path" => sincedb_path)
419
-
420
- File.open(file_path, "w") do |fd|
421
- fd.puts('foo')
422
- fd.puts('bar')
423
- fd.fsync
424
- end
425
- end
426
-
427
- it "should only actually open files when content changes are detected" do
428
- subject.register
429
- expect(lsof_proc.call).to eq("")
430
- # first run processes the file and records sincedb progress
431
- run_thread_proc.call
432
- wait(1).for{lsof_proc.call.scan(file_path).size}.to eq(1)
433
- # second run quits the first run
434
- # sees the file has not changed size and does not open it
435
- run_thread_proc.call
436
- wait(1).for{lsof_proc.call.scan(file_path).size}.to eq(0)
437
- # truncate and write less than before
438
- File.open(file_path, "w"){ |fd| fd.puts('baz'); fd.fsync }
439
- # sees the file has changed size and does open it
440
- wait(1).for{lsof_proc.call.scan(file_path).size}.to eq(1)
441
- end
442
- end
443
-
444
412
  describe "specifying max_open_files" do
413
+ let(:suffix) { "P" }
414
+ let(:tmpfile_path2) { ::File.join(tmpdir_path, "Q.txt") }
445
415
  subject { described_class.new(conf) }
446
416
  before do
447
- File.open("#{tmpdir_path}/a.log", "w") do |fd|
448
- fd.puts("line1-of-a")
449
- fd.puts("line2-of-a")
417
+ File.open(tmpfile_path, "w") do |fd|
418
+ fd.puts("line1-of-P")
419
+ fd.puts("line2-of-P")
450
420
  fd.fsync
451
421
  end
452
- File.open("#{tmpdir_path}/z.log", "w") do |fd|
453
- fd.puts("line1-of-z")
454
- fd.puts("line2-of-z")
422
+ File.open(tmpfile_path2, "w") do |fd|
423
+ fd.puts("line1-of-Q")
424
+ fd.puts("line2-of-Q")
455
425
  fd.fsync
456
426
  end
457
427
  end
@@ -461,37 +431,32 @@ describe LogStash::Inputs::File do
461
431
  conf.clear
462
432
  conf.update(
463
433
  "type" => "blah",
464
- "path" => "#{tmpdir_path}/*.log",
434
+ "path" => path_path,
465
435
  "sincedb_path" => sincedb_path,
466
436
  "stat_interval" => 0.1,
467
437
  "max_open_files" => 1,
468
438
  "start_position" => "beginning",
439
+ "file_sort_by" => "path",
469
440
  "delimiter" => TEST_FILE_DELIMITER)
470
441
  subject.register
471
442
  end
472
443
  it "collects line events from only one file" do
473
444
  actions = RSpec::Sequencing
474
- .run_after(0.2, "assert one identity is mapped") do
475
- expect(subject.codec.identity_count).to eq(1)
445
+ .run("assert one identity is mapped") do
446
+ wait(0.4).for{subject.codec.identity_count}.to be > 0, "no identity is mapped"
476
447
  end
477
- .then_after(0.1, "stop") do
448
+ .then("stop") do
478
449
  subject.stop
479
450
  end
480
- .then_after(0.1, "stop flushes last event") do
481
- expect(events.size).to eq(2)
482
- e1, e2 = events
483
- if Dir.glob("#{tmpdir_path}/*.log").first =~ %r{a\.log}
484
- #linux and OSX have different retrieval order
485
- expect(e1.get("message")).to eq("line1-of-a")
486
- expect(e2.get("message")).to eq("line2-of-a")
487
- else
488
- expect(e1.get("message")).to eq("line1-of-z")
489
- expect(e2.get("message")).to eq("line2-of-z")
490
- end
451
+ .then("stop flushes last event") do
452
+ wait(0.4).for{events.size}.to eq(2), "events size does not equal 2"
491
453
  end
492
454
  subject.run(events)
493
455
  # wait for actions future value
494
- actions.value
456
+ actions.assert_no_errors
457
+ e1, e2 = events
458
+ expect(e1.get("message")).to eq("line1-of-P")
459
+ expect(e2.get("message")).to eq("line2-of-P")
495
460
  end
496
461
  end
497
462
 
@@ -499,41 +464,36 @@ describe LogStash::Inputs::File do
499
464
  before do
500
465
  conf.update(
501
466
  "type" => "blah",
502
- "path" => "#{tmpdir_path}/*.log",
467
+ "path" => path_path,
503
468
  "sincedb_path" => sincedb_path,
504
469
  "stat_interval" => 0.1,
505
470
  "max_open_files" => 1,
506
471
  "close_older" => 0.5,
507
472
  "start_position" => "beginning",
473
+ "file_sort_by" => "path",
508
474
  "delimiter" => TEST_FILE_DELIMITER)
509
475
  subject.register
510
476
  end
511
477
 
512
478
  it "collects line events from both files" do
513
479
  actions = RSpec::Sequencing
514
- .run_after(0.2, "assert both identities are mapped and the first two events are built") do
515
- expect(subject.codec.identity_count).to eq(2)
516
- expect(events.size).to eq(2)
480
+ .run("assert both identities are mapped and the first two events are built") do
481
+ wait(0.4).for{subject.codec.identity_count == 1 && events.size == 2}.to eq(true), "both identities are not mapped and the first two events are not built"
517
482
  end
518
- .then_after(0.8, "wait for close to flush last event of each identity") do
519
- expect(events.size).to eq(4)
520
- if Dir.glob("#{tmpdir_path}/*.log").first =~ %r{a\.log}
521
- #linux and OSX have different retrieval order
522
- e1, e2, e3, e4 = events
523
- else
524
- e3, e4, e1, e2 = events
525
- end
526
- expect(e1.get("message")).to eq("line1-of-a")
527
- expect(e2.get("message")).to eq("line2-of-a")
528
- expect(e3.get("message")).to eq("line1-of-z")
529
- expect(e4.get("message")).to eq("line2-of-z")
483
+ .then("wait for close to flush last event of each identity") do
484
+ wait(0.8).for{events.size}.to eq(4), "close does not flush last event of each identity"
530
485
  end
531
486
  .then_after(0.1, "stop") do
532
487
  subject.stop
533
488
  end
534
489
  subject.run(events)
535
490
  # wait for actions future value
536
- actions.value
491
+ actions.assert_no_errors
492
+ e1, e2, e3, e4 = events
493
+ expect(e1.get("message")).to eq("line1-of-P")
494
+ expect(e2.get("message")).to eq("line2-of-P")
495
+ expect(e3.get("message")).to eq("line1-of-Q")
496
+ expect(e4.get("message")).to eq("line2-of-Q")
537
497
  end
538
498
  end
539
499
  end