mirror of
https://github.com/sphinx-doc/sphinx.git
synced 2025-02-25 18:55:22 -06:00
[gettext] fix flaky test on windows (#11940)
This commit is contained in:
parent
faa33a53a3
commit
707bfbd669
@ -8,7 +8,6 @@ import os.path
|
|||||||
import re
|
import re
|
||||||
import shutil
|
import shutil
|
||||||
import time
|
import time
|
||||||
from pathlib import Path
|
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
from babel.messages import mofile, pofile
|
from babel.messages import mofile, pofile
|
||||||
@ -19,10 +18,12 @@ from sphinx import locale
|
|||||||
from sphinx.testing.util import assert_node, etree_parse, strip_escseq
|
from sphinx.testing.util import assert_node, etree_parse, strip_escseq
|
||||||
from sphinx.util.nodes import NodeMatcher
|
from sphinx.util.nodes import NodeMatcher
|
||||||
|
|
||||||
|
_CATALOG_LOCALE = 'xx'
|
||||||
|
|
||||||
sphinx_intl = pytest.mark.sphinx(
|
sphinx_intl = pytest.mark.sphinx(
|
||||||
testroot='intl',
|
testroot='intl',
|
||||||
confoverrides={
|
confoverrides={
|
||||||
'language': 'xx', 'locale_dirs': ['.'],
|
'language': _CATALOG_LOCALE, 'locale_dirs': ['.'],
|
||||||
'gettext_compact': False,
|
'gettext_compact': False,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
@ -38,22 +39,20 @@ def write_mo(pathname, po):
|
|||||||
return mofile.write_mo(f, po)
|
return mofile.write_mo(f, po)
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture(autouse=True)
|
def _set_mtime_ns(target, value):
|
||||||
def _setup_intl(app_params):
|
os.utime(target, ns=(value, value))
|
||||||
assert isinstance(app_params.kwargs['srcdir'], Path)
|
return os.stat(target).st_mtime_ns
|
||||||
srcdir = app_params.kwargs['srcdir']
|
|
||||||
for dirpath, _dirs, files in os.walk(srcdir):
|
|
||||||
dirpath = Path(dirpath)
|
|
||||||
for f in [f for f in files if f.endswith('.po')]:
|
|
||||||
po = str(dirpath / f)
|
|
||||||
mo = srcdir / 'xx' / 'LC_MESSAGES' / (
|
|
||||||
os.path.relpath(po[:-3], srcdir) + '.mo')
|
|
||||||
if not mo.parent.exists():
|
|
||||||
mo.parent.mkdir(parents=True, exist_ok=True)
|
|
||||||
|
|
||||||
if not mo.exists() or os.stat(mo).st_mtime < os.stat(po).st_mtime:
|
|
||||||
# compile .mo file only if needed
|
def _get_bom_intl_path(app):
|
||||||
write_mo(mo, read_po(po))
|
basedir = app.srcdir / _CATALOG_LOCALE / 'LC_MESSAGES'
|
||||||
|
return basedir / 'bom.po', basedir / 'bom.mo'
|
||||||
|
|
||||||
|
|
||||||
|
def _get_update_targets(app):
|
||||||
|
app.env.find_files(app.config, app.builder)
|
||||||
|
added, changed, removed = app.env.get_outdated_files(config_changed=False)
|
||||||
|
return added, changed, removed
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture(autouse=True)
|
@pytest.fixture(autouse=True)
|
||||||
@ -316,7 +315,7 @@ def test_text_glossary_term_inconsistencies(app, warning):
|
|||||||
def test_gettext_section(app):
|
def test_gettext_section(app):
|
||||||
app.build()
|
app.build()
|
||||||
# --- section
|
# --- section
|
||||||
expect = read_po(app.srcdir / 'xx' / 'LC_MESSAGES' / 'section.po')
|
expect = read_po(app.srcdir / _CATALOG_LOCALE / 'LC_MESSAGES' / 'section.po')
|
||||||
actual = read_po(app.outdir / 'section.pot')
|
actual = read_po(app.outdir / 'section.pot')
|
||||||
for expect_msg in [m for m in expect if m.id]:
|
for expect_msg in [m for m in expect if m.id]:
|
||||||
assert expect_msg.id in [m.id for m in actual if m.id]
|
assert expect_msg.id in [m.id for m in actual if m.id]
|
||||||
@ -329,7 +328,7 @@ def test_text_section(app):
|
|||||||
app.build()
|
app.build()
|
||||||
# --- section
|
# --- section
|
||||||
result = (app.outdir / 'section.txt').read_text(encoding='utf8')
|
result = (app.outdir / 'section.txt').read_text(encoding='utf8')
|
||||||
expect = read_po(app.srcdir / 'xx' / 'LC_MESSAGES' / 'section.po')
|
expect = read_po(app.srcdir / _CATALOG_LOCALE / 'LC_MESSAGES' / 'section.po')
|
||||||
for expect_msg in [m for m in expect if m.id]:
|
for expect_msg in [m for m in expect if m.id]:
|
||||||
assert expect_msg.string in result
|
assert expect_msg.string in result
|
||||||
|
|
||||||
@ -468,12 +467,12 @@ def test_text_admonitions(app):
|
|||||||
def test_gettext_toctree(app):
|
def test_gettext_toctree(app):
|
||||||
app.build()
|
app.build()
|
||||||
# --- toctree (index.rst)
|
# --- toctree (index.rst)
|
||||||
expect = read_po(app.srcdir / 'xx' / 'LC_MESSAGES' / 'index.po')
|
expect = read_po(app.srcdir / _CATALOG_LOCALE / 'LC_MESSAGES' / 'index.po')
|
||||||
actual = read_po(app.outdir / 'index.pot')
|
actual = read_po(app.outdir / 'index.pot')
|
||||||
for expect_msg in [m for m in expect if m.id]:
|
for expect_msg in [m for m in expect if m.id]:
|
||||||
assert expect_msg.id in [m.id for m in actual if m.id]
|
assert expect_msg.id in [m.id for m in actual if m.id]
|
||||||
# --- toctree (toctree.rst)
|
# --- toctree (toctree.rst)
|
||||||
expect = read_po(app.srcdir / 'xx' / 'LC_MESSAGES' / 'toctree.po')
|
expect = read_po(app.srcdir / _CATALOG_LOCALE / 'LC_MESSAGES' / 'toctree.po')
|
||||||
actual = read_po(app.outdir / 'toctree.pot')
|
actual = read_po(app.outdir / 'toctree.pot')
|
||||||
for expect_msg in [m for m in expect if m.id]:
|
for expect_msg in [m for m in expect if m.id]:
|
||||||
assert expect_msg.id in [m.id for m in actual if m.id]
|
assert expect_msg.id in [m.id for m in actual if m.id]
|
||||||
@ -485,7 +484,7 @@ def test_gettext_toctree(app):
|
|||||||
def test_gettext_table(app):
|
def test_gettext_table(app):
|
||||||
app.build()
|
app.build()
|
||||||
# --- toctree
|
# --- toctree
|
||||||
expect = read_po(app.srcdir / 'xx' / 'LC_MESSAGES' / 'table.po')
|
expect = read_po(app.srcdir / _CATALOG_LOCALE / 'LC_MESSAGES' / 'table.po')
|
||||||
actual = read_po(app.outdir / 'table.pot')
|
actual = read_po(app.outdir / 'table.pot')
|
||||||
for expect_msg in [m for m in expect if m.id]:
|
for expect_msg in [m for m in expect if m.id]:
|
||||||
assert expect_msg.id in [m.id for m in actual if m.id]
|
assert expect_msg.id in [m.id for m in actual if m.id]
|
||||||
@ -498,7 +497,7 @@ def test_text_table(app):
|
|||||||
app.build()
|
app.build()
|
||||||
# --- toctree
|
# --- toctree
|
||||||
result = (app.outdir / 'table.txt').read_text(encoding='utf8')
|
result = (app.outdir / 'table.txt').read_text(encoding='utf8')
|
||||||
expect = read_po(app.srcdir / 'xx' / 'LC_MESSAGES' / 'table.po')
|
expect = read_po(app.srcdir / _CATALOG_LOCALE / 'LC_MESSAGES' / 'table.po')
|
||||||
for expect_msg in [m for m in expect if m.id]:
|
for expect_msg in [m for m in expect if m.id]:
|
||||||
assert expect_msg.string in result
|
assert expect_msg.string in result
|
||||||
|
|
||||||
@ -515,7 +514,7 @@ def test_text_toctree(app):
|
|||||||
assert 'TABLE OF CONTENTS' in result
|
assert 'TABLE OF CONTENTS' in result
|
||||||
# --- toctree (toctree.rst)
|
# --- toctree (toctree.rst)
|
||||||
result = (app.outdir / 'toctree.txt').read_text(encoding='utf8')
|
result = (app.outdir / 'toctree.txt').read_text(encoding='utf8')
|
||||||
expect = read_po(app.srcdir / 'xx' / 'LC_MESSAGES' / 'toctree.po')
|
expect = read_po(app.srcdir / _CATALOG_LOCALE / 'LC_MESSAGES' / 'toctree.po')
|
||||||
for expect_msg in (m for m in expect if m.id):
|
for expect_msg in (m for m in expect if m.id):
|
||||||
assert expect_msg.string in result
|
assert expect_msg.string in result
|
||||||
|
|
||||||
@ -526,7 +525,7 @@ def test_text_toctree(app):
|
|||||||
def test_gettext_topic(app):
|
def test_gettext_topic(app):
|
||||||
app.build()
|
app.build()
|
||||||
# --- topic
|
# --- topic
|
||||||
expect = read_po(app.srcdir / 'xx' / 'LC_MESSAGES' / 'topic.po')
|
expect = read_po(app.srcdir / _CATALOG_LOCALE / 'LC_MESSAGES' / 'topic.po')
|
||||||
actual = read_po(app.outdir / 'topic.pot')
|
actual = read_po(app.outdir / 'topic.pot')
|
||||||
for expect_msg in [m for m in expect if m.id]:
|
for expect_msg in [m for m in expect if m.id]:
|
||||||
assert expect_msg.id in [m.id for m in actual if m.id]
|
assert expect_msg.id in [m.id for m in actual if m.id]
|
||||||
@ -539,7 +538,7 @@ def test_text_topic(app):
|
|||||||
app.build()
|
app.build()
|
||||||
# --- topic
|
# --- topic
|
||||||
result = (app.outdir / 'topic.txt').read_text(encoding='utf8')
|
result = (app.outdir / 'topic.txt').read_text(encoding='utf8')
|
||||||
expect = read_po(app.srcdir / 'xx' / 'LC_MESSAGES' / 'topic.po')
|
expect = read_po(app.srcdir / _CATALOG_LOCALE / 'LC_MESSAGES' / 'topic.po')
|
||||||
for expect_msg in [m for m in expect if m.id]:
|
for expect_msg in [m for m in expect if m.id]:
|
||||||
assert expect_msg.string in result
|
assert expect_msg.string in result
|
||||||
|
|
||||||
@ -550,7 +549,7 @@ def test_text_topic(app):
|
|||||||
def test_gettext_definition_terms(app):
|
def test_gettext_definition_terms(app):
|
||||||
app.build()
|
app.build()
|
||||||
# --- definition terms: regression test for #2198, #2205
|
# --- definition terms: regression test for #2198, #2205
|
||||||
expect = read_po(app.srcdir / 'xx' / 'LC_MESSAGES' / 'definition_terms.po')
|
expect = read_po(app.srcdir / _CATALOG_LOCALE / 'LC_MESSAGES' / 'definition_terms.po')
|
||||||
actual = read_po(app.outdir / 'definition_terms.pot')
|
actual = read_po(app.outdir / 'definition_terms.pot')
|
||||||
for expect_msg in [m for m in expect if m.id]:
|
for expect_msg in [m for m in expect if m.id]:
|
||||||
assert expect_msg.id in [m.id for m in actual if m.id]
|
assert expect_msg.id in [m.id for m in actual if m.id]
|
||||||
@ -562,7 +561,7 @@ def test_gettext_definition_terms(app):
|
|||||||
def test_gettext_glossary_terms(app, warning):
|
def test_gettext_glossary_terms(app, warning):
|
||||||
app.build()
|
app.build()
|
||||||
# --- glossary terms: regression test for #1090
|
# --- glossary terms: regression test for #1090
|
||||||
expect = read_po(app.srcdir / 'xx' / 'LC_MESSAGES' / 'glossary_terms.po')
|
expect = read_po(app.srcdir / _CATALOG_LOCALE / 'LC_MESSAGES' / 'glossary_terms.po')
|
||||||
actual = read_po(app.outdir / 'glossary_terms.pot')
|
actual = read_po(app.outdir / 'glossary_terms.pot')
|
||||||
for expect_msg in [m for m in expect if m.id]:
|
for expect_msg in [m for m in expect if m.id]:
|
||||||
assert expect_msg.id in [m.id for m in actual if m.id]
|
assert expect_msg.id in [m.id for m in actual if m.id]
|
||||||
@ -576,7 +575,7 @@ def test_gettext_glossary_terms(app, warning):
|
|||||||
def test_gettext_glossary_term_inconsistencies(app):
|
def test_gettext_glossary_term_inconsistencies(app):
|
||||||
app.build()
|
app.build()
|
||||||
# --- glossary term inconsistencies: regression test for #1090
|
# --- glossary term inconsistencies: regression test for #1090
|
||||||
expect = read_po(app.srcdir / 'xx' / 'LC_MESSAGES' / 'glossary_terms_inconsistency.po')
|
expect = read_po(app.srcdir / _CATALOG_LOCALE / 'LC_MESSAGES' / 'glossary_terms_inconsistency.po')
|
||||||
actual = read_po(app.outdir / 'glossary_terms_inconsistency.pot')
|
actual = read_po(app.outdir / 'glossary_terms_inconsistency.pot')
|
||||||
for expect_msg in [m for m in expect if m.id]:
|
for expect_msg in [m for m in expect if m.id]:
|
||||||
assert expect_msg.id in [m.id for m in actual if m.id]
|
assert expect_msg.id in [m.id for m in actual if m.id]
|
||||||
@ -588,7 +587,7 @@ def test_gettext_glossary_term_inconsistencies(app):
|
|||||||
def test_gettext_literalblock(app):
|
def test_gettext_literalblock(app):
|
||||||
app.build()
|
app.build()
|
||||||
# --- gettext builder always ignores ``only`` directive
|
# --- gettext builder always ignores ``only`` directive
|
||||||
expect = read_po(app.srcdir / 'xx' / 'LC_MESSAGES' / 'literalblock.po')
|
expect = read_po(app.srcdir / _CATALOG_LOCALE / 'LC_MESSAGES' / 'literalblock.po')
|
||||||
actual = read_po(app.outdir / 'literalblock.pot')
|
actual = read_po(app.outdir / 'literalblock.pot')
|
||||||
for expect_msg in [m for m in expect if m.id]:
|
for expect_msg in [m for m in expect if m.id]:
|
||||||
if len(expect_msg.id.splitlines()) == 1:
|
if len(expect_msg.id.splitlines()) == 1:
|
||||||
@ -604,7 +603,7 @@ def test_gettext_literalblock(app):
|
|||||||
def test_gettext_buildr_ignores_only_directive(app):
|
def test_gettext_buildr_ignores_only_directive(app):
|
||||||
app.build()
|
app.build()
|
||||||
# --- gettext builder always ignores ``only`` directive
|
# --- gettext builder always ignores ``only`` directive
|
||||||
expect = read_po(app.srcdir / 'xx' / 'LC_MESSAGES' / 'only.po')
|
expect = read_po(app.srcdir / _CATALOG_LOCALE / 'LC_MESSAGES' / 'only.po')
|
||||||
actual = read_po(app.outdir / 'only.pot')
|
actual = read_po(app.outdir / 'only.pot')
|
||||||
for expect_msg in [m for m in expect if m.id]:
|
for expect_msg in [m for m in expect if m.id]:
|
||||||
assert expect_msg.id in [m.id for m in actual if m.id]
|
assert expect_msg.id in [m.id for m in actual if m.id]
|
||||||
@ -633,7 +632,7 @@ def test_translation_progress_substitution(app):
|
|||||||
|
|
||||||
|
|
||||||
@pytest.mark.sphinx(testroot='intl', freshenv=True, confoverrides={
|
@pytest.mark.sphinx(testroot='intl', freshenv=True, confoverrides={
|
||||||
'language': 'xx', 'locale_dirs': ['.'],
|
'language': _CATALOG_LOCALE, 'locale_dirs': ['.'],
|
||||||
'gettext_compact': False,
|
'gettext_compact': False,
|
||||||
'translation_progress_classes': True,
|
'translation_progress_classes': True,
|
||||||
})
|
})
|
||||||
@ -681,50 +680,188 @@ def test_translation_progress_classes_true(app):
|
|||||||
assert len(doctree[0]) == 20
|
assert len(doctree[0]) == 20
|
||||||
|
|
||||||
|
|
||||||
|
class _MockClock:
|
||||||
|
"""Object for mocking :func:`time.time_ns` (if needed).
|
||||||
|
|
||||||
|
Use :meth:`sleep` to make this specific clock sleep for some time.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def time(self) -> int:
|
||||||
|
"""Nanosecond since 'fake' epoch."""
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
|
def sleep(self, ds: float) -> None:
|
||||||
|
"""Sleep *ds* seconds."""
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
|
|
||||||
|
class _MockWindowsClock(_MockClock):
|
||||||
|
"""Object for mocking :func:`time.time_ns` on Windows platforms.
|
||||||
|
|
||||||
|
The result is in 'nanoseconds' but with a microsecond resolution
|
||||||
|
so that the division by 1_000 does not cause rounding issues.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self) -> None:
|
||||||
|
self.us: int = 0 # current microsecond 'tick'
|
||||||
|
|
||||||
|
def time(self) -> int:
|
||||||
|
ret = 1_000 * self.us
|
||||||
|
self.us += 1
|
||||||
|
return ret
|
||||||
|
|
||||||
|
def sleep(self, ds: float) -> None:
|
||||||
|
self.us += int(ds * 1e6)
|
||||||
|
|
||||||
|
|
||||||
|
class _MockUnixClock(_MockClock):
|
||||||
|
"""Object for mocking :func:`time.time_ns` on Unix platforms.
|
||||||
|
|
||||||
|
Since nothing is needed for Unix platforms, this object acts as
|
||||||
|
a proxy so that the API is the same as :class:`_MockWindowsClock`.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def time(self) -> int:
|
||||||
|
return time.time_ns()
|
||||||
|
|
||||||
|
def sleep(self, ds: float) -> None:
|
||||||
|
time.sleep(ds)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture()
|
||||||
|
def mock_time_and_i18n(
|
||||||
|
monkeypatch: pytest.MonkeyPatch,
|
||||||
|
) -> tuple[pytest.MonkeyPatch, _MockClock]:
|
||||||
|
from sphinx.util.i18n import CatalogInfo
|
||||||
|
|
||||||
|
# save the 'original' definition
|
||||||
|
catalog_write_mo = CatalogInfo.write_mo
|
||||||
|
|
||||||
|
def mock_write_mo(self, locale, use_fuzzy=False):
|
||||||
|
catalog_write_mo(self, locale, use_fuzzy)
|
||||||
|
# ensure that the .mo file being written has a correct fake timestamp
|
||||||
|
_set_mtime_ns(self.mo_path, time.time_ns())
|
||||||
|
|
||||||
|
# see: https://github.com/pytest-dev/pytest/issues/363
|
||||||
|
with pytest.MonkeyPatch.context() as mock:
|
||||||
|
if os.name == 'posix':
|
||||||
|
clock = _MockUnixClock()
|
||||||
|
else:
|
||||||
|
# When using pytest.mark.parametrize() to emulate test repetition,
|
||||||
|
# the teardown phase on Windows fails due to an error apparently in
|
||||||
|
# the colorama.ansitowin32 module, so we forcibly disable colors.
|
||||||
|
mock.setenv('NO_COLOR', '1')
|
||||||
|
# apply the patch only for Windows
|
||||||
|
clock = _MockWindowsClock()
|
||||||
|
mock.setattr('time.time_ns', clock.time)
|
||||||
|
# Use clock.sleep() to emulate time.sleep() but do not try
|
||||||
|
# to mock the latter since this might break other libraries.
|
||||||
|
mock.setattr('sphinx.util.i18n.CatalogInfo.write_mo', mock_write_mo)
|
||||||
|
yield mock, clock
|
||||||
|
|
||||||
|
|
||||||
@sphinx_intl
|
@sphinx_intl
|
||||||
# use individual shared_result directory to avoid "incompatible doctree" error
|
# use the same testroot as 'gettext' since the latter contains less PO files
|
||||||
@pytest.mark.sphinx(testroot='builder-gettext-dont-rebuild-mo')
|
@pytest.mark.sphinx('dummy', testroot='builder-gettext-dont-rebuild-mo', freshenv=True)
|
||||||
def test_gettext_dont_rebuild_mo(make_app, app_params):
|
def test_dummy_should_rebuild_mo(mock_time_and_i18n, make_app, app_params):
|
||||||
# --- don't rebuild by .mo mtime
|
mock, clock = mock_time_and_i18n
|
||||||
def get_update_targets(app_):
|
assert os.name == 'posix' or clock.time() == 0
|
||||||
app_.env.find_files(app_.config, app_.builder)
|
|
||||||
added, changed, removed = app_.env.get_outdated_files(config_changed=False)
|
|
||||||
return added, changed, removed
|
|
||||||
|
|
||||||
args, kwargs = app_params
|
args, kwargs = app_params
|
||||||
|
app = make_app(*args, **kwargs)
|
||||||
|
po_path, mo_path = _get_bom_intl_path(app)
|
||||||
|
|
||||||
|
# creation time of the those files (order does not matter)
|
||||||
|
bom_rst = app.srcdir / 'bom.rst'
|
||||||
|
bom_rst_time = time.time_ns()
|
||||||
|
|
||||||
|
index_rst = app.srcdir / 'index.rst'
|
||||||
|
index_rst_time = time.time_ns()
|
||||||
|
po_time = time.time_ns()
|
||||||
|
|
||||||
|
# patch the 'creation time' of the source files
|
||||||
|
assert _set_mtime_ns(po_path, po_time) == po_time
|
||||||
|
assert _set_mtime_ns(bom_rst, bom_rst_time) == bom_rst_time
|
||||||
|
assert _set_mtime_ns(index_rst, index_rst_time) == index_rst_time
|
||||||
|
|
||||||
|
assert not mo_path.exists()
|
||||||
|
# when writing mo files, the counter is updated by calling
|
||||||
|
# patch_write_mo which is called to create .mo files (and
|
||||||
|
# thus the timestamp of the files are not those given by
|
||||||
|
# the OS but our fake ones)
|
||||||
|
app.build()
|
||||||
|
assert mo_path.exists()
|
||||||
|
# Do a real sleep on POSIX, or simulate a sleep on Windows
|
||||||
|
# to ensure that calls to time.time_ns() remain consistent.
|
||||||
|
clock.sleep(0.1 if os.name == 'posix' else 1)
|
||||||
|
|
||||||
|
# check that the source files were not modified
|
||||||
|
assert bom_rst.stat().st_mtime_ns == bom_rst_time
|
||||||
|
assert index_rst.stat().st_mtime_ns == index_rst_time
|
||||||
|
# check that the 'bom' document is discovered after the .mo
|
||||||
|
# file has been written on the disk (i.e., read_doc() is called
|
||||||
|
# after the creation of the .mo files)
|
||||||
|
assert app.env.all_docs['bom'] > mo_path.stat().st_mtime_ns // 1000
|
||||||
|
|
||||||
# phase1: build document with non-gettext builder and generate mo file in srcdir
|
|
||||||
app0 = make_app('dummy', *args, **kwargs)
|
|
||||||
app0.build()
|
|
||||||
time.sleep(0.01)
|
|
||||||
assert (app0.srcdir / 'xx' / 'LC_MESSAGES' / 'bom.mo').exists()
|
|
||||||
# Since it is after the build, the number of documents to be updated is 0
|
# Since it is after the build, the number of documents to be updated is 0
|
||||||
update_targets = get_update_targets(app0)
|
update_targets = _get_update_targets(app)
|
||||||
assert update_targets[1] == set(), update_targets
|
assert update_targets[1] == set()
|
||||||
# When rewriting the timestamp of mo file, the number of documents to be
|
# When rewriting the timestamp of mo file, the number of documents to be
|
||||||
# updated will be changed.
|
# updated will be changed.
|
||||||
mtime = (app0.srcdir / 'xx' / 'LC_MESSAGES' / 'bom.mo').stat().st_mtime
|
new_mo_time = time.time_ns()
|
||||||
os.utime(app0.srcdir / 'xx' / 'LC_MESSAGES' / 'bom.mo', (mtime + 5, mtime + 5))
|
assert _set_mtime_ns(mo_path, new_mo_time) == new_mo_time
|
||||||
update_targets = get_update_targets(app0)
|
update_targets = _get_update_targets(app)
|
||||||
assert update_targets[1] == {'bom'}, update_targets
|
assert update_targets[1] == {'bom'}
|
||||||
|
mock.undo() # explicit call since it's not a context
|
||||||
|
|
||||||
# Because doctree for gettext builder can not be shared with other builders,
|
# remove all sources for the next test
|
||||||
# erase doctreedir before gettext build.
|
shutil.rmtree(app.srcdir, ignore_errors=True)
|
||||||
shutil.rmtree(app0.doctreedir)
|
time.sleep(0.1 if os.name == 'posix' else 0.5) # real sleep
|
||||||
|
|
||||||
# phase2: build document with gettext builder.
|
|
||||||
|
@sphinx_intl
|
||||||
|
@pytest.mark.sphinx('gettext', testroot='builder-gettext-dont-rebuild-mo', freshenv=True)
|
||||||
|
def test_gettext_dont_rebuild_mo(mock_time_and_i18n, app):
|
||||||
|
mock, clock = mock_time_and_i18n
|
||||||
|
assert os.name == 'posix' or clock.time() == 0
|
||||||
|
|
||||||
|
assert app.srcdir.exists()
|
||||||
|
|
||||||
|
# patch the 'creation time' of the source files
|
||||||
|
bom_rst = app.srcdir / 'bom.rst'
|
||||||
|
bom_rst_time = time.time_ns()
|
||||||
|
assert _set_mtime_ns(bom_rst, bom_rst_time) == bom_rst_time
|
||||||
|
|
||||||
|
index_rst = app.srcdir / 'index.rst'
|
||||||
|
index_rst_time = time.time_ns()
|
||||||
|
assert _set_mtime_ns(index_rst, index_rst_time) == index_rst_time
|
||||||
|
|
||||||
|
# phase 1: create fake MO file in the src directory
|
||||||
|
po_path, mo_path = _get_bom_intl_path(app)
|
||||||
|
write_mo(mo_path, read_po(po_path))
|
||||||
|
po_time = time.time_ns()
|
||||||
|
assert _set_mtime_ns(po_path, po_time) == po_time
|
||||||
|
|
||||||
|
# phase 2: build document with gettext builder.
|
||||||
# The mo file in the srcdir directory is retained.
|
# The mo file in the srcdir directory is retained.
|
||||||
app = make_app('gettext', *args, **kwargs)
|
|
||||||
app.build()
|
app.build()
|
||||||
time.sleep(0.01)
|
# Do a real sleep on POSIX, or simulate a sleep on Windows
|
||||||
|
# to ensure that calls to time.time_ns() remain consistent.
|
||||||
|
clock.sleep(0.5 if os.name == 'posix' else 1)
|
||||||
# Since it is after the build, the number of documents to be updated is 0
|
# Since it is after the build, the number of documents to be updated is 0
|
||||||
update_targets = get_update_targets(app)
|
update_targets = _get_update_targets(app)
|
||||||
assert update_targets[1] == set(), update_targets
|
assert update_targets[1] == set()
|
||||||
# Even if the timestamp of the mo file is updated, the number of documents
|
# Even if the timestamp of the mo file is updated, the number of documents
|
||||||
# to be updated is 0. gettext builder does not rebuild because of mo update.
|
# to be updated is 0. gettext builder does not rebuild because of mo update.
|
||||||
os.utime(app0.srcdir / 'xx' / 'LC_MESSAGES' / 'bom.mo', (mtime + 10, mtime + 10))
|
new_mo_time = time.time_ns()
|
||||||
update_targets = get_update_targets(app)
|
assert _set_mtime_ns(mo_path, new_mo_time) == new_mo_time
|
||||||
assert update_targets[1] == set(), update_targets
|
update_targets = _get_update_targets(app)
|
||||||
|
assert update_targets[1] == set()
|
||||||
|
mock.undo() # remove the patch
|
||||||
|
|
||||||
|
# remove all sources for the next test
|
||||||
|
shutil.rmtree(app.srcdir, ignore_errors=True)
|
||||||
|
time.sleep(0.1 if os.name == 'posix' else 0.5) # real sleep
|
||||||
|
|
||||||
|
|
||||||
@sphinx_intl
|
@sphinx_intl
|
||||||
@ -875,15 +1012,16 @@ def test_html_rebuild_mo(app):
|
|||||||
app.build()
|
app.build()
|
||||||
# --- rebuild by .mo mtime
|
# --- rebuild by .mo mtime
|
||||||
app.build()
|
app.build()
|
||||||
app.env.find_files(app.config, app.builder)
|
_, updated, _ = _get_update_targets(app)
|
||||||
_, updated, _ = app.env.get_outdated_files(config_changed=False)
|
assert updated == set()
|
||||||
assert len(updated) == 0
|
|
||||||
|
|
||||||
mtime = (app.srcdir / 'xx' / 'LC_MESSAGES' / 'bom.mo').stat().st_mtime
|
_, bom_file = _get_bom_intl_path(app)
|
||||||
os.utime(app.srcdir / 'xx' / 'LC_MESSAGES' / 'bom.mo', (mtime + 5, mtime + 5))
|
old_mtime = bom_file.stat().st_mtime
|
||||||
app.env.find_files(app.config, app.builder)
|
new_mtime = old_mtime + (dt := 5)
|
||||||
_, updated, _ = app.env.get_outdated_files(config_changed=False)
|
os.utime(bom_file, (new_mtime, new_mtime))
|
||||||
assert len(updated) == 1
|
assert old_mtime + dt == new_mtime, (old_mtime + dt, new_mtime)
|
||||||
|
_, updated, _ = _get_update_targets(app)
|
||||||
|
assert updated == {'bom'}
|
||||||
|
|
||||||
|
|
||||||
@sphinx_intl
|
@sphinx_intl
|
||||||
@ -1222,7 +1360,7 @@ def test_additional_targets_should_not_be_translated(app):
|
|||||||
'html',
|
'html',
|
||||||
srcdir='test_additional_targets_should_be_translated',
|
srcdir='test_additional_targets_should_be_translated',
|
||||||
confoverrides={
|
confoverrides={
|
||||||
'language': 'xx', 'locale_dirs': ['.'],
|
'language': _CATALOG_LOCALE, 'locale_dirs': ['.'],
|
||||||
'gettext_compact': False,
|
'gettext_compact': False,
|
||||||
'gettext_additional_targets': [
|
'gettext_additional_targets': [
|
||||||
'index',
|
'index',
|
||||||
@ -1300,7 +1438,7 @@ def test_additional_targets_should_be_translated(app):
|
|||||||
'html',
|
'html',
|
||||||
testroot='intl_substitution_definitions',
|
testroot='intl_substitution_definitions',
|
||||||
confoverrides={
|
confoverrides={
|
||||||
'language': 'xx', 'locale_dirs': ['.'],
|
'language': _CATALOG_LOCALE, 'locale_dirs': ['.'],
|
||||||
'gettext_compact': False,
|
'gettext_compact': False,
|
||||||
'gettext_additional_targets': [
|
'gettext_additional_targets': [
|
||||||
'index',
|
'index',
|
||||||
@ -1342,7 +1480,7 @@ def test_text_references(app, warning):
|
|||||||
'text',
|
'text',
|
||||||
testroot='intl_substitution_definitions',
|
testroot='intl_substitution_definitions',
|
||||||
confoverrides={
|
confoverrides={
|
||||||
'language': 'xx', 'locale_dirs': ['.'],
|
'language': _CATALOG_LOCALE, 'locale_dirs': ['.'],
|
||||||
'gettext_compact': False,
|
'gettext_compact': False,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
@ -1368,7 +1506,7 @@ SUBSTITUTED IMAGE [image: SUBST_EPILOG_2 TRANSLATED][image] HERE.
|
|||||||
@pytest.mark.sphinx(
|
@pytest.mark.sphinx(
|
||||||
'dummy', testroot='images',
|
'dummy', testroot='images',
|
||||||
srcdir='test_intl_images',
|
srcdir='test_intl_images',
|
||||||
confoverrides={'language': 'xx'},
|
confoverrides={'language': _CATALOG_LOCALE},
|
||||||
)
|
)
|
||||||
def test_image_glob_intl(app):
|
def test_image_glob_intl(app):
|
||||||
app.build()
|
app.build()
|
||||||
@ -1412,7 +1550,7 @@ def test_image_glob_intl(app):
|
|||||||
'dummy', testroot='images',
|
'dummy', testroot='images',
|
||||||
srcdir='test_intl_images',
|
srcdir='test_intl_images',
|
||||||
confoverrides={
|
confoverrides={
|
||||||
'language': 'xx',
|
'language': _CATALOG_LOCALE,
|
||||||
'figure_language_filename': '{root}{ext}.{language}',
|
'figure_language_filename': '{root}{ext}.{language}',
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
Loading…
Reference in New Issue
Block a user