Browse files

Add File::Tail::Group and use File::Tail::Group in rtail cli util

This change also includes some code reorganisation into more files.
  • Loading branch information...
1 parent b35b674 commit 51f466ddfcb5e1c329b137baa7436f0d4cb59a0b @flori committed Jun 22, 2011
View
3 .gitignore
@@ -1,4 +1,5 @@
-.*.sw[pon]
coverage
pkg
Gemfile.lock
+.*
+doc
View
2 CHANGES
@@ -1,3 +1,5 @@
+2011-06-25 * 1.0.6 * Create a gem spec file again.
+ * Added a File::Tail::Group to tail multiple files more easily.
2010-03-25 * 1.0.5 * Added rtail executable, a nice app to supervise logfiles
and logdirs.
* Disabled creation of gem spec file.
View
3 Gemfile
@@ -2,6 +2,9 @@
source :rubygems
+gem 'spruz', '~>0.2'
+
group :development do
gem 'sdoc'
+ gem 'rcov'
end
View
14 README → README.rdoc
@@ -1,3 +1,5 @@
+= File::Tail for Ruby
+
== Description
This is a small ruby library that allows it to "tail" files in Ruby, including
@@ -23,10 +25,6 @@ To install from the source repository, just type into the command line as root:
# rake install
-or
-
-# ruby install.rb
-
== Usage
File::Tail is a module in the File class. A lightweight class interface for
@@ -54,17 +52,15 @@ The forward/backward method returns self, so it's possible to chain
methods together like that:
log.backward(10).tail { |line| puts line }
+A command line utility named rtail, that uses File::Tail is provided as well.
+
== Documentation
To create the documentation of this module, type
$ rake doc
-or
-
-$ ruby make_doc.rb
-
-and the API documentation is generated by your rdoc command.
+and the API documentation is generated.
In the examples direcotry is a small example of tail and
pager program that use this module. You also may want look
View
38 Rakefile
@@ -10,10 +10,10 @@ include Config
PKG_NAME = 'file-tail'
PKG_VERSION = File.read('VERSION').chomp
-PKG_FILES = FileList["**/*"].exclude(/^(pkg|coverage|doc|.bundle|.git)/)
+PKG_FILES = FileList["**/*"].exclude(/^(pkg|coverage|doc|\..*|Gemfile.lock)/)
CLEAN.include 'coverage', 'doc'
-desc "Installing library"
+desc "Install executable/library into site_ruby directories"
task :install do
cd 'lib' do
libdir = CONFIG["sitelibdir"]
@@ -25,24 +25,34 @@ task :install do
dest = File.join(dest, 'tail')
mkdir_p(dest)
- file = File.join('file', 'tail', 'version.rb')
- install(file, dest, :verbose => true)
+ for file in Dir[File.join('file', 'tail', '*.rb')]
+ install(file, dest, :verbose => true)
+ end
end
+ bindir = CONFIG["bindir"]
+ install('bin/rtail', bindir, :verbose => true, :mode => 0755)
end
-desc "Creating documentation"
+desc "Create documentation"
task :doc do
- sh "sdoc -m README -t 'File::Tail - Tailing files in Ruby' README #{Dir['lib/**/*.rb'] * ' '}"
+ sh "sdoc -m README.rdoc -t 'File::Tail - Tailing files in Ruby' README.rdoc #{Dir['lib/**/*.rb'] * ' '}"
end
desc "Testing library"
task :test do
- ruby %{-Ilib tests/test_file-tail.rb}
+ ruby %{-Ilib tests/test_file-tail*.rb}
end
desc "Testing library with rcov"
task :coverage do
- system %{rcov -x '\\btests\/' -Ilib tests/test_file-tail.rb}
+ sh %{rcov -x '\\b/gems\/' -x '\\btests\/' -Ilib tests/test_file-tail*.rb}
+end
+
+namespace :gems do
+ desc "Install all gems from the Gemfile"
+ task :install do
+ sh 'bundle install'
+ end
end
if defined? Gem
@@ -57,11 +67,11 @@ if defined? Gem
s.require_path = 'lib'
- s.add_dependency 'spruz', '>=0.1.0'
+ s.add_dependency 'spruz', '~>0.2'
- s.rdoc_options << '--main' << 'README' << '--title' << 'File::Tail - Tailing files in Ruby'
- s.extra_rdoc_files << 'README'
- s.test_files << 'tests/test_file-tail.rb'
+ s.rdoc_options << '--main' << 'README.rdoc' << '--title' << 'File::Tail - Tailing files in Ruby'
+ s.extra_rdoc_files << 'README.rdoc'
+ s.test_files.concat Dir['tests/test_*.rb']
s.author = "Florian Frank"
s.email = "flori@ping.de"
@@ -101,6 +111,8 @@ EOT
end
end
+desc "Run the tests by default"
task :default => [ :version, :test ]
-task :release => [ :clean, :version, :package ]
+desc "Prepare release of the library"
+task :release => [ :clean, :gemspec, :package ]
View
54 bin/rtail
@@ -6,60 +6,58 @@ include Spruz::GO
require 'thread'
Thread.abort_on_exception = true
-$opt = go 'm:h'
+$opt = go 'n:m:Mh'
if $opt['h']
puts <<EOT
Usage: #{File.basename($0)} [OPTS] PATHES
OPTS are
+ -n NUMBER show the last NUMBER of lines in the tailed files
-m PATTERN only tail files matching PATTERN, e. g. '*.log'
+ -M prefix every line with the logfile name
-h to display this help
EOT
+ exit
end
dirs, logfiles = ARGV.partition { |path| File.directory?(path) }
-$log_threads = {}
-$log_mutex = Mutex.new
-def add_log(logfile)
- logfile = File.expand_path logfile
- $log_threads.key?(logfile) and return
- warn "Tailing '#{logfile}'."
- $log_threads[logfile] = Thread.new do
- File.open(logfile) do |l|
- l.sync = true
- l.extend File::Tail
- l.backward
- l.tail do |line|
- $log_mutex.synchronize do
- print line
- end
- end
- end
+$n = ($opt['n'] || 0).to_i
+$logfiles = File::Tail::Group.new
+
+def add_logfiles(logfiles)
+ logfiles = logfiles.map { |l| File.expand_path(l) }
+ $opt['m'] and logfiles =
+ logfiles.select { |l| !$opt['m'] || File.fnmatch?($opt['m'], File.basename(l)) }
+ for l in logfiles
+ $logfiles.each_file.any? { |f| l == f.path } and next
+ warn "Tailing '#{l}'."
+ $logfiles.add_filename l, $n
end
end
-def add_logs(logfiles)
- for l in logfiles
- if $opt['m']
- File.fnmatch?($opt['m'], l) and add_log l
+add_logfiles logfiles
+
+t = Thread.new do
+ $logfiles.tail do |line|
+ if $opt['M']
+ puts "#{line.file.path}: #{line}"
else
- add_log l
+ puts line
end
end
end
-add_logs(logfiles)
-
begin
loop do
+ logfiles = []
for d in dirs
- logfiles = Dir[File.join(d, '*')].select do |x|
+ logfiles.concat Dir[File.join(d, '*')].select { |x|
File.file?(x) || File.symlink?(x)
- end
- add_logs logfiles
+ }
end
+ add_logfiles logfiles
sleep 1
end
rescue Interrupt
View
16 file-tail.gemspec
@@ -6,29 +6,29 @@ Gem::Specification.new do |s|
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
s.authors = [%q{Florian Frank}]
- s.date = %q{2011-06-21}
+ s.date = %q{2011-06-25}
s.description = %q{Library to tail files in Ruby}
s.email = %q{flori@ping.de}
s.executables = [%q{rtail}]
- s.extra_rdoc_files = [%q{README}]
- s.files = [%q{tests}, %q{tests/test_file-tail.rb}, %q{examples}, %q{examples/tail.rb}, %q{examples/pager.rb}, %q{COPYING}, %q{file-tail.gemspec}, %q{Rakefile}, %q{make_doc.rb}, %q{lib}, %q{lib/file}, %q{lib/file/tail.rb}, %q{lib/file/tail}, %q{lib/file/tail/version.rb}, %q{CHANGES}, %q{bin}, %q{bin/rtail}, %q{README}, %q{VERSION}, %q{install.rb}]
+ s.extra_rdoc_files = [%q{README.rdoc}]
+ s.files = [%q{tests}, %q{tests/test_file-tail_group.rb}, %q{tests/test_file-tail.rb}, %q{examples}, %q{examples/tail.rb}, %q{examples/pager.rb}, %q{COPYING}, %q{file-tail.gemspec}, %q{Rakefile}, %q{lib}, %q{lib/file}, %q{lib/file/tail.rb}, %q{lib/file/tail}, %q{lib/file/tail/version.rb}, %q{lib/file/tail/line_extension.rb}, %q{lib/file/tail/tailer.rb}, %q{lib/file/tail/group.rb}, %q{lib/file/tail/logfile.rb}, %q{lib/file-tail.rb}, %q{Gemfile}, %q{README.rdoc}, %q{CHANGES}, %q{bin}, %q{bin/rtail}, %q{VERSION}]
s.homepage = %q{http://flori.github.com/file-tail}
- s.rdoc_options = [%q{--main}, %q{README}, %q{--title}, %q{File::Tail - Tailing files in Ruby}]
+ s.rdoc_options = [%q{--main}, %q{README.rdoc}, %q{--title}, %q{File::Tail - Tailing files in Ruby}]
s.require_paths = [%q{lib}]
s.rubyforge_project = %q{file-tail}
s.rubygems_version = %q{1.8.5}
s.summary = %q{File::Tail for Ruby}
- s.test_files = [%q{tests/test_file-tail.rb}]
+ s.test_files = [%q{tests/test_file-tail_group.rb}, %q{tests/test_file-tail.rb}]
if s.respond_to? :specification_version then
s.specification_version = 3
if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
- s.add_runtime_dependency(%q<spruz>, [">= 0.1.0"])
+ s.add_runtime_dependency(%q<spruz>, ["~> 0.2"])
else
- s.add_dependency(%q<spruz>, [">= 0.1.0"])
+ s.add_dependency(%q<spruz>, ["~> 0.2"])
end
else
- s.add_dependency(%q<spruz>, [">= 0.1.0"])
+ s.add_dependency(%q<spruz>, ["~> 0.2"])
end
end
View
1 lib/file-tail.rb
@@ -0,0 +1 @@
+require 'file/tail'
View
126 lib/file/tail.rb
@@ -1,89 +1,12 @@
-require 'file/tail/version'
-
class File
+ # This module can be included in your own File subclasses or used to extend
+ # files you want to tail.
module Tail
- # This is an easy to use Logfile class that includes
- # the File::Tail module.
- #
- # === Usage
- # The unix command "tail -10f filename" can be emulated like that:
- # File::Tail::Logfile.open(filename, :backward => 10) do |log|
- # log.tail { |line| puts line }
- # end
- #
- # Or a bit shorter:
- # File::Tail::Logfile.tail(filename, :backward => 10) do |line|
- # puts line
- # end
- #
- # To skip the first 10 lines of the file do that:
- # File::Tail::Logfile.open(filename, :forward => 10) do |log|
- # log.tail { |line| puts line }
- # end
- #
- # The unix command "head -10 filename" can be emulated like that:
- # File::Tail::Logfile.open(filename, :return_if_eof => true) do |log|
- # log.tail(10) { |line| puts line }
- # end
- class Logfile < File
- include File::Tail
-
- # This method creates an File::Tail::Logfile object and
- # yields to it, and closes it, if a block is given, otherwise it just
- # returns it. The opts hash takes an option like
- # * <code>:backward => 10</code> to go backwards
- # * <code>:forward => 10</code> to go forwards
- # in the logfile for 10 lines at the start. The buffersize
- # for going backwards can be set with the
- # * <code>:bufsiz => 8192</code> option.
- # To define a callback, that will be called after a reopening occurs, use:
- # * <code>:after_reopen => lambda { |file| p file }</code>
- #
- # Every attribute of File::Tail can be set with a <code>:attributename =>
- # value</code> option.
- def self.open(filename, opts = {}, &block) # :yields: file
- file = new filename
- opts.each do |o, v|
- writer = o.to_s + "="
- file.__send__(writer, v) if file.respond_to? writer
- end
- if opts.key?(:wind) or opts.key?(:rewind)
- warn ":wind and :rewind options are deprecated, "\
- "use :forward and :backward instead!"
- end
- if backward = opts[:backward] || opts[:rewind]
- (args = []) << backward
- args << opt[:bufsiz] if opts[:bufsiz]
- file.backward(*args)
- elsif forward = opts[:forward] || opts[:wind]
- file.forward(forward)
- end
- if opts[:after_reopen]
- file.after_reopen(&opts[:after_reopen])
- end
- if block_given?
- begin
- block.call file
- ensure
- file.close
- nil
- end
- else
- file
- end
- end
-
- # Like open, but yields to every new line encountered in the logfile in
- # +block+.
- def self.tail(filename, opts = {}, &block)
- if ([ :forward, :backward ] & opts.keys).empty?
- opts[:backward] = 0
- end
- open(filename, opts) do |log|
- log.tail { |line| block.call line }
- end
- end
- end
+ require 'file/tail/version'
+ require 'file/tail/logfile'
+ require 'file/tail/group'
+ require 'file/tail/tailer'
+ require 'file/tail/line_extension'
# This is the base class of all exceptions that are raised
# in File::Tail.
@@ -166,6 +89,11 @@ def after_reopen(&block)
# just returns if the end of the file is reached.
attr_accessor :return_if_eof
+ # Default buffer size, that is used while going backward from a file's end.
+ # This defaults to nil, which means that File::Tail attempts to derive this
+ # value from the filesystem block size.
+ attr_accessor :default_bufsize
+
# Skip the first <code>n</code> lines of this file. The default is to don't
# skip any lines at all and start at the beginning of this file.
def forward(n = 0)
@@ -181,27 +109,27 @@ def forward(n = 0)
# from the end. The default is to start tailing directly from the
# end of the file.
#
- # The additional argument <code>bufsiz</code> is
+ # The additional argument <code>bufsize</code> is
# used to determine the buffer size that is used to step through
# the file backwards. It defaults to the block size of the
# filesystem this file belongs to or 8192 bytes if this cannot
# be determined.
- def backward(n = 0, bufsiz = nil)
+ def backward(n = 0, bufsize = nil)
if n <= 0
seek(0, File::SEEK_END)
return self
end
- bufsiz ||= stat.blksize || 8192
+ bufsize ||= default_bufsize || stat.blksize || 8192
size = stat.size
begin
- if bufsiz < size
+ if bufsize < size
seek(0, File::SEEK_END)
while n > 0 and tell > 0 do
start = tell
- seek(-bufsiz, File::SEEK_CUR)
- buffer = read(bufsiz)
+ seek(-bufsize, File::SEEK_CUR)
+ buffer = read(bufsize)
n -= buffer.count("\n")
- seek(-bufsiz, File::SEEK_CUR)
+ seek(-bufsize, File::SEEK_CUR)
end
else
seek(0, File::SEEK_SET)
@@ -267,17 +195,17 @@ def read_line(&block)
if @n
until @n == 0
block.call readline
- @lines += 1
+ @lines += 1
@no_read = 0
- @n -= 1
- debug
+ @n -= 1
+ output_debug_information
end
raise ReturnException
else
block.call readline
- @lines += 1
+ @lines += 1
@no_read = 0
- debug
+ output_debug_information
end
rescue EOFError
seek(0, File::SEEK_CUR)
@@ -333,7 +261,7 @@ def sleep_interval
# max. wait @max_interval
@interval = @max_interval
end
- debug
+ output_debug_information
sleep @interval
@no_read += @interval
end
@@ -354,14 +282,16 @@ def reopen_file(mode)
end
end
- def debug
+ def output_debug_information
$DEBUG or return
STDERR.puts({
+ :path => path,
:lines => @lines,
:interval => @interval,
:no_read => @no_read,
:n => @n,
}.inspect)
+ self
end
end
end
View
125 lib/file/tail/group.rb
@@ -0,0 +1,125 @@
+require 'thread'
+
+class File
+ module Tail
+ # This class can be used to coordinate tailing of many files, which have
+ # been added to the group.
+ class Group
+ # Creates a new File::Tail::Group instance.
+ #
+ # The following options can be given as arguments:
+ # :files:: an array of files (or filenames to open) that are placed into
+ # the group.
+ def initialize(opts = {})
+ @tailers = ThreadGroup.new
+ if files = opts[:files]
+ Array(files).each { |file| add file }
+ end
+ end
+
+ # Creates a group for +files+ (IO instances or filename strings).
+ def self.[](*files)
+ new(:files => files)
+ end
+
+ # Add a file (IO instance) or filename (responding to to_str) to this
+ # group.
+ def add(file_or_filename)
+ if file_or_filename.respond_to?(:to_io)
+ add_file file_or_filename.to_io
+ elsif file_or_filename.respond_to?(:to_str)
+ add_filename file_or_filename
+ end
+ end
+
+ alias << add
+
+ # Add the IO instance +file+ to this group.
+ def add_file(file)
+ setup_file_tailer file
+ self
+ end
+
+ # Add a file created by opening +filename+ to this group after stepping
+ # +n+ lines backwards from the end of it.
+ def add_filename(filename, n = 0)
+ file = Logfile.open(filename.to_str, :backward => n)
+ file.backward n
+ setup_file_tailer file
+ self
+ end
+
+ # Iterate over all files contained in this group yielding to +block+ for
+ # each of them.
+ def each_file(&block)
+ each_tailer { |t| t.file }.map(&block)
+ end
+
+ # Iterate over all tailers in this group yielding to +block+ for each of
+ # them.
+ def each_tailer(&block)
+ @tailers.list.map { |t| t }.map(&block)
+ end
+
+ # Stop all tailers in this group at once.
+ def stop
+ each_tailer { |t| t.stop }
+ each_tailer { |t| t.join }
+ self
+ end
+
+ # Tail all the lines of all the files in the Tail::Group instance, that
+ # is yield to each of them.
+ #
+ # Every line is extended with the LineExtension module, that adds some
+ # methods to the line string. To get the path of the file this line was
+ # received from call line.file.path.
+ def tail
+ wait_for_activity do |tailer|
+ tailer.pending_lines.each do |line|
+ line.extend LineExtension
+ line.instance_variable_set :@tailer, tailer
+ yield line
+ end
+ end
+ end
+
+ private
+
+ def setup_file_tailer(file)
+ file.extend File::Tail
+ setup = ConditionVariable.new
+ mutex = Mutex.new
+ ft = nil
+ mutex.synchronize do
+ ft = Tailer.new do
+ t = Thread.current
+ t[:queue] = Queue.new
+ t[:file] = file
+ mutex.synchronize do
+ setup.signal
+ end
+ file.tail { |line| t[:queue] << line }
+ end
+ setup.wait mutex
+ end
+ @tailers.add ft
+ nil
+ end
+
+ # Wait until new input is receіved on any of the tailers in the group. If
+ # so call +block+ with all of these trailers as an argument.
+ def wait_for_activity(&block)
+ loop do
+ pending = each_tailer.select(&:pending_lines?)
+ if pending.empty?
+ interval = each_file.map { |t| t.interval }.compact.min || 0.1
+ sleep interval
+ else
+ pending.each(&block)
+ end
+ end
+ end
+ end
+ end
+end
View
15 lib/file/tail/line_extension.rb
@@ -0,0 +1,15 @@
+class File
+ module Tail
+ # This module is used to extend all lines received via one of the tailers
+ # of a File::Tail::Group.
+ module LineExtension
+ # The file as a File instance this line was read from.
+ def file
+ tailer.file
+ end
+
+ # This is the tailer this line was received from.
+ attr_reader :tailer
+ end
+ end
+end
View
86 lib/file/tail/logfile.rb
@@ -0,0 +1,86 @@
+class File
+ module Tail
+ # This is an easy to use Logfile class that includes
+ # the File::Tail module.
+ #
+ # === Usage
+ # The unix command "tail -10f filename" can be emulated like that:
+ # File::Tail::Logfile.open(filename, :backward => 10) do |log|
+ # log.tail { |line| puts line }
+ # end
+ #
+ # Or a bit shorter:
+ # File::Tail::Logfile.tail(filename, :backward => 10) do |line|
+ # puts line
+ # end
+ #
+ # To skip the first 10 lines of the file do that:
+ # File::Tail::Logfile.open(filename, :forward => 10) do |log|
+ # log.tail { |line| puts line }
+ # end
+ #
+ # The unix command "head -10 filename" can be emulated like that:
+ # File::Tail::Logfile.open(filename, :return_if_eof => true) do |log|
+ # log.tail(10) { |line| puts line }
+ # end
+ class Logfile < File
+ include File::Tail
+
+ # This method creates an File::Tail::Logfile object and
+ # yields to it, and closes it, if a block is given, otherwise it just
+ # returns it. The opts hash takes an option like
+ # * <code>:backward => 10</code> to go backwards
+ # * <code>:forward => 10</code> to go forwards
+ # in the logfile for 10 lines at the start. The buffersize
+ # for going backwards can be set with the
+ # * <code>:bufsiz => 8192</code> option.
+ # To define a callback, that will be called after a reopening occurs, use:
+ # * <code>:after_reopen => lambda { |file| p file }</code>
+ #
+ # Every attribute of File::Tail can be set with a <code>:attributename =>
+ # value</code> option.
+ def self.open(filename, opts = {}, &block) # :yields: file
+ file = new filename
+ opts.each do |o, v|
+ writer = o.to_s + "="
+ file.__send__(writer, v) if file.respond_to? writer
+ end
+ if opts.key?(:wind) or opts.key?(:rewind)
+ warn ":wind and :rewind options are deprecated, "\
+ "use :forward and :backward instead!"
+ end
+ if backward = opts[:backward] || opts[:rewind]
+ (args = []) << backward
+ args << opt[:bufsiz] if opts[:bufsiz]
+ file.backward(*args)
+ elsif forward = opts[:forward] || opts[:wind]
+ file.forward(forward)
+ end
+ if opts[:after_reopen]
+ file.after_reopen(&opts[:after_reopen])
+ end
+ if block_given?
+ begin
+ block.call file
+ ensure
+ file.close
+ nil
+ end
+ else
+ file
+ end
+ end
+
+ # Like open, but yields to every new line encountered in the logfile in
+ # +block+.
+ def self.tail(filename, opts = {}, &block)
+ if ([ :forward, :backward ] & opts.keys).empty?
+ opts[:backward] = 0
+ end
+ open(filename, opts) do |log|
+ log.tail { |line| block.call line }
+ end
+ end
+ end
+ end
+end
View
29 lib/file/tail/tailer.rb
@@ -0,0 +1,29 @@
+class File
+ module Tail
+ # This class supervises activity on a tailed fail and collects newly read
+ # lines until the Tail::Group fetches and processes them.
+ class Tailer < ::Thread
+
+ # True if there are any lines pending on this Tailer, false
+ # otherwise.
+ def pending_lines?
+ !queue.empty?
+ end
+
+ # Fetch all the pending lines from this Tailer and thereby remove them from the Tailer's queue.
+ def pending_lines
+ Array.new(queue.size) { queue.deq(true) }
+ end
+
+ alias stop exit # Stop tailing this file and remove it from its File::Tail::Group.
+
+ def method_missing(id, *args, &block)
+ if args.empty? && !(value = self[id]).nil?
+ value
+ else
+ super
+ end
+ end
+ end
+ end
+end
View
26 tests/test_file-tail.rb
@@ -8,12 +8,11 @@
require 'test/unit'
require 'file/tail'
-require 'tempfile'
require 'timeout'
require 'thread'
Thread.abort_on_exception = true
-class TC_FileTail < Test::Unit::TestCase
+class TestFileTail < Test::Unit::TestCase
include File::Tail
def setup
@@ -46,6 +45,25 @@ def test_backward
assert_equal(100, count(@in))
end
+ def test_backward_small_buffer
+ [ 0, 1, 2, 10, 100 ].each do |lines|
+ @in.backward(lines, 100)
+ assert_equal(lines, count(@in))
+ end
+ @in.backward(101, 100)
+ assert_equal(100, count(@in))
+ end
+
+ def test_backward_small_buffer2
+ @in.default_bufsize = 100
+ [ 0, 1, 2, 10, 100 ].each do |lines|
+ @in.backward(lines)
+ assert_equal(lines, count(@in))
+ end
+ @in.backward(101)
+ assert_equal(100, count(@in))
+ end
+
def test_tail_with_block_without_n
timeout(10) do
lines = []
@@ -294,8 +312,8 @@ def count(file)
return n
end
- def append(file, n)
- (1..n).each { |x| file << "#{x} #{"A" * 70}\n" }
+ def append(file, n, size = 70)
+ (1..n).each { |x| file << "#{x} #{"A" * size}\n" }
file.flush
end
end
View
91 tests/test_file-tail_group.rb
@@ -0,0 +1,91 @@
+#!/usr/bin/env ruby
+
+base = File.basename(Dir.pwd)
+if base == 'tests' || base =~ /file-tail/
+ Dir.chdir('..') if base == 'tests'
+ $LOAD_PATH.unshift(File.join(Dir.pwd, 'lib'))
+end
+
+require 'test/unit'
+require 'file/tail'
+require 'timeout'
+require 'thread'
+require 'tempfile'
+Thread.abort_on_exception = true
+
+class TestFileTailGroup < Test::Unit::TestCase
+ include File::Tail
+
+ def test_create_group
+ t, = make_file
+ g = Group[t]
+ assert_equal t.path, g.each_tailer.first.file.path
+ assert_equal t.path, g.each_file.first.path
+ end
+
+ def test_stop_group
+ t, = make_file
+ g = Group[t]
+ assert_equal t.path, g.each_tailer.first.file.path
+ assert_equal t.path, g.each_file.first.path
+ g.stop
+ assert_nil g.each_file.first
+ end
+
+ def test_add_file_to_group
+ g = Group.new
+ t, = make_file
+ g.add_file t
+ assert_equal t.path, g.each_tailer.first.file.path
+ assert_equal t.path, g.each_file.first.path
+ end
+
+ def test_add_filename_to_group
+ g = Group.new
+ t, name = make_file
+ t.close
+ g.add_filename name
+ assert_equal name, g.each_tailer.first.file.path
+ assert_equal t.path, g.each_file.first.path
+ end
+
+ def test_add_generic_to_group
+ g = Group.new
+ t1, n1 = make_file
+ t1.close
+ t2, n1 = make_file
+ g << n1
+ g << t2
+ assert g.each_tailer.any? { |t| t.file.path == n1 }
+ assert g.each_tailer.any? { |t| t.file.path == t2.path }
+ assert g.each_file.any? { |t| t.path == n1 }
+ assert g.each_file.any? { |t| t.path == t2.path }
+ end
+
+ def test_tail_multiple_files
+ t1, = make_file
+ t1.max_interval = 0.1
+ t2, = make_file
+ t2.max_interval = 0.1
+ g = Group[t1, t2]
+ q = Queue.new
+ t = Thread.new do
+ g.tail { |l| q << l }
+ end
+ t1.puts "foo"
+ assert_equal "foo\n", q.pop
+ t2.puts "bar"
+ assert_equal "bar\n", q.pop
+ ensure
+ t and t.exit
+ end
+
+ private
+
+ def make_file
+ name = File.expand_path(File.join(Dir.tmpdir, "tmp.#$$"))
+ file = File.open(name, 'w+')
+ file.extend File::Tail
+ return file, name
+ end
+end

0 comments on commit 51f466d

Please sign in to comment.