From: Aarni Koskela Date: Wed, 15 Jan 2025 13:01:58 +0000 (+0200) Subject: Small test cleanup (#1172) X-Git-Tag: v2.17.0~6 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=0c1091c9de9543e30bc4b845eb10b5bf84516d7b;p=thirdparty%2Fbabel.git Small test cleanup (#1172) * Configure Ruff formatter to preserve quotes for now * Split support functionality tests to a separate modules * Use standard `monkeypatch` fixture for `os.environ` patching * Use even more `monkeypatch` for patching --------- Co-authored-by: Tomas R. --- diff --git a/pyproject.toml b/pyproject.toml index 2e23f7a6..e68b6d5d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,6 +4,9 @@ extend-exclude = [ "tests/messages/data", ] +[tool.ruff.format] +quote-style = "preserve" + [tool.ruff.lint] select = [ "B", diff --git a/tests/conftest.py b/tests/conftest.py index 67e3ce92..dab67a9a 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,5 +1,3 @@ -import os - import pytest try: @@ -16,13 +14,6 @@ except ModuleNotFoundError: pytz = None -@pytest.fixture -def os_environ(monkeypatch): - mock_environ = dict(os.environ) - monkeypatch.setattr(os, 'environ', mock_environ) - return mock_environ - - def pytest_generate_tests(metafunc): if hasattr(metafunc.function, "pytestmark"): for mark in metafunc.function.pytestmark: diff --git a/tests/messages/test_setuptools_frontend.py b/tests/messages/test_setuptools_frontend.py index f3686a8b..a623efd2 100644 --- a/tests/messages/test_setuptools_frontend.py +++ b/tests/messages/test_setuptools_frontend.py @@ -56,12 +56,11 @@ def test_setuptools_commands(tmp_path, monkeypatch): shutil.copytree(data_dir, dest) monkeypatch.chdir(dest) - env = os.environ.copy() # When in Tox, we need to hack things a bit so as not to have the # sub-interpreter `sys.executable` use the tox virtualenv's Babel # installation, so the locale data is where we expect it to be. - if "BABEL_TOX_INI_DIR" in env: - env["PYTHONPATH"] = env["BABEL_TOX_INI_DIR"] + if "BABEL_TOX_INI_DIR" in os.environ: + monkeypatch.setenv("PYTHONPATH", os.environ["BABEL_TOX_INI_DIR"]) # Initialize an empty catalog subprocess.check_call([ @@ -71,7 +70,7 @@ def test_setuptools_commands(tmp_path, monkeypatch): "-i", os.devnull, "-l", "fi", "-d", "inited", - ], env=env) + ]) po_file = Path("inited/fi/LC_MESSAGES/messages.po") orig_po_data = po_file.read_text() subprocess.check_call([ @@ -79,7 +78,7 @@ def test_setuptools_commands(tmp_path, monkeypatch): "setup.py", "extract_messages", "-o", "extracted.pot", - ], env=env) + ]) pot_file = Path("extracted.pot") pot_data = pot_file.read_text() assert "FooBar, TM" in pot_data # should be read from setup.cfg @@ -90,7 +89,7 @@ def test_setuptools_commands(tmp_path, monkeypatch): "update_catalog", "-i", "extracted.pot", "-d", "inited", - ], env=env) + ]) new_po_data = po_file.read_text() assert new_po_data != orig_po_data # check we updated the file subprocess.check_call([ @@ -98,5 +97,5 @@ def test_setuptools_commands(tmp_path, monkeypatch): "setup.py", "compile_catalog", "-d", "inited", - ], env=env) + ]) assert po_file.with_suffix(".mo").exists() diff --git a/tests/test_core.py b/tests/test_core.py index f51eedb9..b6c55626 100644 --- a/tests/test_core.py +++ b/tests/test_core.py @@ -42,15 +42,15 @@ def test_locale_comparison(): assert fi_FI != bad_en_US -def test_can_return_default_locale(os_environ): - os_environ['LC_MESSAGES'] = 'fr_FR.UTF-8' +def test_can_return_default_locale(monkeypatch): + monkeypatch.setenv('LC_MESSAGES', 'fr_FR.UTF-8') assert Locale('fr', 'FR') == Locale.default('LC_MESSAGES') -def test_ignore_invalid_locales_in_lc_ctype(os_environ): +def test_ignore_invalid_locales_in_lc_ctype(monkeypatch): # This is a regression test specifically for a bad LC_CTYPE setting on # MacOS X 10.6 (#200) - os_environ['LC_CTYPE'] = 'UTF-8' + monkeypatch.setenv('LC_CTYPE', 'UTF-8') # must not throw an exception default_locale('LC_CTYPE') @@ -76,10 +76,10 @@ class TestLocaleClass: assert locale.language == 'en' assert locale.territory == 'US' - def test_default(self, os_environ): + def test_default(self, monkeypatch): for name in ['LANGUAGE', 'LC_ALL', 'LC_CTYPE', 'LC_MESSAGES']: - os_environ[name] = '' - os_environ['LANG'] = 'fr_FR.UTF-8' + monkeypatch.setenv(name, '') + monkeypatch.setenv('LANG', 'fr_FR.UTF-8') default = Locale.default('LC_MESSAGES') assert (default.language, default.territory) == ('fr', 'FR') @@ -264,17 +264,16 @@ class TestLocaleClass: assert Locale('ru').plural_form(100) == 'many' -def test_default_locale(os_environ): +def test_default_locale(monkeypatch): for name in ['LANGUAGE', 'LC_ALL', 'LC_CTYPE', 'LC_MESSAGES']: - os_environ[name] = '' - os_environ['LANG'] = 'fr_FR.UTF-8' + monkeypatch.setenv(name, '') + monkeypatch.setenv('LANG', 'fr_FR.UTF-8') assert default_locale('LC_MESSAGES') == 'fr_FR' - - os_environ['LC_MESSAGES'] = 'POSIX' + monkeypatch.setenv('LC_MESSAGES', 'POSIX') assert default_locale('LC_MESSAGES') == 'en_US_POSIX' for value in ['C', 'C.UTF-8', 'POSIX']: - os_environ['LANGUAGE'] = value + monkeypatch.setenv('LANGUAGE', value) assert default_locale() == 'en_US_POSIX' diff --git a/tests/test_support.py b/tests/test_support.py deleted file mode 100644 index 44d5afb1..00000000 --- a/tests/test_support.py +++ /dev/null @@ -1,393 +0,0 @@ -# -# Copyright (C) 2007-2011 Edgewall Software, 2013-2024 the Babel team -# All rights reserved. -# -# This software is licensed as described in the file LICENSE, which -# you should have received as part of this distribution. The terms -# are also available at https://github.com/python-babel/babel/blob/master/LICENSE. -# -# This software consists of voluntary contributions made by many -# individuals. For the exact contribution history, see the revision -# history and logs, available at https://github.com/python-babel/babel/commits/master/. - -import datetime -import inspect -import os -import shutil -import sys -import tempfile -import unittest -from decimal import Decimal -from io import BytesIO - -import pytest - -from babel import support -from babel.messages import Catalog -from babel.messages.mofile import write_mo - -SKIP_LGETTEXT = sys.version_info >= (3, 8) - - -@pytest.mark.usefixtures("os_environ") -class TranslationsTestCase(unittest.TestCase): - - def setUp(self): - # Use a locale which won't fail to run the tests - os.environ['LANG'] = 'en_US.UTF-8' - messages1 = [ - ('foo', {'string': 'Voh'}), - ('foo', {'string': 'VohCTX', 'context': 'foo'}), - (('foo1', 'foos1'), {'string': ('Voh1', 'Vohs1')}), - (('foo1', 'foos1'), {'string': ('VohCTX1', 'VohsCTX1'), 'context': 'foo'}), - ] - messages2 = [ - ('foo', {'string': 'VohD'}), - ('foo', {'string': 'VohCTXD', 'context': 'foo'}), - (('foo1', 'foos1'), {'string': ('VohD1', 'VohsD1')}), - (('foo1', 'foos1'), {'string': ('VohCTXD1', 'VohsCTXD1'), 'context': 'foo'}), - ] - catalog1 = Catalog(locale='en_GB', domain='messages') - catalog2 = Catalog(locale='en_GB', domain='messages1') - for ids, kwargs in messages1: - catalog1.add(ids, **kwargs) - for ids, kwargs in messages2: - catalog2.add(ids, **kwargs) - catalog1_fp = BytesIO() - catalog2_fp = BytesIO() - write_mo(catalog1_fp, catalog1) - catalog1_fp.seek(0) - write_mo(catalog2_fp, catalog2) - catalog2_fp.seek(0) - translations1 = support.Translations(catalog1_fp) - translations2 = support.Translations(catalog2_fp, domain='messages1') - self.translations = translations1.add(translations2, merge=False) - - def assertEqualTypeToo(self, expected, result): - assert expected == result - assert type(expected) is type(result), f"instance types do not match: {type(expected)!r}!={type(result)!r}" - - def test_pgettext(self): - self.assertEqualTypeToo('Voh', self.translations.gettext('foo')) - self.assertEqualTypeToo('VohCTX', self.translations.pgettext('foo', - 'foo')) - self.assertEqualTypeToo('VohCTX1', self.translations.pgettext('foo', - 'foo1')) - - def test_pgettext_fallback(self): - fallback = self.translations._fallback - self.translations._fallback = support.NullTranslations() - assert self.translations.pgettext('foo', 'bar') == 'bar' - self.translations._fallback = fallback - - def test_upgettext(self): - self.assertEqualTypeToo('Voh', self.translations.ugettext('foo')) - self.assertEqualTypeToo('VohCTX', self.translations.upgettext('foo', - 'foo')) - - @pytest.mark.skipif(SKIP_LGETTEXT, reason="lgettext is deprecated") - def test_lpgettext(self): - self.assertEqualTypeToo(b'Voh', self.translations.lgettext('foo')) - self.assertEqualTypeToo(b'VohCTX', self.translations.lpgettext('foo', - 'foo')) - - def test_npgettext(self): - self.assertEqualTypeToo('Voh1', - self.translations.ngettext('foo1', 'foos1', 1)) - self.assertEqualTypeToo('Vohs1', - self.translations.ngettext('foo1', 'foos1', 2)) - self.assertEqualTypeToo('VohCTX1', - self.translations.npgettext('foo', 'foo1', - 'foos1', 1)) - self.assertEqualTypeToo('VohsCTX1', - self.translations.npgettext('foo', 'foo1', - 'foos1', 2)) - - def test_unpgettext(self): - self.assertEqualTypeToo('Voh1', - self.translations.ungettext('foo1', 'foos1', 1)) - self.assertEqualTypeToo('Vohs1', - self.translations.ungettext('foo1', 'foos1', 2)) - self.assertEqualTypeToo('VohCTX1', - self.translations.unpgettext('foo', 'foo1', - 'foos1', 1)) - self.assertEqualTypeToo('VohsCTX1', - self.translations.unpgettext('foo', 'foo1', - 'foos1', 2)) - - @pytest.mark.skipif(SKIP_LGETTEXT, reason="lgettext is deprecated") - def test_lnpgettext(self): - self.assertEqualTypeToo(b'Voh1', - self.translations.lngettext('foo1', 'foos1', 1)) - self.assertEqualTypeToo(b'Vohs1', - self.translations.lngettext('foo1', 'foos1', 2)) - self.assertEqualTypeToo(b'VohCTX1', - self.translations.lnpgettext('foo', 'foo1', - 'foos1', 1)) - self.assertEqualTypeToo(b'VohsCTX1', - self.translations.lnpgettext('foo', 'foo1', - 'foos1', 2)) - - def test_dpgettext(self): - self.assertEqualTypeToo( - 'VohD', self.translations.dgettext('messages1', 'foo')) - self.assertEqualTypeToo( - 'VohCTXD', self.translations.dpgettext('messages1', 'foo', 'foo')) - - def test_dupgettext(self): - self.assertEqualTypeToo( - 'VohD', self.translations.dugettext('messages1', 'foo')) - self.assertEqualTypeToo( - 'VohCTXD', self.translations.dupgettext('messages1', 'foo', 'foo')) - - @pytest.mark.skipif(SKIP_LGETTEXT, reason="lgettext is deprecated") - def test_ldpgettext(self): - self.assertEqualTypeToo( - b'VohD', self.translations.ldgettext('messages1', 'foo')) - self.assertEqualTypeToo( - b'VohCTXD', self.translations.ldpgettext('messages1', 'foo', 'foo')) - - def test_dnpgettext(self): - self.assertEqualTypeToo( - 'VohD1', self.translations.dngettext('messages1', 'foo1', 'foos1', 1)) - self.assertEqualTypeToo( - 'VohsD1', self.translations.dngettext('messages1', 'foo1', 'foos1', 2)) - self.assertEqualTypeToo( - 'VohCTXD1', self.translations.dnpgettext('messages1', 'foo', 'foo1', - 'foos1', 1)) - self.assertEqualTypeToo( - 'VohsCTXD1', self.translations.dnpgettext('messages1', 'foo', 'foo1', - 'foos1', 2)) - - def test_dunpgettext(self): - self.assertEqualTypeToo( - 'VohD1', self.translations.dungettext('messages1', 'foo1', 'foos1', 1)) - self.assertEqualTypeToo( - 'VohsD1', self.translations.dungettext('messages1', 'foo1', 'foos1', 2)) - self.assertEqualTypeToo( - 'VohCTXD1', self.translations.dunpgettext('messages1', 'foo', 'foo1', - 'foos1', 1)) - self.assertEqualTypeToo( - 'VohsCTXD1', self.translations.dunpgettext('messages1', 'foo', 'foo1', - 'foos1', 2)) - - @pytest.mark.skipif(SKIP_LGETTEXT, reason="lgettext is deprecated") - def test_ldnpgettext(self): - self.assertEqualTypeToo( - b'VohD1', self.translations.ldngettext('messages1', 'foo1', 'foos1', 1)) - self.assertEqualTypeToo( - b'VohsD1', self.translations.ldngettext('messages1', 'foo1', 'foos1', 2)) - self.assertEqualTypeToo( - b'VohCTXD1', self.translations.ldnpgettext('messages1', 'foo', 'foo1', - 'foos1', 1)) - self.assertEqualTypeToo( - b'VohsCTXD1', self.translations.ldnpgettext('messages1', 'foo', 'foo1', - 'foos1', 2)) - - def test_load(self): - tempdir = tempfile.mkdtemp() - try: - messages_dir = os.path.join(tempdir, 'fr', 'LC_MESSAGES') - os.makedirs(messages_dir) - catalog = Catalog(locale='fr', domain='messages') - catalog.add('foo', 'bar') - with open(os.path.join(messages_dir, 'messages.mo'), 'wb') as f: - write_mo(f, catalog) - - translations = support.Translations.load(tempdir, locales=('fr',), domain='messages') - assert translations.gettext('foo') == 'bar' - finally: - shutil.rmtree(tempdir) - - -class NullTranslationsTestCase(unittest.TestCase): - - def setUp(self): - fp = BytesIO() - write_mo(fp, Catalog(locale='de')) - fp.seek(0) - self.translations = support.Translations(fp=fp) - self.null_translations = support.NullTranslations(fp=fp) - - def method_names(self): - names = [name for name in dir(self.translations) if 'gettext' in name] - if SKIP_LGETTEXT: - # Remove deprecated l*gettext functions - names = [name for name in names if not name.startswith('l')] - return names - - def test_same_methods(self): - for name in self.method_names(): - if not hasattr(self.null_translations, name): - self.fail(f"NullTranslations does not provide method {name!r}") - - def test_method_signature_compatibility(self): - for name in self.method_names(): - translations_method = getattr(self.translations, name) - null_method = getattr(self.null_translations, name) - assert inspect.getfullargspec(translations_method) == inspect.getfullargspec(null_method) - - def test_same_return_values(self): - data = { - 'message': 'foo', 'domain': 'domain', 'context': 'tests', - 'singular': 'bar', 'plural': 'baz', 'num': 1, - 'msgid1': 'bar', 'msgid2': 'baz', 'n': 1, - } - for name in self.method_names(): - method = getattr(self.translations, name) - null_method = getattr(self.null_translations, name) - signature = inspect.getfullargspec(method) - parameter_names = [name for name in signature.args if name != 'self'] - values = [data[name] for name in parameter_names] - assert method(*values) == null_method(*values) - - -class LazyProxyTestCase(unittest.TestCase): - - def test_proxy_caches_result_of_function_call(self): - self.counter = 0 - - def add_one(): - self.counter += 1 - return self.counter - proxy = support.LazyProxy(add_one) - assert proxy.value == 1 - assert proxy.value == 1 - - def test_can_disable_proxy_cache(self): - self.counter = 0 - - def add_one(): - self.counter += 1 - return self.counter - proxy = support.LazyProxy(add_one, enable_cache=False) - assert proxy.value == 1 - assert proxy.value == 2 - - def test_can_copy_proxy(self): - from copy import copy - - numbers = [1, 2] - - def first(xs): - return xs[0] - - proxy = support.LazyProxy(first, numbers) - proxy_copy = copy(proxy) - - numbers.pop(0) - assert proxy.value == 2 - assert proxy_copy.value == 2 - - def test_can_deepcopy_proxy(self): - from copy import deepcopy - numbers = [1, 2] - - def first(xs): - return xs[0] - - proxy = support.LazyProxy(first, numbers) - proxy_deepcopy = deepcopy(proxy) - - numbers.pop(0) - assert proxy.value == 2 - assert proxy_deepcopy.value == 1 - - def test_handle_attribute_error(self): - - def raise_attribute_error(): - raise AttributeError('message') - - proxy = support.LazyProxy(raise_attribute_error) - with pytest.raises(AttributeError, match='message'): - _ = proxy.value - - -class TestFormat: - def test_format_datetime(self, timezone_getter): - when = datetime.datetime(2007, 4, 1, 15, 30) - fmt = support.Format('en_US', tzinfo=timezone_getter('US/Eastern')) - assert fmt.datetime(when) == 'Apr 1, 2007, 11:30:00\u202fAM' - - def test_format_time(self, timezone_getter): - when = datetime.datetime(2007, 4, 1, 15, 30) - fmt = support.Format('en_US', tzinfo=timezone_getter('US/Eastern')) - assert fmt.time(when) == '11:30:00\u202fAM' - - def test_format_number(self): - assert support.Format('en_US').number(1234) == '1,234' - assert support.Format('ar_EG', numbering_system="default").number(1234) == '1٬234' - - def test_format_decimal(self): - assert support.Format('en_US').decimal(1234.5) == '1,234.5' - assert support.Format('en_US').decimal(Decimal("1234.5")) == '1,234.5' - assert support.Format('ar_EG', numbering_system="default").decimal(1234.5) == '1٬234٫5' - assert support.Format('ar_EG', numbering_system="default").decimal(Decimal("1234.5")) == '1٬234٫5' - - def test_format_compact_decimal(self): - assert support.Format('en_US').compact_decimal(1234) == '1K' - assert support.Format('ar_EG', numbering_system="default").compact_decimal( - 1234, fraction_digits=1) == '1٫2\xa0ألف' - assert support.Format('ar_EG', numbering_system="default").compact_decimal( - Decimal("1234"), fraction_digits=1) == '1٫2\xa0ألف' - - def test_format_currency(self): - assert support.Format('en_US').currency(1099.98, 'USD') == '$1,099.98' - assert support.Format('en_US').currency(Decimal("1099.98"), 'USD') == '$1,099.98' - assert support.Format('ar_EG', numbering_system="default").currency( - 1099.98, 'EGP') == '\u200f1٬099٫98\xa0ج.م.\u200f' - - def test_format_compact_currency(self): - assert support.Format('en_US').compact_currency(1099.98, 'USD') == '$1K' - assert support.Format('en_US').compact_currency(Decimal("1099.98"), 'USD') == '$1K' - assert support.Format('ar_EG', numbering_system="default").compact_currency( - 1099.98, 'EGP') == '1\xa0ألف\xa0ج.م.\u200f' - - def test_format_percent(self): - assert support.Format('en_US').percent(0.34) == '34%' - assert support.Format('en_US').percent(Decimal("0.34")) == '34%' - assert support.Format('ar_EG', numbering_system="default").percent(134.5) == '13٬450%' - - def test_format_scientific(self): - assert support.Format('en_US').scientific(10000) == '1E4' - assert support.Format('en_US').scientific(Decimal("10000")) == '1E4' - assert support.Format('ar_EG', numbering_system="default").scientific(10000) == '1أس4' - - -def test_lazy_proxy(): - def greeting(name='world'): - return f"Hello, {name}!" - - lazy_greeting = support.LazyProxy(greeting, name='Joe') - assert str(lazy_greeting) == "Hello, Joe!" - assert ' ' + lazy_greeting == ' Hello, Joe!' - assert '(%s)' % lazy_greeting == '(Hello, Joe!)' - assert f"[{lazy_greeting}]" == "[Hello, Joe!]" - - greetings = sorted([ - support.LazyProxy(greeting, 'world'), - support.LazyProxy(greeting, 'Joe'), - support.LazyProxy(greeting, 'universe'), - ]) - assert [str(g) for g in greetings] == [ - "Hello, Joe!", - "Hello, universe!", - "Hello, world!", - ] - - -def test_catalog_merge_files(): - # Refs issues #92, #162 - t1 = support.Translations() - assert t1.files == [] - t1._catalog["foo"] = "bar" - fp = BytesIO() - write_mo(fp, Catalog()) - fp.seek(0) - fp.name = "pro.mo" - t2 = support.Translations(fp) - assert t2.files == ["pro.mo"] - t2._catalog["bar"] = "quux" - t1.merge(t2) - assert t1.files == ["pro.mo"] - assert set(t1._catalog.keys()) == {'', 'foo', 'bar'} diff --git a/tests/test_support_format.py b/tests/test_support_format.py new file mode 100644 index 00000000..fdfac844 --- /dev/null +++ b/tests/test_support_format.py @@ -0,0 +1,68 @@ +import datetime +from decimal import Decimal + +import pytest + +from babel import support + + +@pytest.fixture +def ar_eg_format() -> support.Format: + return support.Format('ar_EG', numbering_system="default") + + +@pytest.fixture +def en_us_format(timezone_getter) -> support.Format: + return support.Format('en_US', tzinfo=timezone_getter('US/Eastern')) + + +def test_format_datetime(en_us_format): + when = datetime.datetime(2007, 4, 1, 15, 30) + assert en_us_format.datetime(when) == 'Apr 1, 2007, 11:30:00\u202fAM' + + +def test_format_time(en_us_format): + when = datetime.datetime(2007, 4, 1, 15, 30) + assert en_us_format.time(when) == '11:30:00\u202fAM' + + +def test_format_number(ar_eg_format, en_us_format): + assert en_us_format.number(1234) == '1,234' + assert ar_eg_format.number(1234) == '1٬234' + + +def test_format_decimal(ar_eg_format, en_us_format): + assert en_us_format.decimal(1234.5) == '1,234.5' + assert en_us_format.decimal(Decimal("1234.5")) == '1,234.5' + assert ar_eg_format.decimal(1234.5) == '1٬234٫5' + assert ar_eg_format.decimal(Decimal("1234.5")) == '1٬234٫5' + + +def test_format_compact_decimal(ar_eg_format, en_us_format): + assert en_us_format.compact_decimal(1234) == '1K' + assert ar_eg_format.compact_decimal(1234, fraction_digits=1) == '1٫2\xa0ألف' + assert ar_eg_format.compact_decimal(Decimal("1234"), fraction_digits=1) == '1٫2\xa0ألف' + + +def test_format_currency(ar_eg_format, en_us_format): + assert en_us_format.currency(1099.98, 'USD') == '$1,099.98' + assert en_us_format.currency(Decimal("1099.98"), 'USD') == '$1,099.98' + assert ar_eg_format.currency(1099.98, 'EGP') == '\u200f1٬099٫98\xa0ج.م.\u200f' + + +def test_format_compact_currency(ar_eg_format, en_us_format): + assert en_us_format.compact_currency(1099.98, 'USD') == '$1K' + assert en_us_format.compact_currency(Decimal("1099.98"), 'USD') == '$1K' + assert ar_eg_format.compact_currency(1099.98, 'EGP') == '1\xa0ألف\xa0ج.م.\u200f' + + +def test_format_percent(ar_eg_format, en_us_format): + assert en_us_format.percent(0.34) == '34%' + assert en_us_format.percent(Decimal("0.34")) == '34%' + assert ar_eg_format.percent(134.5) == '13٬450%' + + +def test_format_scientific(ar_eg_format, en_us_format): + assert en_us_format.scientific(10000) == '1E4' + assert en_us_format.scientific(Decimal("10000")) == '1E4' + assert ar_eg_format.scientific(10000) == '1أس4' diff --git a/tests/test_support_lazy_proxy.py b/tests/test_support_lazy_proxy.py new file mode 100644 index 00000000..8445f71f --- /dev/null +++ b/tests/test_support_lazy_proxy.py @@ -0,0 +1,80 @@ +import copy + +import pytest + +from babel import support + + +def test_proxy_caches_result_of_function_call(): + counter = 0 + + def add_one(): + nonlocal counter + counter += 1 + return counter + + proxy = support.LazyProxy(add_one) + assert proxy.value == 1 + assert proxy.value == 1 + + +def test_can_disable_proxy_cache(): + counter = 0 + + def add_one(): + nonlocal counter + counter += 1 + return counter + + proxy = support.LazyProxy(add_one, enable_cache=False) + assert proxy.value == 1 + assert proxy.value == 2 + + +@pytest.mark.parametrize(("copier", "expected_copy_value"), [ + (copy.copy, 2), + (copy.deepcopy, 1), +]) +def test_can_copy_proxy(copier, expected_copy_value): + numbers = [1, 2] + + def first(xs): + return xs[0] + + proxy = support.LazyProxy(first, numbers) + proxy_copy = copier(proxy) + + numbers.pop(0) + assert proxy.value == 2 + assert proxy_copy.value == expected_copy_value + + +def test_handle_attribute_error(): + def raise_attribute_error(): + raise AttributeError('message') + + proxy = support.LazyProxy(raise_attribute_error) + with pytest.raises(AttributeError, match='message'): + _ = proxy.value + + +def test_lazy_proxy(): + def greeting(name='world'): + return f"Hello, {name}!" + + lazy_greeting = support.LazyProxy(greeting, name='Joe') + assert str(lazy_greeting) == "Hello, Joe!" + assert ' ' + lazy_greeting == ' Hello, Joe!' + assert '(%s)' % lazy_greeting == '(Hello, Joe!)' + assert f"[{lazy_greeting}]" == "[Hello, Joe!]" + + greetings = sorted([ + support.LazyProxy(greeting, 'world'), + support.LazyProxy(greeting, 'Joe'), + support.LazyProxy(greeting, 'universe'), + ]) + assert [str(g) for g in greetings] == [ + "Hello, Joe!", + "Hello, universe!", + "Hello, world!", + ] diff --git a/tests/test_support_translations.py b/tests/test_support_translations.py new file mode 100644 index 00000000..7e6dc59f --- /dev/null +++ b/tests/test_support_translations.py @@ -0,0 +1,235 @@ +import inspect +import io +import os +import shutil +import sys +import tempfile + +import pytest + +from babel import support +from babel.messages import Catalog +from babel.messages.mofile import write_mo + +SKIP_LGETTEXT = sys.version_info >= (3, 8) + +messages1 = [ + ('foo', {'string': 'Voh'}), + ('foo', {'string': 'VohCTX', 'context': 'foo'}), + (('foo1', 'foos1'), {'string': ('Voh1', 'Vohs1')}), + (('foo1', 'foos1'), {'string': ('VohCTX1', 'VohsCTX1'), 'context': 'foo'}), +] + +messages2 = [ + ('foo', {'string': 'VohD'}), + ('foo', {'string': 'VohCTXD', 'context': 'foo'}), + (('foo1', 'foos1'), {'string': ('VohD1', 'VohsD1')}), + (('foo1', 'foos1'), {'string': ('VohCTXD1', 'VohsCTXD1'), 'context': 'foo'}), +] + + +@pytest.fixture(autouse=True) +def use_en_us_locale(monkeypatch): + # Use a locale which won't fail to run the tests + monkeypatch.setenv('LANG', 'en_US.UTF-8') + + +@pytest.fixture() +def translations() -> support.Translations: + catalog1 = Catalog(locale='en_GB', domain='messages') + catalog2 = Catalog(locale='en_GB', domain='messages1') + for ids, kwargs in messages1: + catalog1.add(ids, **kwargs) + for ids, kwargs in messages2: + catalog2.add(ids, **kwargs) + catalog1_fp = io.BytesIO() + catalog2_fp = io.BytesIO() + write_mo(catalog1_fp, catalog1) + catalog1_fp.seek(0) + write_mo(catalog2_fp, catalog2) + catalog2_fp.seek(0) + translations1 = support.Translations(catalog1_fp) + translations2 = support.Translations(catalog2_fp, domain='messages1') + return translations1.add(translations2, merge=False) + + +@pytest.fixture(scope='module') +def empty_translations() -> support.Translations: + fp = io.BytesIO() + write_mo(fp, Catalog(locale='de')) + fp.seek(0) + return support.Translations(fp=fp) + + +@pytest.fixture(scope='module') +def null_translations() -> support.NullTranslations: + fp = io.BytesIO() + write_mo(fp, Catalog(locale='de')) + fp.seek(0) + return support.NullTranslations(fp=fp) + + +def assert_equal_type_too(expected, result) -> None: + assert expected == result + assert type(expected) is type(result), ( + f'instance types do not match: {type(expected)!r}!={type(result)!r}' + ) + + +def test_pgettext(translations): + assert_equal_type_too('Voh', translations.gettext('foo')) + assert_equal_type_too('VohCTX', translations.pgettext('foo', 'foo')) + assert_equal_type_too('VohCTX1', translations.pgettext('foo', 'foo1')) + + +def test_pgettext_fallback(translations): + fallback = translations._fallback + translations._fallback = support.NullTranslations() + assert translations.pgettext('foo', 'bar') == 'bar' + translations._fallback = fallback + + +def test_upgettext(translations): + assert_equal_type_too('Voh', translations.ugettext('foo')) + assert_equal_type_too('VohCTX', translations.upgettext('foo', 'foo')) + + +@pytest.mark.skipif(SKIP_LGETTEXT, reason='lgettext is deprecated') +def test_lpgettext(translations): + assert_equal_type_too(b'Voh', translations.lgettext('foo')) + assert_equal_type_too(b'VohCTX', translations.lpgettext('foo', 'foo')) + + +def test_npgettext(translations): + assert_equal_type_too('Voh1', translations.ngettext('foo1', 'foos1', 1)) + assert_equal_type_too('Vohs1', translations.ngettext('foo1', 'foos1', 2)) + assert_equal_type_too('VohCTX1', translations.npgettext('foo', 'foo1', 'foos1', 1)) + assert_equal_type_too('VohsCTX1', translations.npgettext('foo', 'foo1', 'foos1', 2)) + + +def test_unpgettext(translations): + assert_equal_type_too('Voh1', translations.ungettext('foo1', 'foos1', 1)) + assert_equal_type_too('Vohs1', translations.ungettext('foo1', 'foos1', 2)) + assert_equal_type_too('VohCTX1', translations.unpgettext('foo', 'foo1', 'foos1', 1)) + assert_equal_type_too('VohsCTX1', translations.unpgettext('foo', 'foo1', 'foos1', 2)) + + +@pytest.mark.skipif(SKIP_LGETTEXT, reason='lgettext is deprecated') +def test_lnpgettext(translations): + assert_equal_type_too(b'Voh1', translations.lngettext('foo1', 'foos1', 1)) + assert_equal_type_too(b'Vohs1', translations.lngettext('foo1', 'foos1', 2)) + assert_equal_type_too(b'VohCTX1', translations.lnpgettext('foo', 'foo1', 'foos1', 1)) + assert_equal_type_too(b'VohsCTX1', translations.lnpgettext('foo', 'foo1', 'foos1', 2)) + + +def test_dpgettext(translations): + assert_equal_type_too('VohD', translations.dgettext('messages1', 'foo')) + assert_equal_type_too('VohCTXD', translations.dpgettext('messages1', 'foo', 'foo')) + + +def test_dupgettext(translations): + assert_equal_type_too('VohD', translations.dugettext('messages1', 'foo')) + assert_equal_type_too('VohCTXD', translations.dupgettext('messages1', 'foo', 'foo')) + + +@pytest.mark.skipif(SKIP_LGETTEXT, reason='lgettext is deprecated') +def test_ldpgettext(translations): + assert_equal_type_too(b'VohD', translations.ldgettext('messages1', 'foo')) + assert_equal_type_too(b'VohCTXD', translations.ldpgettext('messages1', 'foo', 'foo')) + + +def test_dnpgettext(translations): + assert_equal_type_too('VohD1', translations.dngettext('messages1', 'foo1', 'foos1', 1)) + assert_equal_type_too('VohsD1', translations.dngettext('messages1', 'foo1', 'foos1', 2)) + assert_equal_type_too('VohCTXD1', translations.dnpgettext('messages1', 'foo', 'foo1', 'foos1', 1)) + assert_equal_type_too('VohsCTXD1', translations.dnpgettext('messages1', 'foo', 'foo1', 'foos1', 2)) + + +def test_dunpgettext(translations): + assert_equal_type_too('VohD1', translations.dungettext('messages1', 'foo1', 'foos1', 1)) + assert_equal_type_too('VohsD1', translations.dungettext('messages1', 'foo1', 'foos1', 2)) + assert_equal_type_too('VohCTXD1', translations.dunpgettext('messages1', 'foo', 'foo1', 'foos1', 1)) + assert_equal_type_too('VohsCTXD1', translations.dunpgettext('messages1', 'foo', 'foo1', 'foos1', 2)) + + +@pytest.mark.skipif(SKIP_LGETTEXT, reason='lgettext is deprecated') +def test_ldnpgettext(translations): + assert_equal_type_too(b'VohD1', translations.ldngettext('messages1', 'foo1', 'foos1', 1)) + assert_equal_type_too(b'VohsD1', translations.ldngettext('messages1', 'foo1', 'foos1', 2)) + assert_equal_type_too(b'VohCTXD1', translations.ldnpgettext('messages1', 'foo', 'foo1', 'foos1', 1)) + assert_equal_type_too(b'VohsCTXD1', translations.ldnpgettext('messages1', 'foo', 'foo1', 'foos1', 2)) + + +def test_load(translations): + tempdir = tempfile.mkdtemp() + try: + messages_dir = os.path.join(tempdir, 'fr', 'LC_MESSAGES') + os.makedirs(messages_dir) + catalog = Catalog(locale='fr', domain='messages') + catalog.add('foo', 'bar') + with open(os.path.join(messages_dir, 'messages.mo'), 'wb') as f: + write_mo(f, catalog) + + translations = support.Translations.load(tempdir, locales=('fr',), domain='messages') + assert translations.gettext('foo') == 'bar' + finally: + shutil.rmtree(tempdir) + + +def get_gettext_method_names(obj): + names = [name for name in dir(obj) if 'gettext' in name] + if SKIP_LGETTEXT: + # Remove deprecated l*gettext functions + names = [name for name in names if not name.startswith('l')] + return names + + +def test_null_translations_have_same_methods(empty_translations, null_translations): + for name in get_gettext_method_names(empty_translations): + assert hasattr(null_translations, name), f'NullTranslations does not provide method {name!r}' + + +def test_null_translations_method_signature_compatibility(empty_translations, null_translations): + for name in get_gettext_method_names(empty_translations): + assert ( + inspect.getfullargspec(getattr(empty_translations, name)) == + inspect.getfullargspec(getattr(null_translations, name)) + ) + + +def test_null_translations_same_return_values(empty_translations, null_translations): + data = { + 'message': 'foo', + 'domain': 'domain', + 'context': 'tests', + 'singular': 'bar', + 'plural': 'baz', + 'num': 1, + 'msgid1': 'bar', + 'msgid2': 'baz', + 'n': 1, + } + for name in get_gettext_method_names(empty_translations): + method = getattr(empty_translations, name) + null_method = getattr(null_translations, name) + signature = inspect.getfullargspec(method) + parameter_names = [name for name in signature.args if name != 'self'] + values = [data[name] for name in parameter_names] + assert method(*values) == null_method(*values) + + +def test_catalog_merge_files(): + # Refs issues #92, #162 + t1 = support.Translations() + assert t1.files == [] + t1._catalog['foo'] = 'bar' + fp = io.BytesIO() + write_mo(fp, Catalog()) + fp.seek(0) + fp.name = 'pro.mo' + t2 = support.Translations(fp) + assert t2.files == ['pro.mo'] + t2._catalog['bar'] = 'quux' + t1.merge(t2) + assert t1.files == ['pro.mo'] + assert set(t1._catalog.keys()) == {'', 'foo', 'bar'}