mirror of
https://github.com/sphinx-doc/sphinx.git
synced 2025-02-25 18:55:22 -06:00
Use more precise last-modified times for files (#12661)
This commit is contained in:
@@ -9,7 +9,6 @@ import os
|
|||||||
import posixpath
|
import posixpath
|
||||||
import re
|
import re
|
||||||
import sys
|
import sys
|
||||||
import time
|
|
||||||
import types
|
import types
|
||||||
import warnings
|
import warnings
|
||||||
from os import path
|
from os import path
|
||||||
@@ -40,13 +39,21 @@ from sphinx.locale import _, __
|
|||||||
from sphinx.search import js_index
|
from sphinx.search import js_index
|
||||||
from sphinx.theming import HTMLThemeFactory
|
from sphinx.theming import HTMLThemeFactory
|
||||||
from sphinx.util import isurl, logging
|
from sphinx.util import isurl, logging
|
||||||
|
from sphinx.util._timestamps import _format_rfc3339_microseconds
|
||||||
from sphinx.util.display import progress_message, status_iterator
|
from sphinx.util.display import progress_message, status_iterator
|
||||||
from sphinx.util.docutils import new_document
|
from sphinx.util.docutils import new_document
|
||||||
from sphinx.util.fileutil import copy_asset
|
from sphinx.util.fileutil import copy_asset
|
||||||
from sphinx.util.i18n import format_date
|
from sphinx.util.i18n import format_date
|
||||||
from sphinx.util.inventory import InventoryFile
|
from sphinx.util.inventory import InventoryFile
|
||||||
from sphinx.util.matching import DOTFILES, Matcher, patmatch
|
from sphinx.util.matching import DOTFILES, Matcher, patmatch
|
||||||
from sphinx.util.osutil import SEP, copyfile, ensuredir, os_path, relative_uri
|
from sphinx.util.osutil import (
|
||||||
|
SEP,
|
||||||
|
_last_modified_time,
|
||||||
|
copyfile,
|
||||||
|
ensuredir,
|
||||||
|
os_path,
|
||||||
|
relative_uri,
|
||||||
|
)
|
||||||
from sphinx.writers.html import HTMLWriter
|
from sphinx.writers.html import HTMLWriter
|
||||||
from sphinx.writers.html5 import HTML5Translator
|
from sphinx.writers.html5 import HTML5Translator
|
||||||
|
|
||||||
@@ -397,7 +404,7 @@ class StandaloneHTMLBuilder(Builder):
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
if self.templates:
|
if self.templates:
|
||||||
template_mtime = self.templates.newest_template_mtime()
|
template_mtime = int(self.templates.newest_template_mtime() * 10**6)
|
||||||
else:
|
else:
|
||||||
template_mtime = 0
|
template_mtime = 0
|
||||||
for docname in self.env.found_docs:
|
for docname in self.env.found_docs:
|
||||||
@@ -407,19 +414,19 @@ class StandaloneHTMLBuilder(Builder):
|
|||||||
continue
|
continue
|
||||||
targetname = self.get_outfilename(docname)
|
targetname = self.get_outfilename(docname)
|
||||||
try:
|
try:
|
||||||
targetmtime = path.getmtime(targetname)
|
targetmtime = _last_modified_time(targetname)
|
||||||
except Exception:
|
except Exception:
|
||||||
targetmtime = 0
|
targetmtime = 0
|
||||||
try:
|
try:
|
||||||
srcmtime = max(path.getmtime(self.env.doc2path(docname)), template_mtime)
|
srcmtime = max(_last_modified_time(self.env.doc2path(docname)), template_mtime)
|
||||||
if srcmtime > targetmtime:
|
if srcmtime > targetmtime:
|
||||||
logger.debug(
|
logger.debug(
|
||||||
'[build target] targetname %r(%s), template(%s), docname %r(%s)',
|
'[build target] targetname %r(%s), template(%s), docname %r(%s)',
|
||||||
targetname,
|
targetname,
|
||||||
_format_modified_time(targetmtime),
|
_format_rfc3339_microseconds(targetmtime),
|
||||||
_format_modified_time(template_mtime),
|
_format_rfc3339_microseconds(template_mtime),
|
||||||
docname,
|
docname,
|
||||||
_format_modified_time(path.getmtime(self.env.doc2path(docname))),
|
_format_rfc3339_microseconds(_last_modified_time(self.env.doc2path(docname))),
|
||||||
)
|
)
|
||||||
yield docname
|
yield docname
|
||||||
except OSError:
|
except OSError:
|
||||||
@@ -1224,12 +1231,6 @@ def convert_html_css_files(app: Sphinx, config: Config) -> None:
|
|||||||
config.html_css_files = html_css_files
|
config.html_css_files = html_css_files
|
||||||
|
|
||||||
|
|
||||||
def _format_modified_time(timestamp: float) -> str:
|
|
||||||
"""Return an RFC 3339 formatted string representing the given timestamp."""
|
|
||||||
seconds, fraction = divmod(timestamp, 1)
|
|
||||||
return time.strftime("%Y-%m-%d %H:%M:%S", time.gmtime(seconds)) + f'.{fraction:.3f}'
|
|
||||||
|
|
||||||
|
|
||||||
def convert_html_js_files(app: Sphinx, config: Config) -> None:
|
def convert_html_js_files(app: Sphinx, config: Config) -> None:
|
||||||
"""Convert string styled html_js_files to tuple styled one."""
|
"""Convert string styled html_js_files to tuple styled one."""
|
||||||
html_js_files: list[tuple[str, dict[str, str]]] = []
|
html_js_files: list[tuple[str, dict[str, str]]] = []
|
||||||
|
|||||||
@@ -10,7 +10,11 @@ from docutils.io import StringOutput
|
|||||||
from sphinx.builders import Builder
|
from sphinx.builders import Builder
|
||||||
from sphinx.locale import __
|
from sphinx.locale import __
|
||||||
from sphinx.util import logging
|
from sphinx.util import logging
|
||||||
from sphinx.util.osutil import ensuredir, os_path
|
from sphinx.util.osutil import (
|
||||||
|
_last_modified_time,
|
||||||
|
ensuredir,
|
||||||
|
os_path,
|
||||||
|
)
|
||||||
from sphinx.writers.text import TextTranslator, TextWriter
|
from sphinx.writers.text import TextTranslator, TextWriter
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
@@ -46,11 +50,11 @@ class TextBuilder(Builder):
|
|||||||
continue
|
continue
|
||||||
targetname = path.join(self.outdir, docname + self.out_suffix)
|
targetname = path.join(self.outdir, docname + self.out_suffix)
|
||||||
try:
|
try:
|
||||||
targetmtime = path.getmtime(targetname)
|
targetmtime = _last_modified_time(targetname)
|
||||||
except Exception:
|
except Exception:
|
||||||
targetmtime = 0
|
targetmtime = 0
|
||||||
try:
|
try:
|
||||||
srcmtime = path.getmtime(self.env.doc2path(docname))
|
srcmtime = _last_modified_time(self.env.doc2path(docname))
|
||||||
if srcmtime > targetmtime:
|
if srcmtime > targetmtime:
|
||||||
yield docname
|
yield docname
|
||||||
except OSError:
|
except OSError:
|
||||||
|
|||||||
@@ -12,7 +12,11 @@ from docutils.writers.docutils_xml import XMLTranslator
|
|||||||
from sphinx.builders import Builder
|
from sphinx.builders import Builder
|
||||||
from sphinx.locale import __
|
from sphinx.locale import __
|
||||||
from sphinx.util import logging
|
from sphinx.util import logging
|
||||||
from sphinx.util.osutil import ensuredir, os_path
|
from sphinx.util.osutil import (
|
||||||
|
_last_modified_time,
|
||||||
|
ensuredir,
|
||||||
|
os_path,
|
||||||
|
)
|
||||||
from sphinx.writers.xml import PseudoXMLWriter, XMLWriter
|
from sphinx.writers.xml import PseudoXMLWriter, XMLWriter
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
@@ -52,11 +56,11 @@ class XMLBuilder(Builder):
|
|||||||
continue
|
continue
|
||||||
targetname = path.join(self.outdir, docname + self.out_suffix)
|
targetname = path.join(self.outdir, docname + self.out_suffix)
|
||||||
try:
|
try:
|
||||||
targetmtime = path.getmtime(targetname)
|
targetmtime = _last_modified_time(targetname)
|
||||||
except Exception:
|
except Exception:
|
||||||
targetmtime = 0
|
targetmtime = 0
|
||||||
try:
|
try:
|
||||||
srcmtime = path.getmtime(self.env.doc2path(docname))
|
srcmtime = _last_modified_time(self.env.doc2path(docname))
|
||||||
if srcmtime > targetmtime:
|
if srcmtime > targetmtime:
|
||||||
yield docname
|
yield docname
|
||||||
except OSError:
|
except OSError:
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ from __future__ import annotations
|
|||||||
import functools
|
import functools
|
||||||
import os
|
import os
|
||||||
import pickle
|
import pickle
|
||||||
import time
|
|
||||||
from collections import defaultdict
|
from collections import defaultdict
|
||||||
from copy import copy
|
from copy import copy
|
||||||
from os import path
|
from os import path
|
||||||
@@ -17,10 +16,11 @@ from sphinx.errors import BuildEnvironmentError, DocumentError, ExtensionError,
|
|||||||
from sphinx.locale import __
|
from sphinx.locale import __
|
||||||
from sphinx.transforms import SphinxTransformer
|
from sphinx.transforms import SphinxTransformer
|
||||||
from sphinx.util import DownloadFiles, FilenameUniqDict, logging
|
from sphinx.util import DownloadFiles, FilenameUniqDict, logging
|
||||||
|
from sphinx.util._timestamps import _format_rfc3339_microseconds
|
||||||
from sphinx.util.docutils import LoggingReporter
|
from sphinx.util.docutils import LoggingReporter
|
||||||
from sphinx.util.i18n import CatalogRepository, docname_to_domain
|
from sphinx.util.i18n import CatalogRepository, docname_to_domain
|
||||||
from sphinx.util.nodes import is_translatable
|
from sphinx.util.nodes import is_translatable
|
||||||
from sphinx.util.osutil import canon_path, os_path
|
from sphinx.util.osutil import _last_modified_time, canon_path, os_path
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from collections.abc import Callable, Iterator
|
from collections.abc import Callable, Iterator
|
||||||
@@ -508,7 +508,8 @@ class BuildEnvironment:
|
|||||||
if newmtime > mtime:
|
if newmtime > mtime:
|
||||||
logger.debug('[build target] outdated %r: %s -> %s',
|
logger.debug('[build target] outdated %r: %s -> %s',
|
||||||
docname,
|
docname,
|
||||||
_format_modified_time(mtime), _format_modified_time(newmtime))
|
_format_rfc3339_microseconds(mtime),
|
||||||
|
_format_rfc3339_microseconds(newmtime))
|
||||||
changed.add(docname)
|
changed.add(docname)
|
||||||
continue
|
continue
|
||||||
# finally, check the mtime of dependencies
|
# finally, check the mtime of dependencies
|
||||||
@@ -528,7 +529,8 @@ class BuildEnvironment:
|
|||||||
logger.debug(
|
logger.debug(
|
||||||
'[build target] outdated %r from dependency %r: %s -> %s',
|
'[build target] outdated %r from dependency %r: %s -> %s',
|
||||||
docname, deppath,
|
docname, deppath,
|
||||||
_format_modified_time(mtime), _format_modified_time(depmtime),
|
_format_rfc3339_microseconds(mtime),
|
||||||
|
_format_rfc3339_microseconds(depmtime),
|
||||||
)
|
)
|
||||||
changed.add(docname)
|
changed.add(docname)
|
||||||
break
|
break
|
||||||
@@ -756,26 +758,6 @@ class BuildEnvironment:
|
|||||||
self.events.emit('env-check-consistency', self)
|
self.events.emit('env-check-consistency', self)
|
||||||
|
|
||||||
|
|
||||||
def _last_modified_time(filename: str | os.PathLike[str]) -> int:
|
|
||||||
"""Return the last modified time of ``filename``.
|
|
||||||
|
|
||||||
The time is returned as integer microseconds.
|
|
||||||
The lowest common denominator of modern file-systems seems to be
|
|
||||||
microsecond-level precision.
|
|
||||||
|
|
||||||
We prefer to err on the side of re-rendering a file,
|
|
||||||
so we round up to the nearest microsecond.
|
|
||||||
"""
|
|
||||||
# upside-down floor division to get the ceiling
|
|
||||||
return -(os.stat(filename).st_mtime_ns // -1_000)
|
|
||||||
|
|
||||||
|
|
||||||
def _format_modified_time(timestamp: int) -> str:
|
|
||||||
"""Return an RFC 3339 formatted string representing the given timestamp."""
|
|
||||||
seconds, fraction = divmod(timestamp, 10**6)
|
|
||||||
return time.strftime("%Y-%m-%d %H:%M:%S", time.gmtime(seconds)) + f'.{fraction // 1_000}'
|
|
||||||
|
|
||||||
|
|
||||||
def _traverse_toctree(
|
def _traverse_toctree(
|
||||||
traversed: set[str],
|
traversed: set[str],
|
||||||
parent: str | None,
|
parent: str | None,
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ from sphinx.transforms.post_transforms import SphinxPostTransform
|
|||||||
from sphinx.util import logging
|
from sphinx.util import logging
|
||||||
from sphinx.util.display import status_iterator
|
from sphinx.util.display import status_iterator
|
||||||
from sphinx.util.nodes import make_refnode
|
from sphinx.util.nodes import make_refnode
|
||||||
|
from sphinx.util.osutil import _last_modified_time
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from collections.abc import Iterable, Iterator
|
from collections.abc import Iterable, Iterator
|
||||||
@@ -231,7 +232,7 @@ def should_generate_module_page(app: Sphinx, modname: str) -> bool:
|
|||||||
page_filename = path.join(app.outdir, '_modules/', basename)
|
page_filename = path.join(app.outdir, '_modules/', basename)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
if path.getmtime(module_filename) <= path.getmtime(page_filename):
|
if _last_modified_time(module_filename) <= _last_modified_time(page_filename):
|
||||||
# generation is not needed if the HTML page is newer than module file.
|
# generation is not needed if the HTML page is newer than module file.
|
||||||
return False
|
return False
|
||||||
except OSError:
|
except OSError:
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import os
|
||||||
from os import path
|
from os import path
|
||||||
from pprint import pformat
|
from pprint import pformat
|
||||||
from typing import TYPE_CHECKING, Any
|
from typing import TYPE_CHECKING, Any
|
||||||
@@ -12,7 +13,7 @@ from jinja2.utils import open_if_exists, pass_context
|
|||||||
|
|
||||||
from sphinx.application import TemplateBridge
|
from sphinx.application import TemplateBridge
|
||||||
from sphinx.util import logging
|
from sphinx.util import logging
|
||||||
from sphinx.util.osutil import mtimes_of_files
|
from sphinx.util.osutil import _last_modified_time
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from collections.abc import Callable, Iterator
|
from collections.abc import Callable, Iterator
|
||||||
@@ -127,11 +128,11 @@ class SphinxFileSystemLoader(FileSystemLoader):
|
|||||||
with f:
|
with f:
|
||||||
contents = f.read().decode(self.encoding)
|
contents = f.read().decode(self.encoding)
|
||||||
|
|
||||||
mtime = path.getmtime(filename)
|
mtime = _last_modified_time(filename)
|
||||||
|
|
||||||
def uptodate() -> bool:
|
def uptodate() -> bool:
|
||||||
try:
|
try:
|
||||||
return path.getmtime(filename) == mtime
|
return _last_modified_time(filename) == mtime
|
||||||
except OSError:
|
except OSError:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
@@ -203,7 +204,13 @@ class BuiltinTemplateLoader(TemplateBridge, BaseLoader):
|
|||||||
return self.environment.from_string(source).render(context)
|
return self.environment.from_string(source).render(context)
|
||||||
|
|
||||||
def newest_template_mtime(self) -> float:
|
def newest_template_mtime(self) -> float:
|
||||||
return max(mtimes_of_files(self.pathchain, '.html'))
|
return max(
|
||||||
|
os.stat(os.path.join(root, sfile)).st_mtime_ns / 10**9
|
||||||
|
for dirname in self.pathchain
|
||||||
|
for root, _dirs, files in os.walk(dirname)
|
||||||
|
for sfile in files
|
||||||
|
if sfile.endswith('.html')
|
||||||
|
)
|
||||||
|
|
||||||
# Loader interface
|
# Loader interface
|
||||||
|
|
||||||
|
|||||||
@@ -29,10 +29,8 @@ from sphinx.util.nodes import ( # NoQA: F401
|
|||||||
from sphinx.util.osutil import ( # NoQA: F401
|
from sphinx.util.osutil import ( # NoQA: F401
|
||||||
SEP,
|
SEP,
|
||||||
copyfile,
|
copyfile,
|
||||||
copytimes,
|
|
||||||
ensuredir,
|
ensuredir,
|
||||||
make_filename,
|
make_filename,
|
||||||
mtimes_of_files,
|
|
||||||
os_path,
|
os_path,
|
||||||
relative_uri,
|
relative_uri,
|
||||||
)
|
)
|
||||||
|
|||||||
12
sphinx/util/_timestamps.py
Normal file
12
sphinx/util/_timestamps.py
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import time
|
||||||
|
|
||||||
|
|
||||||
|
def _format_rfc3339_microseconds(timestamp: int, /) -> str:
|
||||||
|
"""Return an RFC 3339 formatted string representing the given timestamp.
|
||||||
|
|
||||||
|
:param timestamp: The timestamp to format, in microseconds.
|
||||||
|
"""
|
||||||
|
seconds, fraction = divmod(timestamp, 10**6)
|
||||||
|
return time.strftime('%Y-%m-%d %H:%M:%S', time.gmtime(seconds)) + f'.{fraction // 1_000}'
|
||||||
@@ -15,7 +15,12 @@ from babel.messages.pofile import read_po
|
|||||||
from sphinx.errors import SphinxError
|
from sphinx.errors import SphinxError
|
||||||
from sphinx.locale import __
|
from sphinx.locale import __
|
||||||
from sphinx.util import logging
|
from sphinx.util import logging
|
||||||
from sphinx.util.osutil import SEP, canon_path, relpath
|
from sphinx.util.osutil import (
|
||||||
|
SEP,
|
||||||
|
_last_modified_time,
|
||||||
|
canon_path,
|
||||||
|
relpath,
|
||||||
|
)
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
import datetime as dt
|
import datetime as dt
|
||||||
@@ -84,7 +89,7 @@ class CatalogInfo(LocaleFileInfoBase):
|
|||||||
def is_outdated(self) -> bool:
|
def is_outdated(self) -> bool:
|
||||||
return (
|
return (
|
||||||
not path.exists(self.mo_path) or
|
not path.exists(self.mo_path) or
|
||||||
path.getmtime(self.mo_path) < path.getmtime(self.po_path))
|
_last_modified_time(self.mo_path) < _last_modified_time(self.po_path))
|
||||||
|
|
||||||
def write_mo(self, locale: str, use_fuzzy: bool = False) -> None:
|
def write_mo(self, locale: str, use_fuzzy: bool = False) -> None:
|
||||||
with open(self.po_path, encoding=self.charset) as file_po:
|
with open(self.po_path, encoding=self.charset) as file_po:
|
||||||
|
|||||||
@@ -16,7 +16,6 @@ from typing import TYPE_CHECKING
|
|||||||
from sphinx.locale import __
|
from sphinx.locale import __
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from collections.abc import Iterator
|
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from types import TracebackType
|
from types import TracebackType
|
||||||
from typing import Any
|
from typing import Any
|
||||||
@@ -72,20 +71,25 @@ def ensuredir(file: str | os.PathLike[str]) -> None:
|
|||||||
os.makedirs(file, exist_ok=True)
|
os.makedirs(file, exist_ok=True)
|
||||||
|
|
||||||
|
|
||||||
def mtimes_of_files(dirnames: list[str], suffix: str) -> Iterator[float]:
|
def _last_modified_time(source: str | os.PathLike[str], /) -> int:
|
||||||
for dirname in dirnames:
|
"""Return the last modified time of ``filename``.
|
||||||
for root, _dirs, files in os.walk(dirname):
|
|
||||||
for sfile in files:
|
The time is returned as integer microseconds.
|
||||||
if sfile.endswith(suffix):
|
The lowest common denominator of modern file-systems seems to be
|
||||||
with contextlib.suppress(OSError):
|
microsecond-level precision.
|
||||||
yield path.getmtime(path.join(root, sfile))
|
|
||||||
|
We prefer to err on the side of re-rendering a file,
|
||||||
|
so we round up to the nearest microsecond.
|
||||||
|
"""
|
||||||
|
st = source.stat() if isinstance(source, os.DirEntry) else os.stat(source)
|
||||||
|
# upside-down floor division to get the ceiling
|
||||||
|
return -(st.st_mtime_ns // -1_000)
|
||||||
|
|
||||||
|
|
||||||
def copytimes(source: str | os.PathLike[str], dest: str | os.PathLike[str]) -> None:
|
def _copy_times(source: str | os.PathLike[str], dest: str | os.PathLike[str]) -> None:
|
||||||
"""Copy a file's modification times."""
|
"""Copy a file's modification times."""
|
||||||
st = os.stat(source)
|
st = source.stat() if isinstance(source, os.DirEntry) else os.stat(source)
|
||||||
if hasattr(os, 'utime'):
|
os.utime(dest, ns=(st.st_atime_ns, st.st_mtime_ns))
|
||||||
os.utime(dest, (st.st_atime, st.st_mtime))
|
|
||||||
|
|
||||||
|
|
||||||
def copyfile(
|
def copyfile(
|
||||||
@@ -128,7 +132,7 @@ def copyfile(
|
|||||||
shutil.copyfile(source, dest)
|
shutil.copyfile(source, dest)
|
||||||
with contextlib.suppress(OSError):
|
with contextlib.suppress(OSError):
|
||||||
# don't do full copystat because the source may be read-only
|
# don't do full copystat because the source may be read-only
|
||||||
copytimes(source, dest)
|
_copy_times(source, dest)
|
||||||
|
|
||||||
|
|
||||||
_no_fn_re = re.compile(r'[^a-zA-Z0-9_-]')
|
_no_fn_re = re.compile(r'[^a-zA-Z0-9_-]')
|
||||||
|
|||||||
@@ -678,7 +678,7 @@ def test_remove_old_files(tmp_path: Path):
|
|||||||
(gen_dir / 'other.rst').write_text('', encoding='utf8')
|
(gen_dir / 'other.rst').write_text('', encoding='utf8')
|
||||||
apidoc_main(['-o', str(gen_dir), str(module_dir)])
|
apidoc_main(['-o', str(gen_dir), str(module_dir)])
|
||||||
assert set(gen_dir.iterdir()) == {gen_dir / 'modules.rst', gen_dir / 'example.rst', gen_dir / 'other.rst'}
|
assert set(gen_dir.iterdir()) == {gen_dir / 'modules.rst', gen_dir / 'example.rst', gen_dir / 'other.rst'}
|
||||||
example_mtime = (gen_dir / 'example.rst').stat().st_mtime
|
example_mtime = (gen_dir / 'example.rst').stat().st_mtime_ns
|
||||||
apidoc_main(['--remove-old', '-o', str(gen_dir), str(module_dir)])
|
apidoc_main(['--remove-old', '-o', str(gen_dir), str(module_dir)])
|
||||||
assert set(gen_dir.iterdir()) == {gen_dir / 'modules.rst', gen_dir / 'example.rst'}
|
assert set(gen_dir.iterdir()) == {gen_dir / 'modules.rst', gen_dir / 'example.rst'}
|
||||||
assert (gen_dir / 'example.rst').stat().st_mtime == example_mtime
|
assert (gen_dir / 'example.rst').stat().st_mtime_ns == example_mtime
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ def write_mo(pathname, po):
|
|||||||
return mofile.write_mo(f, po)
|
return mofile.write_mo(f, po)
|
||||||
|
|
||||||
|
|
||||||
def _set_mtime_ns(target, value):
|
def _set_mtime_ns(target: str | os.PathLike[str], value: int) -> int:
|
||||||
os.utime(target, ns=(value, value))
|
os.utime(target, ns=(value, value))
|
||||||
return os.stat(target).st_mtime_ns
|
return os.stat(target).st_mtime_ns
|
||||||
|
|
||||||
|
|||||||
@@ -46,25 +46,25 @@ def test_SphinxFileOutput(tmpdir):
|
|||||||
filename = str(tmpdir / 'test.txt')
|
filename = str(tmpdir / 'test.txt')
|
||||||
output = SphinxFileOutput(destination_path=filename)
|
output = SphinxFileOutput(destination_path=filename)
|
||||||
output.write(content)
|
output.write(content)
|
||||||
os.utime(filename, (0, 0))
|
os.utime(filename, ns=(0, 0))
|
||||||
|
|
||||||
# overwrite it again
|
# overwrite it again
|
||||||
output.write(content)
|
output.write(content)
|
||||||
assert os.stat(filename).st_mtime != 0 # updated
|
assert os.stat(filename).st_mtime_ns != 0 # updated
|
||||||
|
|
||||||
# write test2.txt at first
|
# write test2.txt at first
|
||||||
filename = str(tmpdir / 'test2.txt')
|
filename = str(tmpdir / 'test2.txt')
|
||||||
output = SphinxFileOutput(destination_path=filename, overwrite_if_changed=True)
|
output = SphinxFileOutput(destination_path=filename, overwrite_if_changed=True)
|
||||||
output.write(content)
|
output.write(content)
|
||||||
os.utime(filename, (0, 0))
|
os.utime(filename, ns=(0, 0))
|
||||||
|
|
||||||
# overwrite it again
|
# overwrite it again
|
||||||
output.write(content)
|
output.write(content)
|
||||||
assert os.stat(filename).st_mtime == 0 # not updated
|
assert os.stat(filename).st_mtime_ns == 0 # not updated
|
||||||
|
|
||||||
# overwrite it again (content changed)
|
# overwrite it again (content changed)
|
||||||
output.write(content + "; content change")
|
output.write(content + "; content change")
|
||||||
assert os.stat(filename).st_mtime != 0 # updated
|
assert os.stat(filename).st_mtime_ns != 0 # updated
|
||||||
|
|
||||||
|
|
||||||
def test_SphinxTranslator(app):
|
def test_SphinxTranslator(app):
|
||||||
|
|||||||
@@ -38,7 +38,8 @@ def test_catalog_outdated(tmp_path):
|
|||||||
mo_file.write_text('#', encoding='utf8')
|
mo_file.write_text('#', encoding='utf8')
|
||||||
assert not cat.is_outdated() # if mo is exist and newer than po
|
assert not cat.is_outdated() # if mo is exist and newer than po
|
||||||
|
|
||||||
os.utime(mo_file, (os.stat(mo_file).st_mtime - 10,) * 2) # to be outdate
|
new_mtime = os.stat(mo_file).st_mtime_ns - 10_000_000_000
|
||||||
|
os.utime(mo_file, ns=(new_mtime, new_mtime)) # to be outdated
|
||||||
assert cat.is_outdated() # if mo is exist and older than po
|
assert cat.is_outdated() # if mo is exist and older than po
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user