관리-도구
편집 파일: fake_stat_time_test.py
# Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. """Unit tests for file timestamp updates.""" import time import unittest from collections import namedtuple from pyfakefs.tests.test_utils import RealFsTestCase FileTime = namedtuple("FileTime", "st_ctime, st_atime, st_mtime") class FakeStatTestBase(RealFsTestCase): def setUp(self): super().setUp() # we disable the tests for MacOS to avoid very long builds due # to the 1s time resolution - we know that the functionality is # similar to Linux self.check_linux_and_windows() self.file_path = self.make_path("some_file") # MacOS has a timestamp resolution of 1 second self.sleep_time = 1.1 if self.is_macos else 0.01 self.mode = "" def stat_time(self, path): stat = self.os.stat(path) if self.use_real_fs(): # sleep a bit so in the next call the time has changed time.sleep(self.sleep_time) else: # calling time.time() advances mocked time time.time() return FileTime( st_ctime=stat.st_ctime, st_atime=stat.st_atime, st_mtime=stat.st_mtime, ) def assertLessExceptWindows(self, time1, time2): if self.is_windows_fs: self.assertLessEqual(time1, time2) else: self.assertLess(time1, time2) def assertLessExceptPosix(self, time1, time2): if self.is_windows_fs: self.assertLess(time1, time2) else: self.assertEqual(time1, time2) def open_close_new_file(self): with self.mock_time(): with self.open(self.file_path, self.mode): created = self.stat_time(self.file_path) closed = self.stat_time(self.file_path) return created, closed def open_write_close_new_file(self): with self.mock_time(): with self.open(self.file_path, self.mode) as f: created = self.stat_time(self.file_path) f.write("foo") written = self.stat_time(self.file_path) closed = self.stat_time(self.file_path) return created, written, closed def open_close(self): with self.mock_time(): self.create_file(self.file_path) before = self.stat_time(self.file_path) with self.open(self.file_path, self.mode): opened = self.stat_time(self.file_path) closed = self.stat_time(self.file_path) return before, opened, closed def open_write_close(self): with self.mock_time(): self.create_file(self.file_path) before = self.stat_time(self.file_path) with self.open(self.file_path, self.mode) as f: opened = self.stat_time(self.file_path) f.write("foo") written = self.stat_time(self.file_path) closed = self.stat_time(self.file_path) return before, opened, written, closed def open_flush_close(self): with self.mock_time(): self.create_file(self.file_path) before = self.stat_time(self.file_path) with self.open(self.file_path, self.mode) as f: opened = self.stat_time(self.file_path) f.flush() flushed = self.stat_time(self.file_path) closed = self.stat_time(self.file_path) return before, opened, flushed, closed def open_write_flush(self): with self.mock_time(): self.create_file(self.file_path) before = self.stat_time(self.file_path) with self.open(self.file_path, self.mode) as f: opened = self.stat_time(self.file_path) f.write("foo") written = self.stat_time(self.file_path) f.flush() flushed = self.stat_time(self.file_path) closed = self.stat_time(self.file_path) return before, opened, written, flushed, closed def open_read_flush(self): with self.mock_time(): self.create_file(self.file_path) before = self.stat_time(self.file_path) with self.open(self.file_path, "r") as f: opened = self.stat_time(self.file_path) f.read() read = self.stat_time(self.file_path) f.flush() flushed = self.stat_time(self.file_path) closed = self.stat_time(self.file_path) return before, opened, read, flushed, closed def open_read_close_new_file(self): with self.mock_time(): with self.open(self.file_path, self.mode) as f: created = self.stat_time(self.file_path) f.read() read = self.stat_time(self.file_path) closed = self.stat_time(self.file_path) return created, read, closed def open_read_close(self): with self.mock_time(): self.create_file(self.file_path) before = self.stat_time(self.file_path) with self.open(self.file_path, self.mode) as f: opened = self.stat_time(self.file_path) f.read() read = self.stat_time(self.file_path) closed = self.stat_time(self.file_path) return before, opened, read, closed def check_open_close_new_file(self): """ When a file is created on opening and closed again, no timestamps are updated on close. """ created, closed = self.open_close_new_file() self.assertEqual(created.st_ctime, closed.st_ctime) self.assertEqual(created.st_atime, closed.st_atime) self.assertEqual(created.st_mtime, closed.st_mtime) def check_open_write_close_new_file(self): """ When a file is created on opening, st_ctime is updated under Posix, and st_mtime is updated on close. """ created, written, closed = self.open_write_close_new_file() self.assertEqual(created.st_ctime, written.st_ctime) self.assertLessExceptWindows(written.st_ctime, closed.st_ctime) self.assertEqual(created.st_atime, written.st_atime) self.assertLessEqual(written.st_atime, closed.st_atime) self.assertEqual(created.st_mtime, written.st_mtime) self.assertLess(written.st_mtime, closed.st_mtime) def check_open_close_w_mode(self): """ When an existing file is opened with 'w' or 'w+' mode, st_ctime (Posix only) and st_mtime are updated on open (truncating), but not on close. """ before, opened, closed = self.open_close() self.assertLessExceptWindows(before.st_ctime, opened.st_ctime) self.assertEqual(opened.st_ctime, closed.st_ctime) self.assertLessEqual(before.st_atime, opened.st_atime) self.assertEqual(opened.st_atime, closed.st_atime) self.assertLess(before.st_mtime, opened.st_mtime) self.assertEqual(opened.st_mtime, closed.st_mtime) def check_open_close_non_w_mode(self): """ When an existing file is opened with any mode other than 'w' or 'w+', no timestamps are updated. """ before, opened, closed = self.open_close() self.assertEqual(before.st_ctime, opened.st_ctime) self.assertEqual(opened.st_ctime, closed.st_ctime) self.assertEqual(before.st_atime, opened.st_atime) self.assertEqual(opened.st_atime, closed.st_atime) self.assertEqual(before.st_mtime, opened.st_mtime) self.assertEqual(opened.st_mtime, closed.st_mtime) def check_open_write_close_w_mode(self): """ When an existing file is opened with 'w' or 'w+' mode and is then written to, st_ctime (Posix only) and st_mtime are updated on open (truncating) and again on close (flush), but not when written to. """ before, opened, written, closed = self.open_write_close() self.assertLessExceptWindows(before.st_ctime, opened.st_ctime) self.assertEqual(opened.st_ctime, written.st_ctime) self.assertLessExceptWindows(written.st_ctime, closed.st_ctime) self.assertLessEqual(before.st_atime, opened.st_atime) self.assertEqual(opened.st_atime, written.st_atime) self.assertLessEqual(written.st_atime, closed.st_atime) self.assertLess(before.st_mtime, opened.st_mtime) self.assertEqual(opened.st_mtime, written.st_mtime) self.assertLess(written.st_mtime, closed.st_mtime) def check_open_flush_close_w_mode(self): """ When an existing file is opened with 'w' or 'w+' mode (truncating), st_ctime (Posix only) and st_mtime are updated. No updates are done on flush or close. """ before, opened, flushed, closed = self.open_flush_close() self.assertLessExceptWindows(before.st_ctime, opened.st_ctime) self.assertEqual(opened.st_ctime, flushed.st_ctime) self.assertEqual(flushed.st_ctime, closed.st_ctime) self.assertLessEqual(before.st_atime, opened.st_atime) self.assertEqual(opened.st_atime, flushed.st_atime) self.assertEqual(flushed.st_atime, closed.st_atime) self.assertLess(before.st_mtime, opened.st_mtime) self.assertEqual(opened.st_mtime, flushed.st_mtime) self.assertEqual(flushed.st_mtime, closed.st_mtime) def check_open_flush_close_non_w_mode(self): """ When an existing file is opened with any mode other than 'w' or 'w+', flushed and closed, no timestamps are updated. """ before, opened, flushed, closed = self.open_flush_close() self.assertEqual(before.st_ctime, opened.st_ctime) self.assertEqual(opened.st_ctime, flushed.st_ctime) self.assertEqual(flushed.st_ctime, closed.st_ctime) self.assertEqual(before.st_atime, opened.st_atime) self.assertEqual(opened.st_atime, flushed.st_atime) self.assertEqual(flushed.st_atime, closed.st_atime) self.assertEqual(before.st_mtime, opened.st_mtime) self.assertEqual(opened.st_mtime, flushed.st_mtime) self.assertEqual(flushed.st_mtime, closed.st_mtime) def check_open_read_close_non_w_mode(self): """ Reading from a file opened with 'r', 'r+', or 'a+' mode updates st_atime under Posix. """ before, opened, read, closed = self.open_read_close() self.assertEqual(before.st_ctime, opened.st_ctime) self.assertEqual(opened.st_ctime, read.st_ctime) self.assertEqual(read.st_ctime, closed.st_ctime) self.assertEqual(before.st_atime, opened.st_atime) self.assertLessEqual(opened.st_atime, read.st_atime) self.assertEqual(read.st_atime, closed.st_atime) self.assertEqual(before.st_mtime, opened.st_mtime) self.assertEqual(opened.st_mtime, read.st_mtime) self.assertEqual(read.st_mtime, closed.st_mtime) def check_open_read_close_new_file(self): """ When a file is created with 'w+' or 'a+' mode and then read from, st_atime is updated under Posix. """ created, read, closed = self.open_read_close_new_file() self.assertEqual(created.st_ctime, read.st_ctime) self.assertEqual(read.st_ctime, closed.st_ctime) self.assertLessEqual(created.st_atime, read.st_atime) self.assertEqual(read.st_atime, closed.st_atime) self.assertEqual(created.st_mtime, read.st_mtime) self.assertEqual(read.st_mtime, closed.st_mtime) def check_open_write_close_non_w_mode(self): """ When an existing file is opened with 'a', 'a+' or 'r+' mode and is then written to, st_ctime (Posix only) and st_mtime are updated close (flush), but not on opening or when written to. """ before, opened, written, closed = self.open_write_close() self.assertEqual(before.st_ctime, opened.st_ctime) self.assertEqual(opened.st_ctime, written.st_ctime) self.assertLessExceptWindows(written.st_ctime, closed.st_ctime) self.assertEqual(before.st_atime, opened.st_atime) self.assertEqual(opened.st_atime, written.st_atime) self.assertLessEqual(written.st_atime, closed.st_atime) self.assertEqual(before.st_mtime, opened.st_mtime) self.assertEqual(opened.st_mtime, written.st_mtime) self.assertLess(written.st_mtime, closed.st_mtime) def check_open_write_flush_close_w_mode(self): """ When an existing file is opened with 'w' or 'w+' mode and is then written to, st_ctime (Posix only) and st_mtime are updated on open (truncating). Under Posix, st_mtime is updated on flush, under Windows, on close instead. """ before, opened, written, flushed, closed = self.open_write_flush() self.assertLessEqual(before.st_ctime, opened.st_ctime) self.assertLessEqual(written.st_ctime, flushed.st_ctime) self.assertEqual(opened.st_ctime, written.st_ctime) self.assertEqual(flushed.st_ctime, closed.st_ctime) self.assertLessEqual(before.st_atime, opened.st_atime) self.assertEqual(opened.st_atime, written.st_atime) self.assertLessEqual(written.st_atime, flushed.st_atime) self.assertLessEqual(flushed.st_atime, closed.st_atime) self.assertLess(before.st_mtime, opened.st_mtime) self.assertEqual(opened.st_mtime, written.st_mtime) self.assertLessExceptWindows(written.st_mtime, flushed.st_mtime) self.assertLessEqual(flushed.st_mtime, closed.st_mtime) def check_open_write_flush_close_non_w_mode(self): """ When an existing file is opened with 'a', 'a+' or 'r+' mode and is then written to, st_ctime and st_mtime are updated on flush under Posix. Under Windows, only st_mtime is updated on close instead. """ before, opened, written, flushed, closed = self.open_write_flush() self.assertEqual(before.st_ctime, opened.st_ctime) self.assertEqual(opened.st_ctime, written.st_ctime) self.assertLessExceptWindows(written.st_ctime, flushed.st_ctime) self.assertEqual(flushed.st_ctime, closed.st_ctime) self.assertEqual(before.st_atime, opened.st_atime) self.assertEqual(opened.st_atime, written.st_atime) self.assertLessEqual(written.st_atime, flushed.st_atime) self.assertLessEqual(flushed.st_atime, closed.st_atime) self.assertEqual(before.st_mtime, opened.st_mtime) self.assertEqual(opened.st_mtime, written.st_mtime) self.assertLessExceptWindows(written.st_mtime, flushed.st_mtime) self.assertLessEqual(flushed.st_mtime, closed.st_mtime) class TestFakeModeW(FakeStatTestBase): def setUp(self): super(TestFakeModeW, self).setUp() self.mode = "w" def test_open_close_new_file(self): self.check_open_close_new_file() def test_open_write_close_new_file(self): self.check_open_write_close_new_file() def test_open_close(self): self.check_open_close_w_mode() def test_open_write_close(self): self.check_open_write_close_w_mode() def test_open_flush_close(self): self.check_open_flush_close_w_mode() def test_open_write_flush_close(self): self.check_open_write_flush_close_w_mode() def test_read_raises(self): with self.open(self.file_path, "w") as f: with self.assertRaises(OSError): f.read() class TestRealModeW(TestFakeModeW): def use_real_fs(self): return True class TestFakeModeWPlus(FakeStatTestBase): def setUp(self): super(TestFakeModeWPlus, self).setUp() self.mode = "w+" def test_open_close_new_file(self): self.check_open_close_new_file() def test_open_write_close_new_file(self): self.check_open_write_close_new_file() def test_open_read_close_new_file(self): self.check_open_read_close_new_file() def test_open_close(self): self.check_open_close_w_mode() def test_open_write_close(self): self.check_open_write_close_w_mode() def test_open_read_close(self): """ When an existing file is opened with 'w+' mode and is then written to, st_ctime (Posix only) and st_mtime are updated on open (truncating) and again on close (flush). Under Posix, st_atime is updated on read. """ before, opened, read, closed = self.open_read_close() self.assertLessExceptWindows(before.st_ctime, opened.st_ctime) self.assertEqual(opened.st_ctime, read.st_ctime) self.assertEqual(read.st_ctime, closed.st_ctime) self.assertLessEqual(before.st_atime, opened.st_atime) self.assertLessEqual(opened.st_atime, read.st_atime) self.assertEqual(read.st_atime, closed.st_atime) self.assertLess(before.st_mtime, opened.st_mtime) self.assertEqual(opened.st_mtime, read.st_mtime) self.assertEqual(read.st_mtime, closed.st_mtime) def test_open_flush_close(self): self.check_open_flush_close_w_mode() def test_open_write_flush_close(self): self.check_open_write_flush_close_w_mode() class TestRealModeWPlus(TestFakeModeWPlus): def use_real_fs(self): return True class TestFakeModeA(FakeStatTestBase): def setUp(self): super(TestFakeModeA, self).setUp() self.mode = "a" def test_open_close_new_file(self): self.check_open_close_new_file() def test_open_write_close_new_file(self): self.check_open_write_close_new_file() def test_open_close(self): self.check_open_close_non_w_mode() def test_open_write_close(self): self.check_open_write_close_non_w_mode() def test_open_flush_close(self): self.check_open_flush_close_non_w_mode() def test_open_write_flush_close(self): self.check_open_write_flush_close_non_w_mode() def test_read_raises(self): with self.open(self.file_path, "a") as f: with self.assertRaises(OSError): f.read() class TestRealModeA(TestFakeModeA): def use_real_fs(self): return True class TestFakeModeAPlus(FakeStatTestBase): def setUp(self): super(TestFakeModeAPlus, self).setUp() self.mode = "a+" def test_open_close_new_file(self): self.check_open_close_new_file() def test_open_write_close_new_file(self): self.check_open_write_close_new_file() def test_open_read_close_new_file(self): self.check_open_read_close_new_file() def test_open_close(self): self.check_open_close_non_w_mode() def test_open_write_close(self): self.check_open_write_close_non_w_mode() def test_open_read_close(self): self.check_open_read_close_non_w_mode() def test_open_flush_close(self): self.check_open_flush_close_non_w_mode() def test_open_write_flush_close(self): self.check_open_write_flush_close_non_w_mode() class TestRealModeAPlus(TestFakeModeAPlus): def use_real_fs(self): return True class TestFakeModeR(FakeStatTestBase): def setUp(self): super(TestFakeModeR, self).setUp() self.mode = "r" def test_open_close(self): self.check_open_close_non_w_mode() def test_open_read_close(self): self.check_open_read_close_non_w_mode() def test_open_flush_close(self): self.check_open_flush_close_non_w_mode() def test_open_read_flush_close(self): """ When an existing file is opened with 'r' mode, read, flushed and closed, st_atime is updated after reading under Posix. """ before, opened, read, flushed, closed = self.open_read_flush() self.assertEqual(before.st_ctime, opened.st_ctime) self.assertEqual(opened.st_ctime, read.st_ctime) self.assertEqual(read.st_ctime, flushed.st_ctime) self.assertEqual(flushed.st_ctime, closed.st_ctime) self.assertEqual(before.st_atime, opened.st_atime) self.assertLessEqual(opened.st_atime, read.st_atime) self.assertEqual(read.st_atime, flushed.st_atime) self.assertEqual(flushed.st_atime, closed.st_atime) self.assertEqual(before.st_mtime, opened.st_mtime) self.assertEqual(opened.st_mtime, read.st_mtime) self.assertEqual(read.st_mtime, flushed.st_mtime) self.assertEqual(flushed.st_mtime, closed.st_mtime) def test_open_not_existing_raises(self): with self.assertRaises(OSError): with self.open(self.file_path, "r"): pass class TestRealModeR(TestFakeModeR): def use_real_fs(self): return True class TestFakeModeRPlus(FakeStatTestBase): def setUp(self): super(TestFakeModeRPlus, self).setUp() self.mode = "r+" def test_open_close(self): self.check_open_close_non_w_mode() def test_open_write_close(self): self.check_open_write_close_non_w_mode() def test_open_read_close(self): self.check_open_read_close_non_w_mode() def test_open_flush_close(self): self.check_open_flush_close_non_w_mode() def test_open_write_flush_close(self): self.check_open_write_flush_close_non_w_mode() def test_open_not_existing_raises(self): with self.assertRaises(OSError): with self.open(self.file_path, "r+"): pass class TestRealModeRPlus(TestFakeModeRPlus): def use_real_fs(self): return True if __name__ == "__main__": unittest.main()