Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 26 additions & 0 deletions Doc/library/test.rst
Original file line number Diff line number Diff line change
Expand Up @@ -1643,6 +1643,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
=================================================================

Expand Down
43 changes: 43 additions & 0 deletions Lib/test/support/os_helper.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import collections.abc
import contextlib
import errno
import functools
import logging
import os
import re
Expand Down Expand Up @@ -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
Expand Down
4 changes: 2 additions & 2 deletions Lib/test/test_compileall.py
Original file line number Diff line number Diff line change
Expand Up @@ -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


Expand Down
9 changes: 3 additions & 6 deletions Lib/test/test_importlib/source/test_file_loader.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
39 changes: 1 addition & 38 deletions Lib/test/test_py_compile.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import functools
import importlib.util
import os
import py_compile
Expand All @@ -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:
Expand Down
29 changes: 14 additions & 15 deletions Lib/test/test_regrtest.py
Original file line number Diff line number Diff line change
Expand Up @@ -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([])
Expand Down
40 changes: 20 additions & 20 deletions Lib/test/test_zipfile/test_core.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
)
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

Expand Down Expand Up @@ -1960,6 +1961,7 @@ def test_repack_file_entry_before_first_file(self):
with zipfile.ZipFile(TESTFN) as zh:
self.assertIsNone(zh.testzip())

@without_source_date_epoch # it would override 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."""
Expand Down Expand Up @@ -2004,6 +2006,7 @@ def test_repack_bytes_before_removed_files(self):
with zipfile.ZipFile(TESTFN) as zh:
self.assertIsNone(zh.testzip())

@without_source_date_epoch # it would override 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."""
Expand Down Expand Up @@ -2047,6 +2050,7 @@ def test_repack_bytes_after_removed_files(self):
with zipfile.ZipFile(TESTFN) as zh:
self.assertIsNone(zh.testzip())

@without_source_date_epoch # it would override 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."""
Expand Down Expand Up @@ -2251,6 +2255,7 @@ def test_repack_removed_partial(self):
with zipfile.ZipFile(TESTFN) as zh:
self.assertIsNone(zh.testzip())

@without_source_date_epoch # it would override 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."""
Expand Down Expand Up @@ -4003,29 +4008,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
Expand Down
Loading