|
11 | 11 | import datetime
|
12 | 12 | import logging
|
13 | 13 | import os
|
| 14 | +import time |
14 | 15 |
|
15 | 16 | # External dependencies.
|
16 | 17 | from executor import ExternalCommandFailed
|
|
21 | 22 | # The module we're testing.
|
22 | 23 | from rotate_backups import (
|
23 | 24 | RotateBackups,
|
| 25 | + FilenameMatcher, |
24 | 26 | coerce_location,
|
25 | 27 | coerce_retention_period,
|
26 | 28 | load_config_file,
|
@@ -217,6 +219,56 @@ def test_rotate_backups(self):
|
217 | 219 | backups_that_were_preserved = set(os.listdir(root))
|
218 | 220 | assert backups_that_were_preserved == expected_to_be_preserved
|
219 | 221 |
|
| 222 | + def test_rotate_backups_mtime(self): |
| 223 | + """Test the :func:`.rotate_backups()` function.""" |
| 224 | + # These are the backups expected to be preserved. After each backup |
| 225 | + # I've noted which rotation scheme it falls in and the number of |
| 226 | + # preserved backups within that rotation scheme (counting up as we |
| 227 | + # progress through the backups sorted by date). |
| 228 | + expected_to_be_preserved = set([ |
| 229 | + '2013-10-10@20:07', # monthly (1), yearly (1) |
| 230 | + '2013-11-01@20:06', # monthly (2) |
| 231 | + '2013-12-01@20:07', # monthly (3) |
| 232 | + '2014-01-01@20:07', # monthly (4), yearly (2) |
| 233 | + '2014-02-01@20:05', # monthly (5) |
| 234 | + '2014-03-01@20:04', # monthly (6) |
| 235 | + '2014-04-01@20:03', # monthly (7) |
| 236 | + '2014-05-01@20:06', # monthly (8) |
| 237 | + '2014-06-01@20:01', # monthly (9) |
| 238 | + '2014-06-09@20:01', # weekly (1) |
| 239 | + '2014-06-16@20:02', # weekly (2) |
| 240 | + '2014-06-23@20:04', # weekly (3) |
| 241 | + '2014-06-26@20:04', # daily (1) |
| 242 | + '2014-06-27@20:02', # daily (2) |
| 243 | + '2014-06-28@20:02', # daily (3) |
| 244 | + '2014-06-29@20:01', # daily (4) |
| 245 | + '2014-06-30@20:03', # daily (5), weekly (4) |
| 246 | + '2014-07-01@20:02', # daily (6), monthly (10) |
| 247 | + '2014-07-02@20:03', # hourly (1), daily (7) |
| 248 | + ]) |
| 249 | + with TemporaryDirectory(prefix='rotate-backups-', suffix='-test-suite') as root: |
| 250 | + # Specify the rotation scheme and options through a configuration file. |
| 251 | + config_file = os.path.join(root, 'rotate-backups.ini') |
| 252 | + subdir = os.path.join(root, 'mtime') |
| 253 | + parser = configparser.RawConfigParser() |
| 254 | + parser.add_section(subdir) |
| 255 | + parser.set(subdir, 'hourly', '24') |
| 256 | + parser.set(subdir, 'daily', '7') |
| 257 | + parser.set(subdir, 'weekly', '4') |
| 258 | + parser.set(subdir, 'monthly', '12') |
| 259 | + parser.set(subdir, 'yearly', 'always') |
| 260 | + parser.set(subdir, 'ionice', 'idle') |
| 261 | + # parser.set(subdir, 'stat-timestamp', 'true') |
| 262 | + parser.set(subdir, 'stat-timestamp', None) |
| 263 | + with open(config_file, 'w') as handle: |
| 264 | + parser.write(handle) |
| 265 | + self.create_sample_backup_set(root) |
| 266 | + map = self.apply_mtime_and_rename(root, subdir) |
| 267 | + run_cli(main, '--verbose', '--config=%s' % config_file, '--stat-timestamp') |
| 268 | + backups_that_were_preserved = set(os.listdir(subdir)) |
| 269 | + assert backups_that_were_preserved == set([map[e] |
| 270 | + for e in expected_to_be_preserved]) |
| 271 | + |
220 | 272 | def test_rotate_concurrent(self):
|
221 | 273 | """Test the :func:`.rotate_concurrent()` function."""
|
222 | 274 | # These are the backups expected to be preserved
|
@@ -506,6 +558,21 @@ def create_sample_backup_set(self, root):
|
506 | 558 | for name in SAMPLE_BACKUP_SET:
|
507 | 559 | os.mkdir(os.path.join(root, name))
|
508 | 560 |
|
| 561 | + def apply_mtime_and_rename(self, root, subdir): |
| 562 | + """Extract mtime from filename, update file stat, rename and return a map of old to new name""" |
| 563 | + os.mkdir(subdir) |
| 564 | + fm = FilenameMatcher() |
| 565 | + map = {} |
| 566 | + for name in os.listdir(root): |
| 567 | + map[name] = name |
| 568 | + if match := fm.search(root, name): |
| 569 | + file = os.path.join(root, name) |
| 570 | + t = time.mktime(match.match_to_datetime().timetuple()) |
| 571 | + os.utime(file, times=(t, t)) |
| 572 | + os.rename(file, os.path.join(subdir, f'{t}s')) |
| 573 | + map[name] = f'{t}s' |
| 574 | + return map |
| 575 | + |
509 | 576 |
|
510 | 577 | @contextlib.contextmanager
|
511 | 578 | def readonly_directory(pathname):
|
|
0 commit comments