https://github.com/python/cpython/commit/82905dd165f198f1b18625ae0f0ede9367df75a4
commit: 82905dd165f198f1b18625ae0f0ede9367df75a4
branch: main
author: Stan Ulbrych <[email protected]>
committer: StanFromIreland <[email protected]>
date: 2026-08-07T16:21:04+02:00
summary:

gh-154948: Fix `test_zipfile` failure when `SOURCE_DATE_EPOCH` is set (#155001)

files:
M Doc/library/test.rst
M Lib/test/support/os_helper.py
M Lib/test/test_compileall.py
M Lib/test/test_importlib/source/test_file_loader.py
M Lib/test/test_py_compile.py
M Lib/test/test_regrtest.py
M Lib/test/test_zipfile/test_core.py

diff --git a/Doc/library/test.rst b/Doc/library/test.rst
index 893154246ae4d81..f3b5383658b5ac4 100644
--- a/Doc/library/test.rst
+++ b/Doc/library/test.rst
@@ -1656,6 +1656,32 @@ The :mod:`!test.support.os_helper` module provides 
support for os tests.
    wrapped with a wait loop that checks for the existence of the file.
 
 
+.. decorator:: with_source_date_epoch(*, epoch=123456789)
+
+   A decorator for running tests with the :envvar:`SOURCE_DATE_EPOCH`
+   environment variable set to *epoch*.
+
+
+.. decorator:: without_source_date_epoch
+
+   A decorator for running tests with the :envvar:`SOURCE_DATE_EPOCH`
+   environment variable unset.
+
+
+.. class:: SourceDateEpochTestMeta
+
+   Metaclass wrapping all test methods of the class with
+   :func:`with_source_date_epoch` if the *source_date_epoch* keyword class
+   argument is true, or with :func:`without_source_date_epoch` otherwise.
+   For example::
+
+      class TestsWithSourceEpoch(Tests,
+                                 metaclass=SourceDateEpochTestMeta,
+                                 source_date_epoch=True):
+          pass
+
+
+
 :mod:`!test.support.import_helper` --- Utilities for import tests
 =================================================================
 
diff --git a/Lib/test/support/os_helper.py b/Lib/test/support/os_helper.py
index daf6060940e97f0..e1e2e69cb3d8334 100644
--- a/Lib/test/support/os_helper.py
+++ b/Lib/test/support/os_helper.py
@@ -1,6 +1,7 @@
 import collections.abc
 import contextlib
 import errno
+import functools
 import logging
 import os
 import re
@@ -806,6 +807,48 @@ def __exit__(self, *ignore_exc):
         os.environ = self._environ
 
 
+def without_source_date_epoch(fxn):
+    """Runs function with SOURCE_DATE_EPOCH unset."""
+    @functools.wraps(fxn)
+    def wrapper(*args, **kwargs):
+        with EnvironmentVarGuard() as env:
+            env.unset('SOURCE_DATE_EPOCH')
+            return fxn(*args, **kwargs)
+    return wrapper
+
+
+_MISSING = sentinel("MISSING")
+
+def with_source_date_epoch(fxn=_MISSING, *, epoch=123456789):
+    """Runs function with SOURCE_DATE_EPOCH set to *epoch*."""
+    if fxn is _MISSING:
+        return functools.partial(with_source_date_epoch, epoch=epoch)
+
+    @functools.wraps(fxn)
+    def wrapper(*args, **kwargs):
+        with EnvironmentVarGuard() as env:
+            env['SOURCE_DATE_EPOCH'] = str(epoch)
+            return fxn(*args, **kwargs)
+    return wrapper
+
+
+# Run tests with SOURCE_DATE_EPOCH set or unset explicitly.
+class SourceDateEpochTestMeta(type(unittest.TestCase)):
+    def __new__(mcls, name, bases, dct, *, source_date_epoch):
+        cls = super().__new__(mcls, name, bases, dct)
+
+        for attr in dir(cls):
+            if attr.startswith('test_'):
+                meth = getattr(cls, attr)
+                if source_date_epoch:
+                    wrapper = with_source_date_epoch(meth)
+                else:
+                    wrapper = without_source_date_epoch(meth)
+                setattr(cls, attr, wrapper)
+
+        return cls
+
+
 try:
     if support.MS_WINDOWS:
         import ctypes
diff --git a/Lib/test/test_compileall.py b/Lib/test/test_compileall.py
index 95dcb4ef9fdc202..9a6ca2be1624c6c 100644
--- a/Lib/test/test_compileall.py
+++ b/Lib/test/test_compileall.py
@@ -28,8 +28,8 @@
 from test import support
 from test.support import os_helper
 from test.support import script_helper
-from test.test_py_compile import without_source_date_epoch
-from test.test_py_compile import SourceDateEpochTestMeta
+from test.support.os_helper import without_source_date_epoch
+from test.support.os_helper import SourceDateEpochTestMeta
 from test.support.os_helper import FakePath
 
 
diff --git a/Lib/test/test_importlib/source/test_file_loader.py 
b/Lib/test/test_importlib/source/test_file_loader.py
index e4bd850f3514ff1..ca2cc045fa674e1 100644
--- a/Lib/test/test_importlib/source/test_file_loader.py
+++ b/Lib/test/test_importlib/source/test_file_loader.py
@@ -5,21 +5,18 @@
 machinery = util.import_importlib('importlib.machinery')
 importlib_util = util.import_importlib('importlib.util')
 
-import errno
 import marshal
 import os
 import py_compile
-import shutil
 import stat
 import sys
 import types
 import unittest
-import warnings
 
-from test.support.import_helper import make_legacy_pyc, unload
+from test.support.import_helper import make_legacy_pyc
 
-from test.test_py_compile import without_source_date_epoch
-from test.test_py_compile import SourceDateEpochTestMeta
+from test.support.os_helper import without_source_date_epoch
+from test.support.os_helper import SourceDateEpochTestMeta
 
 
 class SimpleTest:
diff --git a/Lib/test/test_py_compile.py b/Lib/test/test_py_compile.py
index b4265e6a0b458db..616e5d0c7cf971c 100644
--- a/Lib/test/test_py_compile.py
+++ b/Lib/test/test_py_compile.py
@@ -1,4 +1,3 @@
-import functools
 import importlib.util
 import os
 import py_compile
@@ -11,43 +10,7 @@
 
 from test import support
 from test.support import os_helper, script_helper
-
-
-def without_source_date_epoch(fxn):
-    """Runs function with SOURCE_DATE_EPOCH unset."""
-    @functools.wraps(fxn)
-    def wrapper(*args, **kwargs):
-        with os_helper.EnvironmentVarGuard() as env:
-            env.unset('SOURCE_DATE_EPOCH')
-            return fxn(*args, **kwargs)
-    return wrapper
-
-
-def with_source_date_epoch(fxn):
-    """Runs function with SOURCE_DATE_EPOCH set."""
-    @functools.wraps(fxn)
-    def wrapper(*args, **kwargs):
-        with os_helper.EnvironmentVarGuard() as env:
-            env['SOURCE_DATE_EPOCH'] = '123456789'
-            return fxn(*args, **kwargs)
-    return wrapper
-
-
-# Run tests with SOURCE_DATE_EPOCH set or unset explicitly.
-class SourceDateEpochTestMeta(type(unittest.TestCase)):
-    def __new__(mcls, name, bases, dct, *, source_date_epoch):
-        cls = super().__new__(mcls, name, bases, dct)
-
-        for attr in dir(cls):
-            if attr.startswith('test_'):
-                meth = getattr(cls, attr)
-                if source_date_epoch:
-                    wrapper = with_source_date_epoch(meth)
-                else:
-                    wrapper = without_source_date_epoch(meth)
-                setattr(cls, attr, wrapper)
-
-        return cls
+from test.support.os_helper import SourceDateEpochTestMeta
 
 
 class PyCompileTestsBase:
diff --git a/Lib/test/test_regrtest.py b/Lib/test/test_regrtest.py
index 6d30d267cd5ad29..6ba440053089161 100644
--- a/Lib/test/test_regrtest.py
+++ b/Lib/test/test_regrtest.py
@@ -170,21 +170,20 @@ def test_randomize(self):
                 ns = self.parse_args([opt])
                 self.assertTrue(ns.randomize)
 
-        with os_helper.EnvironmentVarGuard() as env:
-            # with SOURCE_DATE_EPOCH
-            env['SOURCE_DATE_EPOCH'] = '1697839080'
-            ns = self.parse_args(['--randomize'])
-            regrtest = main.Regrtest(ns)
-            self.assertFalse(regrtest.randomize)
-            self.assertIsInstance(regrtest.random_seed, str)
-            self.assertEqual(regrtest.random_seed, '1697839080')
-
-            # without SOURCE_DATE_EPOCH
-            del env['SOURCE_DATE_EPOCH']
-            ns = self.parse_args(['--randomize'])
-            regrtest = main.Regrtest(ns)
-            self.assertTrue(regrtest.randomize)
-            self.assertIsInstance(regrtest.random_seed, int)
+    @os_helper.with_source_date_epoch(epoch=1697839080)
+    def test_randomize_with_source_date_epoch(self):
+        ns = self.parse_args(['--randomize'])
+        regrtest = main.Regrtest(ns)
+        self.assertFalse(regrtest.randomize)
+        self.assertIsInstance(regrtest.random_seed, str)
+        self.assertEqual(regrtest.random_seed, '1697839080')
+
+    @os_helper.without_source_date_epoch
+    def test_randomize_without_source_date_epoch(self):
+        ns = self.parse_args(['--randomize'])
+        regrtest = main.Regrtest(ns)
+        self.assertTrue(regrtest.randomize)
+        self.assertIsInstance(regrtest.random_seed, int)
 
     def test_no_randomize(self):
         ns = self.parse_args([])
diff --git a/Lib/test/test_zipfile/test_core.py 
b/Lib/test/test_zipfile/test_core.py
index 83f2eef6b6f8ab6..e9974d6c05648bb 100644
--- a/Lib/test/test_zipfile/test_core.py
+++ b/Lib/test/test_zipfile/test_core.py
@@ -22,14 +22,15 @@
 from random import randint, random, randbytes
 
 from test import archiver_tests
-from test.support import script_helper, os_helper
+from test.support import script_helper
 from test.support import (
     findfile, requires_zlib, requires_bz2, requires_lzma,
     requires_zstd, captured_stdout, captured_stderr, requires_subprocess,
     cpython_only, gc_collect
 )
 from test.support.os_helper import (
-    TESTFN, unlink, rmtree, temp_dir, temp_cwd, fd_count, FakePath
+    TESTFN, unlink, rmtree, temp_dir, temp_cwd, fd_count, FakePath,
+    with_source_date_epoch, without_source_date_epoch,
 )
 from test.support.import_helper import ensure_lazy_imports
 from test.support.warnings_helper import check_no_resource_warning
@@ -1961,6 +1962,7 @@ def test_repack_file_entry_before_first_file(self):
                 with zipfile.ZipFile(TESTFN) as zh:
                     self.assertIsNone(zh.testzip())
 
+    @without_source_date_epoch  # SOURCE_DATE_EPOCH would bypass the time mock 
below
     @mock.patch.object(time, 'time', new=lambda: 315590400)  # fix time for 
ZipFile.writestr()
     def test_repack_bytes_before_removed_files(self):
         """Should preserve if there are bytes before stale local file 
entries."""
@@ -2005,6 +2007,7 @@ def test_repack_bytes_before_removed_files(self):
                 with zipfile.ZipFile(TESTFN) as zh:
                     self.assertIsNone(zh.testzip())
 
+    @without_source_date_epoch  # SOURCE_DATE_EPOCH would bypass the time mock 
below
     @mock.patch.object(time, 'time', new=lambda: 315590400)  # fix time for 
ZipFile.writestr()
     def test_repack_bytes_after_removed_files(self):
         """Should keep extra bytes if there are bytes after stale local file 
entries."""
@@ -2048,6 +2051,7 @@ def test_repack_bytes_after_removed_files(self):
                 with zipfile.ZipFile(TESTFN) as zh:
                     self.assertIsNone(zh.testzip())
 
+    @without_source_date_epoch  # SOURCE_DATE_EPOCH would bypass the time mock 
below
     @mock.patch.object(time, 'time', new=lambda: 315590400)  # fix time for 
ZipFile.writestr()
     def test_repack_bytes_between_removed_files(self):
         """Should strip only local file entries before random bytes."""
@@ -2252,6 +2256,7 @@ def test_repack_removed_partial(self):
                 with zipfile.ZipFile(TESTFN) as zh:
                     self.assertIsNone(zh.testzip())
 
+    @without_source_date_epoch  # SOURCE_DATE_EPOCH would bypass the time mock 
below
     @mock.patch.object(time, 'time', new=lambda: 315590400)  # fix time for 
ZipFile.writestr()
     def test_repack_removed_bytes_between_files(self):
         """Should not remove bytes between local file entries."""
@@ -4004,29 +4009,24 @@ def test_writestr_extended_local_header_issue1202(self):
                 zinfo.flag_bits |= zipfile._MASK_USE_DATA_DESCRIPTOR  # 
Include an extended local header.
                 orig_zip.writestr(zinfo, data)
 
+    @with_source_date_epoch(epoch=1735715999)
     def test_write_with_source_date_epoch(self):
-        with os_helper.EnvironmentVarGuard() as env:
-            # Set the SOURCE_DATE_EPOCH environment variable to a specific 
timestamp
-            env['SOURCE_DATE_EPOCH'] = "1735715999"
-
-            with zipfile.ZipFile(TESTFN, "w") as zf:
-                zf.writestr("test_source_date_epoch.txt", "Testing 
SOURCE_DATE_EPOCH")
+        with zipfile.ZipFile(TESTFN, "w") as zf:
+            zf.writestr("test_source_date_epoch.txt", "Testing 
SOURCE_DATE_EPOCH")
 
-            with zipfile.ZipFile(TESTFN, "r") as zf:
-                zip_info = zf.getinfo("test_source_date_epoch.txt")
-                expected_utc = (2025, 1, 1, 7, 19, 58)
-                self.assertEqual(zip_info.date_time, expected_utc)
+        with zipfile.ZipFile(TESTFN, "r") as zf:
+            zip_info = zf.getinfo("test_source_date_epoch.txt")
+            expected_utc = (2025, 1, 1, 7, 19, 58)
+            self.assertEqual(zip_info.date_time, expected_utc)
 
+    @without_source_date_epoch
     def test_write_without_source_date_epoch(self):
-        with os_helper.EnvironmentVarGuard() as env:
-            del env['SOURCE_DATE_EPOCH']
-
-            with zipfile.ZipFile(TESTFN, "w") as zf:
-                zf.writestr("test_no_source_date_epoch.txt", "Testing without 
SOURCE_DATE_EPOCH")
+        with zipfile.ZipFile(TESTFN, "w") as zf:
+            zf.writestr("test_no_source_date_epoch.txt", "Testing without 
SOURCE_DATE_EPOCH")
 
-            with zipfile.ZipFile(TESTFN, "r") as zf:
-                zip_info = zf.getinfo("test_no_source_date_epoch.txt")
-                self.assertTimestampAlmostEqual(time.localtime(), 
zip_info.date_time, tolerance=2)
+        with zipfile.ZipFile(TESTFN, "r") as zf:
+            zip_info = zf.getinfo("test_no_source_date_epoch.txt")
+            self.assertTimestampAlmostEqual(time.localtime(), 
zip_info.date_time, tolerance=2)
 
     def assertTimestampAlmostEqual(self, time1, time2, tolerance):
         import datetime

_______________________________________________
Python-checkins mailing list -- [email protected]
To unsubscribe send an email to [email protected]
https://mail.python.org/mailman3//lists/python-checkins.python.org
Member address: [email protected]

Reply via email to