|
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533 |
- # Copyright (c) Twisted Matrix Laboratories.
- # See LICENSE for details.
-
-
- import contextlib
- import errno
- import os
- import stat
- import time
- from unittest import skipIf
-
- from twisted.python import logfile, runtime
- from twisted.trial.unittest import TestCase
-
-
- class LogFileTests(TestCase):
- """
- Test the rotating log file.
- """
-
- def setUp(self):
- self.dir = self.mktemp()
- os.makedirs(self.dir)
- self.name = "test.log"
- self.path = os.path.join(self.dir, self.name)
-
- def tearDown(self):
- """
- Restore back write rights on created paths: if tests modified the
- rights, that will allow the paths to be removed easily afterwards.
- """
- os.chmod(self.dir, 0o777)
- if os.path.exists(self.path):
- os.chmod(self.path, 0o777)
-
- def test_abstractShouldRotate(self):
- """
- L{BaseLogFile.shouldRotate} is abstract and must be implemented by
- subclass.
- """
- log = logfile.BaseLogFile(self.name, self.dir)
- self.addCleanup(log.close)
- self.assertRaises(NotImplementedError, log.shouldRotate)
-
- def test_writing(self):
- """
- Log files can be written to, flushed and closed. Closing a log file
- also flushes it.
- """
- with contextlib.closing(logfile.LogFile(self.name, self.dir)) as log:
- log.write("123")
- log.write("456")
- log.flush()
- log.write("7890")
-
- with open(self.path) as f:
- self.assertEqual(f.read(), "1234567890")
-
- def test_rotation(self):
- """
- Rotating log files autorotate after a period of time, and can also be
- manually rotated.
- """
- # this logfile should rotate every 10 bytes
- with contextlib.closing(
- logfile.LogFile(self.name, self.dir, rotateLength=10)
- ) as log:
-
- # test automatic rotation
- log.write("123")
- log.write("4567890")
- log.write("1" * 11)
- self.assertTrue(os.path.exists(f"{self.path}.1"))
- self.assertFalse(os.path.exists(f"{self.path}.2"))
- log.write("")
- self.assertTrue(os.path.exists(f"{self.path}.1"))
- self.assertTrue(os.path.exists(f"{self.path}.2"))
- self.assertFalse(os.path.exists(f"{self.path}.3"))
- log.write("3")
- self.assertFalse(os.path.exists(f"{self.path}.3"))
-
- # test manual rotation
- log.rotate()
- self.assertTrue(os.path.exists(f"{self.path}.3"))
- self.assertFalse(os.path.exists(f"{self.path}.4"))
-
- self.assertEqual(log.listLogs(), [1, 2, 3])
-
- def test_append(self):
- """
- Log files can be written to, closed. Their size is the number of
- bytes written to them. Everything that was written to them can
- be read, even if the writing happened on separate occasions,
- and even if the log file was closed in between.
- """
- with contextlib.closing(logfile.LogFile(self.name, self.dir)) as log:
- log.write("0123456789")
-
- log = logfile.LogFile(self.name, self.dir)
- self.addCleanup(log.close)
- self.assertEqual(log.size, 10)
- self.assertEqual(log._file.tell(), log.size)
- log.write("abc")
- self.assertEqual(log.size, 13)
- self.assertEqual(log._file.tell(), log.size)
- f = log._file
- f.seek(0, 0)
- self.assertEqual(f.read(), b"0123456789abc")
-
- def test_logReader(self):
- """
- Various tests for log readers.
-
- First of all, log readers can get logs by number and read what
- was written to those log files. Getting nonexistent log files
- raises C{ValueError}. Using anything other than an integer
- index raises C{TypeError}. As logs get older, their log
- numbers increase.
- """
- log = logfile.LogFile(self.name, self.dir)
- self.addCleanup(log.close)
- log.write("abc\n")
- log.write("def\n")
- log.rotate()
- log.write("ghi\n")
- log.flush()
-
- # check reading logs
- self.assertEqual(log.listLogs(), [1])
- with contextlib.closing(log.getCurrentLog()) as reader:
- reader._file.seek(0)
- self.assertEqual(reader.readLines(), ["ghi\n"])
- self.assertEqual(reader.readLines(), [])
- with contextlib.closing(log.getLog(1)) as reader:
- self.assertEqual(reader.readLines(), ["abc\n", "def\n"])
- self.assertEqual(reader.readLines(), [])
-
- # check getting illegal log readers
- self.assertRaises(ValueError, log.getLog, 2)
- self.assertRaises(TypeError, log.getLog, "1")
-
- # check that log numbers are higher for older logs
- log.rotate()
- self.assertEqual(log.listLogs(), [1, 2])
- with contextlib.closing(log.getLog(1)) as reader:
- reader._file.seek(0)
- self.assertEqual(reader.readLines(), ["ghi\n"])
- self.assertEqual(reader.readLines(), [])
- with contextlib.closing(log.getLog(2)) as reader:
- self.assertEqual(reader.readLines(), ["abc\n", "def\n"])
- self.assertEqual(reader.readLines(), [])
-
- def test_LogReaderReadsZeroLine(self):
- """
- L{LogReader.readLines} supports reading no line.
- """
- # We don't need any content, just a file path that can be opened.
- with open(self.path, "w"):
- pass
-
- reader = logfile.LogReader(self.path)
- self.addCleanup(reader.close)
- self.assertEqual([], reader.readLines(0))
-
- def test_modePreservation(self):
- """
- Check rotated files have same permissions as original.
- """
- open(self.path, "w").close()
- os.chmod(self.path, 0o707)
- mode = os.stat(self.path)[stat.ST_MODE]
- log = logfile.LogFile(self.name, self.dir)
- self.addCleanup(log.close)
- log.write("abc")
- log.rotate()
- self.assertEqual(mode, os.stat(self.path)[stat.ST_MODE])
-
- def test_noPermission(self):
- """
- Check it keeps working when permission on dir changes.
- """
- log = logfile.LogFile(self.name, self.dir)
- self.addCleanup(log.close)
- log.write("abc")
-
- # change permissions so rotation would fail
- os.chmod(self.dir, 0o555)
-
- # if this succeeds, chmod doesn't restrict us, so we can't
- # do the test
- try:
- f = open(os.path.join(self.dir, "xxx"), "w")
- except OSError:
- pass
- else:
- f.close()
- return
-
- log.rotate() # this should not fail
-
- log.write("def")
- log.flush()
-
- f = log._file
- self.assertEqual(f.tell(), 6)
- f.seek(0, 0)
- self.assertEqual(f.read(), b"abcdef")
-
- def test_maxNumberOfLog(self):
- """
- Test it respect the limit on the number of files when maxRotatedFiles
- is not None.
- """
- log = logfile.LogFile(self.name, self.dir, rotateLength=10, maxRotatedFiles=3)
- self.addCleanup(log.close)
- log.write("1" * 11)
- log.write("2" * 11)
- self.assertTrue(os.path.exists(f"{self.path}.1"))
-
- log.write("3" * 11)
- self.assertTrue(os.path.exists(f"{self.path}.2"))
-
- log.write("4" * 11)
- self.assertTrue(os.path.exists(f"{self.path}.3"))
- with open(f"{self.path}.3") as fp:
- self.assertEqual(fp.read(), "1" * 11)
-
- log.write("5" * 11)
- with open(f"{self.path}.3") as fp:
- self.assertEqual(fp.read(), "2" * 11)
- self.assertFalse(os.path.exists(f"{self.path}.4"))
-
- def test_fromFullPath(self):
- """
- Test the fromFullPath method.
- """
- log1 = logfile.LogFile(self.name, self.dir, 10, defaultMode=0o777)
- self.addCleanup(log1.close)
- log2 = logfile.LogFile.fromFullPath(self.path, 10, defaultMode=0o777)
- self.addCleanup(log2.close)
- self.assertEqual(log1.name, log2.name)
- self.assertEqual(os.path.abspath(log1.path), log2.path)
- self.assertEqual(log1.rotateLength, log2.rotateLength)
- self.assertEqual(log1.defaultMode, log2.defaultMode)
-
- def test_defaultPermissions(self):
- """
- Test the default permission of the log file: if the file exist, it
- should keep the permission.
- """
- with open(self.path, "wb"):
- os.chmod(self.path, 0o707)
- currentMode = stat.S_IMODE(os.stat(self.path)[stat.ST_MODE])
- log1 = logfile.LogFile(self.name, self.dir)
- self.assertEqual(stat.S_IMODE(os.stat(self.path)[stat.ST_MODE]), currentMode)
- self.addCleanup(log1.close)
-
- def test_specifiedPermissions(self):
- """
- Test specifying the permissions used on the log file.
- """
- log1 = logfile.LogFile(self.name, self.dir, defaultMode=0o066)
- self.addCleanup(log1.close)
- mode = stat.S_IMODE(os.stat(self.path)[stat.ST_MODE])
- if runtime.platform.isWindows():
- # The only thing we can get here is global read-only
- self.assertEqual(mode, 0o444)
- else:
- self.assertEqual(mode, 0o066)
-
- @skipIf(runtime.platform.isWindows(), "Can't test reopen on Windows")
- def test_reopen(self):
- """
- L{logfile.LogFile.reopen} allows to rename the currently used file and
- make L{logfile.LogFile} create a new file.
- """
- with contextlib.closing(logfile.LogFile(self.name, self.dir)) as log1:
- log1.write("hello1")
- savePath = os.path.join(self.dir, "save.log")
- os.rename(self.path, savePath)
- log1.reopen()
- log1.write("hello2")
-
- with open(self.path) as f:
- self.assertEqual(f.read(), "hello2")
- with open(savePath) as f:
- self.assertEqual(f.read(), "hello1")
-
- def test_nonExistentDir(self):
- """
- Specifying an invalid directory to L{LogFile} raises C{IOError}.
- """
- e = self.assertRaises(
- IOError, logfile.LogFile, self.name, "this_dir_does_not_exist"
- )
- self.assertEqual(e.errno, errno.ENOENT)
-
- def test_cantChangeFileMode(self):
- """
- Opening a L{LogFile} which can be read and write but whose mode can't
- be changed doesn't trigger an error.
- """
- if runtime.platform.isWindows():
- name, directory = "NUL", ""
- expectedPath = "NUL"
- else:
- name, directory = "null", "/dev"
- expectedPath = "/dev/null"
-
- log = logfile.LogFile(name, directory, defaultMode=0o555)
- self.addCleanup(log.close)
-
- self.assertEqual(log.path, expectedPath)
- self.assertEqual(log.defaultMode, 0o555)
-
- def test_listLogsWithBadlyNamedFiles(self):
- """
- L{LogFile.listLogs} doesn't choke if it encounters a file with an
- unexpected name.
- """
- log = logfile.LogFile(self.name, self.dir)
- self.addCleanup(log.close)
-
- with open(f"{log.path}.1", "w") as fp:
- fp.write("123")
- with open(f"{log.path}.bad-file", "w") as fp:
- fp.write("123")
-
- self.assertEqual([1], log.listLogs())
-
- def test_listLogsIgnoresZeroSuffixedFiles(self):
- """
- L{LogFile.listLogs} ignores log files which rotated suffix is 0.
- """
- log = logfile.LogFile(self.name, self.dir)
- self.addCleanup(log.close)
-
- for i in range(0, 3):
- with open(f"{log.path}.{i}", "w") as fp:
- fp.write("123")
-
- self.assertEqual([1, 2], log.listLogs())
-
-
- class RiggedDailyLogFile(logfile.DailyLogFile):
- _clock = 0.0
-
- def _openFile(self):
- logfile.DailyLogFile._openFile(self)
- # rig the date to match _clock, not mtime
- self.lastDate = self.toDate()
-
- def toDate(self, *args):
- if args:
- return time.gmtime(*args)[:3]
- return time.gmtime(self._clock)[:3]
-
-
- class DailyLogFileTests(TestCase):
- """
- Test rotating log file.
- """
-
- def setUp(self):
- self.dir = self.mktemp()
- os.makedirs(self.dir)
- self.name = "testdaily.log"
- self.path = os.path.join(self.dir, self.name)
-
- def test_writing(self):
- """
- A daily log file can be written to like an ordinary log file.
- """
- with contextlib.closing(RiggedDailyLogFile(self.name, self.dir)) as log:
- log.write("123")
- log.write("456")
- log.flush()
- log.write("7890")
-
- with open(self.path) as f:
- self.assertEqual(f.read(), "1234567890")
-
- def test_rotation(self):
- """
- Daily log files rotate daily.
- """
- log = RiggedDailyLogFile(self.name, self.dir)
- self.addCleanup(log.close)
- days = [(self.path + "." + log.suffix(day * 86400)) for day in range(3)]
-
- # test automatic rotation
- log._clock = 0.0 # 1970/01/01 00:00.00
- log.write("123")
- log._clock = 43200 # 1970/01/01 12:00.00
- log.write("4567890")
- log._clock = 86400 # 1970/01/02 00:00.00
- log.write("1" * 11)
- self.assertTrue(os.path.exists(days[0]))
- self.assertFalse(os.path.exists(days[1]))
- log._clock = 172800 # 1970/01/03 00:00.00
- log.write("")
- self.assertTrue(os.path.exists(days[0]))
- self.assertTrue(os.path.exists(days[1]))
- self.assertFalse(os.path.exists(days[2]))
- log._clock = 259199 # 1970/01/03 23:59.59
- log.write("3")
- self.assertFalse(os.path.exists(days[2]))
-
- def test_getLog(self):
- """
- Test retrieving log files with L{DailyLogFile.getLog}.
- """
- data = ["1\n", "2\n", "3\n"]
- log = RiggedDailyLogFile(self.name, self.dir)
- self.addCleanup(log.close)
- for d in data:
- log.write(d)
- log.flush()
-
- # This returns the current log file.
- r = log.getLog(0.0)
- self.addCleanup(r.close)
-
- self.assertEqual(data, r.readLines())
-
- # We can't get this log, it doesn't exist yet.
- self.assertRaises(ValueError, log.getLog, 86400)
-
- log._clock = 86401 # New day
- r.close()
- log.rotate()
- r = log.getLog(0) # We get the previous log
- self.addCleanup(r.close)
- self.assertEqual(data, r.readLines())
-
- def test_rotateAlreadyExists(self):
- """
- L{DailyLogFile.rotate} doesn't do anything if they new log file already
- exists on the disk.
- """
- log = RiggedDailyLogFile(self.name, self.dir)
- self.addCleanup(log.close)
-
- # Build a new file with the same name as the file which would be created
- # if the log file is to be rotated.
- newFilePath = f"{log.path}.{log.suffix(log.lastDate)}"
- with open(newFilePath, "w") as fp:
- fp.write("123")
- previousFile = log._file
- log.rotate()
- self.assertEqual(previousFile, log._file)
-
- @skipIf(
- runtime.platform.isWindows(),
- "Making read-only directories on Windows is too complex for this "
- "test to reasonably do.",
- )
- def test_rotatePermissionDirectoryNotOk(self):
- """
- L{DailyLogFile.rotate} doesn't do anything if the directory containing
- the log files can't be written to.
- """
- log = logfile.DailyLogFile(self.name, self.dir)
- self.addCleanup(log.close)
-
- os.chmod(log.directory, 0o444)
- # Restore permissions so tests can be cleaned up.
- self.addCleanup(os.chmod, log.directory, 0o755)
- previousFile = log._file
- log.rotate()
- self.assertEqual(previousFile, log._file)
-
- def test_rotatePermissionFileNotOk(self):
- """
- L{DailyLogFile.rotate} doesn't do anything if the log file can't be
- written to.
- """
- log = logfile.DailyLogFile(self.name, self.dir)
- self.addCleanup(log.close)
-
- os.chmod(log.path, 0o444)
- previousFile = log._file
- log.rotate()
- self.assertEqual(previousFile, log._file)
-
- def test_toDate(self):
- """
- Test that L{DailyLogFile.toDate} converts its timestamp argument to a
- time tuple (year, month, day).
- """
- log = logfile.DailyLogFile(self.name, self.dir)
- self.addCleanup(log.close)
-
- timestamp = time.mktime((2000, 1, 1, 0, 0, 0, 0, 0, 0))
- self.assertEqual((2000, 1, 1), log.toDate(timestamp))
-
- def test_toDateDefaultToday(self):
- """
- Test that L{DailyLogFile.toDate} returns today's date by default.
-
- By mocking L{time.localtime}, we ensure that L{DailyLogFile.toDate}
- returns the first 3 values of L{time.localtime} which is the current
- date.
-
- Note that we don't compare the *real* result of L{DailyLogFile.toDate}
- to the *real* current date, as there's a slight possibility that the
- date changes between the 2 function calls.
- """
-
- def mock_localtime(*args):
- self.assertEqual((), args)
- return list(range(0, 9))
-
- log = logfile.DailyLogFile(self.name, self.dir)
- self.addCleanup(log.close)
-
- self.patch(time, "localtime", mock_localtime)
- logDate = log.toDate()
- self.assertEqual([0, 1, 2], logDate)
-
- def test_toDateUsesArgumentsToMakeADate(self):
- """
- Test that L{DailyLogFile.toDate} uses its arguments to create a new
- date.
- """
- log = logfile.DailyLogFile(self.name, self.dir)
- self.addCleanup(log.close)
-
- date = (2014, 10, 22)
- seconds = time.mktime(date + (0,) * 6)
-
- logDate = log.toDate(seconds)
- self.assertEqual(date, logDate)
|