Skip to content

Commit 0432647

Browse files
Fix test_zipfile failure when SOURCE_DATE_EPOCH is set
Additionally, move the `SOURCE_DATE_EPOCH` test helpers from `test_py_compile` to `test.support.os_helper`.
1 parent a646c99 commit 0432647

7 files changed

Lines changed: 109 additions & 81 deletions

File tree

Doc/library/test.rst

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1643,6 +1643,32 @@ The :mod:`!test.support.os_helper` module provides support for os tests.
16431643
wrapped with a wait loop that checks for the existence of the file.
16441644

16451645

1646+
.. decorator:: with_source_date_epoch(*, epoch=123456789)
1647+
1648+
A decorator for running tests with the :envvar:`SOURCE_DATE_EPOCH`
1649+
environment variable set to *epoch*.
1650+
1651+
1652+
.. decorator:: without_source_date_epoch
1653+
1654+
A decorator for running tests with the :envvar:`SOURCE_DATE_EPOCH`
1655+
environment variable unset.
1656+
1657+
1658+
.. class:: SourceDateEpochTestMeta
1659+
1660+
Metaclass wrapping all test methods of the class with
1661+
:func:`with_source_date_epoch` if the *source_date_epoch* keyword class
1662+
argument is true, or with :func:`without_source_date_epoch` otherwise.
1663+
For example::
1664+
1665+
class TestsWithSourceEpoch(Tests,
1666+
metaclass=SourceDateEpochTestMeta,
1667+
source_date_epoch=True):
1668+
pass
1669+
1670+
1671+
16461672
:mod:`!test.support.import_helper` --- Utilities for import tests
16471673
=================================================================
16481674

Lib/test/support/os_helper.py

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import collections.abc
22
import contextlib
33
import errno
4+
import functools
45
import logging
56
import os
67
import re
@@ -806,6 +807,48 @@ def __exit__(self, *ignore_exc):
806807
os.environ = self._environ
807808

808809

810+
def without_source_date_epoch(fxn):
811+
"""Runs function with SOURCE_DATE_EPOCH unset."""
812+
@functools.wraps(fxn)
813+
def wrapper(*args, **kwargs):
814+
with EnvironmentVarGuard() as env:
815+
env.unset('SOURCE_DATE_EPOCH')
816+
return fxn(*args, **kwargs)
817+
return wrapper
818+
819+
820+
_MISSING = sentinel("MISSING")
821+
822+
def with_source_date_epoch(fxn=_MISSING, *, epoch=123456789):
823+
"""Runs function with SOURCE_DATE_EPOCH set to *epoch*."""
824+
if fxn is _MISSING:
825+
return functools.partial(with_source_date_epoch, epoch=epoch)
826+
827+
@functools.wraps(fxn)
828+
def wrapper(*args, **kwargs):
829+
with EnvironmentVarGuard() as env:
830+
env['SOURCE_DATE_EPOCH'] = str(epoch)
831+
return fxn(*args, **kwargs)
832+
return wrapper
833+
834+
835+
# Run tests with SOURCE_DATE_EPOCH set or unset explicitly.
836+
class SourceDateEpochTestMeta(type(unittest.TestCase)):
837+
def __new__(mcls, name, bases, dct, *, source_date_epoch):
838+
cls = super().__new__(mcls, name, bases, dct)
839+
840+
for attr in dir(cls):
841+
if attr.startswith('test_'):
842+
meth = getattr(cls, attr)
843+
if source_date_epoch:
844+
wrapper = with_source_date_epoch(meth)
845+
else:
846+
wrapper = without_source_date_epoch(meth)
847+
setattr(cls, attr, wrapper)
848+
849+
return cls
850+
851+
809852
try:
810853
if support.MS_WINDOWS:
811854
import ctypes

Lib/test/test_compileall.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,8 @@
2828
from test import support
2929
from test.support import os_helper
3030
from test.support import script_helper
31-
from test.test_py_compile import without_source_date_epoch
32-
from test.test_py_compile import SourceDateEpochTestMeta
31+
from test.support.os_helper import without_source_date_epoch
32+
from test.support.os_helper import SourceDateEpochTestMeta
3333
from test.support.os_helper import FakePath
3434

3535

Lib/test/test_importlib/source/test_file_loader.py

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,21 +5,18 @@
55
machinery = util.import_importlib('importlib.machinery')
66
importlib_util = util.import_importlib('importlib.util')
77

8-
import errno
98
import marshal
109
import os
1110
import py_compile
12-
import shutil
1311
import stat
1412
import sys
1513
import types
1614
import unittest
17-
import warnings
1815

19-
from test.support.import_helper import make_legacy_pyc, unload
16+
from test.support.import_helper import make_legacy_pyc
2017

21-
from test.test_py_compile import without_source_date_epoch
22-
from test.test_py_compile import SourceDateEpochTestMeta
18+
from test.support.os_helper import without_source_date_epoch
19+
from test.support.os_helper import SourceDateEpochTestMeta
2320

2421

2522
class SimpleTest:

Lib/test/test_py_compile.py

Lines changed: 1 addition & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import functools
21
import importlib.util
32
import os
43
import py_compile
@@ -11,43 +10,7 @@
1110

1211
from test import support
1312
from test.support import os_helper, script_helper
14-
15-
16-
def without_source_date_epoch(fxn):
17-
"""Runs function with SOURCE_DATE_EPOCH unset."""
18-
@functools.wraps(fxn)
19-
def wrapper(*args, **kwargs):
20-
with os_helper.EnvironmentVarGuard() as env:
21-
env.unset('SOURCE_DATE_EPOCH')
22-
return fxn(*args, **kwargs)
23-
return wrapper
24-
25-
26-
def with_source_date_epoch(fxn):
27-
"""Runs function with SOURCE_DATE_EPOCH set."""
28-
@functools.wraps(fxn)
29-
def wrapper(*args, **kwargs):
30-
with os_helper.EnvironmentVarGuard() as env:
31-
env['SOURCE_DATE_EPOCH'] = '123456789'
32-
return fxn(*args, **kwargs)
33-
return wrapper
34-
35-
36-
# Run tests with SOURCE_DATE_EPOCH set or unset explicitly.
37-
class SourceDateEpochTestMeta(type(unittest.TestCase)):
38-
def __new__(mcls, name, bases, dct, *, source_date_epoch):
39-
cls = super().__new__(mcls, name, bases, dct)
40-
41-
for attr in dir(cls):
42-
if attr.startswith('test_'):
43-
meth = getattr(cls, attr)
44-
if source_date_epoch:
45-
wrapper = with_source_date_epoch(meth)
46-
else:
47-
wrapper = without_source_date_epoch(meth)
48-
setattr(cls, attr, wrapper)
49-
50-
return cls
13+
from test.support.os_helper import SourceDateEpochTestMeta
5114

5215

5316
class PyCompileTestsBase:

Lib/test/test_regrtest.py

Lines changed: 14 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -170,21 +170,20 @@ def test_randomize(self):
170170
ns = self.parse_args([opt])
171171
self.assertTrue(ns.randomize)
172172

173-
with os_helper.EnvironmentVarGuard() as env:
174-
# with SOURCE_DATE_EPOCH
175-
env['SOURCE_DATE_EPOCH'] = '1697839080'
176-
ns = self.parse_args(['--randomize'])
177-
regrtest = main.Regrtest(ns)
178-
self.assertFalse(regrtest.randomize)
179-
self.assertIsInstance(regrtest.random_seed, str)
180-
self.assertEqual(regrtest.random_seed, '1697839080')
181-
182-
# without SOURCE_DATE_EPOCH
183-
del env['SOURCE_DATE_EPOCH']
184-
ns = self.parse_args(['--randomize'])
185-
regrtest = main.Regrtest(ns)
186-
self.assertTrue(regrtest.randomize)
187-
self.assertIsInstance(regrtest.random_seed, int)
173+
@os_helper.with_source_date_epoch(epoch=1697839080)
174+
def test_randomize_with_source_date_epoch(self):
175+
ns = self.parse_args(['--randomize'])
176+
regrtest = main.Regrtest(ns)
177+
self.assertFalse(regrtest.randomize)
178+
self.assertIsInstance(regrtest.random_seed, str)
179+
self.assertEqual(regrtest.random_seed, '1697839080')
180+
181+
@os_helper.without_source_date_epoch
182+
def test_randomize_without_source_date_epoch(self):
183+
ns = self.parse_args(['--randomize'])
184+
regrtest = main.Regrtest(ns)
185+
self.assertTrue(regrtest.randomize)
186+
self.assertIsInstance(regrtest.random_seed, int)
188187

189188
def test_no_randomize(self):
190189
ns = self.parse_args([])

Lib/test/test_zipfile/test_core.py

Lines changed: 20 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -22,14 +22,15 @@
2222
from random import randint, random, randbytes
2323

2424
from test import archiver_tests
25-
from test.support import script_helper, os_helper
25+
from test.support import script_helper
2626
from test.support import (
2727
findfile, requires_zlib, requires_bz2, requires_lzma,
2828
requires_zstd, captured_stdout, captured_stderr, requires_subprocess,
2929
cpython_only
3030
)
3131
from test.support.os_helper import (
32-
TESTFN, unlink, rmtree, temp_dir, temp_cwd, fd_count, FakePath
32+
TESTFN, unlink, rmtree, temp_dir, temp_cwd, fd_count, FakePath,
33+
with_source_date_epoch, without_source_date_epoch,
3334
)
3435
from test.support.import_helper import ensure_lazy_imports
3536

@@ -1960,6 +1961,7 @@ def test_repack_file_entry_before_first_file(self):
19601961
with zipfile.ZipFile(TESTFN) as zh:
19611962
self.assertIsNone(zh.testzip())
19621963

1964+
@without_source_date_epoch # it would override the time mock below
19631965
@mock.patch.object(time, 'time', new=lambda: 315590400) # fix time for ZipFile.writestr()
19641966
def test_repack_bytes_before_removed_files(self):
19651967
"""Should preserve if there are bytes before stale local file entries."""
@@ -2004,6 +2006,7 @@ def test_repack_bytes_before_removed_files(self):
20042006
with zipfile.ZipFile(TESTFN) as zh:
20052007
self.assertIsNone(zh.testzip())
20062008

2009+
@without_source_date_epoch # it would override the time mock below
20072010
@mock.patch.object(time, 'time', new=lambda: 315590400) # fix time for ZipFile.writestr()
20082011
def test_repack_bytes_after_removed_files(self):
20092012
"""Should keep extra bytes if there are bytes after stale local file entries."""
@@ -2047,6 +2050,7 @@ def test_repack_bytes_after_removed_files(self):
20472050
with zipfile.ZipFile(TESTFN) as zh:
20482051
self.assertIsNone(zh.testzip())
20492052

2053+
@without_source_date_epoch # it would override the time mock below
20502054
@mock.patch.object(time, 'time', new=lambda: 315590400) # fix time for ZipFile.writestr()
20512055
def test_repack_bytes_between_removed_files(self):
20522056
"""Should strip only local file entries before random bytes."""
@@ -2251,6 +2255,7 @@ def test_repack_removed_partial(self):
22512255
with zipfile.ZipFile(TESTFN) as zh:
22522256
self.assertIsNone(zh.testzip())
22532257

2258+
@without_source_date_epoch # it would override the time mock below
22542259
@mock.patch.object(time, 'time', new=lambda: 315590400) # fix time for ZipFile.writestr()
22552260
def test_repack_removed_bytes_between_files(self):
22562261
"""Should not remove bytes between local file entries."""
@@ -4003,29 +4008,24 @@ def test_writestr_extended_local_header_issue1202(self):
40034008
zinfo.flag_bits |= zipfile._MASK_USE_DATA_DESCRIPTOR # Include an extended local header.
40044009
orig_zip.writestr(zinfo, data)
40054010

4011+
@with_source_date_epoch(epoch=1735715999)
40064012
def test_write_with_source_date_epoch(self):
4007-
with os_helper.EnvironmentVarGuard() as env:
4008-
# Set the SOURCE_DATE_EPOCH environment variable to a specific timestamp
4009-
env['SOURCE_DATE_EPOCH'] = "1735715999"
4010-
4011-
with zipfile.ZipFile(TESTFN, "w") as zf:
4012-
zf.writestr("test_source_date_epoch.txt", "Testing SOURCE_DATE_EPOCH")
4013+
with zipfile.ZipFile(TESTFN, "w") as zf:
4014+
zf.writestr("test_source_date_epoch.txt", "Testing SOURCE_DATE_EPOCH")
40134015

4014-
with zipfile.ZipFile(TESTFN, "r") as zf:
4015-
zip_info = zf.getinfo("test_source_date_epoch.txt")
4016-
expected_utc = (2025, 1, 1, 7, 19, 58)
4017-
self.assertEqual(zip_info.date_time, expected_utc)
4016+
with zipfile.ZipFile(TESTFN, "r") as zf:
4017+
zip_info = zf.getinfo("test_source_date_epoch.txt")
4018+
expected_utc = (2025, 1, 1, 7, 19, 58)
4019+
self.assertEqual(zip_info.date_time, expected_utc)
40184020

4021+
@without_source_date_epoch
40194022
def test_write_without_source_date_epoch(self):
4020-
with os_helper.EnvironmentVarGuard() as env:
4021-
del env['SOURCE_DATE_EPOCH']
4022-
4023-
with zipfile.ZipFile(TESTFN, "w") as zf:
4024-
zf.writestr("test_no_source_date_epoch.txt", "Testing without SOURCE_DATE_EPOCH")
4023+
with zipfile.ZipFile(TESTFN, "w") as zf:
4024+
zf.writestr("test_no_source_date_epoch.txt", "Testing without SOURCE_DATE_EPOCH")
40254025

4026-
with zipfile.ZipFile(TESTFN, "r") as zf:
4027-
zip_info = zf.getinfo("test_no_source_date_epoch.txt")
4028-
self.assertTimestampAlmostEqual(time.localtime(), zip_info.date_time, tolerance=2)
4026+
with zipfile.ZipFile(TESTFN, "r") as zf:
4027+
zip_info = zf.getinfo("test_no_source_date_epoch.txt")
4028+
self.assertTimestampAlmostEqual(time.localtime(), zip_info.date_time, tolerance=2)
40294029

40304030
def assertTimestampAlmostEqual(self, time1, time2, tolerance):
40314031
import datetime

0 commit comments

Comments
 (0)