1# coding: US-ASCII
2# frozen_string_literal: false
3require 'test/unit'
4require 'logger'
5require 'tempfile'
6require 'tmpdir'
7
8class TestLogDevice < Test::Unit::TestCase
9  class LogExcnRaiser
10    def write(*arg)
11      raise 'disk is full'
12    end
13
14    def close
15    end
16
17    def stat
18      Object.new
19    end
20  end
21
22  def setup
23    @tempfile = Tempfile.new("logger")
24    @tempfile.close
25    @filename = @tempfile.path
26    File.unlink(@filename)
27  end
28
29  def teardown
30    @tempfile.close(true)
31  end
32
33  def d(log, opt = {})
34    Logger::LogDevice.new(log, opt)
35  end
36
37  def test_initialize
38    logdev = d(STDERR)
39    assert_equal(STDERR, logdev.dev)
40    assert_nil(logdev.filename)
41    assert_raise(TypeError) do
42      d(nil)
43    end
44    #
45    logdev = d(@filename)
46    begin
47      assert_file.exist?(@filename)
48      assert_predicate(logdev.dev, :sync)
49      assert_equal(@filename, logdev.filename)
50      logdev.write('hello')
51    ensure
52      logdev.close
53    end
54    # create logfile whitch is already exist.
55    logdev = d(@filename)
56    begin
57      logdev.write('world')
58      logfile = File.read(@filename)
59      assert_equal(2, logfile.split(/\n/).size)
60      assert_match(/^helloworld$/, logfile)
61    ensure
62      logdev.close
63    end
64  end
65
66  def test_write
67    r, w = IO.pipe
68    logdev = d(w)
69    logdev.write("msg2\n\n")
70    IO.select([r], nil, nil, 0.1)
71    w.close
72    msg = r.read
73    r.close
74    assert_equal("msg2\n\n", msg)
75    #
76    logdev = d(LogExcnRaiser.new)
77    class << (stderr = '')
78      alias write concat
79    end
80    $stderr, stderr = stderr, $stderr
81    begin
82      assert_nothing_raised do
83        logdev.write('hello')
84      end
85    ensure
86      logdev.close
87      $stderr, stderr = stderr, $stderr
88    end
89    assert_equal "log writing failed. disk is full\n", stderr
90  end
91
92  def test_close
93    r, w = IO.pipe
94    logdev = d(w)
95    logdev.write("msg2\n\n")
96    IO.select([r], nil, nil, 0.1)
97    assert_not_predicate(w, :closed?)
98    logdev.close
99    assert_predicate(w, :closed?)
100    r.close
101  end
102
103  def test_reopen_io
104    logdev  = d(STDERR)
105    old_dev = logdev.dev
106    logdev.reopen
107    assert_equal(STDERR, logdev.dev)
108    assert_not_predicate(old_dev, :closed?)
109  end
110
111  def test_reopen_io_by_io
112    logdev  = d(STDERR)
113    old_dev = logdev.dev
114    logdev.reopen(STDOUT)
115    assert_equal(STDOUT, logdev.dev)
116    assert_not_predicate(old_dev, :closed?)
117  end
118
119  def test_reopen_io_by_file
120    logdev  = d(STDERR)
121    old_dev = logdev.dev
122    logdev.reopen(@filename)
123    begin
124      assert_file.exist?(@filename)
125      assert_equal(@filename, logdev.filename)
126      assert_not_predicate(old_dev, :closed?)
127    ensure
128      logdev.close
129    end
130  end
131
132  def test_reopen_file
133    logdev = d(@filename)
134    old_dev = logdev.dev
135
136    logdev.reopen
137    begin
138      assert_file.exist?(@filename)
139      assert_equal(@filename, logdev.filename)
140      assert_predicate(old_dev, :closed?)
141    ensure
142      logdev.close
143    end
144  end
145
146  def test_reopen_file_by_io
147    logdev = d(@filename)
148    old_dev = logdev.dev
149    logdev.reopen(STDOUT)
150    assert_equal(STDOUT, logdev.dev)
151    assert_nil(logdev.filename)
152    assert_predicate(old_dev, :closed?)
153  end
154
155  def test_reopen_file_by_file
156    logdev = d(@filename)
157    old_dev = logdev.dev
158
159    tempfile2 = Tempfile.new("logger")
160    tempfile2.close
161    filename2 = tempfile2.path
162    File.unlink(filename2)
163
164    logdev.reopen(filename2)
165    begin
166      assert_file.exist?(filename2)
167      assert_equal(filename2, logdev.filename)
168      assert_predicate(old_dev, :closed?)
169    ensure
170      logdev.close
171      tempfile2.close(true)
172    end
173  end
174
175  def test_shifting_size
176    tmpfile = Tempfile.new([File.basename(__FILE__, '.*'), '_1.log'])
177    logfile = tmpfile.path
178    logfile0 = logfile + '.0'
179    logfile1 = logfile + '.1'
180    logfile2 = logfile + '.2'
181    logfile3 = logfile + '.3'
182    tmpfile.close(true)
183    File.unlink(logfile) if File.exist?(logfile)
184    File.unlink(logfile0) if File.exist?(logfile0)
185    File.unlink(logfile1) if File.exist?(logfile1)
186    File.unlink(logfile2) if File.exist?(logfile2)
187    logger = Logger.new(logfile, 4, 100)
188    logger.error("0" * 15)
189    assert_file.exist?(logfile)
190    assert_file.not_exist?(logfile0)
191    logger.error("0" * 15)
192    assert_file.exist?(logfile0)
193    assert_file.not_exist?(logfile1)
194    logger.error("0" * 15)
195    assert_file.exist?(logfile1)
196    assert_file.not_exist?(logfile2)
197    logger.error("0" * 15)
198    assert_file.exist?(logfile2)
199    assert_file.not_exist?(logfile3)
200    logger.error("0" * 15)
201    assert_file.not_exist?(logfile3)
202    logger.error("0" * 15)
203    assert_file.not_exist?(logfile3)
204    logger.close
205    File.unlink(logfile)
206    File.unlink(logfile0)
207    File.unlink(logfile1)
208    File.unlink(logfile2)
209
210    tmpfile = Tempfile.new([File.basename(__FILE__, '.*'), '_2.log'])
211    logfile = tmpfile.path
212    logfile0 = logfile + '.0'
213    logfile1 = logfile + '.1'
214    logfile2 = logfile + '.2'
215    logfile3 = logfile + '.3'
216    tmpfile.close(true)
217    logger = Logger.new(logfile, 4, 150)
218    logger.error("0" * 15)
219    assert_file.exist?(logfile)
220    assert_file.not_exist?(logfile0)
221    logger.error("0" * 15)
222    assert_file.not_exist?(logfile0)
223    logger.error("0" * 15)
224    assert_file.exist?(logfile0)
225    assert_file.not_exist?(logfile1)
226    logger.error("0" * 15)
227    assert_file.not_exist?(logfile1)
228    logger.error("0" * 15)
229    assert_file.exist?(logfile1)
230    assert_file.not_exist?(logfile2)
231    logger.error("0" * 15)
232    assert_file.not_exist?(logfile2)
233    logger.error("0" * 15)
234    assert_file.exist?(logfile2)
235    assert_file.not_exist?(logfile3)
236    logger.error("0" * 15)
237    assert_file.not_exist?(logfile3)
238    logger.error("0" * 15)
239    assert_file.not_exist?(logfile3)
240    logger.error("0" * 15)
241    assert_file.not_exist?(logfile3)
242    logger.close
243    File.unlink(logfile)
244    File.unlink(logfile0)
245    File.unlink(logfile1)
246    File.unlink(logfile2)
247  end
248
249  def test_shifting_age_variants
250    logger = Logger.new(@filename, 'daily')
251    logger.info('daily')
252    logger.close
253    logger = Logger.new(@filename, 'weekly')
254    logger.info('weekly')
255    logger.close
256    logger = Logger.new(@filename, 'monthly')
257    logger.info('monthly')
258    logger.close
259  end
260
261  def test_shifting_age
262    # shift_age other than 'daily', 'weekly', and 'monthly' means 'everytime'
263    yyyymmdd = Time.now.strftime("%Y%m%d")
264    filename1 = @filename + ".#{yyyymmdd}"
265    filename2 = @filename + ".#{yyyymmdd}.1"
266    filename3 = @filename + ".#{yyyymmdd}.2"
267    begin
268      logger = Logger.new(@filename, 'now')
269      assert_file.exist?(@filename)
270      assert_file.not_exist?(filename1)
271      assert_file.not_exist?(filename2)
272      assert_file.not_exist?(filename3)
273      logger.info("0" * 15)
274      assert_file.exist?(@filename)
275      assert_file.exist?(filename1)
276      assert_file.not_exist?(filename2)
277      assert_file.not_exist?(filename3)
278      logger.warn("0" * 15)
279      assert_file.exist?(@filename)
280      assert_file.exist?(filename1)
281      assert_file.exist?(filename2)
282      assert_file.not_exist?(filename3)
283      logger.error("0" * 15)
284      assert_file.exist?(@filename)
285      assert_file.exist?(filename1)
286      assert_file.exist?(filename2)
287      assert_file.exist?(filename3)
288    ensure
289      logger.close if logger
290      [filename1, filename2, filename3].each do |filename|
291        File.unlink(filename) if File.exist?(filename)
292      end
293    end
294  end
295
296  def test_shifting_period_suffix
297    # shift_age other than 'daily', 'weekly', and 'monthly' means 'everytime'
298    ['%Y%m%d', '%Y-%m-%d', '%Y'].each do |format|
299      if format == '%Y%m%d' # default
300        logger = Logger.new(@filename, 'now', 1048576)
301      else # config
302        logger = Logger.new(@filename, 'now', 1048576, shift_period_suffix: format)
303      end
304      begin
305        yyyymmdd = Time.now.strftime(format)
306        filename1 = @filename + ".#{yyyymmdd}"
307        filename2 = @filename + ".#{yyyymmdd}.1"
308        filename3 = @filename + ".#{yyyymmdd}.2"
309        logger.info("0" * 15)
310        logger.info("0" * 15)
311        logger.info("0" * 15)
312        assert_file.exist?(@filename)
313        assert_file.exist?(filename1)
314        assert_file.exist?(filename2)
315        assert_file.exist?(filename3)
316      ensure
317        logger.close if logger
318        [filename1, filename2, filename3].each do |filename|
319          File.unlink(filename) if File.exist?(filename)
320        end
321      end
322    end
323  end
324
325  def test_shifting_size_in_multiprocess
326    tmpfile = Tempfile.new([File.basename(__FILE__, '.*'), '_1.log'])
327    logfile = tmpfile.path
328    logfile0 = logfile + '.0'
329    logfile1 = logfile + '.1'
330    logfile2 = logfile + '.2'
331    tmpfile.close(true)
332    File.unlink(logfile) if File.exist?(logfile)
333    File.unlink(logfile0) if File.exist?(logfile0)
334    File.unlink(logfile1) if File.exist?(logfile1)
335    File.unlink(logfile2) if File.exist?(logfile2)
336    begin
337      stderr = run_children(2, [logfile], "#{<<-"begin;"}\n#{<<-'end;'}")
338      begin;
339        logger = Logger.new(ARGV[0], 4, 10)
340        10.times do
341          logger.info '0' * 15
342        end
343      end;
344      assert_no_match(/log shifting failed/, stderr)
345      assert_no_match(/log writing failed/, stderr)
346      assert_no_match(/log rotation inter-process lock failed/, stderr)
347    ensure
348      File.unlink(logfile) if File.exist?(logfile)
349      File.unlink(logfile0) if File.exist?(logfile0)
350      File.unlink(logfile1) if File.exist?(logfile1)
351      File.unlink(logfile2) if File.exist?(logfile2)
352    end
353  end
354
355  def test_shifting_age_in_multiprocess
356    yyyymmdd = Time.now.strftime("%Y%m%d")
357    begin
358      stderr = run_children(2, [@filename], "#{<<-"begin;"}\n#{<<-'end;'}")
359      begin;
360        logger = Logger.new(ARGV[0], 'now')
361        10.times do
362          logger.info '0' * 15
363        end
364      end;
365      assert_no_match(/log shifting failed/, stderr)
366      assert_no_match(/log writing failed/, stderr)
367      assert_no_match(/log rotation inter-process lock failed/, stderr)
368    ensure
369      Dir.glob("#{@filename}.#{yyyymmdd}{,.[1-9]*}") do |filename|
370        File.unlink(filename) if File.exist?(filename)
371      end
372    end
373  end
374
375  def test_open_logfile_in_multiprocess
376    tmpfile = Tempfile.new([File.basename(__FILE__, '.*'), '_1.log'])
377    logfile = tmpfile.path
378    tmpfile.close(true)
379    begin
380      20.times do
381        run_children(2, [logfile], "#{<<-"begin;"}\n#{<<-'end;'}")
382        begin;
383          logfile = ARGV[0]
384          logdev = Logger::LogDevice.new(logfile)
385          logdev.send(:open_logfile, logfile)
386        end;
387        assert_equal(1, File.readlines(logfile).grep(/# Logfile created on/).size)
388        File.unlink(logfile)
389      end
390    ensure
391      File.unlink(logfile) if File.exist?(logfile)
392    end
393  end
394
395  def test_shifting_size_not_rotate_too_much
396    logdev0 = d(@filename)
397    logdev0.__send__(:add_log_header, @tempfile)
398    header_size = @tempfile.size
399    message = "*" * 99 + "\n"
400    shift_size = header_size + message.size * 3 - 1
401    opt = {shift_age: 1, shift_size: shift_size}
402
403    Dir.mktmpdir do |tmpdir|
404      begin
405        log = File.join(tmpdir, "log")
406        logdev1 = d(log, opt)
407        logdev2 = d(log, opt)
408
409        assert_file.identical?(log, logdev1.dev)
410        assert_file.identical?(log, logdev2.dev)
411
412        3.times{logdev1.write(message)}
413        assert_file.identical?(log, logdev1.dev)
414        assert_file.identical?(log, logdev2.dev)
415
416        logdev1.write(message)
417        assert_file.identical?(log, logdev1.dev)
418        assert_file.identical?(log + ".0", logdev2.dev)
419
420        logdev2.write(message)
421        assert_file.identical?(log, logdev1.dev)
422        assert_file.identical?(log, logdev2.dev)
423
424        logdev1.write(message)
425        assert_file.identical?(log, logdev1.dev)
426        assert_file.identical?(log, logdev2.dev)
427      ensure
428        logdev1.close if logdev1
429        logdev2.close if logdev2
430      end
431    end
432  ensure
433    logdev0.close
434  end unless /mswin|mingw/ =~ RUBY_PLATFORM
435
436  def test_shifting_midnight
437    Dir.mktmpdir do |tmpdir|
438      assert_in_out_err([*%W"--disable=gems -rlogger -C#{tmpdir} -"], "#{<<-"begin;"}\n#{<<-'end;'}")
439      begin;
440        begin
441          module FakeTime
442            attr_accessor :now
443          end
444
445          class << Time
446            prepend FakeTime
447          end
448
449          log = "log"
450          File.open(log, "w") {}
451          File.utime(*[Time.mktime(2014, 1, 2, 0, 0, 0)]*2, log)
452
453          Time.now = Time.mktime(2014, 1, 2, 23, 59, 59, 999000)
454          dev = Logger::LogDevice.new(log, shift_age: 'daily')
455          dev.write("#{Time.now} hello-1\n")
456          File.utime(Time.now, Time.now, log)
457
458          Time.now = Time.mktime(2014, 1, 3, 1, 1, 1)
459          dev.write("#{Time.now} hello-2\n")
460          File.utime(Time.now, Time.now, log)
461        ensure
462          dev.close
463        end
464      end;
465
466      bug = '[GH-539]'
467      log = File.join(tmpdir, "log")
468      cont = File.read(log)
469      assert_match(/hello-2/, cont)
470      assert_not_match(/hello-1/, cont)
471      assert_file.for(bug).exist?(log+".20140102")
472      assert_match(/hello-1/, File.read(log+".20140102"), bug)
473    end
474  end
475
476  env_tz_works = /linux|darwin|freebsd/ =~ RUBY_PLATFORM # borrow from test/ruby/test_time_tz.rb
477
478  def test_shifting_weekly
479    Dir.mktmpdir do |tmpdir|
480      assert_in_out_err([{"TZ"=>"UTC"}, *%W"-rlogger -C#{tmpdir} -"], "#{<<-"begin;"}\n#{<<-'end;'}")
481      begin;
482        begin
483          module FakeTime
484            attr_accessor :now
485          end
486
487          class << Time
488            prepend FakeTime
489          end
490
491          log = "log"
492          File.open(log, "w") {}
493          Time.now = Time.utc(2015, 12, 14, 0, 1, 1)
494          File.utime(Time.now, Time.now, log)
495
496          dev = Logger::LogDevice.new("log", shift_age: 'weekly')
497
498          Time.now = Time.utc(2015, 12, 19, 12, 34, 56)
499          dev.write("#{Time.now} hello-1\n")
500          File.utime(Time.now, Time.now, log)
501
502          Time.now = Time.utc(2015, 12, 20, 0, 1, 1)
503          dev.write("#{Time.now} hello-2\n")
504          File.utime(Time.now, Time.now, log)
505        ensure
506          dev.close if dev
507        end
508      end;
509      log = File.join(tmpdir, "log")
510      cont = File.read(log)
511      assert_match(/hello-2/, cont)
512      assert_not_match(/hello-1/, cont)
513      log = Dir.glob(log+".*")
514      assert_equal(1, log.size)
515      log, = *log
516      cont = File.read(log)
517      assert_match(/hello-1/, cont)
518      assert_equal("2015-12-19", cont[/^[-\d]+/])
519      assert_equal("20151219", log[/\d+\z/])
520    end
521  end if env_tz_works
522
523  def test_shifting_monthly
524    Dir.mktmpdir do |tmpdir|
525      assert_in_out_err([{"TZ"=>"UTC"}, *%W"-rlogger -C#{tmpdir} -"], "#{<<-"begin;"}\n#{<<-'end;'}")
526      begin;
527        begin
528          module FakeTime
529            attr_accessor :now
530          end
531
532          class << Time
533            prepend FakeTime
534          end
535
536          log = "log"
537          File.open(log, "w") {}
538          Time.now = Time.utc(2015, 12, 14, 0, 1, 1)
539          File.utime(Time.now, Time.now, log)
540
541          dev = Logger::LogDevice.new("log", shift_age: 'monthly')
542
543          Time.now = Time.utc(2015, 12, 31, 12, 34, 56)
544          dev.write("#{Time.now} hello-1\n")
545          File.utime(Time.now, Time.now, log)
546
547          Time.now = Time.utc(2016, 1, 1, 0, 1, 1)
548          dev.write("#{Time.now} hello-2\n")
549          File.utime(Time.now, Time.now, log)
550        ensure
551          dev.close if dev
552        end
553      end;
554      log = File.join(tmpdir, "log")
555      cont = File.read(log)
556      assert_match(/hello-2/, cont)
557      assert_not_match(/hello-1/, cont)
558      log = Dir.glob(log+".*")
559      assert_equal(1, log.size)
560      log, = *log
561      cont = File.read(log)
562      assert_match(/hello-1/, cont)
563      assert_equal("2015-12-31", cont[/^[-\d]+/])
564      assert_equal("20151231", log[/\d+\z/])
565    end
566  end if env_tz_works
567
568  def test_shifting_dst_change
569    Dir.mktmpdir do |tmpdir|
570      assert_in_out_err([{"TZ"=>"Europe/London"}, *%W"--disable=gems -rlogger -C#{tmpdir} -"], "#{<<-"begin;"}\n#{<<-'end;'}")
571      begin;
572        begin
573          module FakeTime
574            attr_accessor :now
575          end
576
577          class << Time
578            prepend FakeTime
579          end
580
581          log = "log"
582          File.open(log, "w") {}
583
584          Time.now = Time.mktime(2014, 3, 30, 0, 1, 1)
585          File.utime(Time.now, Time.now, log)
586
587          dev = Logger::LogDevice.new(log, shift_age: 'daily')
588          dev.write("#{Time.now} hello-1\n")
589          File.utime(*[Time.mktime(2014, 3, 30, 0, 2, 3)]*2, log)
590
591          Time.now = Time.mktime(2014, 3, 31, 0, 1, 1)
592          dev.write("#{Time.now} hello-2\n")
593          File.utime(Time.now, Time.now, log)
594        ensure
595          dev.close
596        end
597      end;
598
599      log = File.join(tmpdir, "log")
600      cont = File.read(log)
601      assert_match(/hello-2/, cont)
602      assert_not_match(/hello-1/, cont)
603      assert_file.exist?(log+".20140330")
604    end
605  end if env_tz_works
606
607  def test_shifting_weekly_dst_change
608    Dir.mktmpdir do |tmpdir|
609      assert_separately([{"TZ"=>"Europe/London"}, *%W"-rlogger -C#{tmpdir} -"], "#{<<-"begin;"}\n#{<<-'end;'}")
610      begin;
611        begin
612          module FakeTime
613            attr_accessor :now
614          end
615
616          class << Time
617            prepend FakeTime
618          end
619
620          log = "log"
621          File.open(log, "w") {}
622          Time.now = Time.mktime(2015, 10, 25, 0, 1, 1)
623          File.utime(Time.now, Time.now, log)
624
625          dev = Logger::LogDevice.new("log", shift_age: 'weekly')
626          dev.write("#{Time.now} hello-1\n")
627          File.utime(Time.now, Time.now, log)
628        ensure
629          dev.close if dev
630        end
631      end;
632      log = File.join(tmpdir, "log")
633      cont = File.read(log)
634      assert_match(/hello-1/, cont)
635    end
636  end if env_tz_works
637
638  def test_shifting_monthly_dst_change
639    Dir.mktmpdir do |tmpdir|
640      assert_separately([{"TZ"=>"Europe/London"}, *%W"-rlogger -C#{tmpdir} -"], "#{<<-"begin;"}\n#{<<-'end;'}")
641      begin;
642        begin
643          module FakeTime
644            attr_accessor :now
645          end
646
647          class << Time
648            prepend FakeTime
649          end
650
651          log = "log"
652          File.open(log, "w") {}
653          Time.now = Time.utc(2016, 9, 1, 0, 1, 1)
654          File.utime(Time.now, Time.now, log)
655
656          dev = Logger::LogDevice.new("log", shift_age: 'monthly')
657
658          Time.now = Time.utc(2016, 9, 8, 7, 6, 5)
659          dev.write("#{Time.now} hello-1\n")
660          File.utime(Time.now, Time.now, log)
661
662          Time.now = Time.utc(2016, 10, 9, 8, 7, 6)
663          dev.write("#{Time.now} hello-2\n")
664          File.utime(Time.now, Time.now, log)
665
666          Time.now = Time.utc(2016, 10, 9, 8, 7, 7)
667          dev.write("#{Time.now} hello-3\n")
668          File.utime(Time.now, Time.now, log)
669        ensure
670          dev.close if dev
671        end
672      end;
673      log = File.join(tmpdir, "log")
674      cont = File.read(log)
675      assert_match(/hello-2/, cont)
676      assert_not_match(/hello-1/, cont)
677      log = Dir.glob(log+".*")
678      assert_equal(1, log.size)
679      log, = *log
680      cont = File.read(log)
681      assert_match(/hello-1/, cont)
682      assert_equal("2016-09-08", cont[/^[-\d]+/])
683      assert_equal("20160930", log[/\d+\z/])
684    end
685  end if env_tz_works
686
687  def test_shifting_midnight_exist_file
688    Dir.mktmpdir do |tmpdir|
689      assert_in_out_err([*%W"--disable=gems -rlogger -C#{tmpdir} -"], "#{<<-"begin;"}\n#{<<-'end;'}")
690      begin;
691        begin
692          module FakeTime
693            attr_accessor :now
694          end
695
696          class << Time
697            prepend FakeTime
698          end
699
700          log = "log"
701          File.open(log, "w") {}
702          File.utime(*[Time.mktime(2014, 1, 2, 0, 0, 0)]*2, log)
703
704          Time.now = Time.mktime(2014, 1, 2, 23, 59, 59, 999000)
705          dev = Logger::LogDevice.new(log, shift_age: 'daily')
706          dev.write("#{Time.now} hello-1\n")
707          dev.close
708          File.utime(Time.now, Time.now, log)
709
710          Time.now = Time.mktime(2014, 1, 3, 1, 1, 1)
711          dev = Logger::LogDevice.new(log, shift_age: 'daily')
712          dev.write("#{Time.now} hello-2\n")
713          File.utime(Time.now, Time.now, log)
714        ensure
715          dev.close
716        end
717      end;
718
719      bug = '[GH-539]'
720      log = File.join(tmpdir, "log")
721      cont = File.read(log)
722      assert_match(/hello-2/, cont)
723      assert_not_match(/hello-1/, cont)
724      assert_file.for(bug).exist?(log+".20140102")
725      assert_match(/hello-1/, File.read(log+".20140102"), bug)
726    end
727  end
728
729  env_tz_works = /linux|darwin|freebsd/ =~ RUBY_PLATFORM # borrow from test/ruby/test_time_tz.rb
730
731  def test_shifting_weekly_exist_file
732    Dir.mktmpdir do |tmpdir|
733      assert_in_out_err([{"TZ"=>"UTC"}, *%W"-rlogger -C#{tmpdir} -"], "#{<<-"begin;"}\n#{<<-'end;'}")
734      begin;
735        begin
736          module FakeTime
737            attr_accessor :now
738          end
739
740          class << Time
741            prepend FakeTime
742          end
743
744          log = "log"
745          File.open(log, "w") {}
746          Time.now = Time.utc(2015, 12, 14, 0, 1, 1)
747          File.utime(Time.now, Time.now, log)
748
749          dev = Logger::LogDevice.new("log", shift_age: 'weekly')
750
751          Time.now = Time.utc(2015, 12, 19, 12, 34, 56)
752          dev.write("#{Time.now} hello-1\n")
753          dev.close
754          File.utime(Time.now, Time.now, log)
755
756          Time.now = Time.utc(2015, 12, 20, 0, 1, 1)
757          dev = Logger::LogDevice.new("log", shift_age: 'weekly')
758          dev.write("#{Time.now} hello-2\n")
759          File.utime(Time.now, Time.now, log)
760        ensure
761          dev.close if dev
762        end
763      end;
764      log = File.join(tmpdir, "log")
765      cont = File.read(log)
766      assert_match(/hello-2/, cont)
767      assert_not_match(/hello-1/, cont)
768      log = Dir.glob(log+".*")
769      assert_equal(1, log.size)
770      log, = *log
771      cont = File.read(log)
772      assert_match(/hello-1/, cont)
773      assert_equal("2015-12-19", cont[/^[-\d]+/])
774      assert_equal("20151219", log[/\d+\z/])
775    end
776  end if env_tz_works
777
778  def test_shifting_monthly_exist_file
779    Dir.mktmpdir do |tmpdir|
780      assert_in_out_err([{"TZ"=>"UTC"}, *%W"-rlogger -C#{tmpdir} -"], "#{<<-"begin;"}\n#{<<-'end;'}")
781      begin;
782        begin
783          module FakeTime
784            attr_accessor :now
785          end
786
787          class << Time
788            prepend FakeTime
789          end
790
791          log = "log"
792          File.open(log, "w") {}
793          Time.now = Time.utc(2015, 12, 14, 0, 1, 1)
794          File.utime(Time.now, Time.now, log)
795
796          dev = Logger::LogDevice.new("log", shift_age: 'monthly')
797
798          Time.now = Time.utc(2015, 12, 31, 12, 34, 56)
799          dev.write("#{Time.now} hello-1\n")
800          dev.close
801          File.utime(Time.now, Time.now, log)
802
803          Time.now = Time.utc(2016, 1, 1, 0, 1, 1)
804          dev = Logger::LogDevice.new("log", shift_age: 'monthly')
805          dev.write("#{Time.now} hello-2\n")
806          File.utime(Time.now, Time.now, log)
807        ensure
808          dev.close if dev
809        end
810      end;
811      log = File.join(tmpdir, "log")
812      cont = File.read(log)
813      assert_match(/hello-2/, cont)
814      assert_not_match(/hello-1/, cont)
815      log = Dir.glob(log+".*")
816      assert_equal(1, log.size)
817      log, = *log
818      cont = File.read(log)
819      assert_match(/hello-1/, cont)
820      assert_equal("2015-12-31", cont[/^[-\d]+/])
821      assert_equal("20151231", log[/\d+\z/])
822    end
823  end if env_tz_works
824
825  private
826
827  def run_children(n, args, src)
828    r, w = IO.pipe
829    [w, *(1..n).map do
830       f = IO.popen([EnvUtil.rubybin, *%w[--disable=gems -rlogger -], *args], "w", err: w)
831       f.puts(src)
832       f
833     end].each(&:close)
834    stderr = r.read
835    r.close
836    stderr
837  end
838end
839