diff --git a/sphinx/config.py b/sphinx/config.py index 0fd2102e732..3096cef0359 100644 --- a/sphinx/config.py +++ b/sphinx/config.py @@ -648,6 +648,7 @@ def _substitute_copyright_year(copyright_line: str, replace_year: str) -> str: * ``YYYY`` * ``YYYY,`` * ``YYYY `` + * ``YYYY-YYYY`` * ``YYYY-YYYY,`` * ``YYYY-YYYY `` diff --git a/tests/roots/test-copyright-multiline/index.rst b/tests/roots/test-copyright-multiline/index.rst index aa32ae60f4a..e69de29bb2d 100644 --- a/tests/roots/test-copyright-multiline/index.rst +++ b/tests/roots/test-copyright-multiline/index.rst @@ -1,3 +0,0 @@ -======================== -test-copyright-multiline -======================== diff --git a/tests/roots/test-correct-year/conf.py b/tests/roots/test-correct-year/conf.py deleted file mode 100644 index 814c08b55e0..00000000000 --- a/tests/roots/test-correct-year/conf.py +++ /dev/null @@ -1 +0,0 @@ -copyright = '2006-2009, Author' diff --git a/tests/roots/test-correct-year/index.rst b/tests/roots/test-correct-year/index.rst deleted file mode 100644 index 938dfd50310..00000000000 --- a/tests/roots/test-correct-year/index.rst +++ /dev/null @@ -1,4 +0,0 @@ -================= -test-correct-year -================= - diff --git a/tests/test_builders/test_build_html_copyright.py b/tests/test_builders/test_build_html_copyright.py new file mode 100644 index 00000000000..2ddc50532aa --- /dev/null +++ b/tests/test_builders/test_build_html_copyright.py @@ -0,0 +1,76 @@ +import time + +import pytest + + +@pytest.fixture( + params=[ + 1293840000, # 2011-01-01 00:00:00 + 1293839999, # 2010-12-31 23:59:59 + ] +) +def source_date_year(request, monkeypatch): + source_date_epoch = request.param + with monkeypatch.context() as m: + m.setenv('SOURCE_DATE_EPOCH', str(source_date_epoch)) + yield time.gmtime(source_date_epoch).tm_year + + +@pytest.mark.sphinx('html', testroot='copyright-multiline') +def test_html_multi_line_copyright(app): + app.build(force_all=True) + + content = (app.outdir / 'index.html').read_text(encoding='utf-8') + + # check the copyright footer line by line (empty lines ignored) + assert ' © Copyright 2006.
\n' in content + assert ' © Copyright 2006-2009, Alice.
\n' in content + assert ' © Copyright 2010-2013, Bob.
\n' in content + assert ' © Copyright 2014-2017, Charlie.
\n' in content + assert ' © Copyright 2018-2021, David.
\n' in content + assert ' © Copyright 2022-2025, Eve.' in content + + # check the raw copyright footer block (empty lines included) + assert ( + ' © Copyright 2006.
\n' + ' \n' + ' © Copyright 2006-2009, Alice.
\n' + ' \n' + ' © Copyright 2010-2013, Bob.
\n' + ' \n' + ' © Copyright 2014-2017, Charlie.
\n' + ' \n' + ' © Copyright 2018-2021, David.
\n' + ' \n' + ' © Copyright 2022-2025, Eve.' + ) in content + + +@pytest.mark.sphinx('html', testroot='copyright-multiline') +def test_html_multi_line_copyright_sde(source_date_year, app): + app.build(force_all=True) + + content = (app.outdir / 'index.html').read_text(encoding='utf-8') + + # check the copyright footer line by line (empty lines ignored) + assert f' © Copyright {source_date_year}.
\n' in content + assert f' © Copyright 2006-{source_date_year}, Alice.
\n' in content + assert f' © Copyright 2010-{source_date_year}, Bob.
\n' in content + assert f' © Copyright 2014-{source_date_year}, Charlie.
\n' in content + assert f' © Copyright 2018-{source_date_year}, David.
\n' in content + assert f' © Copyright 2022-{source_date_year}, Eve.' in content + + # check the raw copyright footer block (empty lines included) + assert ( + f' © Copyright {source_date_year}.
\n' + f' \n' + f' © Copyright 2006-{source_date_year}, Alice.
\n' + f' \n' + f' © Copyright 2010-{source_date_year}, Bob.
\n' + f' \n' + f' © Copyright 2014-{source_date_year}, Charlie.
\n' + f' \n' + f' © Copyright 2018-{source_date_year}, David.
\n' + f' \n' + f' © Copyright 2022-{source_date_year}, Eve.' + ) in content diff --git a/tests/test_config/test_config.py b/tests/test_config/test_config.py index 7f748f11e70..f1d6c12a0fd 100644 --- a/tests/test_config/test_config.py +++ b/tests/test_config/test_config.py @@ -3,7 +3,6 @@ from __future__ import annotations import pickle -import time from collections import Counter from pathlib import Path from typing import TYPE_CHECKING, Any @@ -18,7 +17,6 @@ Config, _Opt, check_confval_types, - correct_copyright_year, is_serializable, ) from sphinx.deprecation import RemovedInSphinx90Warning @@ -741,101 +739,6 @@ def test_conf_py_nitpick_ignore_list(tmp_path): assert cfg.nitpick_ignore_regex == [] -@pytest.fixture( - params=[ - # test with SOURCE_DATE_EPOCH unset: no modification - None, - # test with SOURCE_DATE_EPOCH set: copyright year should be updated - 1293840000, - 1293839999, - ] -) -def source_date_year(request, monkeypatch): - sde = request.param - with monkeypatch.context() as m: - if sde: - m.setenv('SOURCE_DATE_EPOCH', str(sde)) - yield time.gmtime(sde).tm_year - else: - m.delenv('SOURCE_DATE_EPOCH', raising=False) - yield None - - -@pytest.mark.sphinx('html', testroot='copyright-multiline') -def test_multi_line_copyright(source_date_year, app): - app.build(force_all=True) - - content = (app.outdir / 'index.html').read_text(encoding='utf-8') - - if source_date_year is None: - # check the copyright footer line by line (empty lines ignored) - assert ' © Copyright 2006.
\n' in content - assert ' © Copyright 2006-2009, Alice.
\n' in content - assert ' © Copyright 2010-2013, Bob.
\n' in content - assert ' © Copyright 2014-2017, Charlie.
\n' in content - assert ' © Copyright 2018-2021, David.
\n' in content - assert ' © Copyright 2022-2025, Eve.' in content - - # check the raw copyright footer block (empty lines included) - assert ( - ' © Copyright 2006.
\n' - ' \n' - ' © Copyright 2006-2009, Alice.
\n' - ' \n' - ' © Copyright 2010-2013, Bob.
\n' - ' \n' - ' © Copyright 2014-2017, Charlie.
\n' - ' \n' - ' © Copyright 2018-2021, David.
\n' - ' \n' - ' © Copyright 2022-2025, Eve.' - ) in content - else: - # check the copyright footer line by line (empty lines ignored) - assert f' © Copyright {source_date_year}.
\n' in content - assert f' © Copyright 2006-{source_date_year}, Alice.
\n' in content - assert f' © Copyright 2010-{source_date_year}, Bob.
\n' in content - assert f' © Copyright 2014-{source_date_year}, Charlie.
\n' in content - assert f' © Copyright 2018-{source_date_year}, David.
\n' in content - assert f' © Copyright 2022-{source_date_year}, Eve.' in content - - # check the raw copyright footer block (empty lines included) - assert ( - f' © Copyright {source_date_year}.
\n' - f' \n' - f' © Copyright 2006-{source_date_year}, Alice.
\n' - f' \n' - f' © Copyright 2010-{source_date_year}, Bob.
\n' - f' \n' - f' © Copyright 2014-{source_date_year}, Charlie.
\n' - f' \n' - f' © Copyright 2018-{source_date_year}, David.
\n' - f' \n' - f' © Copyright 2022-{source_date_year}, Eve.' - ) in content - - -@pytest.mark.parametrize( - ('conf_copyright', 'expected_copyright'), - [ - ('1970', '{current_year}'), - # https://github.com/sphinx-doc/sphinx/issues/11913 - ('1970-1990', '1970-{current_year}'), - ('1970-1990 Alice', '1970-{current_year} Alice'), - ], -) -def test_correct_copyright_year(conf_copyright, expected_copyright, source_date_year): - config = Config({}, {'copyright': conf_copyright}) - correct_copyright_year(_app=None, config=config) - actual_copyright = config['copyright'] - - if source_date_year is None: - expected_copyright = conf_copyright - else: - expected_copyright = expected_copyright.format(current_year=source_date_year) - assert actual_copyright == expected_copyright - - def test_gettext_compact_command_line_true(): config = Config({}, {'gettext_compact': '1'}) config.add('gettext_compact', True, '', {bool, str}) diff --git a/tests/test_config/test_correct_year.py b/tests/test_config/test_correct_year.py index 815383dfaf4..4d0c70f32e4 100644 --- a/tests/test_config/test_correct_year.py +++ b/tests/test_config/test_correct_year.py @@ -1,20 +1,31 @@ """Test copyright year adjustment""" +import time + import pytest +from sphinx.config import Config, correct_copyright_year + +LT = time.localtime() +LT_NEW = (2009, *LT[1:], LT.tm_zone, LT.tm_gmtoff) +LOCALTIME_2009 = type(LT)(LT_NEW) + @pytest.fixture( params=[ # test with SOURCE_DATE_EPOCH unset: no modification - (None, '2006-2009'), + (None, ''), # test with SOURCE_DATE_EPOCH set: copyright year should be updated - ('1293840000', '2006-2011'), - ('1293839999', '2006-2010'), + ('1293840000', '2011'), + ('1293839999', '2010'), + ('1199145600', '2008'), + ('1199145599', '2007'), ], ) def expect_date(request, monkeypatch): sde, expect = request.param with monkeypatch.context() as m: + m.setattr(time, 'localtime', lambda *a: LOCALTIME_2009) if sde: m.setenv('SOURCE_DATE_EPOCH', sde) else: @@ -22,8 +33,140 @@ def expect_date(request, monkeypatch): yield expect -@pytest.mark.sphinx('html', testroot='correct-year') -def test_correct_year(expect_date, app): - app.build() - content = (app.outdir / 'index.html').read_text(encoding='utf8') - assert expect_date in content +def test_correct_year(expect_date): + # test that copyright is substituted + copyright_date = '2006-2009, Alice' + cfg = Config({'copyright': copyright_date}, {}) + assert cfg.copyright == copyright_date + correct_copyright_year(None, cfg) # type: ignore[arg-type] + if expect_date: + assert cfg.copyright == f'2006-{expect_date}, Alice' + else: + assert cfg.copyright == copyright_date + + +def test_correct_year_space(expect_date): + # test that copyright is substituted + copyright_date = '2006-2009 Alice' + cfg = Config({'copyright': copyright_date}, {}) + assert cfg.copyright == copyright_date + correct_copyright_year(None, cfg) # type: ignore[arg-type] + if expect_date: + assert cfg.copyright == f'2006-{expect_date} Alice' + else: + assert cfg.copyright == copyright_date + + +def test_correct_year_no_author(expect_date): + # test that copyright is substituted + copyright_date = '2006-2009' + cfg = Config({'copyright': copyright_date}, {}) + assert cfg.copyright == copyright_date + correct_copyright_year(None, cfg) # type: ignore[arg-type] + if expect_date: + assert cfg.copyright == f'2006-{expect_date}' + else: + assert cfg.copyright == copyright_date + + +def test_correct_year_single(expect_date): + # test that copyright is substituted + copyright_date = '2009, Alice' + cfg = Config({'copyright': copyright_date}, {}) + assert cfg.copyright == copyright_date + correct_copyright_year(None, cfg) # type: ignore[arg-type] + if expect_date: + assert cfg.copyright == f'{expect_date}, Alice' + else: + assert cfg.copyright == copyright_date + + +def test_correct_year_single_space(expect_date): + # test that copyright is substituted + copyright_date = '2009 Alice' + cfg = Config({'copyright': copyright_date}, {}) + assert cfg.copyright == copyright_date + correct_copyright_year(None, cfg) # type: ignore[arg-type] + if expect_date: + assert cfg.copyright == f'{expect_date} Alice' + else: + assert cfg.copyright == copyright_date + + +def test_correct_year_single_no_author(expect_date): + # test that copyright is substituted + copyright_date = '2009' + cfg = Config({'copyright': copyright_date}, {}) + assert cfg.copyright == copyright_date + correct_copyright_year(None, cfg) # type: ignore[arg-type] + if expect_date: + assert cfg.copyright == f'{expect_date}' + else: + assert cfg.copyright == copyright_date + + +def test_correct_year_multi_line(expect_date): + # test that copyright is substituted + copyright_dates = ( + '2006', + '2006-2009, Alice', + '2010-2013, Bob', + '2014-2017, Charlie', + '2018-2021, David', + '2022-2025, Eve', + ) + cfg = Config({'copyright': copyright_dates}, {}) + assert cfg.copyright == copyright_dates + correct_copyright_year(None, cfg) # type: ignore[arg-type] + if expect_date: + assert cfg.copyright == ( + f'{expect_date}', + f'2006-{expect_date}, Alice', + f'2010-{expect_date}, Bob', + f'2014-{expect_date}, Charlie', + f'2018-{expect_date}, David', + f'2022-{expect_date}, Eve', + ) + else: + assert cfg.copyright == copyright_dates + + +def test_correct_year_multi_line_all_formats(expect_date): + # test that copyright is substituted + copyright_dates = ( + '2009', + '2009 Alice', + '2009, Bob', + '2006-2009', + '2006-2009 Charlie', + '2006-2009, David', + ) + cfg = Config({'copyright': copyright_dates}, {}) + assert cfg.copyright == copyright_dates + correct_copyright_year(None, cfg) # type: ignore[arg-type] + if expect_date: + assert cfg.copyright == ( + f'{expect_date}', + f'{expect_date} Alice', + f'{expect_date}, Bob', + f'2006-{expect_date}', + f'2006-{expect_date} Charlie', + f'2006-{expect_date}, David', + ) + else: + assert cfg.copyright == copyright_dates + + +def test_correct_year_app(expect_date, tmp_path, make_app): + # integration test + copyright_date = '2006-2009, Alice' + (tmp_path / 'conf.py').touch() + app = make_app( + 'dummy', + srcdir=tmp_path, + confoverrides={'copyright': copyright_date}, + ) + if expect_date: + assert app.config.copyright == f'2006-{expect_date}, Alice' + else: + assert app.config.copyright == copyright_date