diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ba6bdf0f..25c2ea1f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -31,8 +31,8 @@ jobs: with: python-version: ${{ matrix.python-version }} - run: uv sync --dev --all-packages - - run: uv run python -m unittest discover -s fluent.syntax - - run: uv run python -m unittest discover -s fluent.runtime + - run: uv run pytest fluent.syntax + - run: uv run pytest fluent.runtime # Test fluent.runtime's compatibility with a range of fluent.syntax versions. compatibility: @@ -52,4 +52,4 @@ jobs: - run: uv venv - run: uv pip install ${{ matrix.fluent-syntax }} - run: uv pip install ./fluent.runtime - - run: uv run python -m unittest discover -s fluent.runtime + - run: uv run pytest fluent.runtime diff --git a/fluent.runtime/tests/format/test_arguments.py b/fluent.runtime/tests/format/test_arguments.py index 6016d5f7..5d49f526 100644 --- a/fluent.runtime/tests/format/test_arguments.py +++ b/fluent.runtime/tests/format/test_arguments.py @@ -1,74 +1,61 @@ -import unittest +import pytest from fluent.runtime import FluentBundle, FluentResource from ..utils import dedent_ftl -class TestNumbersInValues(unittest.TestCase): - def setUp(self): - self.bundle = FluentBundle(["en-US"], use_isolating=False) - self.bundle.add_resource( +class TestNumbersInValues: + @pytest.fixture + def bundle(self): + bundle = FluentBundle(["en-US"], use_isolating=False) + bundle.add_resource( FluentResource( dedent_ftl( """ - foo = Foo { $num } - bar = { foo } - baz = - .attr = Baz Attribute { $num } - qux = { "a" -> - *[a] Baz Variant A { $num } - } - """ + foo = Foo { $num } + bar = { foo } + baz = + .attr = Baz Attribute { $num } + qux = { "a" -> + *[a] Baz Variant A { $num } + } + """ ) ) ) + return bundle - def test_can_be_used_in_the_message_value(self): - val, errs = self.bundle.format_pattern( - self.bundle.get_message("foo").value, {"num": 3} - ) - self.assertEqual(val, "Foo 3") - self.assertEqual(len(errs), 0) - - def test_can_be_used_in_the_message_value_which_is_referenced(self): - val, errs = self.bundle.format_pattern( - self.bundle.get_message("bar").value, {"num": 3} - ) - self.assertEqual(val, "Foo 3") - self.assertEqual(len(errs), 0) + def test_can_be_used_in_the_message_value(self, bundle): + val, errs = bundle.format_pattern(bundle.get_message("foo").value, {"num": 3}) + assert val == "Foo 3" + assert len(errs) == 0 - def test_can_be_used_in_an_attribute(self): - val, errs = self.bundle.format_pattern( - self.bundle.get_message("baz").attributes["attr"], {"num": 3} - ) - self.assertEqual(val, "Baz Attribute 3") - self.assertEqual(len(errs), 0) + def test_can_be_used_in_the_message_value_which_is_referenced(self, bundle): + val, errs = bundle.format_pattern(bundle.get_message("bar").value, {"num": 3}) + assert val == "Foo 3" + assert len(errs) == 0 - def test_can_be_used_in_a_variant(self): - val, errs = self.bundle.format_pattern( - self.bundle.get_message("qux").value, {"num": 3} + def test_can_be_used_in_an_attribute(self, bundle): + val, errs = bundle.format_pattern( + bundle.get_message("baz").attributes["attr"], {"num": 3} ) - self.assertEqual(val, "Baz Variant A 3") - self.assertEqual(len(errs), 0) + assert val == "Baz Attribute 3" + assert len(errs) == 0 + def test_can_be_used_in_a_variant(self, bundle): + val, errs = bundle.format_pattern(bundle.get_message("qux").value, {"num": 3}) + assert val == "Baz Variant A 3" + assert len(errs) == 0 -class TestStrings(unittest.TestCase): - def setUp(self): - self.bundle = FluentBundle(["en-US"], use_isolating=False) - self.bundle.add_resource( - FluentResource( - dedent_ftl( - """ - foo = { $arg } - """ - ) - ) - ) +class TestStrings: def test_can_be_a_string(self): - val, errs = self.bundle.format_pattern( - self.bundle.get_message("foo").value, {"arg": "Argument"} + bundle = FluentBundle(["en-US"], use_isolating=False) + bundle.add_resource(FluentResource("foo = { $arg }")) + + val, errs = bundle.format_pattern( + bundle.get_message("foo").value, {"arg": "Argument"} ) - self.assertEqual(val, "Argument") - self.assertEqual(len(errs), 0) + assert val == "Argument" + assert len(errs) == 0 diff --git a/fluent.runtime/tests/format/test_attributes.py b/fluent.runtime/tests/format/test_attributes.py index 040cda9b..915fb165 100644 --- a/fluent.runtime/tests/format/test_attributes.py +++ b/fluent.runtime/tests/format/test_attributes.py @@ -1,190 +1,173 @@ -import unittest - +import pytest from fluent.runtime import FluentBundle, FluentResource from fluent.runtime.errors import FluentReferenceError from ..utils import dedent_ftl -class TestAttributesWithStringValues(unittest.TestCase): - - def setUp(self): - self.bundle = FluentBundle(["en-US"], use_isolating=False) - self.bundle.add_resource( +class TestAttributesWithStringValues: + @pytest.fixture + def bundle(self): + bundle = FluentBundle(["en-US"], use_isolating=False) + bundle.add_resource( FluentResource( dedent_ftl( """ - foo = Foo - .attr = Foo Attribute - bar = { foo } Bar - .attr = Bar Attribute - ref-foo = { foo.attr } - ref-bar = { bar.attr } - """ + foo = Foo + .attr = Foo Attribute + bar = { foo } Bar + .attr = Bar Attribute + ref-foo = { foo.attr } + ref-bar = { bar.attr } + """ ) ) ) + return bundle - def test_can_be_referenced_for_entities_with_string_values(self): - val, errs = self.bundle.format_pattern( - self.bundle.get_message("ref-foo").value, {} - ) - self.assertEqual(val, "Foo Attribute") - self.assertEqual(len(errs), 0) + def test_can_be_referenced_for_entities_with_string_values(self, bundle): + val, errs = bundle.format_pattern(bundle.get_message("ref-foo").value, {}) + assert val == "Foo Attribute" + assert len(errs) == 0 - def test_can_be_referenced_for_entities_with_pattern_values(self): - val, errs = self.bundle.format_pattern( - self.bundle.get_message("ref-bar").value, {} - ) - self.assertEqual(val, "Bar Attribute") - self.assertEqual(len(errs), 0) + def test_can_be_referenced_for_entities_with_pattern_values(self, bundle): + val, errs = bundle.format_pattern(bundle.get_message("ref-bar").value, {}) + assert val == "Bar Attribute" + assert len(errs) == 0 - def test_can_be_formatted_directly_for_entities_with_string_values(self): - val, errs = self.bundle.format_pattern( - self.bundle.get_message("foo").attributes["attr"], {} + def test_can_be_formatted_directly_for_entities_with_string_values(self, bundle): + val, errs = bundle.format_pattern( + bundle.get_message("foo").attributes["attr"], {} ) - self.assertEqual(val, "Foo Attribute") - self.assertEqual(len(errs), 0) + assert val == "Foo Attribute" + assert len(errs) == 0 - def test_can_be_formatted_directly_for_entities_with_pattern_values(self): - val, errs = self.bundle.format_pattern( - self.bundle.get_message("bar").attributes["attr"], {} + def test_can_be_formatted_directly_for_entities_with_pattern_values(self, bundle): + val, errs = bundle.format_pattern( + bundle.get_message("bar").attributes["attr"], {} ) - self.assertEqual(val, "Bar Attribute") - self.assertEqual(len(errs), 0) - + assert val == "Bar Attribute" + assert len(errs) == 0 -class TestAttributesWithSimplePatternValues(unittest.TestCase): - def setUp(self): - self.bundle = FluentBundle(["en-US"], use_isolating=False) - self.bundle.add_resource( +class TestAttributesWithSimplePatternValues: + @pytest.fixture + def bundle(self): + bundle = FluentBundle(["en-US"], use_isolating=False) + bundle.add_resource( FluentResource( dedent_ftl( """ - foo = Foo - bar = Bar - .attr = { foo } Attribute - baz = { foo } Baz - .attr = { foo } Attribute - qux = Qux - .attr = { qux } Attribute - ref-bar = { bar.attr } - ref-baz = { baz.attr } - ref-qux = { qux.attr } - """ + foo = Foo + bar = Bar + .attr = { foo } Attribute + baz = { foo } Baz + .attr = { foo } Attribute + qux = Qux + .attr = { qux } Attribute + ref-bar = { bar.attr } + ref-baz = { baz.attr } + ref-qux = { qux.attr } + """ ) ) ) + return bundle - def test_can_be_referenced_for_entities_with_string_values(self): - val, errs = self.bundle.format_pattern( - self.bundle.get_message("ref-bar").value, {} - ) - self.assertEqual(val, "Foo Attribute") - self.assertEqual(len(errs), 0) + def test_can_be_referenced_for_entities_with_string_values(self, bundle): + val, errs = bundle.format_pattern(bundle.get_message("ref-bar").value, {}) + assert val == "Foo Attribute" + assert len(errs) == 0 - def test_can_be_formatted_directly_for_entities_with_string_values(self): - val, errs = self.bundle.format_pattern( - self.bundle.get_message("bar").attributes["attr"], {} + def test_can_be_formatted_directly_for_entities_with_string_values(self, bundle): + val, errs = bundle.format_pattern( + bundle.get_message("bar").attributes["attr"], {} ) - self.assertEqual(val, "Foo Attribute") - self.assertEqual(len(errs), 0) + assert val == "Foo Attribute" + assert len(errs) == 0 - def test_can_be_referenced_for_entities_with_pattern_values(self): - val, errs = self.bundle.format_pattern( - self.bundle.get_message("ref-baz").value, {} - ) - self.assertEqual(val, "Foo Attribute") - self.assertEqual(len(errs), 0) + def test_can_be_referenced_for_entities_with_pattern_values(self, bundle): + val, errs = bundle.format_pattern(bundle.get_message("ref-baz").value, {}) + assert val == "Foo Attribute" + assert len(errs) == 0 - def test_can_be_formatted_directly_for_entities_with_pattern_values(self): - val, errs = self.bundle.format_pattern( - self.bundle.get_message("baz").attributes["attr"], {} + def test_can_be_formatted_directly_for_entities_with_pattern_values(self, bundle): + val, errs = bundle.format_pattern( + bundle.get_message("baz").attributes["attr"], {} ) - self.assertEqual(val, "Foo Attribute") - self.assertEqual(len(errs), 0) + assert val == "Foo Attribute" + assert len(errs) == 0 - def test_works_with_self_references(self): - val, errs = self.bundle.format_pattern( - self.bundle.get_message("ref-qux").value, {} - ) - self.assertEqual(val, "Qux Attribute") - self.assertEqual(len(errs), 0) + def test_works_with_self_references(self, bundle): + val, errs = bundle.format_pattern(bundle.get_message("ref-qux").value, {}) + assert val == "Qux Attribute" + assert len(errs) == 0 - def test_works_with_self_references_direct(self): - val, errs = self.bundle.format_pattern( - self.bundle.get_message("qux").attributes["attr"], {} + def test_works_with_self_references_direct(self, bundle): + val, errs = bundle.format_pattern( + bundle.get_message("qux").attributes["attr"], {} ) - self.assertEqual(val, "Qux Attribute") - self.assertEqual(len(errs), 0) + assert val == "Qux Attribute" + assert len(errs) == 0 -class TestMissing(unittest.TestCase): - def setUp(self): - self.bundle = FluentBundle(["en-US"], use_isolating=False) - self.bundle.add_resource( +class TestMissing: + @pytest.fixture + def bundle(self): + bundle = FluentBundle(["en-US"], use_isolating=False) + bundle.add_resource( FluentResource( dedent_ftl( """ - foo = Foo - bar = Bar - .attr = Bar Attribute - baz = { foo } Baz - qux = { foo } Qux - .attr = Qux Attribute - ref-foo = { foo.missing } - ref-bar = { bar.missing } - ref-baz = { baz.missing } - ref-qux = { qux.missing } - attr-only = - .attr = Attr Only Attribute - ref-double-missing = { missing.attr } - """ + foo = Foo + bar = Bar + .attr = Bar Attribute + baz = { foo } Baz + qux = { foo } Qux + .attr = Qux Attribute + ref-foo = { foo.missing } + ref-bar = { bar.missing } + ref-baz = { baz.missing } + ref-qux = { qux.missing } + attr-only = + .attr = Attr Only Attribute + ref-double-missing = { missing.attr } + """ ) ) ) + return bundle - def test_msg_with_string_value_and_no_attributes(self): - val, errs = self.bundle.format_pattern( - self.bundle.get_message("ref-foo").value, {} - ) - self.assertEqual(val, "{foo.missing}") - self.assertEqual(errs, [FluentReferenceError("Unknown attribute: foo.missing")]) + def test_msg_with_string_value_and_no_attributes(self, bundle): + val, errs = bundle.format_pattern(bundle.get_message("ref-foo").value, {}) + assert val == "{foo.missing}" + assert errs == [FluentReferenceError("Unknown attribute: foo.missing")] - def test_msg_with_string_value_and_other_attributes(self): - val, errs = self.bundle.format_pattern( - self.bundle.get_message("ref-bar").value, {} - ) - self.assertEqual(val, "{bar.missing}") - self.assertEqual(errs, [FluentReferenceError("Unknown attribute: bar.missing")]) + def test_msg_with_string_value_and_other_attributes(self, bundle): + val, errs = bundle.format_pattern(bundle.get_message("ref-bar").value, {}) + assert val == "{bar.missing}" + assert errs == [FluentReferenceError("Unknown attribute: bar.missing")] - def test_msg_with_pattern_value_and_no_attributes(self): - val, errs = self.bundle.format_pattern( - self.bundle.get_message("ref-baz").value, {} - ) - self.assertEqual(val, "{baz.missing}") - self.assertEqual(errs, [FluentReferenceError("Unknown attribute: baz.missing")]) + def test_msg_with_pattern_value_and_no_attributes(self, bundle): + val, errs = bundle.format_pattern(bundle.get_message("ref-baz").value, {}) + assert val == "{baz.missing}" + assert errs == [FluentReferenceError("Unknown attribute: baz.missing")] - def test_msg_with_pattern_value_and_other_attributes(self): - val, errs = self.bundle.format_pattern( - self.bundle.get_message("ref-qux").value, {} - ) - self.assertEqual(val, "{qux.missing}") - self.assertEqual(errs, [FluentReferenceError("Unknown attribute: qux.missing")]) + def test_msg_with_pattern_value_and_other_attributes(self, bundle): + val, errs = bundle.format_pattern(bundle.get_message("ref-qux").value, {}) + assert val == "{qux.missing}" + assert errs == [FluentReferenceError("Unknown attribute: qux.missing")] - def test_attr_only_attribute(self): - val, errs = self.bundle.format_pattern( - self.bundle.get_message("attr-only").attributes["attr"], {} + def test_attr_only_attribute(self, bundle): + val, errs = bundle.format_pattern( + bundle.get_message("attr-only").attributes["attr"], {} ) - self.assertEqual(val, "Attr Only Attribute") - self.assertEqual(len(errs), 0) + assert val == "Attr Only Attribute" + assert len(errs) == 0 - def test_missing_message_and_attribute(self): - val, errs = self.bundle.format_pattern( - self.bundle.get_message("ref-double-missing").value, {} - ) - self.assertEqual(val, "{missing.attr}") - self.assertEqual( - errs, [FluentReferenceError("Unknown attribute: missing.attr")] + def test_missing_message_and_attribute(self, bundle): + val, errs = bundle.format_pattern( + bundle.get_message("ref-double-missing").value, {} ) + assert val == "{missing.attr}" + assert errs == [FluentReferenceError("Unknown attribute: missing.attr")] diff --git a/fluent.runtime/tests/format/test_builtins.py b/fluent.runtime/tests/format/test_builtins.py index ea5b3383..8f4c0fd3 100644 --- a/fluent.runtime/tests/format/test_builtins.py +++ b/fluent.runtime/tests/format/test_builtins.py @@ -1,7 +1,8 @@ -import unittest from datetime import date, datetime from decimal import Decimal +import pytest + from fluent.runtime import FluentBundle, FluentResource from fluent.runtime.errors import FluentReferenceError from fluent.runtime.types import fluent_date, fluent_number @@ -9,187 +10,181 @@ from ..utils import dedent_ftl -class TestNumberBuiltin(unittest.TestCase): - - def setUp(self): - self.bundle = FluentBundle(["en-US"], use_isolating=False) - self.bundle.add_resource( +class TestNumberBuiltin: + @pytest.fixture + def bundle(self): + bundle = FluentBundle(["en-US"], use_isolating=False) + bundle.add_resource( FluentResource( dedent_ftl( """ - implicit-call = { 123456 } - implicit-call2 = { $arg } - defaults = { NUMBER(123456) } - percent-style = { NUMBER(1.234, style: "percent") } - currency-style = { NUMBER(123456, style: "currency", currency: "USD") } - from-arg = { NUMBER($arg) } - merge-params = { NUMBER($arg, useGrouping: 0) } - """ + implicit-call = { 123456 } + implicit-call2 = { $arg } + defaults = { NUMBER(123456) } + percent-style = { NUMBER(1.234, style: "percent") } + currency-style = { NUMBER(123456, style: "currency", currency: "USD") } + from-arg = { NUMBER($arg) } + merge-params = { NUMBER($arg, useGrouping: 0) } + """ ) ) ) + return bundle - def test_implicit_call(self): - val, errs = self.bundle.format_pattern( - self.bundle.get_message("implicit-call").value, {} - ) - self.assertEqual(val, "123,456") - self.assertEqual(len(errs), 0) + def test_implicit_call(self, bundle): + val, errs = bundle.format_pattern(bundle.get_message("implicit-call").value, {}) + assert val == "123,456" + assert len(errs) == 0 - def test_implicit_call2_int(self): - val, errs = self.bundle.format_pattern( - self.bundle.get_message("implicit-call2").value, {"arg": 123456} + def test_implicit_call2_int(self, bundle): + val, errs = bundle.format_pattern( + bundle.get_message("implicit-call2").value, {"arg": 123456} ) - self.assertEqual(val, "123,456") - self.assertEqual(len(errs), 0) + assert val == "123,456" + assert len(errs) == 0 - def test_implicit_call2_float(self): - val, errs = self.bundle.format_pattern( - self.bundle.get_message("implicit-call2").value, {"arg": 123456.0} + def test_implicit_call2_float(self, bundle): + val, errs = bundle.format_pattern( + bundle.get_message("implicit-call2").value, {"arg": 123456.0} ) - self.assertEqual(val, "123,456") - self.assertEqual(len(errs), 0) + assert val == "123,456" + assert len(errs) == 0 - def test_implicit_call2_decimal(self): - val, errs = self.bundle.format_pattern( - self.bundle.get_message("implicit-call2").value, + def test_implicit_call2_decimal(self, bundle): + val, errs = bundle.format_pattern( + bundle.get_message("implicit-call2").value, {"arg": Decimal("123456.0")}, ) - self.assertEqual(val, "123,456") - self.assertEqual(len(errs), 0) + assert val == "123,456" + assert len(errs) == 0 - def test_defaults(self): - val, errs = self.bundle.format_pattern( - self.bundle.get_message("defaults").value, {} - ) - self.assertEqual(val, "123,456") - self.assertEqual(len(errs), 0) + def test_defaults(self, bundle): + val, errs = bundle.format_pattern(bundle.get_message("defaults").value, {}) + assert val == "123,456" + assert len(errs) == 0 - def test_percent_style(self): - val, errs = self.bundle.format_pattern( - self.bundle.get_message("percent-style").value, {} - ) - self.assertEqual(val, "123%") - self.assertEqual(len(errs), 0) + def test_percent_style(self, bundle): + val, errs = bundle.format_pattern(bundle.get_message("percent-style").value, {}) + assert val == "123%" + assert len(errs) == 0 - def test_currency_style(self): - val, errs = self.bundle.format_pattern( - self.bundle.get_message("currency-style").value, {} + def test_currency_style(self, bundle): + val, errs = bundle.format_pattern( + bundle.get_message("currency-style").value, {} ) - self.assertEqual(val, "$123,456.00") - self.assertEqual(len(errs), 0) + assert val == "$123,456.00" + assert len(errs) == 0 - def test_from_arg_int(self): - val, errs = self.bundle.format_pattern( - self.bundle.get_message("from-arg").value, {"arg": 123456} + def test_from_arg_int(self, bundle): + val, errs = bundle.format_pattern( + bundle.get_message("from-arg").value, {"arg": 123456} ) - self.assertEqual(val, "123,456") - self.assertEqual(len(errs), 0) + assert val == "123,456" + assert len(errs) == 0 - def test_from_arg_float(self): - val, errs = self.bundle.format_pattern( - self.bundle.get_message("from-arg").value, {"arg": 123456.0} + def test_from_arg_float(self, bundle): + val, errs = bundle.format_pattern( + bundle.get_message("from-arg").value, {"arg": 123456.0} ) - self.assertEqual(val, "123,456") - self.assertEqual(len(errs), 0) + assert val == "123,456" + assert len(errs) == 0 - def test_from_arg_decimal(self): - val, errs = self.bundle.format_pattern( - self.bundle.get_message("from-arg").value, {"arg": Decimal("123456.0")} + def test_from_arg_decimal(self, bundle): + val, errs = bundle.format_pattern( + bundle.get_message("from-arg").value, {"arg": Decimal("123456.0")} ) - self.assertEqual(val, "123,456") - self.assertEqual(len(errs), 0) + assert val == "123,456" + assert len(errs) == 0 - def test_from_arg_missing(self): - val, errs = self.bundle.format_pattern( - self.bundle.get_message("from-arg").value, {} - ) - self.assertEqual(val, "arg") - self.assertEqual(len(errs), 1) - self.assertEqual(errs, [FluentReferenceError("Unknown external: arg")]) + def test_from_arg_missing(self, bundle): + val, errs = bundle.format_pattern(bundle.get_message("from-arg").value, {}) + assert val == "arg" + assert len(errs) == 1 + assert errs == [FluentReferenceError("Unknown external: arg")] - def test_partial_application(self): + def test_partial_application(self, bundle): number = fluent_number(123456.78, currency="USD", style="currency") - val, errs = self.bundle.format_pattern( - self.bundle.get_message("from-arg").value, {"arg": number} + val, errs = bundle.format_pattern( + bundle.get_message("from-arg").value, {"arg": number} ) - self.assertEqual(val, "$123,456.78") - self.assertEqual(len(errs), 0) + assert val == "$123,456.78" + assert len(errs) == 0 - def test_merge_params(self): + def test_merge_params(self, bundle): number = fluent_number(123456.78, currency="USD", style="currency") - val, errs = self.bundle.format_pattern( - self.bundle.get_message("merge-params").value, {"arg": number} + val, errs = bundle.format_pattern( + bundle.get_message("merge-params").value, {"arg": number} ) - self.assertEqual(val, "$123456.78") - self.assertEqual(len(errs), 0) + assert val == "$123456.78" + assert len(errs) == 0 -class TestDatetimeBuiltin(unittest.TestCase): - - def setUp(self): - self.bundle = FluentBundle(["en-US"], use_isolating=False) - self.bundle.add_resource( +class TestDatetimeBuiltin: + @pytest.fixture + def bundle(self): + bundle = FluentBundle(["en-US"], use_isolating=False) + bundle.add_resource( FluentResource( dedent_ftl( """ - implicit-call = { $date } - explicit-call = { DATETIME($date) } - call-with-arg = { DATETIME($date, dateStyle: "long") } - """ + implicit-call = { $date } + explicit-call = { DATETIME($date) } + call-with-arg = { DATETIME($date, dateStyle: "long") } + """ ) ) ) + return bundle - def test_implicit_call_date(self): - val, errs = self.bundle.format_pattern( - self.bundle.get_message("implicit-call").value, {"date": date(2018, 2, 1)} + def test_implicit_call_date(self, bundle): + val, errs = bundle.format_pattern( + bundle.get_message("implicit-call").value, {"date": date(2018, 2, 1)} ) - self.assertEqual(val, "Feb 1, 2018") - self.assertEqual(len(errs), 0) + assert val == "Feb 1, 2018" + assert len(errs) == 0 - def test_implicit_call_datetime(self): - val, errs = self.bundle.format_pattern( - self.bundle.get_message("implicit-call").value, + def test_implicit_call_datetime(self, bundle): + val, errs = bundle.format_pattern( + bundle.get_message("implicit-call").value, {"date": datetime(2018, 2, 1, 14, 15, 16)}, ) - self.assertEqual(val, "Feb 1, 2018") - self.assertEqual(len(errs), 0) + assert val == "Feb 1, 2018" + assert len(errs) == 0 - def test_explicit_call_date(self): - val, errs = self.bundle.format_pattern( - self.bundle.get_message("explicit-call").value, {"date": date(2018, 2, 1)} + def test_explicit_call_date(self, bundle): + val, errs = bundle.format_pattern( + bundle.get_message("explicit-call").value, {"date": date(2018, 2, 1)} ) - self.assertEqual(val, "Feb 1, 2018") - self.assertEqual(len(errs), 0) + assert val == "Feb 1, 2018" + assert len(errs) == 0 - def test_explicit_call_datetime(self): - val, errs = self.bundle.format_pattern( - self.bundle.get_message("explicit-call").value, + def test_explicit_call_datetime(self, bundle): + val, errs = bundle.format_pattern( + bundle.get_message("explicit-call").value, {"date": datetime(2018, 2, 1, 14, 15, 16)}, ) - self.assertEqual(val, "Feb 1, 2018") - self.assertEqual(len(errs), 0) + assert val == "Feb 1, 2018" + assert len(errs) == 0 - def test_explicit_call_date_fluent_date(self): - val, errs = self.bundle.format_pattern( - self.bundle.get_message("explicit-call").value, + def test_explicit_call_date_fluent_date(self, bundle): + val, errs = bundle.format_pattern( + bundle.get_message("explicit-call").value, {"date": fluent_date(date(2018, 2, 1), dateStyle="short")}, ) - self.assertEqual(val, "2/1/18") - self.assertEqual(len(errs), 0) + assert val == "2/1/18" + assert len(errs) == 0 - def test_arg(self): - val, errs = self.bundle.format_pattern( - self.bundle.get_message("call-with-arg").value, {"date": date(2018, 2, 1)} + def test_arg(self, bundle): + val, errs = bundle.format_pattern( + bundle.get_message("call-with-arg").value, {"date": date(2018, 2, 1)} ) - self.assertEqual(val, "February 1, 2018") - self.assertEqual(len(errs), 0) + assert val == "February 1, 2018" + assert len(errs) == 0 - def test_arg_overrides_fluent_date(self): - val, errs = self.bundle.format_pattern( - self.bundle.get_message("call-with-arg").value, + def test_arg_overrides_fluent_date(self, bundle): + val, errs = bundle.format_pattern( + bundle.get_message("call-with-arg").value, {"date": fluent_date(date(2018, 2, 1), dateStyle="short")}, ) - self.assertEqual(val, "February 1, 2018") - self.assertEqual(len(errs), 0) + assert val == "February 1, 2018" + assert len(errs) == 0 diff --git a/fluent.runtime/tests/format/test_functions.py b/fluent.runtime/tests/format/test_functions.py index e4011328..2135b3f4 100644 --- a/fluent.runtime/tests/format/test_functions.py +++ b/fluent.runtime/tests/format/test_functions.py @@ -1,5 +1,4 @@ -import unittest - +import pytest from fluent.runtime import FluentBundle, FluentResource from fluent.runtime.errors import FluentReferenceError from fluent.runtime.types import FluentNone @@ -7,203 +6,171 @@ from ..utils import dedent_ftl -class TestFunctionCalls(unittest.TestCase): - - def setUp(self): - self.bundle = FluentBundle( +class TestFunctionCalls: + @pytest.fixture + def bundle(self): + bundle = FluentBundle( ["en-US"], use_isolating=False, functions={"IDENTITY": lambda x: x} ) - self.bundle.add_resource( + bundle.add_resource( FluentResource( dedent_ftl( """ - foo = Foo - .attr = Attribute - pass-nothing = { IDENTITY() } - pass-string = { IDENTITY("a") } - pass-number = { IDENTITY(1) } - pass-message = { IDENTITY(foo) } - pass-attr = { IDENTITY(foo.attr) } - pass-external = { IDENTITY($ext) } - pass-function-call = { IDENTITY(IDENTITY(1)) } - """ + foo = Foo + .attr = Attribute + pass-nothing = { IDENTITY() } + pass-string = { IDENTITY("a") } + pass-number = { IDENTITY(1) } + pass-message = { IDENTITY(foo) } + pass-attr = { IDENTITY(foo.attr) } + pass-external = { IDENTITY($ext) } + pass-function-call = { IDENTITY(IDENTITY(1)) } + """ ) ) ) + return bundle - def test_accepts_strings(self): - val, errs = self.bundle.format_pattern( - self.bundle.get_message("pass-string").value, {} - ) - self.assertEqual(val, "a") - self.assertEqual(len(errs), 0) + def test_accepts_strings(self, bundle): + val, errs = bundle.format_pattern(bundle.get_message("pass-string").value, {}) + assert val == "a" + assert len(errs) == 0 - def test_accepts_numbers(self): - val, errs = self.bundle.format_pattern( - self.bundle.get_message("pass-number").value, {} - ) - self.assertEqual(val, "1") - self.assertEqual(len(errs), 0) + def test_accepts_numbers(self, bundle): + val, errs = bundle.format_pattern(bundle.get_message("pass-number").value, {}) + assert val == "1" + assert len(errs) == 0 - def test_accepts_entities(self): - val, errs = self.bundle.format_pattern( - self.bundle.get_message("pass-message").value, {} - ) - self.assertEqual(val, "Foo") - self.assertEqual(len(errs), 0) + def test_accepts_entities(self, bundle): + val, errs = bundle.format_pattern(bundle.get_message("pass-message").value, {}) + assert val == "Foo" + assert len(errs) == 0 - def test_accepts_attributes(self): - val, errs = self.bundle.format_pattern( - self.bundle.get_message("pass-attr").value, {} - ) - self.assertEqual(val, "Attribute") - self.assertEqual(len(errs), 0) + def test_accepts_attributes(self, bundle): + val, errs = bundle.format_pattern(bundle.get_message("pass-attr").value, {}) + assert val == "Attribute" + assert len(errs) == 0 - def test_accepts_externals(self): - val, errs = self.bundle.format_pattern( - self.bundle.get_message("pass-external").value, {"ext": "Ext"} + def test_accepts_externals(self, bundle): + val, errs = bundle.format_pattern( + bundle.get_message("pass-external").value, {"ext": "Ext"} ) - self.assertEqual(val, "Ext") - self.assertEqual(len(errs), 0) + assert val == "Ext" + assert len(errs) == 0 - def test_accepts_function_calls(self): - val, errs = self.bundle.format_pattern( - self.bundle.get_message("pass-function-call").value, {} + def test_accepts_function_calls(self, bundle): + val, errs = bundle.format_pattern( + bundle.get_message("pass-function-call").value, {} ) - self.assertEqual(val, "1") - self.assertEqual(len(errs), 0) + assert val == "1" + assert len(errs) == 0 - def test_wrong_arity(self): - val, errs = self.bundle.format_pattern( - self.bundle.get_message("pass-nothing").value, {} - ) - self.assertEqual(val, "IDENTITY()") - self.assertEqual(len(errs), 1) - self.assertEqual(type(errs[0]), TypeError) + def test_wrong_arity(self, bundle): + val, errs = bundle.format_pattern(bundle.get_message("pass-nothing").value, {}) + assert val == "IDENTITY()" + assert len(errs) == 1 + assert isinstance(errs[0], TypeError) -class TestMissing(unittest.TestCase): - - def setUp(self): - self.bundle = FluentBundle(["en-US"], use_isolating=False) - self.bundle.add_resource( - FluentResource( - dedent_ftl( - """ - missing = { MISSING(1) } - """ - ) - ) - ) - +class TestMissing: def test_falls_back_to_name_of_function(self): - val, errs = self.bundle.format_pattern( - self.bundle.get_message("missing").value, {} - ) - self.assertEqual(val, "MISSING()") - self.assertEqual(errs, [FluentReferenceError("Unknown function: MISSING")]) + bundle = FluentBundle(["en-US"], use_isolating=False) + bundle.add_resource(FluentResource("missing = { MISSING(1) }")) + val, errs = bundle.format_pattern(bundle.get_message("missing").value, {}) + assert val == "MISSING()" + assert errs == [FluentReferenceError("Unknown function: MISSING")] -class TestResolving(unittest.TestCase): - def setUp(self): - self.args_passed = [] +class TestResolving: + @pytest.fixture + def args_passed(self): + return [] + @pytest.fixture + def bundle(self, args_passed): def number_processor(number): - self.args_passed.append(number) + args_passed.append(number) return number - self.bundle = FluentBundle( + bundle = FluentBundle( ["en-US"], use_isolating=False, functions={"NUMBER_PROCESSOR": number_processor}, ) - - self.bundle.add_resource( + bundle.add_resource( FluentResource( - dedent_ftl( - """ - pass-number = { NUMBER_PROCESSOR(1) } - pass-arg = { NUMBER_PROCESSOR($arg) } - """ - ) + "pass-number = { NUMBER_PROCESSOR(1) }\n" + + "pass-arg = { NUMBER_PROCESSOR($arg) }\n" ) ) + return bundle - def test_args_passed_as_numbers(self): - val, errs = self.bundle.format_pattern( - self.bundle.get_message("pass-arg").value, {"arg": 1} - ) - self.assertEqual(val, "1") - self.assertEqual(len(errs), 0) - self.assertEqual(self.args_passed, [1]) - - def test_literals_passed_as_numbers(self): - val, errs = self.bundle.format_pattern( - self.bundle.get_message("pass-number").value, {} + def test_args_passed_as_numbers(self, args_passed, bundle): + val, errs = bundle.format_pattern( + bundle.get_message("pass-arg").value, {"arg": 1} ) - self.assertEqual(val, "1") - self.assertEqual(len(errs), 0) - self.assertEqual(self.args_passed, [1]) + assert val == "1" + assert len(errs) == 0 + assert args_passed == [1] + def test_literals_passed_as_numbers(self, args_passed, bundle): + val, errs = bundle.format_pattern(bundle.get_message("pass-number").value, {}) + assert val == "1" + assert len(errs) == 0 + assert args_passed == [1] -class TestKeywordArgs(unittest.TestCase): - def setUp(self): - self.args_passed = [] +class TestKeywordArgs: + @pytest.fixture + def args_passed(self): + return [] + @pytest.fixture + def bundle(self, args_passed): def my_function(arg, kwarg1=None, kwarg2="default"): - self.args_passed.append((arg, kwarg1, kwarg2)) + args_passed.append((arg, kwarg1, kwarg2)) return arg - self.bundle = FluentBundle( + bundle = FluentBundle( ["en-US"], use_isolating=False, functions={"MYFUNC": my_function} ) - self.bundle.add_resource( + bundle.add_resource( FluentResource( dedent_ftl( """ - pass-arg = { MYFUNC("a") } - pass-kwarg1 = { MYFUNC("a", kwarg1: 1) } - pass-kwarg2 = { MYFUNC("a", kwarg2: "other") } - pass-kwargs = { MYFUNC("a", kwarg1: 1, kwarg2: "other") } - pass-user-arg = { MYFUNC($arg) } - """ + pass-arg = { MYFUNC("a") } + pass-kwarg1 = { MYFUNC("a", kwarg1: 1) } + pass-kwarg2 = { MYFUNC("a", kwarg2: "other") } + pass-kwargs = { MYFUNC("a", kwarg1: 1, kwarg2: "other") } + pass-user-arg = { MYFUNC($arg) } + """ ) ) ) + return bundle - def test_defaults(self): - val, errs = self.bundle.format_pattern( - self.bundle.get_message("pass-arg").value, {} - ) - self.assertEqual(self.args_passed, [("a", None, "default")]) - self.assertEqual(len(errs), 0) + def test_defaults(self, args_passed, bundle): + val, errs = bundle.format_pattern(bundle.get_message("pass-arg").value, {}) + assert args_passed == [("a", None, "default")] + assert len(errs) == 0 - def test_pass_kwarg1(self): - val, errs = self.bundle.format_pattern( - self.bundle.get_message("pass-kwarg1").value, {} - ) - self.assertEqual(self.args_passed, [("a", 1, "default")]) - self.assertEqual(len(errs), 0) + def test_pass_kwarg1(self, args_passed, bundle): + val, errs = bundle.format_pattern(bundle.get_message("pass-kwarg1").value, {}) + assert args_passed == [("a", 1, "default")] + assert len(errs) == 0 - def test_pass_kwarg2(self): - val, errs = self.bundle.format_pattern( - self.bundle.get_message("pass-kwarg2").value, {} - ) - self.assertEqual(self.args_passed, [("a", None, "other")]) - self.assertEqual(len(errs), 0) + def test_pass_kwarg2(self, args_passed, bundle): + val, errs = bundle.format_pattern(bundle.get_message("pass-kwarg2").value, {}) + assert args_passed == [("a", None, "other")] + assert len(errs) == 0 - def test_pass_kwargs(self): - val, errs = self.bundle.format_pattern( - self.bundle.get_message("pass-kwargs").value, {} - ) - self.assertEqual(self.args_passed, [("a", 1, "other")]) - self.assertEqual(len(errs), 0) + def test_pass_kwargs(self, args_passed, bundle): + val, errs = bundle.format_pattern(bundle.get_message("pass-kwargs").value, {}) + assert args_passed == [("a", 1, "other")] + assert len(errs) == 0 - def test_missing_arg(self): - val, errs = self.bundle.format_pattern( - self.bundle.get_message("pass-user-arg").value, {} - ) - self.assertEqual(self.args_passed, [(FluentNone("arg"), None, "default")]) - self.assertEqual(len(errs), 1) + def test_missing_arg(self, args_passed, bundle): + val, errs = bundle.format_pattern(bundle.get_message("pass-user-arg").value, {}) + assert args_passed == [(FluentNone("arg"), None, "default")] + assert len(errs) == 1 diff --git a/fluent.runtime/tests/format/test_isolating.py b/fluent.runtime/tests/format/test_isolating.py index fff01e97..6c39333e 100644 --- a/fluent.runtime/tests/format/test_isolating.py +++ b/fluent.runtime/tests/format/test_isolating.py @@ -1,4 +1,4 @@ -import unittest +import pytest from fluent.runtime import FluentBundle, FluentResource @@ -9,76 +9,76 @@ PDI = "\u2069" -class TestUseIsolating(unittest.TestCase): - - def setUp(self): - self.bundle = FluentBundle(["en-US"]) - self.bundle.add_resource( +class TestUseIsolating: + @pytest.fixture + def bundle(self): + bundle = FluentBundle(["en-US"]) + bundle.add_resource( FluentResource( dedent_ftl( """ - foo = Foo - bar = { foo } Bar - baz = { $arg } Baz - qux = { bar } { baz } - """ + foo = Foo + bar = { foo } Bar + baz = { $arg } Baz + qux = { bar } { baz } + """ ) ) ) + return bundle - def test_isolates_interpolated_message_references(self): - val, errs = self.bundle.format_pattern(self.bundle.get_message("bar").value, {}) - self.assertEqual(val, FSI + "Foo" + PDI + " Bar") - self.assertEqual(len(errs), 0) + def test_isolates_interpolated_message_references(self, bundle): + val, errs = bundle.format_pattern(bundle.get_message("bar").value, {}) + assert val == FSI + "Foo" + PDI + " Bar" + assert len(errs) == 0 - def test_isolates_interpolated_string_typed_variable_references(self): - val, errs = self.bundle.format_pattern( - self.bundle.get_message("baz").value, {"arg": "Arg"} + def test_isolates_interpolated_string_typed_variable_references(self, bundle): + val, errs = bundle.format_pattern( + bundle.get_message("baz").value, {"arg": "Arg"} ) - self.assertEqual(val, FSI + "Arg" + PDI + " Baz") - self.assertEqual(len(errs), 0) + assert val == FSI + "Arg" + PDI + " Baz" + assert len(errs) == 0 - def test_isolates_interpolated_number_typed_variable_references(self): - val, errs = self.bundle.format_pattern( - self.bundle.get_message("baz").value, {"arg": 1} - ) - self.assertEqual(val, FSI + "1" + PDI + " Baz") - self.assertEqual(len(errs), 0) + def test_isolates_interpolated_number_typed_variable_references(self, bundle): + val, errs = bundle.format_pattern(bundle.get_message("baz").value, {"arg": 1}) + assert val == FSI + "1" + PDI + " Baz" + assert len(errs) == 0 - def test_isolates_complex_interpolations(self): - val, errs = self.bundle.format_pattern( - self.bundle.get_message("qux").value, {"arg": "Arg"} + def test_isolates_complex_interpolations(self, bundle): + val, errs = bundle.format_pattern( + bundle.get_message("qux").value, {"arg": "Arg"} ) expected_bar = FSI + FSI + "Foo" + PDI + " Bar" + PDI expected_baz = FSI + FSI + "Arg" + PDI + " Baz" + PDI - self.assertEqual(val, expected_bar + " " + expected_baz) - self.assertEqual(len(errs), 0) + assert val == expected_bar + " " + expected_baz + assert len(errs) == 0 -class TestSkipIsolating(unittest.TestCase): - - def setUp(self): - self.bundle = FluentBundle(["en-US"]) - self.bundle.add_resource( +class TestSkipIsolating: + @pytest.fixture + def bundle(self): + bundle = FluentBundle(["en-US"]) + bundle.add_resource( FluentResource( dedent_ftl( """ - -brand-short-name = Amaya - foo = { -brand-short-name } - with-arg = { $arg } - """ + -brand-short-name = Amaya + foo = { -brand-short-name } + with-arg = { $arg } + """ ) ) ) + return bundle - def test_skip_isolating_chars_if_just_one_message_ref(self): - val, errs = self.bundle.format_pattern(self.bundle.get_message("foo").value, {}) - self.assertEqual(val, "Amaya") - self.assertEqual(len(errs), 0) + def test_skip_isolating_chars_if_just_one_message_ref(self, bundle): + val, errs = bundle.format_pattern(bundle.get_message("foo").value, {}) + assert val == "Amaya" + assert len(errs) == 0 - def test_skip_isolating_chars_if_just_one_placeable_arg(self): - val, errs = self.bundle.format_pattern( - self.bundle.get_message("with-arg").value, {"arg": "Arg"} + def test_skip_isolating_chars_if_just_one_placeable_arg(self, bundle): + val, errs = bundle.format_pattern( + bundle.get_message("with-arg").value, {"arg": "Arg"} ) - self.assertEqual(val, "Arg") - self.assertEqual(len(errs), 0) + assert val == "Arg" + assert len(errs) == 0 diff --git a/fluent.runtime/tests/format/test_parameterized_terms.py b/fluent.runtime/tests/format/test_parameterized_terms.py index 8aed061a..5f7c5d96 100644 --- a/fluent.runtime/tests/format/test_parameterized_terms.py +++ b/fluent.runtime/tests/format/test_parameterized_terms.py @@ -1,265 +1,247 @@ -import unittest - +import pytest from fluent.runtime import FluentBundle, FluentResource from fluent.runtime.errors import FluentFormatError, FluentReferenceError from ..utils import dedent_ftl -class TestParameterizedTerms(unittest.TestCase): - - def setUp(self): - self.bundle = FluentBundle(["en-US"], use_isolating=False) - self.bundle.add_resource( +class TestParameterizedTerms: + @pytest.fixture + def bundle(self): + bundle = FluentBundle(["en-US"], use_isolating=False) + bundle.add_resource( FluentResource( dedent_ftl( """ - -thing = { $article -> - *[definite] the thing - [indefinite] a thing - [none] thing - } - thing-no-arg = { -thing } - thing-no-arg-alt = { -thing() } - thing-with-arg = { -thing(article: "indefinite") } - thing-positional-arg = { -thing("foo") } - thing-fallback = { -thing(article: "somethingelse") } - bad-term = { -missing() } - """ + -thing = { $article -> + *[definite] the thing + [indefinite] a thing + [none] thing + } + thing-no-arg = { -thing } + thing-no-arg-alt = { -thing() } + thing-with-arg = { -thing(article: "indefinite") } + thing-positional-arg = { -thing("foo") } + thing-fallback = { -thing(article: "somethingelse") } + bad-term = { -missing() } + """ ) ) ) + return bundle - def test_argument_omitted(self): - val, errs = self.bundle.format_pattern( - self.bundle.get_message("thing-no-arg").value, {} - ) - self.assertEqual(val, "the thing") - self.assertEqual(errs, []) + def test_argument_omitted(self, bundle): + val, errs = bundle.format_pattern(bundle.get_message("thing-no-arg").value, {}) + assert val == "the thing" + assert errs == [] - def test_argument_omitted_alt(self): - val, errs = self.bundle.format_pattern( - self.bundle.get_message("thing-no-arg-alt").value, {} + def test_argument_omitted_alt(self, bundle): + val, errs = bundle.format_pattern( + bundle.get_message("thing-no-arg-alt").value, {} ) - self.assertEqual(val, "the thing") - self.assertEqual(errs, []) + assert val == "the thing" + assert errs == [] - def test_with_argument(self): - val, errs = self.bundle.format_pattern( - self.bundle.get_message("thing-with-arg").value, {} + def test_with_argument(self, bundle): + val, errs = bundle.format_pattern( + bundle.get_message("thing-with-arg").value, {} ) - self.assertEqual(val, "a thing") - self.assertEqual(errs, []) + assert val == "a thing" + assert errs == [] - def test_positional_arg(self): - val, errs = self.bundle.format_pattern( - self.bundle.get_message("thing-positional-arg").value, {} - ) - self.assertEqual(val, "the thing") - self.assertEqual( - errs, - [FluentFormatError("Ignored positional arguments passed to term '-thing'")], + def test_positional_arg(self, bundle): + val, errs = bundle.format_pattern( + bundle.get_message("thing-positional-arg").value, {} ) + assert val == "the thing" + assert errs == [ + FluentFormatError("Ignored positional arguments passed to term '-thing'") + ] - def test_fallback(self): - val, errs = self.bundle.format_pattern( - self.bundle.get_message("thing-fallback").value, {} + def test_fallback(self, bundle): + val, errs = bundle.format_pattern( + bundle.get_message("thing-fallback").value, {} ) - self.assertEqual(val, "the thing") - self.assertEqual(errs, []) + assert val == "the thing" + assert errs == [] - def test_no_implicit_access_to_external_args(self): + def test_no_implicit_access_to_external_args(self, bundle): # The '-thing' term should not get passed article="indefinite" - val, errs = self.bundle.format_pattern( - self.bundle.get_message("thing-no-arg").value, {"article": "indefinite"} - ) - self.assertEqual(val, "the thing") - self.assertEqual(errs, []) - - def test_no_implicit_access_to_external_args_but_term_args_still_passed(self): - val, errs = self.bundle.format_pattern( - self.bundle.get_message("thing-with-arg").value, {"article": "none"} + val, errs = bundle.format_pattern( + bundle.get_message("thing-no-arg").value, {"article": "indefinite"} ) - self.assertEqual(val, "a thing") - self.assertEqual(errs, []) + assert val == "the thing" + assert errs == [] - def test_bad_term(self): - val, errs = self.bundle.format_pattern( - self.bundle.get_message("bad-term").value, {} + def test_no_implicit_access_to_external_args_but_term_args_still_passed( + self, bundle + ): + val, errs = bundle.format_pattern( + bundle.get_message("thing-with-arg").value, {"article": "none"} ) - self.assertEqual(val, "{-missing}") - self.assertEqual(errs, [FluentReferenceError("Unknown term: -missing")]) + assert val == "a thing" + assert errs == [] + def test_bad_term(self, bundle): + val, errs = bundle.format_pattern(bundle.get_message("bad-term").value, {}) + assert val == "{-missing}" + assert errs == [FluentReferenceError("Unknown term: -missing")] -class TestParameterizedTermAttributes(unittest.TestCase): - def setUp(self): - self.bundle = FluentBundle(["en-US"], use_isolating=False) - self.bundle.add_resource( +class TestParameterizedTermAttributes: + @pytest.fixture + def bundle(self): + bundle = FluentBundle(["en-US"], use_isolating=False) + bundle.add_resource( FluentResource( dedent_ftl( """ - -brand = Cool Thing - .status = { $version -> - [v2] available - *[v1] deprecated - } - - attr-with-arg = { -brand } is { -brand.status(version: "v2") -> - [available] available, yay! - *[deprecated] deprecated, sorry - } - - -other = { $arg -> - [a] ABC - *[d] DEF - } - - missing-attr-ref = { -other.missing(arg: "a") -> - [ABC] ABC option - *[DEF] DEF option - } - """ + -brand = Cool Thing + .status = { $version -> + [v2] available + *[v1] deprecated + } + + attr-with-arg = { -brand } is { -brand.status(version: "v2") -> + [available] available, yay! + *[deprecated] deprecated, sorry + } + + -other = { $arg -> + [a] ABC + *[d] DEF + } + + missing-attr-ref = { -other.missing(arg: "a") -> + [ABC] ABC option + *[DEF] DEF option + } + """ ) ) ) + return bundle - def test_with_argument(self): - val, errs = self.bundle.format_pattern( - self.bundle.get_message("attr-with-arg").value, {} - ) - self.assertEqual(val, "Cool Thing is available, yay!") - self.assertEqual(errs, []) + def test_with_argument(self, bundle): + val, errs = bundle.format_pattern(bundle.get_message("attr-with-arg").value, {}) + assert val == "Cool Thing is available, yay!" + assert errs == [] - def test_missing_attr(self): + def test_missing_attr(self, bundle): # We don't fall back from attributes, get default. - val, errs = self.bundle.format_pattern( - self.bundle.get_message("missing-attr-ref").value, {} + val, errs = bundle.format_pattern( + bundle.get_message("missing-attr-ref").value, {} ) - self.assertEqual(val, "DEF option") - self.assertEqual( - errs, [FluentReferenceError("Unknown attribute: -other.missing")] - ) - + assert val == "DEF option" + assert errs == [FluentReferenceError("Unknown attribute: -other.missing")] -class TestNestedParameterizedTerms(unittest.TestCase): - def setUp(self): - self.bundle = FluentBundle(["en-US"], use_isolating=False) - self.bundle.add_resource( +class TestNestedParameterizedTerms: + @pytest.fixture + def bundle(self): + bundle = FluentBundle(["en-US"], use_isolating=False) + bundle.add_resource( FluentResource( dedent_ftl( """ - -thing = { $article -> - *[definite] { $first-letter -> - *[lower] the thing - [upper] The thing - } - [indefinite] { $first-letter -> - *[lower] a thing - [upper] A thing - } - } - - both-args = { -thing(first-letter: "upper", article: "indefinite") }. - outer-arg = This is { -thing(article: "indefinite") }. - inner-arg = { -thing(first-letter: "upper") }. - neither-arg = { -thing() }. - """ + -thing = { $article -> + *[definite] { $first-letter -> + *[lower] the thing + [upper] The thing + } + [indefinite] { $first-letter -> + *[lower] a thing + [upper] A thing + } + } + + both-args = { -thing(first-letter: "upper", article: "indefinite") }. + outer-arg = This is { -thing(article: "indefinite") }. + inner-arg = { -thing(first-letter: "upper") }. + neither-arg = { -thing() }. + """ ) ) ) + return bundle - def test_both_args(self): - val, errs = self.bundle.format_pattern( - self.bundle.get_message("both-args").value, {} - ) - self.assertEqual(val, "A thing.") - self.assertEqual(errs, []) + def test_both_args(self, bundle): + val, errs = bundle.format_pattern(bundle.get_message("both-args").value, {}) + assert val == "A thing." + assert errs == [] - def test_outer_arg(self): - val, errs = self.bundle.format_pattern( - self.bundle.get_message("outer-arg").value, {} - ) - self.assertEqual(val, "This is a thing.") - self.assertEqual(errs, []) + def test_outer_arg(self, bundle): + val, errs = bundle.format_pattern(bundle.get_message("outer-arg").value, {}) + assert val == "This is a thing." + assert errs == [] - def test_inner_arg(self): - val, errs = self.bundle.format_pattern( - self.bundle.get_message("inner-arg").value, {} - ) - self.assertEqual(val, "The thing.") - self.assertEqual(errs, []) - - def test_inner_arg_with_external_args(self): - val, errs = self.bundle.format_pattern( - self.bundle.get_message("inner-arg").value, {"article": "indefinite"} - ) - self.assertEqual(val, "The thing.") - self.assertEqual(errs, []) + def test_inner_arg(self, bundle): + val, errs = bundle.format_pattern(bundle.get_message("inner-arg").value, {}) + assert val == "The thing." + assert errs == [] - def test_neither_arg(self): - val, errs = self.bundle.format_pattern( - self.bundle.get_message("neither-arg").value, {} + def test_inner_arg_with_external_args(self, bundle): + val, errs = bundle.format_pattern( + bundle.get_message("inner-arg").value, {"article": "indefinite"} ) - self.assertEqual(val, "the thing.") - self.assertEqual(errs, []) + assert val == "The thing." + assert errs == [] + def test_neither_arg(self, bundle): + val, errs = bundle.format_pattern(bundle.get_message("neither-arg").value, {}) + assert val == "the thing." + assert errs == [] -class TestTermsCalledFromTerms(unittest.TestCase): - def setUp(self): - self.bundle = FluentBundle(["en-US"], use_isolating=False) - self.bundle.add_resource( +class TestTermsCalledFromTerms: + @pytest.fixture + def bundle(self): + bundle = FluentBundle(["en-US"], use_isolating=False) + bundle.add_resource( FluentResource( dedent_ftl( """ - -foo = {$a} {$b} - -bar = {-foo(b: 2)} - -baz = {-foo} - ref-bar = {-bar(a: 1)} - ref-baz = {-baz(a: 1)} - """ + -foo = {$a} {$b} + -bar = {-foo(b: 2)} + -baz = {-foo} + ref-bar = {-bar(a: 1)} + ref-baz = {-baz(a: 1)} + """ ) ) ) + return bundle - def test_term_args_isolated_with_call_syntax(self): - val, errs = self.bundle.format_pattern( - self.bundle.get_message("ref-bar").value, {} - ) - self.assertEqual(val, "a 2") - self.assertEqual(errs, []) - - def test_term_args_isolated_without_call_syntax(self): - val, errs = self.bundle.format_pattern( - self.bundle.get_message("ref-baz").value, {} - ) - self.assertEqual(val, "a b") - self.assertEqual(errs, []) + def test_term_args_isolated_with_call_syntax(self, bundle): + val, errs = bundle.format_pattern(bundle.get_message("ref-bar").value, {}) + assert val == "a 2" + assert errs == [] + def test_term_args_isolated_without_call_syntax(self, bundle): + val, errs = bundle.format_pattern(bundle.get_message("ref-baz").value, {}) + assert val == "a b" + assert errs == [] -class TestMessagesCalledFromTerms(unittest.TestCase): - def setUp(self): - self.bundle = FluentBundle(["en-US"], use_isolating=False) - self.bundle.add_resource( +class TestMessagesCalledFromTerms: + def test_messages_inherit_term_args(self): + bundle = FluentBundle(["en-US"], use_isolating=False) + bundle.add_resource( FluentResource( dedent_ftl( """ - msg = Msg is {$arg} - -foo = {msg} - ref-foo = {-foo(arg: 1)} - """ + msg = Msg is {$arg} + -foo = {msg} + ref-foo = {-foo(arg: 1)} + """ ) ) ) - def test_messages_inherit_term_args(self): # This behaviour may change in future, message calls might be # disallowed from inside terms - val, errs = self.bundle.format_pattern( - self.bundle.get_message("ref-foo").value, {"arg": 2} + val, errs = bundle.format_pattern( + bundle.get_message("ref-foo").value, {"arg": 2} ) - self.assertEqual(val, "Msg is 1") - self.assertEqual(errs, []) + assert val == "Msg is 1" + assert errs == [] diff --git a/fluent.runtime/tests/format/test_placeables.py b/fluent.runtime/tests/format/test_placeables.py index 460c34b2..d1abd21f 100644 --- a/fluent.runtime/tests/format/test_placeables.py +++ b/fluent.runtime/tests/format/test_placeables.py @@ -1,169 +1,156 @@ -import unittest - +import pytest from fluent.runtime import FluentBundle, FluentResource from fluent.runtime.errors import FluentCyclicReferenceError, FluentReferenceError from ..utils import dedent_ftl -class TestPlaceables(unittest.TestCase): - def setUp(self): - self.bundle = FluentBundle(["en-US"], use_isolating=False) - self.bundle.add_resource( +class TestPlaceables: + @pytest.fixture + def bundle(self): + bundle = FluentBundle(["en-US"], use_isolating=False) + bundle.add_resource( FluentResource( dedent_ftl( """ - message = Message - .attr = Message Attribute - -term = Term - .attr = Term Attribute - -term2 = { - *[variant1] Term Variant 1 - [variant2] Term Variant 2 - } - - uses-message = { message } - uses-message-attr = { message.attr } - uses-term = { -term } - - bad-message-ref = Text { not-a-message } - bad-message-attr-ref = Text { message.not-an-attr } - bad-term-ref = Text { -not-a-term } - - self-referencing-message = Text { self-referencing-message } - cyclic-msg1 = Text1 { cyclic-msg2 } - cyclic-msg2 = Text2 { cyclic-msg1 } - self-cyclic-message = Parent { self-cyclic-message.attr } - .attr = Attribute { self-cyclic-message } - - self-attribute-ref-ok = Parent { self-attribute-ref-ok.attr } - .attr = Attribute - self-parent-ref-ok = Parent - .attr = Attribute { self-parent-ref-ok } - """ + message = Message + .attr = Message Attribute + -term = Term + .attr = Term Attribute + -term2 = { + *[variant1] Term Variant 1 + [variant2] Term Variant 2 + } + + uses-message = { message } + uses-message-attr = { message.attr } + uses-term = { -term } + + bad-message-ref = Text { not-a-message } + bad-message-attr-ref = Text { message.not-an-attr } + bad-term-ref = Text { -not-a-term } + + self-referencing-message = Text { self-referencing-message } + cyclic-msg1 = Text1 { cyclic-msg2 } + cyclic-msg2 = Text2 { cyclic-msg1 } + self-cyclic-message = Parent { self-cyclic-message.attr } + .attr = Attribute { self-cyclic-message } + + self-attribute-ref-ok = Parent { self-attribute-ref-ok.attr } + .attr = Attribute + self-parent-ref-ok = Parent + .attr = Attribute { self-parent-ref-ok } + """ ) ) ) + return bundle - def test_placeable_message(self): - val, errs = self.bundle.format_pattern( - self.bundle.get_message("uses-message").value, {} - ) - self.assertEqual(val, "Message") - self.assertEqual(len(errs), 0) + def test_placeable_message(self, bundle): + val, errs = bundle.format_pattern(bundle.get_message("uses-message").value, {}) + assert val == "Message" + assert len(errs) == 0 - def test_placeable_message_attr(self): - val, errs = self.bundle.format_pattern( - self.bundle.get_message("uses-message-attr").value, {} + def test_placeable_message_attr(self, bundle): + val, errs = bundle.format_pattern( + bundle.get_message("uses-message-attr").value, {} ) - self.assertEqual(val, "Message Attribute") - self.assertEqual(len(errs), 0) + assert val == "Message Attribute" + assert len(errs) == 0 - def test_placeable_term(self): - val, errs = self.bundle.format_pattern( - self.bundle.get_message("uses-term").value, {} - ) - self.assertEqual(val, "Term") - self.assertEqual(len(errs), 0) + def test_placeable_term(self, bundle): + val, errs = bundle.format_pattern(bundle.get_message("uses-term").value, {}) + assert val == "Term" + assert len(errs) == 0 - def test_placeable_bad_message(self): - val, errs = self.bundle.format_pattern( - self.bundle.get_message("bad-message-ref").value, {} + def test_placeable_bad_message(self, bundle): + val, errs = bundle.format_pattern( + bundle.get_message("bad-message-ref").value, {} ) - self.assertEqual(val, "Text {not-a-message}") - self.assertEqual(len(errs), 1) - self.assertEqual(errs, [FluentReferenceError("Unknown message: not-a-message")]) + assert val == "Text {not-a-message}" + assert len(errs) == 1 + assert errs == [FluentReferenceError("Unknown message: not-a-message")] - def test_placeable_bad_message_attr(self): - val, errs = self.bundle.format_pattern( - self.bundle.get_message("bad-message-attr-ref").value, {} + def test_placeable_bad_message_attr(self, bundle): + val, errs = bundle.format_pattern( + bundle.get_message("bad-message-attr-ref").value, {} ) - self.assertEqual(val, "Text {message.not-an-attr}") - self.assertEqual(len(errs), 1) - self.assertEqual( - errs, [FluentReferenceError("Unknown attribute: message.not-an-attr")] + assert val == "Text {message.not-an-attr}" + assert len(errs) == 1 + assert errs == [FluentReferenceError("Unknown attribute: message.not-an-attr")] + + def test_placeable_bad_term(self, bundle): + val, errs = bundle.format_pattern(bundle.get_message("bad-term-ref").value, {}) + assert val == "Text {-not-a-term}" + assert len(errs) == 1 + assert errs == [FluentReferenceError("Unknown term: -not-a-term")] + + def test_cycle_detection(self, bundle): + val, errs = bundle.format_pattern( + bundle.get_message("self-referencing-message").value, {} ) - - def test_placeable_bad_term(self): - val, errs = self.bundle.format_pattern( - self.bundle.get_message("bad-term-ref").value, {} + assert val == "Text ???" + assert len(errs) == 1 + assert errs == [FluentCyclicReferenceError("Cyclic reference")] + + def test_mutual_cycle_detection(self, bundle): + val, errs = bundle.format_pattern(bundle.get_message("cyclic-msg1").value, {}) + assert val == "Text1 Text2 ???" + assert len(errs) == 1 + assert errs == [FluentCyclicReferenceError("Cyclic reference")] + + def test_allowed_self_reference(self, bundle): + val, errs = bundle.format_pattern( + bundle.get_message("self-attribute-ref-ok").value, {} ) - self.assertEqual(val, "Text {-not-a-term}") - self.assertEqual(len(errs), 1) - self.assertEqual(errs, [FluentReferenceError("Unknown term: -not-a-term")]) - - def test_cycle_detection(self): - val, errs = self.bundle.format_pattern( - self.bundle.get_message("self-referencing-message").value, {} + assert val == "Parent Attribute" + assert len(errs) == 0 + val, errs = bundle.format_pattern( + bundle.get_message("self-parent-ref-ok").attributes["attr"], {} ) - self.assertEqual(val, "Text ???") - self.assertEqual(len(errs), 1) - self.assertEqual(errs, [FluentCyclicReferenceError("Cyclic reference")]) + assert val == "Attribute Parent" + assert len(errs) == 0 - def test_mutual_cycle_detection(self): - val, errs = self.bundle.format_pattern( - self.bundle.get_message("cyclic-msg1").value, {} - ) - self.assertEqual(val, "Text1 Text2 ???") - self.assertEqual(len(errs), 1) - self.assertEqual(errs, [FluentCyclicReferenceError("Cyclic reference")]) - def test_allowed_self_reference(self): - val, errs = self.bundle.format_pattern( - self.bundle.get_message("self-attribute-ref-ok").value, {} - ) - self.assertEqual(val, "Parent Attribute") - self.assertEqual(len(errs), 0) - val, errs = self.bundle.format_pattern( - self.bundle.get_message("self-parent-ref-ok").attributes["attr"], {} - ) - self.assertEqual(val, "Attribute Parent") - self.assertEqual(len(errs), 0) - - -class TestSingleElementPattern(unittest.TestCase): +class TestSingleElementPattern: def test_single_literal_number_isolating(self): - self.bundle = FluentBundle(["en-US"], use_isolating=True) - self.bundle.add_resource(FluentResource("foo = { 1 }")) - val, errs = self.bundle.format_pattern(self.bundle.get_message("foo").value) - self.assertEqual(val, "1") - self.assertEqual(errs, []) + bundle = FluentBundle(["en-US"], use_isolating=True) + bundle.add_resource(FluentResource("foo = { 1 }")) + val, errs = bundle.format_pattern(bundle.get_message("foo").value) + assert val == "1" + assert errs == [] def test_single_literal_number_non_isolating(self): - self.bundle = FluentBundle(["en-US"], use_isolating=False) - self.bundle.add_resource(FluentResource("foo = { 1 }")) - val, errs = self.bundle.format_pattern(self.bundle.get_message("foo").value) - self.assertEqual(val, "1") - self.assertEqual(errs, []) + bundle = FluentBundle(["en-US"], use_isolating=False) + bundle.add_resource(FluentResource("foo = { 1 }")) + val, errs = bundle.format_pattern(bundle.get_message("foo").value) + assert val == "1" + assert errs == [] def test_single_arg_number_isolating(self): - self.bundle = FluentBundle(["en-US"], use_isolating=True) - self.bundle.add_resource(FluentResource("foo = { $arg }")) - val, errs = self.bundle.format_pattern( - self.bundle.get_message("foo").value, {"arg": 1} - ) - self.assertEqual(val, "1") - self.assertEqual(errs, []) + bundle = FluentBundle(["en-US"], use_isolating=True) + bundle.add_resource(FluentResource("foo = { $arg }")) + val, errs = bundle.format_pattern(bundle.get_message("foo").value, {"arg": 1}) + assert val == "1" + assert errs == [] def test_single_arg_number_non_isolating(self): - self.bundle = FluentBundle(["en-US"], use_isolating=False) - self.bundle.add_resource(FluentResource("foo = { $arg }")) - val, errs = self.bundle.format_pattern( - self.bundle.get_message("foo").value, {"arg": 1} - ) - self.assertEqual(val, "1") - self.assertEqual(errs, []) + bundle = FluentBundle(["en-US"], use_isolating=False) + bundle.add_resource(FluentResource("foo = { $arg }")) + val, errs = bundle.format_pattern(bundle.get_message("foo").value, {"arg": 1}) + assert val == "1" + assert errs == [] def test_single_arg_missing_isolating(self): - self.bundle = FluentBundle(["en-US"], use_isolating=True) - self.bundle.add_resource(FluentResource("foo = { $arg }")) - val, errs = self.bundle.format_pattern(self.bundle.get_message("foo").value) - self.assertEqual(val, "arg") - self.assertEqual(len(errs), 1) + bundle = FluentBundle(["en-US"], use_isolating=True) + bundle.add_resource(FluentResource("foo = { $arg }")) + val, errs = bundle.format_pattern(bundle.get_message("foo").value) + assert val == "arg" + assert len(errs) == 1 def test_single_arg_missing_non_isolating(self): - self.bundle = FluentBundle(["en-US"], use_isolating=False) - self.bundle.add_resource(FluentResource("foo = { $arg }")) - val, errs = self.bundle.format_pattern(self.bundle.get_message("foo").value) - self.assertEqual(val, "arg") - self.assertEqual(len(errs), 1) + bundle = FluentBundle(["en-US"], use_isolating=False) + bundle.add_resource(FluentResource("foo = { $arg }")) + val, errs = bundle.format_pattern(bundle.get_message("foo").value) + assert val == "arg" + assert len(errs) == 1 diff --git a/fluent.runtime/tests/format/test_primitives.py b/fluent.runtime/tests/format/test_primitives.py index 38e94e36..e50091ae 100644 --- a/fluent.runtime/tests/format/test_primitives.py +++ b/fluent.runtime/tests/format/test_primitives.py @@ -1,194 +1,189 @@ -import unittest - +import pytest from fluent.runtime import FluentBundle, FluentResource from ..utils import dedent_ftl -class TestSimpleStringValue(unittest.TestCase): - def setUp(self): - self.bundle = FluentBundle(["en-US"], use_isolating=False) - self.bundle.add_resource( +class TestSimpleStringValue: + @pytest.fixture + def bundle(self): + bundle = FluentBundle(["en-US"], use_isolating=False) + bundle.add_resource( FluentResource( dedent_ftl( r""" - foo = Foo - placeable-literal = { "Foo" } Bar - placeable-message = { foo } Bar - selector-literal = { "Foo" -> - [Foo] Member 1 - *[Bar] Member 2 - } - bar = - .attr = Bar Attribute - placeable-attr = { bar.attr } - -baz = Baz - .attr = BazAttribute - selector-attr = { -baz.attr -> - [BazAttribute] Member 3 - *[other] Member 4 - } - escapes = {" "}stuff{"\u0258}\"\\end"} - """ + foo = Foo + placeable-literal = { "Foo" } Bar + placeable-message = { foo } Bar + selector-literal = { "Foo" -> + [Foo] Member 1 + *[Bar] Member 2 + } + bar = + .attr = Bar Attribute + placeable-attr = { bar.attr } + -baz = Baz + .attr = BazAttribute + selector-attr = { -baz.attr -> + [BazAttribute] Member 3 + *[other] Member 4 + } + escapes = {" "}stuff{"\u0258}\"\\end"} + """ ) ) ) + return bundle - def test_can_be_used_as_a_value(self): - val, errs = self.bundle.format_pattern(self.bundle.get_message("foo").value, {}) - self.assertEqual(val, "Foo") - self.assertEqual(len(errs), 0) + def test_can_be_used_as_a_value(self, bundle): + val, errs = bundle.format_pattern(bundle.get_message("foo").value, {}) + assert val == "Foo" + assert len(errs) == 0 - def test_can_be_used_in_a_placeable(self): - val, errs = self.bundle.format_pattern( - self.bundle.get_message("placeable-literal").value, {} + def test_can_be_used_in_a_placeable(self, bundle): + val, errs = bundle.format_pattern( + bundle.get_message("placeable-literal").value, {} ) - self.assertEqual(val, "Foo Bar") - self.assertEqual(len(errs), 0) + assert val == "Foo Bar" + assert len(errs) == 0 - def test_can_be_a_value_of_a_message_referenced_in_a_placeable(self): - val, errs = self.bundle.format_pattern( - self.bundle.get_message("placeable-message").value, {} + def test_can_be_a_value_of_a_message_referenced_in_a_placeable(self, bundle): + val, errs = bundle.format_pattern( + bundle.get_message("placeable-message").value, {} ) - self.assertEqual(val, "Foo Bar") - self.assertEqual(len(errs), 0) + assert val == "Foo Bar" + assert len(errs) == 0 - def test_can_be_a_selector(self): - val, errs = self.bundle.format_pattern( - self.bundle.get_message("selector-literal").value, {} + def test_can_be_a_selector(self, bundle): + val, errs = bundle.format_pattern( + bundle.get_message("selector-literal").value, {} ) - self.assertEqual(val, "Member 1") - self.assertEqual(len(errs), 0) + assert val == "Member 1" + assert len(errs) == 0 - def test_can_be_used_as_an_attribute_value(self): - val, errs = self.bundle.format_pattern( - self.bundle.get_message("bar").attributes["attr"], {} + def test_can_be_used_as_an_attribute_value(self, bundle): + val, errs = bundle.format_pattern( + bundle.get_message("bar").attributes["attr"], {} ) - self.assertEqual(val, "Bar Attribute") - self.assertEqual(len(errs), 0) + assert val == "Bar Attribute" + assert len(errs) == 0 - def test_can_be_a_value_of_an_attribute_used_in_a_placeable(self): - val, errs = self.bundle.format_pattern( - self.bundle.get_message("placeable-attr").value, {} + def test_can_be_a_value_of_an_attribute_used_in_a_placeable(self, bundle): + val, errs = bundle.format_pattern( + bundle.get_message("placeable-attr").value, {} ) - self.assertEqual(val, "Bar Attribute") - self.assertEqual(len(errs), 0) + assert val == "Bar Attribute" + assert len(errs) == 0 - def test_can_be_a_value_of_an_attribute_used_as_a_selector(self): - val, errs = self.bundle.format_pattern( - self.bundle.get_message("selector-attr").value, {} - ) - self.assertEqual(val, "Member 3") - self.assertEqual(len(errs), 0) + def test_can_be_a_value_of_an_attribute_used_as_a_selector(self, bundle): + val, errs = bundle.format_pattern(bundle.get_message("selector-attr").value, {}) + assert val == "Member 3" + assert len(errs) == 0 - def test_escapes(self): - val, errs = self.bundle.format_pattern( - self.bundle.get_message("escapes").value, {} - ) - self.assertEqual(val, r' stuffɘ}"\end') - self.assertEqual(len(errs), 0) + def test_escapes(self, bundle): + val, errs = bundle.format_pattern(bundle.get_message("escapes").value, {}) + assert val == r' stuffɘ}"\end' + assert len(errs) == 0 -class TestComplexStringValue(unittest.TestCase): - def setUp(self): - self.bundle = FluentBundle(["en-US"], use_isolating=False) - self.bundle.add_resource( +class TestComplexStringValue: + @pytest.fixture + def bundle(self): + bundle = FluentBundle(["en-US"], use_isolating=False) + bundle.add_resource( FluentResource( dedent_ftl( """ - foo = Foo - bar = { foo }Bar + foo = Foo + bar = { foo }Bar - placeable-message = { bar }Baz + placeable-message = { bar }Baz - baz = - .attr = { bar }BazAttribute + baz = + .attr = { bar }BazAttribute - -qux = Qux - .attr = { bar }QuxAttribute + -qux = Qux + .attr = { bar }QuxAttribute - placeable-attr = { baz.attr } + placeable-attr = { baz.attr } - selector-attr = { -qux.attr -> - [FooBarQuxAttribute] FooBarQux - *[other] Other - } - """ + selector-attr = { -qux.attr -> + [FooBarQuxAttribute] FooBarQux + *[other] Other + } + """ ) ) ) + return bundle - def test_can_be_used_as_a_value(self): - val, errs = self.bundle.format_pattern(self.bundle.get_message("bar").value, {}) - self.assertEqual(val, "FooBar") - self.assertEqual(len(errs), 0) + def test_can_be_used_as_a_value(self, bundle): + val, errs = bundle.format_pattern(bundle.get_message("bar").value, {}) + assert val == "FooBar" + assert len(errs) == 0 - def test_can_be_value_of_a_message_referenced_in_a_placeable(self): - val, errs = self.bundle.format_pattern( - self.bundle.get_message("placeable-message").value, {} + def test_can_be_value_of_a_message_referenced_in_a_placeable(self, bundle): + val, errs = bundle.format_pattern( + bundle.get_message("placeable-message").value, {} ) - self.assertEqual(val, "FooBarBaz") - self.assertEqual(len(errs), 0) + assert val == "FooBarBaz" + assert len(errs) == 0 - def test_can_be_used_as_an_attribute_value(self): - val, errs = self.bundle.format_pattern( - self.bundle.get_message("baz").attributes["attr"], {} + def test_can_be_used_as_an_attribute_value(self, bundle): + val, errs = bundle.format_pattern( + bundle.get_message("baz").attributes["attr"], {} ) - self.assertEqual(val, "FooBarBazAttribute") - self.assertEqual(len(errs), 0) + assert val == "FooBarBazAttribute" + assert len(errs) == 0 - def test_can_be_a_value_of_an_attribute_used_in_a_placeable(self): - val, errs = self.bundle.format_pattern( - self.bundle.get_message("placeable-attr").value, {} + def test_can_be_a_value_of_an_attribute_used_in_a_placeable(self, bundle): + val, errs = bundle.format_pattern( + bundle.get_message("placeable-attr").value, {} ) - self.assertEqual(val, "FooBarBazAttribute") - self.assertEqual(len(errs), 0) + assert val == "FooBarBazAttribute" + assert len(errs) == 0 - def test_can_be_a_value_of_an_attribute_used_as_a_selector(self): - val, errs = self.bundle.format_pattern( - self.bundle.get_message("selector-attr").value, {} - ) - self.assertEqual(val, "FooBarQux") - self.assertEqual(len(errs), 0) + def test_can_be_a_value_of_an_attribute_used_as_a_selector(self, bundle): + val, errs = bundle.format_pattern(bundle.get_message("selector-attr").value, {}) + assert val == "FooBarQux" + assert len(errs) == 0 -class TestNumbers(unittest.TestCase): - def setUp(self): - self.bundle = FluentBundle(["en-US"], use_isolating=False) - self.bundle.add_resource( +class TestNumbers: + @pytest.fixture + def bundle(self): + bundle = FluentBundle(["en-US"], use_isolating=False) + bundle.add_resource( FluentResource( dedent_ftl( """ - one = { 1 } - one_point_two = { 1.2 } - select = { 1 -> - *[0] Zero - [1] One - } - position = { NUMBER(1, type: "ordinal") -> - *[other] Zero - [one] ${1}st - } - """ + one = { 1 } + one_point_two = { 1.2 } + select = { 1 -> + *[0] Zero + [1] One + } + position = { NUMBER(1, type: "ordinal") -> + *[other] Zero + [one] ${1}st + } + """ ) ) ) + return bundle - def test_int_number_used_in_placeable(self): - val, errs = self.bundle.format_pattern(self.bundle.get_message("one").value, {}) - self.assertEqual(val, "1") - self.assertEqual(len(errs), 0) + def test_int_number_used_in_placeable(self, bundle): + val, errs = bundle.format_pattern(bundle.get_message("one").value, {}) + assert val == "1" + assert len(errs) == 0 - def test_float_number_used_in_placeable(self): - val, errs = self.bundle.format_pattern( - self.bundle.get_message("one_point_two").value, {} - ) - self.assertEqual(val, "1.2") - self.assertEqual(len(errs), 0) + def test_float_number_used_in_placeable(self, bundle): + val, errs = bundle.format_pattern(bundle.get_message("one_point_two").value, {}) + assert val == "1.2" + assert len(errs) == 0 - def test_can_be_used_as_a_selector(self): - val, errs = self.bundle.format_pattern( - self.bundle.get_message("select").value, {} - ) - self.assertEqual(val, "One") - self.assertEqual(len(errs), 0) + def test_can_be_used_as_a_selector(self, bundle): + val, errs = bundle.format_pattern(bundle.get_message("select").value, {}) + assert val == "One" + assert len(errs) == 0 diff --git a/fluent.runtime/tests/format/test_select_expression.py b/fluent.runtime/tests/format/test_select_expression.py index d4402996..cf33e416 100644 --- a/fluent.runtime/tests/format/test_select_expression.py +++ b/fluent.runtime/tests/format/test_select_expression.py @@ -1,337 +1,315 @@ -import unittest - +import pytest from fluent.runtime import FluentBundle, FluentResource from fluent.runtime.errors import FluentReferenceError from ..utils import dedent_ftl -class TestSelectExpressionWithStrings(unittest.TestCase): - - def setUp(self): - self.bundle = FluentBundle(["en-US"], use_isolating=False) +class TestSelectExpressionWithStrings: + @pytest.fixture + def bundle(self): + return FluentBundle(["en-US"], use_isolating=False) - def test_with_a_matching_selector(self): - self.bundle.add_resource( + def test_with_a_matching_selector(self, bundle): + bundle.add_resource( FluentResource( dedent_ftl( """ - foo = { "a" -> - [a] A - *[b] B - } - """ + foo = { "a" -> + [a] A + *[b] B + } + """ ) ) ) - val, errs = self.bundle.format_pattern(self.bundle.get_message("foo").value, {}) - self.assertEqual(val, "A") - self.assertEqual(len(errs), 0) + val, errs = bundle.format_pattern(bundle.get_message("foo").value, {}) + assert val == "A" + assert len(errs) == 0 - def test_with_a_non_matching_selector(self): - self.bundle.add_resource( + def test_with_a_non_matching_selector(self, bundle): + bundle.add_resource( FluentResource( dedent_ftl( """ - foo = { "c" -> - [a] A - *[b] B - } - """ + foo = { "c" -> + [a] A + *[b] B + } + """ ) ) ) - val, errs = self.bundle.format_pattern(self.bundle.get_message("foo").value, {}) - self.assertEqual(val, "B") - self.assertEqual(len(errs), 0) + val, errs = bundle.format_pattern(bundle.get_message("foo").value, {}) + assert val == "B" + assert len(errs) == 0 - def test_with_a_missing_selector(self): - self.bundle.add_resource( + def test_with_a_missing_selector(self, bundle): + bundle.add_resource( FluentResource( dedent_ftl( """ - foo = { $none -> - [a] A - *[b] B - } - """ + foo = { $none -> + [a] A + *[b] B + } + """ ) ) ) - val, errs = self.bundle.format_pattern(self.bundle.get_message("foo").value, {}) - self.assertEqual(val, "B") - self.assertEqual(errs, [FluentReferenceError("Unknown external: none")]) + val, errs = bundle.format_pattern(bundle.get_message("foo").value, {}) + assert val == "B" + assert errs == [FluentReferenceError("Unknown external: none")] - def test_with_argument_expression(self): - self.bundle.add_resource( + def test_with_argument_expression(self, bundle): + bundle.add_resource( FluentResource( dedent_ftl( """ - foo = { $arg -> - [a] A - *[b] B - } - """ + foo = { $arg -> + [a] A + *[b] B + } + """ ) ) ) - val, errs = self.bundle.format_pattern( - self.bundle.get_message("foo").value, {"arg": "a"} - ) - self.assertEqual(val, "A") - + val, errs = bundle.format_pattern(bundle.get_message("foo").value, {"arg": "a"}) + assert val == "A" -class TestSelectExpressionWithNumbers(unittest.TestCase): - def setUp(self): - self.bundle = FluentBundle(["en-US"], use_isolating=False) - self.bundle.add_resource( +class TestSelectExpressionWithNumbers: + @pytest.fixture + def bundle(self): + bundle = FluentBundle(["en-US"], use_isolating=False) + bundle.add_resource( FluentResource( dedent_ftl( """ - foo = { 1 -> - *[0] A - [1] B - } - - bar = { 2 -> - *[0] A - [1] B - } - - baz = { $num -> - *[0] A - [1] B - } - - qux = { 1.0 -> - *[0] A - [1] B - } - """ + foo = { 1 -> + *[0] A + [1] B + } + + bar = { 2 -> + *[0] A + [1] B + } + + baz = { $num -> + *[0] A + [1] B + } + + qux = { 1.0 -> + *[0] A + [1] B + } + """ ) ) ) + return bundle - def test_selects_the_right_variant(self): - val, errs = self.bundle.format_pattern(self.bundle.get_message("foo").value, {}) - self.assertEqual(val, "B") - self.assertEqual(len(errs), 0) - - def test_with_a_non_matching_selector(self): - val, errs = self.bundle.format_pattern(self.bundle.get_message("bar").value, {}) - self.assertEqual(val, "A") - self.assertEqual(len(errs), 0) + def test_selects_the_right_variant(self, bundle): + val, errs = bundle.format_pattern(bundle.get_message("foo").value, {}) + assert val == "B" + assert len(errs) == 0 - def test_with_a_missing_selector(self): - val, errs = self.bundle.format_pattern(self.bundle.get_message("baz").value, {}) - self.assertEqual(val, "A") - self.assertEqual(errs, [FluentReferenceError("Unknown external: num")]) + def test_with_a_non_matching_selector(self, bundle): + val, errs = bundle.format_pattern(bundle.get_message("bar").value, {}) + assert val == "A" + assert len(errs) == 0 - def test_with_argument_int(self): - val, errs = self.bundle.format_pattern( - self.bundle.get_message("baz").value, {"num": 1} - ) - self.assertEqual(val, "B") + def test_with_a_missing_selector(self, bundle): + val, errs = bundle.format_pattern(bundle.get_message("baz").value, {}) + assert val == "A" + assert errs == [FluentReferenceError("Unknown external: num")] - def test_with_argument_float(self): - val, errs = self.bundle.format_pattern( - self.bundle.get_message("baz").value, {"num": 1.0} - ) - self.assertEqual(val, "B") + def test_with_argument_int(self, bundle): + val, errs = bundle.format_pattern(bundle.get_message("baz").value, {"num": 1}) + assert val == "B" - def test_with_float(self): - val, errs = self.bundle.format_pattern(self.bundle.get_message("qux").value, {}) - self.assertEqual(val, "B") + def test_with_argument_float(self, bundle): + val, errs = bundle.format_pattern(bundle.get_message("baz").value, {"num": 1.0}) + assert val == "B" + def test_with_float(self, bundle): + val, errs = bundle.format_pattern(bundle.get_message("qux").value, {}) + assert val == "B" -class TestSelectExpressionWithPluralCategories(unittest.TestCase): - def setUp(self): - self.bundle = FluentBundle(["en-US"], use_isolating=False) - self.bundle.add_resource( +class TestSelectExpressionWithPluralCategories: + @pytest.fixture + def bundle(self): + bundle = FluentBundle(["en-US"], use_isolating=False) + bundle.add_resource( FluentResource( dedent_ftl( """ - foo = { 1 -> - [one] A - *[other] B - } - - bar = { 1 -> - [1] A - *[other] B - } - - baz = { "not a number" -> - [one] A - *[other] B - } - - qux = { $num -> - [one] A - *[other] B - } - - count = { NUMBER($num, type: "cardinal") -> - *[other] B - [one] A - } - - order = { NUMBER($num, type: "ordinal") -> - *[other] {$num}th - [one] {$num}st - [two] {$num}nd - [few] {$num}rd - } - """ + foo = { 1 -> + [one] A + *[other] B + } + + bar = { 1 -> + [1] A + *[other] B + } + + baz = { "not a number" -> + [one] A + *[other] B + } + + qux = { $num -> + [one] A + *[other] B + } + + count = { NUMBER($num, type: "cardinal") -> + *[other] B + [one] A + } + + order = { NUMBER($num, type: "ordinal") -> + *[other] {$num}th + [one] {$num}st + [two] {$num}nd + [few] {$num}rd + } + """ ) ) ) - - def test_selects_the_right_category(self): - val, errs = self.bundle.format_pattern(self.bundle.get_message("foo").value, {}) - self.assertEqual(val, "A") - self.assertEqual(len(errs), 0) - - def test_selects_exact_match(self): - val, errs = self.bundle.format_pattern(self.bundle.get_message("bar").value, {}) - self.assertEqual(val, "A") - self.assertEqual(len(errs), 0) - - def test_selects_default_with_invalid_selector(self): - val, errs = self.bundle.format_pattern(self.bundle.get_message("baz").value, {}) - self.assertEqual(val, "B") - self.assertEqual(len(errs), 0) - - def test_with_a_missing_selector(self): - val, errs = self.bundle.format_pattern(self.bundle.get_message("qux").value, {}) - self.assertEqual(val, "B") - self.assertEqual(errs, [FluentReferenceError("Unknown external: num")]) - - def test_with_argument_integer(self): - val, errs = self.bundle.format_pattern( - self.bundle.get_message("qux").value, {"num": 1} - ) - self.assertEqual(val, "A") - self.assertEqual(len(errs), 0) - - val, errs = self.bundle.format_pattern( - self.bundle.get_message("qux").value, {"num": 2} - ) - self.assertEqual(val, "B") - self.assertEqual(len(errs), 0) - - def test_with_argument_float(self): - val, errs = self.bundle.format_pattern( - self.bundle.get_message("qux").value, {"num": 1.0} - ) - self.assertEqual(val, "A") - self.assertEqual(len(errs), 0) - - def test_with_cardinal_integer(self): - val, errs = self.bundle.format_pattern( - self.bundle.get_message("count").value, {"num": 1} + return bundle + + def test_selects_the_right_category(self, bundle): + val, errs = bundle.format_pattern(bundle.get_message("foo").value, {}) + assert val == "A" + assert len(errs) == 0 + + def test_selects_exact_match(self, bundle): + val, errs = bundle.format_pattern(bundle.get_message("bar").value, {}) + assert val == "A" + assert len(errs) == 0 + + def test_selects_default_with_invalid_selector(self, bundle): + val, errs = bundle.format_pattern(bundle.get_message("baz").value, {}) + assert val == "B" + assert len(errs) == 0 + + def test_with_a_missing_selector(self, bundle): + val, errs = bundle.format_pattern(bundle.get_message("qux").value, {}) + assert val == "B" + assert errs == [FluentReferenceError("Unknown external: num")] + + def test_with_argument_integer(self, bundle): + val, errs = bundle.format_pattern(bundle.get_message("qux").value, {"num": 1}) + assert val == "A" + assert len(errs) == 0 + + val, errs = bundle.format_pattern(bundle.get_message("qux").value, {"num": 2}) + assert val == "B" + assert len(errs) == 0 + + def test_with_argument_float(self, bundle): + val, errs = bundle.format_pattern(bundle.get_message("qux").value, {"num": 1.0}) + assert val == "A" + assert len(errs) == 0 + + def test_with_cardinal_integer(self, bundle): + val, errs = bundle.format_pattern(bundle.get_message("count").value, {"num": 1}) + assert val == "A" + assert len(errs) == 0 + + val, errs = bundle.format_pattern(bundle.get_message("count").value, {"num": 2}) + assert val == "B" + assert len(errs) == 0 + + def test_with_cardinal_float(self, bundle): + val, errs = bundle.format_pattern( + bundle.get_message("count").value, {"num": 1.0} ) - self.assertEqual(val, "A") - self.assertEqual(len(errs), 0) + assert val == "A" + assert len(errs) == 0 - val, errs = self.bundle.format_pattern( - self.bundle.get_message("count").value, {"num": 2} - ) - self.assertEqual(val, "B") - self.assertEqual(len(errs), 0) + def test_with_ordinal_integer(self, bundle): + val, errs = bundle.format_pattern(bundle.get_message("order").value, {"num": 1}) + assert val == "1st" + assert len(errs) == 0 - def test_with_cardinal_float(self): - val, errs = self.bundle.format_pattern( - self.bundle.get_message("count").value, {"num": 1.0} - ) - self.assertEqual(val, "A") - self.assertEqual(len(errs), 0) + val, errs = bundle.format_pattern(bundle.get_message("order").value, {"num": 2}) + assert val == "2nd" + assert len(errs) == 0 - def test_with_ordinal_integer(self): - val, errs = self.bundle.format_pattern( - self.bundle.get_message("order").value, {"num": 1} + val, errs = bundle.format_pattern( + bundle.get_message("order").value, {"num": 11} ) - self.assertEqual(val, "1st") - self.assertEqual(len(errs), 0) + assert val == "11th" + assert len(errs) == 0 - val, errs = self.bundle.format_pattern( - self.bundle.get_message("order").value, {"num": 2} + val, errs = bundle.format_pattern( + bundle.get_message("order").value, {"num": 21} ) - self.assertEqual(val, "2nd") - self.assertEqual(len(errs), 0) + assert val == "21st" + assert len(errs) == 0 - val, errs = self.bundle.format_pattern( - self.bundle.get_message("order").value, {"num": 11} + def test_with_ordinal_float(self, bundle): + val, errs = bundle.format_pattern( + bundle.get_message("order").value, {"num": 1.0} ) - self.assertEqual(val, "11th") - self.assertEqual(len(errs), 0) + assert val == "1st" + assert len(errs) == 0 - val, errs = self.bundle.format_pattern( - self.bundle.get_message("order").value, {"num": 21} - ) - self.assertEqual(val, "21st") - self.assertEqual(len(errs), 0) - def test_with_ordinal_float(self): - val, errs = self.bundle.format_pattern( - self.bundle.get_message("order").value, {"num": 1.0} - ) - self.assertEqual(val, "1st") - self.assertEqual(len(errs), 0) - - -class TestSelectExpressionWithTerms(unittest.TestCase): - - def setUp(self): - self.bundle = FluentBundle(["en-US"], use_isolating=False) - self.bundle.add_resource( +class TestSelectExpressionWithTerms: + @pytest.fixture + def bundle(self): + bundle = FluentBundle(["en-US"], use_isolating=False) + bundle.add_resource( FluentResource( dedent_ftl( """ - -my-term = term - .attr = termattribute - - ref-term-attr = { -my-term.attr -> - [termattribute] Term Attribute - *[other] Other - } - - ref-term-attr-other = { -my-term.attr -> - [x] Term Attribute - *[other] Other - } - - ref-term-attr-missing = { -my-term.missing -> - [x] Term Attribute - *[other] Other - } - """ + -my-term = term + .attr = termattribute + + ref-term-attr = { -my-term.attr -> + [termattribute] Term Attribute + *[other] Other + } + + ref-term-attr-other = { -my-term.attr -> + [x] Term Attribute + *[other] Other + } + + ref-term-attr-missing = { -my-term.missing -> + [x] Term Attribute + *[other] Other + } + """ ) ) ) + return bundle - def test_ref_term_attribute(self): - val, errs = self.bundle.format_pattern( - self.bundle.get_message("ref-term-attr").value - ) - self.assertEqual(val, "Term Attribute") - self.assertEqual(len(errs), 0) + def test_ref_term_attribute(self, bundle): + val, errs = bundle.format_pattern(bundle.get_message("ref-term-attr").value) + assert val == "Term Attribute" + assert len(errs) == 0 - def test_ref_term_attribute_fallback(self): - val, errs = self.bundle.format_pattern( - self.bundle.get_message("ref-term-attr-other").value + def test_ref_term_attribute_fallback(self, bundle): + val, errs = bundle.format_pattern( + bundle.get_message("ref-term-attr-other").value ) - self.assertEqual(val, "Other") - self.assertEqual(len(errs), 0) + assert val == "Other" + assert len(errs) == 0 - def test_ref_term_attribute_missing(self): - val, errs = self.bundle.format_pattern( - self.bundle.get_message("ref-term-attr-missing").value - ) - self.assertEqual(val, "Other") - self.assertEqual(len(errs), 1) - self.assertEqual( - errs, [FluentReferenceError("Unknown attribute: -my-term.missing")] + def test_ref_term_attribute_missing(self, bundle): + val, errs = bundle.format_pattern( + bundle.get_message("ref-term-attr-missing").value ) + assert val == "Other" + assert len(errs) == 1 + assert errs == [FluentReferenceError("Unknown attribute: -my-term.missing")] diff --git a/fluent.runtime/tests/test_bomb.py b/fluent.runtime/tests/test_bomb.py index 479747d6..687e9141 100644 --- a/fluent.runtime/tests/test_bomb.py +++ b/fluent.runtime/tests/test_bomb.py @@ -1,49 +1,44 @@ -import unittest - +import pytest from fluent.runtime import FluentBundle, FluentResource -from .utils import dedent_ftl - - -class TestBillionLaughs(unittest.TestCase): - - def setUp(self): - self.ctx = FluentBundle(["en-US"], use_isolating=False) - self.ctx.add_resource( - FluentResource( - dedent_ftl( - """ - lol0 = 01234567890123456789012345678901234567890123456789 - lol1 = {lol0}{lol0}{lol0}{lol0}{lol0}{lol0}{lol0}{lol0}{lol0}{lol0} - lol2 = {lol1}{lol1}{lol1}{lol1}{lol1}{lol1}{lol1}{lol1}{lol1}{lol1} - lol3 = {lol2}{lol2}{lol2}{lol2}{lol2}{lol2}{lol2}{lol2}{lol2}{lol2} - lol4 = {lol3}{lol3}{lol3}{lol3}{lol3}{lol3}{lol3}{lol3}{lol3}{lol3} - lolz = {lol4} - - elol0 = { "" } - elol1 = {elol0}{elol0}{elol0}{elol0}{elol0}{elol0}{elol0}{elol0}{elol0}{elol0} - elol2 = {elol1}{elol1}{elol1}{elol1}{elol1}{elol1}{elol1}{elol1}{elol1}{elol1} - elol3 = {elol2}{elol2}{elol2}{elol2}{elol2}{elol2}{elol2}{elol2}{elol2}{elol2} - elol4 = {elol3}{elol3}{elol3}{elol3}{elol3}{elol3}{elol3}{elol3}{elol3}{elol3} - elol5 = {elol4}{elol4}{elol4}{elol4}{elol4}{elol4}{elol4}{elol4}{elol4}{elol4} - elol6 = {elol5}{elol5}{elol5}{elol5}{elol5}{elol5}{elol5}{elol5}{elol5}{elol5} - emptylolz = {elol6} - - """ - ) - ) - ) - - def test_max_length_protection(self): - val, errs = self.ctx.format_pattern(self.ctx.get_message("lolz").value) - self.assertEqual(val, "{???}") - self.assertNotEqual(len(errs), 0) - self.assertIn("Too many characters", str(errs[-1])) - - def test_max_expansions_protection(self): + +bomb_source = """\ +lol0 = 01234567890123456789012345678901234567890123456789 +lol1 = {lol0}{lol0}{lol0}{lol0}{lol0}{lol0}{lol0}{lol0}{lol0}{lol0} +lol2 = {lol1}{lol1}{lol1}{lol1}{lol1}{lol1}{lol1}{lol1}{lol1}{lol1} +lol3 = {lol2}{lol2}{lol2}{lol2}{lol2}{lol2}{lol2}{lol2}{lol2}{lol2} +lol4 = {lol3}{lol3}{lol3}{lol3}{lol3}{lol3}{lol3}{lol3}{lol3}{lol3} +lolz = {lol4} + +elol0 = { "" } +elol1 = {elol0}{elol0}{elol0}{elol0}{elol0}{elol0}{elol0}{elol0}{elol0}{elol0} +elol2 = {elol1}{elol1}{elol1}{elol1}{elol1}{elol1}{elol1}{elol1}{elol1}{elol1} +elol3 = {elol2}{elol2}{elol2}{elol2}{elol2}{elol2}{elol2}{elol2}{elol2}{elol2} +elol4 = {elol3}{elol3}{elol3}{elol3}{elol3}{elol3}{elol3}{elol3}{elol3}{elol3} +elol5 = {elol4}{elol4}{elol4}{elol4}{elol4}{elol4}{elol4}{elol4}{elol4}{elol4} +elol6 = {elol5}{elol5}{elol5}{elol5}{elol5}{elol5}{elol5}{elol5}{elol5}{elol5} +emptylolz = {elol6} +""" + + +@pytest.fixture +def bundle(): + bundle = FluentBundle(["en-US"], use_isolating=False) + bundle.add_resource(FluentResource(bomb_source)) + return bundle + + +class TestBillionLaughs: + def test_max_length_protection(self, bundle): + val, errs = bundle.format_pattern(bundle.get_message("lolz").value) + assert val == "{???}" + assert len(errs) != 0 + assert "Too many characters" in str(errs[-1]) + + def test_max_expansions_protection(self, bundle): # Without protection, emptylolz will take a really long time to # evaluate, although it generates an empty message. - val, errs = self.ctx.format_pattern(self.ctx.get_message("emptylolz").value) - self.assertEqual(val, "{???}") - self.assertEqual(len(errs), 1) - self.assertIn("Too many parts", str(errs[-1])) + val, errs = bundle.format_pattern(bundle.get_message("emptylolz").value) + assert val == "{???}" + assert len(errs) == 1 + assert "Too many parts" in str(errs[-1]) diff --git a/fluent.runtime/tests/test_bundle.py b/fluent.runtime/tests/test_bundle.py index d15eb362..82809222 100644 --- a/fluent.runtime/tests/test_bundle.py +++ b/fluent.runtime/tests/test_bundle.py @@ -1,16 +1,17 @@ -import unittest - +import pytest from fluent.runtime import FluentBundle, FluentResource from .utils import dedent_ftl -class TestFluentBundle(unittest.TestCase): - def setUp(self): - self.bundle = FluentBundle(["en-US"]) +@pytest.fixture +def bundle(): + return FluentBundle(["en-US"]) + - def test_add_resource(self): - self.bundle.add_resource( +class TestFluentBundle: + def test_add_resource(self, bundle): + bundle.add_resource( FluentResource( dedent_ftl( """ @@ -21,12 +22,12 @@ def test_add_resource(self): ) ) ) - self.assertIn("foo", self.bundle._messages) - self.assertIn("bar", self.bundle._messages) - self.assertIn("baz", self.bundle._terms) + assert "foo" in bundle._messages + assert "bar" in bundle._messages + assert "baz" in bundle._terms - def test_has_message(self): - self.bundle.add_resource( + def test_has_message(self, bundle): + bundle.add_resource( FluentResource( dedent_ftl( """ @@ -36,11 +37,11 @@ def test_has_message(self): ) ) - self.assertTrue(self.bundle.has_message("foo")) - self.assertFalse(self.bundle.has_message("bar")) + assert bundle.has_message("foo") + assert not bundle.has_message("bar") - def test_has_message_for_term(self): - self.bundle.add_resource( + def test_has_message_for_term(self, bundle): + bundle.add_resource( FluentResource( dedent_ftl( """ @@ -50,10 +51,10 @@ def test_has_message_for_term(self): ) ) - self.assertFalse(self.bundle.has_message("-foo")) + assert not bundle.has_message("-foo") - def test_has_message_with_attribute(self): - self.bundle.add_resource( + def test_has_message_with_attribute(self, bundle): + bundle.add_resource( FluentResource( dedent_ftl( """ @@ -64,53 +65,51 @@ def test_has_message_with_attribute(self): ) ) - self.assertTrue(self.bundle.has_message("foo")) - self.assertFalse(self.bundle.has_message("foo.attr")) - self.assertFalse(self.bundle.has_message("foo.other-attribute")) + assert bundle.has_message("foo") + assert not bundle.has_message("foo.attr") + assert not bundle.has_message("foo.other-attribute") - def test_plural_form_english_ints(self): - bundle = FluentBundle(["en-US"]) - self.assertEqual(bundle._plural_form(0), "other") - self.assertEqual(bundle._plural_form(1), "one") - self.assertEqual(bundle._plural_form(2), "other") + def test_plural_form_english_ints(self, bundle): + assert bundle._plural_form(0) == "other" + assert bundle._plural_form(1) == "one" + assert bundle._plural_form(2) == "other" - def test_plural_form_english_floats(self): - bundle = FluentBundle(["en-US"]) - self.assertEqual(bundle._plural_form(0.0), "other") - self.assertEqual(bundle._plural_form(1.0), "one") - self.assertEqual(bundle._plural_form(2.0), "other") - self.assertEqual(bundle._plural_form(0.5), "other") + def test_plural_form_english_floats(self, bundle): + assert bundle._plural_form(0.0) == "other" + assert bundle._plural_form(1.0) == "one" + assert bundle._plural_form(2.0) == "other" + assert bundle._plural_form(0.5) == "other" def test_plural_form_french(self): # Just spot check one other, to ensure that we # are not getting the EN locale by accident or bundle = FluentBundle(["fr"]) - self.assertEqual(bundle._plural_form(0), "one") - self.assertEqual(bundle._plural_form(1), "one") - self.assertEqual(bundle._plural_form(2), "other") - - def test_ordinal_form_english_ints(self): - bundle = FluentBundle(["en-US"]) - self.assertEqual(bundle._ordinal_form(0), "other") - self.assertEqual(bundle._ordinal_form(1), "one") - self.assertEqual(bundle._ordinal_form(2), "two") - self.assertEqual(bundle._ordinal_form(3), "few") - self.assertEqual(bundle._ordinal_form(11), "other") - self.assertEqual(bundle._ordinal_form(21), "one") - - def test_format_args(self): - self.bundle.add_resource(FluentResource("foo = Foo")) - val, errs = self.bundle.format_pattern(self.bundle.get_message("foo").value) - self.assertEqual(val, "Foo") - - val, errs = self.bundle.format_pattern(self.bundle.get_message("foo").value, {}) - self.assertEqual(val, "Foo") - - def test_format_missing(self): - self.assertRaises(LookupError, self.bundle.get_message, "a-missing-message") - - def test_format_term(self): - self.bundle.add_resource( + assert bundle._plural_form(0) == "one" + assert bundle._plural_form(1) == "one" + assert bundle._plural_form(2) == "other" + + def test_ordinal_form_english_ints(self, bundle): + assert bundle._ordinal_form(0) == "other" + assert bundle._ordinal_form(1) == "one" + assert bundle._ordinal_form(2) == "two" + assert bundle._ordinal_form(3) == "few" + assert bundle._ordinal_form(11) == "other" + assert bundle._ordinal_form(21) == "one" + + def test_format_args(self, bundle): + bundle.add_resource(FluentResource("foo = Foo")) + val, errs = bundle.format_pattern(bundle.get_message("foo").value) + assert val == "Foo" + + val, errs = bundle.format_pattern(bundle.get_message("foo").value, {}) + assert val == "Foo" + + def test_format_missing(self, bundle): + with pytest.raises(LookupError): + bundle.get_message("a-missing-message") + + def test_format_term(self, bundle): + bundle.add_resource( FluentResource( dedent_ftl( """ @@ -119,11 +118,13 @@ def test_format_term(self): ) ) ) - self.assertRaises(LookupError, self.bundle.get_message, "-foo") - self.assertRaises(LookupError, self.bundle.get_message, "foo") + with pytest.raises(LookupError): + bundle.get_message("-foo") + with pytest.raises(LookupError): + bundle.get_message("foo") - def test_message_and_term_separate(self): - self.bundle.add_resource( + def test_message_and_term_separate(self, bundle): + bundle.add_resource( FluentResource( dedent_ftl( """ @@ -133,6 +134,6 @@ def test_message_and_term_separate(self): ) ) ) - val, errs = self.bundle.format_pattern(self.bundle.get_message("foo").value, {}) - self.assertEqual(val, "Refers to \u2068Foo\u2069") - self.assertEqual(errs, []) + val, errs = bundle.format_pattern(bundle.get_message("foo").value, {}) + assert val == "Refers to \u2068Foo\u2069" + assert errs == [] diff --git a/fluent.runtime/tests/test_fallback.py b/fluent.runtime/tests/test_fallback.py index 6e43f795..71cd8fb0 100644 --- a/fluent.runtime/tests/test_fallback.py +++ b/fluent.runtime/tests/test_fallback.py @@ -1,118 +1,110 @@ -import unittest from os.path import join -from .utils import patch_files from fluent.runtime import FluentLocalization, FluentResourceLoader +from .utils import build_file_tree -class TestLocalization(unittest.TestCase): + +class TestLocalization: def test_init(self): l10n = FluentLocalization( ["en"], ["file.ftl"], FluentResourceLoader("{locale}") ) - self.assertTrue(callable(l10n.format_value)) + assert callable(l10n.format_value) - @patch_files( - { - "de": { - "one.ftl": "one = in German\n .foo = one in German\n", - "two.ftl": "two = in German\n .foo = two in German\n", - }, - "fr": {"two.ftl": "three = in French\n .foo = three in French\n"}, - "en": { - "one.ftl": "four = exists\n .foo = four in English\n", - "two.ftl": "five = exists\n .foo = five in English\n" - + "bar =\n .foo = bar in English\n" - + "baz = baz in English\n", + def test_bundles(self, tmp_path): + build_file_tree( + tmp_path, + { + "de": { + "one.ftl": "one = in German\n .foo = one in German\n", + "two.ftl": "two = in German\n .foo = two in German\n", + }, + "fr": {"two.ftl": "three = in French\n .foo = three in French\n"}, + "en": { + "one.ftl": "four = exists\n .foo = four in English\n", + "two.ftl": "five = exists\n .foo = five in English\n" + + "bar =\n .foo = bar in English\n" + + "baz = baz in English\n", + }, }, - } - ) - def test_bundles(self, root): + ) l10n = FluentLocalization( ["de", "fr", "en"], ["one.ftl", "two.ftl"], - FluentResourceLoader(join(root, "{locale}")), + FluentResourceLoader(join(tmp_path, "{locale}")), ) bundles_gen = l10n._bundles() bundle_de = next(bundles_gen) - self.assertEqual(bundle_de.locales[0], "de") - self.assertTrue(bundle_de.has_message("one")) - self.assertTrue(bundle_de.has_message("two")) + assert bundle_de.locales[0] == "de" + assert bundle_de.has_message("one") + assert bundle_de.has_message("two") bundle_fr = next(bundles_gen) - self.assertEqual(bundle_fr.locales[0], "fr") - self.assertFalse(bundle_fr.has_message("one")) - self.assertTrue(bundle_fr.has_message("three")) - self.assertListEqual(list(l10n._bundles())[:2], [bundle_de, bundle_fr]) + assert bundle_fr.locales[0] == "fr" + assert not bundle_fr.has_message("one") + assert bundle_fr.has_message("three") + assert list(l10n._bundles())[:2] == [bundle_de, bundle_fr] bundle_en = next(bundles_gen) - self.assertEqual(bundle_en.locales[0], "en") - self.assertEqual(l10n.format_value("one"), "in German") - self.assertEqual(l10n.format_value("two"), "in German") - self.assertEqual(l10n.format_value("three"), "in French") - self.assertEqual(l10n.format_value("four"), "exists") - self.assertEqual(l10n.format_value("five"), "exists") - self.assertEqual(l10n.format_value("bar"), "bar") - self.assertEqual(l10n.format_value("baz"), "baz in English") - self.assertEqual(l10n.format_value("not-exists"), "not-exists") - self.assertEqual( - tuple(l10n.format_message("one")), - ("in German", {"foo": "one in German"}), - ) - self.assertEqual( - tuple(l10n.format_message("two")), - ("in German", {"foo": "two in German"}), + assert bundle_en.locales[0] == "en" + assert l10n.format_value("one") == "in German" + assert l10n.format_value("two") == "in German" + assert l10n.format_value("three") == "in French" + assert l10n.format_value("four") == "exists" + assert l10n.format_value("five") == "exists" + assert l10n.format_value("bar") == "bar" + assert l10n.format_value("baz") == "baz in English" + assert l10n.format_value("not-exists") == "not-exists" + assert tuple(l10n.format_message("one")) == ( + "in German", + {"foo": "one in German"}, ) - self.assertEqual( - tuple(l10n.format_message("three")), - ("in French", {"foo": "three in French"}), + assert tuple(l10n.format_message("two")) == ( + "in German", + {"foo": "two in German"}, ) - self.assertEqual( - tuple(l10n.format_message("four")), - ("exists", {"foo": "four in English"}), + assert tuple(l10n.format_message("three")) == ( + "in French", + {"foo": "three in French"}, ) - self.assertEqual( - tuple(l10n.format_message("five")), - ("exists", {"foo": "five in English"}), + assert tuple(l10n.format_message("four")) == ( + "exists", + {"foo": "four in English"}, ) - self.assertEqual( - tuple(l10n.format_message("bar")), - (None, {"foo": "bar in English"}), - ) - self.assertEqual( - tuple(l10n.format_message("baz")), - ("baz in English", {}), - ) - self.assertEqual( - tuple(l10n.format_message("not-exists")), - ("not-exists", {}), + assert tuple(l10n.format_message("five")) == ( + "exists", + {"foo": "five in English"}, ) + assert tuple(l10n.format_message("bar")) == (None, {"foo": "bar in English"}) + assert tuple(l10n.format_message("baz")) == ("baz in English", {}) + assert tuple(l10n.format_message("not-exists")) == ("not-exists", {}) -class TestResourceLoader(unittest.TestCase): - @patch_files( - { - "en": { - "one.ftl": "one = exists", - "two.ftl": "two = exists", - } - } - ) - def test_all_exist(self, root): - loader = FluentResourceLoader(join(root, "{locale}")) +class TestResourceLoader: + def test_all_exist(self, tmp_path): + build_file_tree( + tmp_path, + { + "en": { + "one.ftl": "one = exists", + "two.ftl": "two = exists", + } + }, + ) + loader = FluentResourceLoader(join(tmp_path, "{locale}")) resources_list = list(loader.resources("en", ["one.ftl", "two.ftl"])) - self.assertEqual(len(resources_list), 1) + assert len(resources_list) == 1 resources = resources_list[0] - self.assertEqual(len(resources), 2) + assert len(resources) == 2 - @patch_files({"en": {"two.ftl": "two = exists"}}) - def test_one_exists(self, root): - loader = FluentResourceLoader(join(root, "{locale}")) + def test_one_exists(self, tmp_path): + build_file_tree(tmp_path, {"en": {"two.ftl": "two = exists"}}) + loader = FluentResourceLoader(join(tmp_path, "{locale}")) resources_list = list(loader.resources("en", ["one.ftl", "two.ftl"])) - self.assertEqual(len(resources_list), 1) + assert len(resources_list) == 1 resources = resources_list[0] - self.assertEqual(len(resources), 1) + assert len(resources) == 1 - @patch_files({}) - def test_none_exist(self, root): - loader = FluentResourceLoader(join(root, "{locale}")) + def test_none_exist(self, tmp_path): + loader = FluentResourceLoader(join(tmp_path, "{locale}")) resources_list = list(loader.resources("en", ["one.ftl", "two.ftl"])) - self.assertEqual(len(resources_list), 0) + assert len(resources_list) == 0 diff --git a/fluent.runtime/tests/test_types.py b/fluent.runtime/tests/test_types.py index 99f288c1..df4ca825 100644 --- a/fluent.runtime/tests/test_types.py +++ b/fluent.runtime/tests/test_types.py @@ -1,8 +1,9 @@ -import unittest +import re import warnings from datetime import date, datetime from decimal import Decimal +import pytest import pytz from babel import Locale from fluent.runtime.types import ( @@ -12,215 +13,210 @@ fluent_number, ) +en_US = Locale.parse("en_US") +cur_pos = fluent_number(123456.78123, currency="USD", style="currency") +cur_neg = fluent_number(-123456.78123, currency="USD", style="currency") -class TestFluentNumber(unittest.TestCase): - - locale = Locale.parse("en_US") - - def setUp(self): - self.cur_pos = fluent_number(123456.78123, currency="USD", style="currency") - self.cur_neg = fluent_number(-123456.78123, currency="USD", style="currency") +class TestFluentNumber: def test_int(self): i = fluent_number(1) - self.assertTrue(isinstance(i, int)) - self.assertTrue(isinstance(i, FluentNumber)) - self.assertEqual(i + 1, 2) + assert isinstance(i, int) + assert isinstance(i, FluentNumber) + assert i + 1 == 2 def test_float(self): f = fluent_number(1.1) - self.assertTrue(isinstance(f, float)) - self.assertTrue(isinstance(f, FluentNumber)) - self.assertEqual(f + 1, 2.1) + assert isinstance(f, float) + assert isinstance(f, FluentNumber) + assert f + 1 == 2.1 def test_decimal(self): d = Decimal("1.1") - self.assertTrue(isinstance(fluent_number(d), Decimal)) - self.assertTrue(isinstance(fluent_number(d), FluentNumber)) - self.assertEqual(d + 1, Decimal("2.1")) + assert isinstance(fluent_number(d), Decimal) + assert isinstance(fluent_number(d), FluentNumber) + assert d + 1 == Decimal("2.1") def test_disallow_nonexistant_options(self): - self.assertRaises( - TypeError, - fluent_number, - 1, - not_a_real_option=True, - ) + with pytest.raises(TypeError): + fluent_number( + 1, + not_a_real_option=True, + ) def test_style_validation(self): - self.assertRaises(ValueError, fluent_number, 1, style="xyz") + with pytest.raises(ValueError): + fluent_number(1, style="xyz") def test_use_grouping(self): f1 = fluent_number(123456.78, useGrouping=True) f2 = fluent_number(123456.78, useGrouping=False) - self.assertEqual(f1.format(self.locale), "123,456.78") - self.assertEqual(f2.format(self.locale), "123456.78") + assert f1.format(en_US) == "123,456.78" + assert f2.format(en_US) == "123456.78" # ensure we didn't mutate anything when we created the new # NumberPattern: - self.assertEqual(f1.format(self.locale), "123,456.78") + assert f1.format(en_US) == "123,456.78" def test_use_grouping_decimal(self): d = Decimal("123456.78") f1 = fluent_number(d, useGrouping=True) f2 = fluent_number(d, useGrouping=False) - self.assertEqual(f1.format(self.locale), "123,456.78") - self.assertEqual(f2.format(self.locale), "123456.78") + assert f1.format(en_US) == "123,456.78" + assert f2.format(en_US) == "123456.78" def test_minimum_integer_digits(self): f = fluent_number(1.23, minimumIntegerDigits=3) - self.assertEqual(f.format(self.locale), "001.23") + assert f.format(en_US) == "001.23" def test_minimum_integer_digits_decimal(self): f = fluent_number(Decimal("1.23"), minimumIntegerDigits=3) - self.assertEqual(f.format(self.locale), "001.23") + assert f.format(en_US) == "001.23" def test_minimum_fraction_digits(self): f = fluent_number(1.2, minimumFractionDigits=3) - self.assertEqual(f.format(self.locale), "1.200") + assert f.format(en_US) == "1.200" def test_maximum_fraction_digits(self): f1 = fluent_number(1.23456) - self.assertEqual(f1.format(self.locale), "1.235") + assert f1.format(en_US) == "1.235" f2 = fluent_number(1.23456, maximumFractionDigits=5) - self.assertEqual(f2.format(self.locale), "1.23456") + assert f2.format(en_US) == "1.23456" def test_minimum_significant_digits(self): f1 = fluent_number(123, minimumSignificantDigits=5) - self.assertEqual(f1.format(self.locale), "123.00") + assert f1.format(en_US) == "123.00" f2 = fluent_number(12.3, minimumSignificantDigits=5) - self.assertEqual(f2.format(self.locale), "12.300") + assert f2.format(en_US) == "12.300" def test_maximum_significant_digits(self): f1 = fluent_number(123456, maximumSignificantDigits=3) - self.assertEqual(f1.format(self.locale), "123,000") + assert f1.format(en_US) == "123,000" f2 = fluent_number(12.3456, maximumSignificantDigits=3) - self.assertEqual(f2.format(self.locale), "12.3") + assert f2.format(en_US) == "12.3" f3 = fluent_number(12, maximumSignificantDigits=5) - self.assertEqual(f3.format(self.locale), "12") + assert f3.format(en_US) == "12" def test_currency(self): # This test the default currencyDisplay value - self.assertEqual(self.cur_pos.format(self.locale), "$123,456.78") + assert cur_pos.format(en_US) == "$123,456.78" def test_currency_display_validation(self): - self.assertRaises(ValueError, fluent_number, 1234, currencyDisplay="junk") + with pytest.raises(ValueError): + fluent_number(1234, currencyDisplay="junk") def test_currency_display_symbol(self): - cur_pos_sym = fluent_number(self.cur_pos, currencyDisplay="symbol") - cur_neg_sym = fluent_number(self.cur_neg, currencyDisplay="symbol") - self.assertEqual(cur_pos_sym.format(self.locale), "$123,456.78") - self.assertEqual(cur_neg_sym.format(self.locale), "-$123,456.78") + cur_pos_sym = fluent_number(cur_pos, currencyDisplay="symbol") + cur_neg_sym = fluent_number(cur_neg, currencyDisplay="symbol") + assert cur_pos_sym.format(en_US) == "$123,456.78" + assert cur_neg_sym.format(en_US) == "-$123,456.78" def test_currency_display_code(self): # Outputs here were determined by comparing with Javascrpt # Intl.NumberFormat in Firefox. - cur_pos_code = fluent_number(self.cur_pos, currencyDisplay="code") - cur_neg_code = fluent_number(self.cur_neg, currencyDisplay="code") - self.assertEqual(cur_pos_code.format(self.locale), "USD123,456.78") - self.assertEqual(cur_neg_code.format(self.locale), "-USD123,456.78") + cur_pos_code = fluent_number(cur_pos, currencyDisplay="code") + cur_neg_code = fluent_number(cur_neg, currencyDisplay="code") + assert cur_pos_code.format(en_US) == "USD123,456.78" + assert cur_neg_code.format(en_US) == "-USD123,456.78" - @unittest.skip("Babel doesn't provide support for this yet") + @pytest.mark.skip("Babel doesn't provide support for this yet") def test_currency_display_name(self): - cur_pos_name = fluent_number(self.cur_pos, currencyDisplay="name") - cur_neg_name = fluent_number(self.cur_neg, currencyDisplay="name") - self.assertEqual(cur_pos_name.format(self.locale), "123,456.78 US dollars") - self.assertEqual(cur_neg_name.format(self.locale), "-123,456.78 US dollars") + cur_pos_name = fluent_number(cur_pos, currencyDisplay="name") + cur_neg_name = fluent_number(cur_neg, currencyDisplay="name") + assert cur_pos_name.format(en_US) == "123,456.78 US dollars" + assert cur_neg_name.format(en_US) == "-123,456.78 US dollars" # Some others locales: hr_BA = Locale.parse("hr_BA") - self.assertEqual(cur_pos_name.format(hr_BA), "123.456,78 američkih dolara") + assert cur_pos_name.format(hr_BA) == "123.456,78 američkih dolara" es_GT = Locale.parse("es_GT") - self.assertEqual( - cur_pos_name.format(es_GT), "dólares estadounidenses 123,456.78" - ) + assert cur_pos_name.format(es_GT) == "dólares estadounidenses 123,456.78" def test_copy_attributes(self): f1 = fluent_number(123456.78, useGrouping=False) - self.assertEqual(f1.options.useGrouping, False) + assert f1.options.useGrouping is False # Check we didn't mutate anything - self.assertIs(FluentNumber.default_number_format_options.useGrouping, True) + assert FluentNumber.default_number_format_options.useGrouping is True f2 = fluent_number(f1, style="percent") - self.assertEqual(f2.options.style, "percent") + assert f2.options.style == "percent" # Check we copied - self.assertEqual(f2.options.useGrouping, False) + assert f2.options.useGrouping is False # and didn't mutate anything - self.assertEqual(f1.options.style, "decimal") - self.assertEqual(FluentNumber.default_number_format_options.style, "decimal") - + assert f1.options.style == "decimal" + assert FluentNumber.default_number_format_options.style == "decimal" -class TestFluentDate(unittest.TestCase): - locale = Locale.parse("en_US") +a_date = date(2018, 2, 1) +a_datetime = datetime(2018, 2, 1, 14, 15, 16, 123456, tzinfo=pytz.UTC) - def setUp(self): - self.a_date = date(2018, 2, 1) - self.a_datetime = datetime(2018, 2, 1, 14, 15, 16, 123456, tzinfo=pytz.UTC) +class TestFluentDate: def test_date(self): - fd = fluent_date(self.a_date) - self.assertTrue(isinstance(fd, date)) - self.assertTrue(isinstance(fd, FluentDateType)) - self.assertEqual(fd.year, self.a_date.year) - self.assertEqual(fd.month, self.a_date.month) - self.assertEqual(fd.day, self.a_date.day) + fd = fluent_date(a_date) + assert isinstance(fd, date) + assert isinstance(fd, FluentDateType) + assert fd.year == a_date.year + assert fd.month == a_date.month + assert fd.day == a_date.day def test_datetime(self): - fd = fluent_date(self.a_datetime) - self.assertTrue(isinstance(fd, datetime)) - self.assertTrue(isinstance(fd, FluentDateType)) - self.assertEqual(fd.year, self.a_datetime.year) - self.assertEqual(fd.month, self.a_datetime.month) - self.assertEqual(fd.day, self.a_datetime.day) - self.assertEqual(fd.hour, self.a_datetime.hour) - self.assertEqual(fd.minute, self.a_datetime.minute) - self.assertEqual(fd.second, self.a_datetime.second) - self.assertEqual(fd.microsecond, self.a_datetime.microsecond) - self.assertEqual(fd.tzinfo, self.a_datetime.tzinfo) + fd = fluent_date(a_datetime) + assert isinstance(fd, datetime) + assert isinstance(fd, FluentDateType) + assert fd.year == a_datetime.year + assert fd.month == a_datetime.month + assert fd.day == a_datetime.day + assert fd.hour == a_datetime.hour + assert fd.minute == a_datetime.minute + assert fd.second == a_datetime.second + assert fd.microsecond == a_datetime.microsecond + assert fd.tzinfo == a_datetime.tzinfo def test_format_defaults(self): - fd = fluent_date(self.a_date) + fd = fluent_date(a_date) en_US = Locale.parse("en_US") en_GB = Locale.parse("en_GB") - self.assertEqual(fd.format(en_GB), "1 Feb 2018") - self.assertEqual(fd.format(en_US), "Feb 1, 2018") + assert fd.format(en_GB) == "1 Feb 2018" + assert fd.format(en_US) == "Feb 1, 2018" def test_dateStyle_date(self): - fd = fluent_date(self.a_date, dateStyle="long") + fd = fluent_date(a_date, dateStyle="long") en_US = Locale.parse("en_US") en_GB = Locale.parse("en_GB") - self.assertEqual(fd.format(en_GB), "1 February 2018") - self.assertEqual(fd.format(en_US), "February 1, 2018") + assert fd.format(en_GB) == "1 February 2018" + assert fd.format(en_US) == "February 1, 2018" def test_dateStyle_datetime(self): - fd = fluent_date(self.a_datetime, dateStyle="long") + fd = fluent_date(a_datetime, dateStyle="long") en_US = Locale.parse("en_US") en_GB = Locale.parse("en_GB") - self.assertEqual(fd.format(en_GB), "1 February 2018") - self.assertEqual(fd.format(en_US), "February 1, 2018") + assert fd.format(en_GB) == "1 February 2018" + assert fd.format(en_US) == "February 1, 2018" def test_timeStyle_datetime(self): - fd = fluent_date(self.a_datetime, timeStyle="short") + fd = fluent_date(a_datetime, timeStyle="short") en_US = Locale.parse("en_US") en_GB = Locale.parse("en_GB") - self.assertRegex(fd.format(en_US), "^2:15\\sPM$") - self.assertEqual(fd.format(en_GB), "14:15") + assert re.search("^2:15\\sPM$", fd.format(en_US)) + assert fd.format(en_GB) == "14:15" def test_dateStyle_and_timeStyle_datetime(self): - fd = fluent_date(self.a_datetime, timeStyle="short", dateStyle="short") + fd = fluent_date(a_datetime, timeStyle="short", dateStyle="short") en_US = Locale.parse("en_US") en_GB = Locale.parse("en_GB") - self.assertRegex(fd.format(en_US), "^2/1/18, 2:15\\sPM$") - self.assertEqual(fd.format(en_GB), "01/02/2018, 14:15") + assert re.search("^2/1/18, 2:15\\sPM$", fd.format(en_US)) + assert fd.format(en_GB) == "01/02/2018, 14:15" def test_validate_dateStyle(self): - self.assertRaises(ValueError, fluent_date, self.a_date, dateStyle="nothing") + with pytest.raises(ValueError): + fluent_date(a_date, dateStyle="nothing") def test_validate_timeStyle(self): - self.assertRaises(ValueError, fluent_date, self.a_datetime, timeStyle="nothing") + with pytest.raises(ValueError): + fluent_date(a_datetime, timeStyle="nothing") def test_timeZone(self): en_GB = Locale.parse("en_GB") @@ -231,41 +227,41 @@ def test_timeZone(self): # datetime object with tzinfo set to BST dt1 = datetime(2018, 7, 1, 23, 30, 0, tzinfo=pytz.UTC).astimezone(LondonTZ) fd1 = fluent_date(dt1, dateStyle="short", timeStyle="short") - self.assertEqual(fd1.format(en_GB), "02/07/2018, 00:30") + assert fd1.format(en_GB) == "02/07/2018, 00:30" fd1b = fluent_date(dt1, dateStyle="full", timeStyle="full") - self.assertRegex( - fd1b.format(en_GB), + assert re.search( "^Monday,? 2 July 2018(,| at) 00:30:00 British Summer Time$", + fd1b.format(en_GB), ) fd1c = fluent_date(dt1, dateStyle="short") - self.assertEqual(fd1c.format(en_GB), "02/07/2018") + assert fd1c.format(en_GB) == "02/07/2018" fd1d = fluent_date(dt1, timeStyle="short") - self.assertEqual(fd1d.format(en_GB), "00:30") + assert fd1d.format(en_GB) == "00:30" # datetime object with no TZ, TZ passed in to fluent_date dt2 = datetime(2018, 7, 1, 23, 30, 0) # Assumed UTC fd2 = fluent_date( dt2, dateStyle="short", timeStyle="short", timeZone="Europe/London" ) - self.assertEqual(fd2.format(en_GB), "02/07/2018, 00:30") + assert fd2.format(en_GB) == "02/07/2018, 00:30" fd2b = fluent_date( dt2, dateStyle="full", timeStyle="full", timeZone="Europe/London" ) - self.assertRegex( - fd2b.format(en_GB), + assert re.search( "^Monday,? 2 July 2018(,| at) 00:30:00 British Summer Time$", + fd2b.format(en_GB), ) fd2c = fluent_date(dt2, dateStyle="short", timeZone="Europe/London") - self.assertEqual(fd2c.format(en_GB), "02/07/2018") + assert fd2c.format(en_GB) == "02/07/2018" fd2d = fluent_date(dt1, timeStyle="short", timeZone="Europe/London") - self.assertEqual(fd2d.format(en_GB), "00:30") + assert fd2d.format(en_GB) == "00:30" def test_allow_unsupported_options(self): # We are just checking that these don't raise exceptions with warnings.catch_warnings(): warnings.simplefilter("ignore") fluent_date( - self.a_date, + a_date, hour12=True, weekday="narrow", era="narrow", @@ -279,32 +275,31 @@ def test_allow_unsupported_options(self): ) def test_disallow_nonexistant_options(self): - self.assertRaises( - TypeError, - fluent_date, - self.a_date, - not_a_real_option=True, - ) + with pytest.raises(TypeError): + fluent_date( + a_date, + not_a_real_option=True, + ) def test_dont_wrap_unnecessarily(self): - f1 = fluent_date(self.a_date) + f1 = fluent_date(a_date) f2 = fluent_date(f1) - self.assertIs(f1, f2) + assert f1 is f2 def test_copy_attributes(self): - f1 = fluent_date(self.a_date, dateStyle="long", hour12=False) - self.assertEqual(f1.options.dateStyle, "long") + f1 = fluent_date(a_date, dateStyle="long", hour12=False) + assert f1.options.dateStyle == "long" f2 = fluent_date(f1, hour12=False) # Check we copied other attributes: - self.assertEqual(f2.options.dateStyle, "long") - self.assertEqual(f2.options.hour12, False) + assert f2.options.dateStyle == "long" + assert f2.options.hour12 is False # Check we can override f3 = fluent_date(f2, dateStyle="full") - self.assertEqual(f3.options.dateStyle, "full") + assert f3.options.dateStyle == "full" # and didn't mutate anything - self.assertEqual(f1.options.dateStyle, "long") - self.assertEqual(f2.options.dateStyle, "long") + assert f1.options.dateStyle == "long" + assert f2.options.dateStyle == "long" diff --git a/fluent.runtime/tests/test_utils.py b/fluent.runtime/tests/test_utils.py deleted file mode 100644 index 24ea14b0..00000000 --- a/fluent.runtime/tests/test_utils.py +++ /dev/null @@ -1,30 +0,0 @@ -import unittest -from .utils import patch_files -from os.path import isdir, isfile, join - - -class TestFileSimulate(unittest.TestCase): - def test_basic(self): - @patch_files( - { - "the.txt": "The", - "en": { - "one.txt": "One", - "two.txt": "Two", - }, - } - ) - def patch_me(a, b, root): - self.assertEqual(a, 10) - self.assertEqual(b, "b") - with open(join(root, "the.txt")) as f: - self.assertEqual(f.read(), "The") - with open(join(root, "en", "one.txt")) as f: - self.assertEqual(f.read(), "One") - with open(join(root, "en", "two.txt")) as f: - self.assertEqual(f.read(), "Two") - self.assertTrue(isdir(join(root, "en"))) - self.assertFalse(isfile(join(root, "none.txt"))) - self.assertFalse(isfile(join(root, "en", "three.txt"))) - - patch_me(10, "b") diff --git a/fluent.runtime/tests/utils.py b/fluent.runtime/tests/utils.py index f4d6a01b..8a54f6b0 100644 --- a/fluent.runtime/tests/utils.py +++ b/fluent.runtime/tests/utils.py @@ -1,35 +1,21 @@ """Utilities for testing.""" import textwrap -from functools import wraps from os import mkdir from os.path import join -from tempfile import TemporaryDirectory def dedent_ftl(text): return textwrap.dedent(f"{text.rstrip()}\n") -def patch_files(tree: dict): - def build_file_tree(root: str, tree: dict) -> None: - for name, value in tree.items(): - path = join(root, name) - if isinstance(value, str): - with open(path, "x", encoding="utf-8", newline="\n") as file: - if value: - file.write(value) - else: - mkdir(path) - build_file_tree(path, value) - - def decorator(fn): - @wraps(fn) - def wrapper(*args, **kwargs): - with TemporaryDirectory() as root: - build_file_tree(root, tree) - return fn(*args, root, **kwargs) - - return wrapper - - return decorator +def build_file_tree(root: str, tree: dict) -> None: + for name, value in tree.items(): + path = join(root, name) + if isinstance(value, str): + with open(path, "x", encoding="utf-8", newline="\n") as file: + if value: + file.write(value) + else: + mkdir(path) + build_file_tree(path, value) diff --git a/fluent.syntax/tests/syntax/test_ast_json.py b/fluent.syntax/tests/syntax/test_ast_json.py index 557a66a0..1fe41393 100644 --- a/fluent.syntax/tests/syntax/test_ast_json.py +++ b/fluent.syntax/tests/syntax/test_ast_json.py @@ -1,27 +1,21 @@ -import unittest - from fluent.syntax.ast import from_json from fluent.syntax.parser import FluentParser -from tests.syntax import dedent_ftl - -class TestASTJSON(unittest.TestCase): - maxDiff = None +from . import dedent_ftl - def setUp(self): - self.parser = FluentParser() +class TestASTJSON: def test_simple_resource(self): input = """\ foo = Foo """ - ast1 = self.parser.parse(dedent_ftl(input)) + ast1 = FluentParser().parse(dedent_ftl(input)) json1 = ast1.to_json() ast2 = from_json(json1) json2 = ast2.to_json() - self.assertEqual(json1, json2) + assert json1 == json2 def test_complex_resource(self): input = """\ @@ -47,21 +41,21 @@ def test_complex_resource(self): } post. """ - ast1 = self.parser.parse(dedent_ftl(input)) + ast1 = FluentParser().parse(dedent_ftl(input)) json1 = ast1.to_json() ast2 = from_json(json1) json2 = ast2.to_json() - self.assertEqual(json1, json2) + assert json1 == json2 def test_syntax_error(self): input = """\ foo = Foo { """ - ast1 = self.parser.parse(dedent_ftl(input)) + ast1 = FluentParser().parse(dedent_ftl(input)) json1 = ast1.to_json() ast2 = from_json(json1) json2 = ast2.to_json() - self.assertEqual(json1, json2) + assert json1 == json2 diff --git a/fluent.syntax/tests/syntax/test_entry.py b/fluent.syntax/tests/syntax/test_entry.py index 250f8488..411d6e42 100644 --- a/fluent.syntax/tests/syntax/test_entry.py +++ b/fluent.syntax/tests/syntax/test_entry.py @@ -1,21 +1,19 @@ -import unittest +import pytest from fluent.syntax.ast import from_json from fluent.syntax.parser import FluentParser from fluent.syntax.serializer import FluentSerializer -from tests.syntax import dedent_ftl +from . import dedent_ftl -class TestParseEntry(unittest.TestCase): - maxDiff = None - def setUp(self): - self.parser = FluentParser(with_spans=False) +class TestParseEntry: + @pytest.fixture + def parser(self): + return FluentParser(with_spans=False) - def test_simple_message(self): - input = """\ - foo = Foo - """ + def test_simple_message(self, parser): + input = "foo = Foo\n" output = { "comment": None, "value": { @@ -29,10 +27,10 @@ def test_simple_message(self): "id": {"span": None, "type": "Identifier", "name": "foo"}, } - message = self.parser.parse_entry(dedent_ftl(input)) - self.assertEqual(message.to_json(), output) + message = parser.parse_entry(dedent_ftl(input)) + assert message.to_json() == output - def test_ignore_attached_comment(self): + def test_ignore_attached_comment(self, parser): input = """\ # Attached Comment foo = Foo @@ -50,10 +48,10 @@ def test_ignore_attached_comment(self): "span": None, } - message = self.parser.parse_entry(dedent_ftl(input)) - self.assertEqual(message.to_json(), output) + message = parser.parse_entry(dedent_ftl(input)) + assert message.to_json() == output - def test_return_junk(self): + def test_return_junk(self, parser): input = """\ # Attached Comment junk @@ -73,10 +71,10 @@ def test_return_junk(self): "type": "Junk", } - message = self.parser.parse_entry(dedent_ftl(input)) - self.assertEqual(message.to_json(), output) + message = parser.parse_entry(dedent_ftl(input)) + assert message.to_json() == output - def test_ignore_all_valid_comments(self): + def test_ignore_all_valid_comments(self, parser): input = """\ # Attached Comment ## Group Comment @@ -96,10 +94,10 @@ def test_ignore_all_valid_comments(self): "id": {"name": "foo", "span": None, "type": "Identifier"}, } - message = self.parser.parse_entry(dedent_ftl(input)) - self.assertEqual(message.to_json(), output) + message = parser.parse_entry(dedent_ftl(input)) + assert message.to_json() == output - def test_do_not_ignore_invalid_comments(self): + def test_do_not_ignore_invalid_comments(self, parser): input = """\ # Attached Comment ##Invalid Comment @@ -119,14 +117,11 @@ def test_do_not_ignore_invalid_comments(self): "type": "Junk", } - message = self.parser.parse_entry(dedent_ftl(input)) - self.assertEqual(message.to_json(), output) - + message = parser.parse_entry(dedent_ftl(input)) + assert message.to_json() == output -class TestSerializeEntry(unittest.TestCase): - def setUp(self): - self.serializer = FluentSerializer() +class TestSerializeEntry: def test_simple_message(self): input = { "comment": None, @@ -142,5 +137,5 @@ def test_simple_message(self): foo = Foo """ - message = self.serializer.serialize_entry(from_json(input)) - self.assertEqual(message, dedent_ftl(output)) + message = FluentSerializer().serialize_entry(from_json(input)) + assert message == dedent_ftl(output) diff --git a/fluent.syntax/tests/syntax/test_equals.py b/fluent.syntax/tests/syntax/test_equals.py index ceb81122..6fb08c9a 100644 --- a/fluent.syntax/tests/syntax/test_equals.py +++ b/fluent.syntax/tests/syntax/test_equals.py @@ -1,28 +1,25 @@ -import unittest - from fluent.syntax.parser import FluentParser -from tests.syntax import dedent_ftl + +from . import dedent_ftl -class TestEntryEqualToSelf(unittest.TestCase): - def setUp(self): - self.parser = FluentParser() +def parse_ftl_entry(string): + return FluentParser().parse_entry(dedent_ftl(string)) - def parse_ftl_entry(self, string): - return self.parser.parse_entry(dedent_ftl(string)) +class TestEntryEqualToSelf: def test_same_simple_message(self): - message1 = self.parse_ftl_entry( + message1 = parse_ftl_entry( """\ foo = Foo """ ) - self.assertTrue(message1.equals(message1)) - self.assertTrue(message1.equals(message1.clone())) + assert message1.equals(message1) + assert message1.equals(message1.clone()) def test_same_selector_message(self): - message1 = self.parse_ftl_entry( + message1 = parse_ftl_entry( """\ foo = { $num -> @@ -35,32 +32,32 @@ def test_same_selector_message(self): """ ) - self.assertTrue(message1.equals(message1)) - self.assertTrue(message1.equals(message1.clone())) + assert message1.equals(message1) + assert message1.equals(message1.clone()) def test_same_complex_placeable_message(self): - message1 = self.parse_ftl_entry( + message1 = parse_ftl_entry( """\ foo = Foo { NUMBER($num, style: "decimal") } Bar """ ) - self.assertTrue(message1.equals(message1)) - self.assertTrue(message1.equals(message1.clone())) + assert message1.equals(message1) + assert message1.equals(message1.clone()) def test_same_message_with_attribute(self): - message1 = self.parse_ftl_entry( + message1 = parse_ftl_entry( """\ foo = .attr = Attr """ ) - self.assertTrue(message1.equals(message1)) - self.assertTrue(message1.equals(message1.clone())) + assert message1.equals(message1) + assert message1.equals(message1.clone()) def test_same_message_with_attributes(self): - message1 = self.parse_ftl_entry( + message1 = parse_ftl_entry( """\ foo = .attr1 = Attr 1 @@ -68,36 +65,30 @@ def test_same_message_with_attributes(self): """ ) - self.assertTrue(message1.equals(message1)) - self.assertTrue(message1.equals(message1.clone())) + assert message1.equals(message1) + assert message1.equals(message1.clone()) def test_same_junk(self): - message1 = self.parse_ftl_entry( + message1 = parse_ftl_entry( """\ foo = Foo { """ ) - self.assertTrue(message1.equals(message1)) - self.assertTrue(message1.equals(message1.clone())) - + assert message1.equals(message1) + assert message1.equals(message1.clone()) -class TestNonEquals(unittest.TestCase): - def setUp(self): - self.parser = FluentParser() - - def parse_ftl_entry(self, string): - return self.parser.parse_entry(dedent_ftl(string)) +class TestNonEquals: def test_attributes(self): - message1 = self.parse_ftl_entry( + message1 = parse_ftl_entry( """\ foo = .attr1 = Attr1 .attr2 = Attr2 """ ) - message2 = self.parse_ftl_entry( + message2 = parse_ftl_entry( """\ foo = .attr2 = Attr2 @@ -105,10 +96,10 @@ def test_attributes(self): """ ) - self.assertFalse(message1.equals(message2)) + assert not message1.equals(message2) def test_variants(self): - message1 = self.parse_ftl_entry( + message1 = parse_ftl_entry( """\ foo = { $num -> @@ -117,7 +108,7 @@ def test_variants(self): } """ ) - message2 = self.parse_ftl_entry( + message2 = parse_ftl_entry( """\ foo = { $num -> @@ -127,10 +118,10 @@ def test_variants(self): """ ) - self.assertFalse(message1.equals(message2)) + assert not message1.equals(message2) def test_variants_with_numbers(self): - message1 = self.parse_ftl_entry( + message1 = parse_ftl_entry( """\ foo = { $num -> @@ -139,7 +130,7 @@ def test_variants_with_numbers(self): } """ ) - message2 = self.parse_ftl_entry( + message2 = parse_ftl_entry( """\ foo = { $num -> @@ -149,10 +140,10 @@ def test_variants_with_numbers(self): """ ) - self.assertFalse(message1.equals(message2)) + assert not message1.equals(message2) -class TestEqualWithSpans(unittest.TestCase): +class TestEqualWithSpans: def test_default_behavior(self): parser = FluentParser() @@ -165,7 +156,7 @@ def test_default_behavior(self): messages = [(parser.parse_entry(a), parser.parse_entry(b)) for a, b in strings] for a, b in messages: - self.assertTrue(a.equals(b)) + assert a.equals(b) def test_parser_without_spans(self): parser = FluentParser(with_spans=False) @@ -179,7 +170,7 @@ def test_parser_without_spans(self): messages = [(parser.parse_entry(a), parser.parse_entry(b)) for a, b in strings] for a, b in messages: - self.assertTrue(a.equals(b)) + assert a.equals(b) def test_equals_with_spans(self): parser = FluentParser() @@ -192,7 +183,7 @@ def test_equals_with_spans(self): messages = [(parser.parse_entry(a), parser.parse_entry(b)) for a, b in strings] for a, b in messages: - self.assertTrue(a.equals(b, ignored_fields=None)) + assert a.equals(b, ignored_fields=None) def test_parser_without_spans_equals_with_spans(self): parser = FluentParser(with_spans=False) @@ -207,7 +198,7 @@ def test_parser_without_spans_equals_with_spans(self): messages = [(parser.parse_entry(a), parser.parse_entry(b)) for a, b in strings] for a, b in messages: - self.assertTrue(a.equals(b, ignored_fields=None)) + assert a.equals(b, ignored_fields=None) def test_differ_with_spans(self): parser = FluentParser() @@ -220,49 +211,43 @@ def test_differ_with_spans(self): messages = [(parser.parse_entry(a), parser.parse_entry(b)) for a, b in strings] for a, b in messages: - self.assertFalse(a.equals(b, ignored_fields=None)) - - -class TestIgnoredFields(unittest.TestCase): - def setUp(self): - self.parser = FluentParser() + assert not a.equals(b, ignored_fields=None) - def parse_ftl_entry(self, string): - return self.parser.parse_entry(dedent_ftl(string)) +class TestIgnoredFields: def test_ignore_value(self): - a = self.parse_ftl_entry("foo = Foo") - b = self.parse_ftl_entry("foo = Bar") + a = parse_ftl_entry("foo = Foo") + b = parse_ftl_entry("foo = Bar") - self.assertTrue(a.equals(b, ignored_fields=["value"])) + assert a.equals(b, ignored_fields=["value"]) def test_ignore_value_span(self): - a = self.parse_ftl_entry("foo = Foo") - b = self.parse_ftl_entry("foo = Foobar") + a = parse_ftl_entry("foo = Foo") + b = parse_ftl_entry("foo = Foobar") - self.assertTrue(a.equals(b, ignored_fields=["span", "value"])) - self.assertFalse(a.equals(b, ignored_fields=["value"])) + assert a.equals(b, ignored_fields=["span", "value"]) + assert not a.equals(b, ignored_fields=["value"]) def test_ignore_comments(self): - a = self.parse_ftl_entry( + a = parse_ftl_entry( """\ # Comment A foo = Foo """ ) - b = self.parse_ftl_entry( + b = parse_ftl_entry( """\ # Comment B foo = Foo """ ) - c = self.parse_ftl_entry( + c = parse_ftl_entry( """\ # Comment CC foo = Foo """ ) - self.assertTrue(a.equals(b, ignored_fields=["comment"])) - self.assertFalse(a.equals(c, ignored_fields=["comment"])) - self.assertTrue(a.equals(c, ignored_fields=["comment", "span"])) + assert a.equals(b, ignored_fields=["comment"]) + assert not a.equals(c, ignored_fields=["comment"]) + assert a.equals(c, ignored_fields=["comment", "span"]) diff --git a/fluent.syntax/tests/syntax/test_literal.py b/fluent.syntax/tests/syntax/test_literal.py index ab22dfcf..ea89919e 100644 --- a/fluent.syntax/tests/syntax/test_literal.py +++ b/fluent.syntax/tests/syntax/test_literal.py @@ -1,5 +1,4 @@ import sys -import unittest from fluent.syntax.parser import FluentParser @@ -11,70 +10,52 @@ def parse_literal(input): return expr.parse() -class TestStringLiteralParse(unittest.TestCase): +class TestStringLiteralParse: def test_no_escape_sequences(self): - self.assertDictEqual(parse_literal('x = {"abc"}'), {"value": "abc"}) + assert parse_literal('x = {"abc"}') == {"value": "abc"} def test_double_quote_backslash(self): - self.assertDictEqual(parse_literal(r'x = {"\""}'), {"value": '"'}) - self.assertDictEqual(parse_literal(r'x = {"\\"}'), {"value": "\\"}) + assert parse_literal(r'x = {"\""}') == {"value": '"'} + assert parse_literal(r'x = {"\\"}') == {"value": "\\"} def test_unicode_escape(self): - self.assertDictEqual(parse_literal('x = {"\\u0041"}'), {"value": "A"}) - self.assertDictEqual(parse_literal('x = {"\\\\u0041"}'), {"value": "\\u0041"}) + assert parse_literal('x = {"\\u0041"}') == {"value": "A"} + assert parse_literal('x = {"\\\\u0041"}') == {"value": "\\u0041"} if sys.maxunicode > 0xFFFF: - self.assertDictEqual(parse_literal('x = {"\\U01F602"}'), {"value": "😂"}) - self.assertDictEqual( - parse_literal('x = {"\\\\U01F602"}'), {"value": "\\U01F602"} - ) + assert parse_literal('x = {"\\U01F602"}') == {"value": "😂"} + assert parse_literal('x = {"\\\\U01F602"}') == {"value": "\\U01F602"} def test_trailing_number(self): - self.assertDictEqual(parse_literal('x = {"\\u004100"}'), {"value": "A00"}) + assert parse_literal('x = {"\\u004100"}') == {"value": "A00"} if sys.maxunicode > 0xFFFF: - self.assertDictEqual( - parse_literal('x = {"\\U01F60200"}'), {"value": "😂00"} - ) + assert parse_literal('x = {"\\U01F60200"}') == {"value": "😂00"} -class TestNumberLiteralParse(unittest.TestCase): +class TestNumberLiteralParse: def test_integers(self): - self.assertDictEqual(parse_literal("x = {0}"), {"value": 0, "precision": 0}) - self.assertDictEqual(parse_literal("x = {1}"), {"value": 1, "precision": 0}) - self.assertDictEqual(parse_literal("x = {-0}"), {"value": 0, "precision": 0}) - self.assertDictEqual(parse_literal("x = {-1}"), {"value": -1, "precision": 0}) + assert parse_literal("x = {0}") == {"value": 0, "precision": 0} + assert parse_literal("x = {1}") == {"value": 1, "precision": 0} + assert parse_literal("x = {-0}") == {"value": 0, "precision": 0} + assert parse_literal("x = {-1}") == {"value": -1, "precision": 0} def test_padded_integers(self): - self.assertDictEqual(parse_literal("x = {00}"), {"value": 0, "precision": 0}) - self.assertDictEqual(parse_literal("x = {01}"), {"value": 1, "precision": 0}) - self.assertDictEqual(parse_literal("x = {-00}"), {"value": 0, "precision": 0}) - self.assertDictEqual(parse_literal("x = {-01}"), {"value": -1, "precision": 0}) + assert parse_literal("x = {00}") == {"value": 0, "precision": 0} + assert parse_literal("x = {01}") == {"value": 1, "precision": 0} + assert parse_literal("x = {-00}") == {"value": 0, "precision": 0} + assert parse_literal("x = {-01}") == {"value": -1, "precision": 0} def test_positive_floats(self): - self.assertDictEqual(parse_literal("x = {0.0}"), {"value": 0, "precision": 1}) - self.assertDictEqual( - parse_literal("x = {0.01}"), {"value": 0.01, "precision": 2} - ) - self.assertDictEqual( - parse_literal("x = {1.03}"), {"value": 1.03, "precision": 2} - ) - self.assertDictEqual(parse_literal("x = {1.000}"), {"value": 1, "precision": 3}) + assert parse_literal("x = {0.0}") == {"value": 0, "precision": 1} + assert parse_literal("x = {0.01}") == {"value": 0.01, "precision": 2} + assert parse_literal("x = {1.03}") == {"value": 1.03, "precision": 2} + assert parse_literal("x = {1.000}") == {"value": 1, "precision": 3} def test_negative_floats(self): - self.assertDictEqual(parse_literal("x = {-0.0}"), {"value": 0, "precision": 1}) - self.assertDictEqual( - parse_literal("x = {-0.01}"), {"value": -0.01, "precision": 2} - ) - self.assertDictEqual( - parse_literal("x = {-1.03}"), {"value": -1.03, "precision": 2} - ) - self.assertDictEqual( - parse_literal("x = {-1.000}"), {"value": -1, "precision": 3} - ) + assert parse_literal("x = {-0.0}") == {"value": 0, "precision": 1} + assert parse_literal("x = {-0.01}") == {"value": -0.01, "precision": 2} + assert parse_literal("x = {-1.03}") == {"value": -1.03, "precision": 2} + assert parse_literal("x = {-1.000}") == {"value": -1, "precision": 3} def test_padded_floats(self): - self.assertDictEqual( - parse_literal("x = {-00.00}"), {"value": 0, "precision": 2} - ) - self.assertDictEqual( - parse_literal("x = {-01.000}"), {"value": -1, "precision": 3} - ) + assert parse_literal("x = {-00.00}") == {"value": 0, "precision": 2} + assert parse_literal("x = {-01.000}") == {"value": -1, "precision": 3} diff --git a/fluent.syntax/tests/syntax/test_reference.py b/fluent.syntax/tests/syntax/test_reference.py index 17463c62..316007d2 100644 --- a/fluent.syntax/tests/syntax/test_reference.py +++ b/fluent.syntax/tests/syntax/test_reference.py @@ -1,58 +1,36 @@ -import json -import os -import unittest - -from fluent.syntax import parse - - -def read_file(path): - with open(path, "r", encoding="utf-8", newline="\n") as file: - text = file.read() - return text - - -fixtures = os.path.join(os.path.dirname(__file__), "fixtures_reference") +from json import loads +from os import listdir +from os.path import dirname, join, splitext +import pytest -class TestReferenceMeta(type): - def __new__(mcs, name, bases, attrs): - - def remove_untested(obj): - if obj["type"] == "Junk": - obj["annotations"] = [] - if "span" in obj: - del obj["span"] - return obj - - def gen_test(file_name): - def test(self): - ftl_path = os.path.join(fixtures, file_name + ".ftl") - ast_path = os.path.join(fixtures, file_name + ".json") - - source = read_file(ftl_path) - expected = read_file(ast_path) - - ast = parse(source) - self.assertEqual(ast.to_json(remove_untested), json.loads(expected)) +from fluent.syntax import parse - return test +fixtures_dir = join(dirname(__file__), "fixtures_reference") +fixture_names = [ + fp[0] for fn in listdir(fixtures_dir) if (fp := splitext(fn))[1] == ".ftl" +] - for f in os.listdir(fixtures): - file_name, ext = os.path.splitext(f) - if ext != ".ftl": - continue +@pytest.mark.parametrize("name", fixture_names) +def test_reference(name): + if name in ("leading_dots",): + pytest.skip("Known difference between reference and tooling parsers") - # Skip fixtures which are known to differ between the reference - # parser and the tooling parser. - if file_name in ("leading_dots", "variant_lists"): - continue + ftl_path = join(fixtures_dir, name + ".ftl") + ast_path = join(fixtures_dir, name + ".json") - test_name = f"test_{file_name}" - attrs[test_name] = gen_test(file_name) + with open(ftl_path, "r", encoding="utf-8", newline="\n") as file: + ast = parse(file.read()) + with open(ast_path, "r", encoding="utf-8", newline="\n") as file: + expected = loads(file.read()) - return type.__new__(mcs, name, bases, attrs) + assert ast.to_json(remove_untested) == expected -class TestReference(unittest.TestCase, metaclass=TestReferenceMeta): - maxDiff = None +def remove_untested(obj): + if obj["type"] == "Junk": + obj["annotations"] = [] + if "span" in obj: + del obj["span"] + return obj diff --git a/fluent.syntax/tests/syntax/test_serializer.py b/fluent.syntax/tests/syntax/test_serializer.py index 80d78407..49a329b9 100644 --- a/fluent.syntax/tests/syntax/test_serializer.py +++ b/fluent.syntax/tests/syntax/test_serializer.py @@ -1,11 +1,11 @@ -import unittest - +import pytest from fluent.syntax import FluentParser, FluentSerializer from fluent.syntax.serializer import serialize_expression, serialize_variant_key -from tests.syntax import dedent_ftl + +from . import dedent_ftl -class TestSerializeResource(unittest.TestCase): +class TestSerializeResource: @staticmethod def pretty_ftl(text): parser = FluentParser() @@ -16,30 +16,30 @@ def pretty_ftl(text): def test_invalid_resource(self): serializer = FluentSerializer() - with self.assertRaisesRegex(Exception, "Unknown resource type"): + with pytest.raises(Exception, match="Unknown resource type"): serializer.serialize(None) - with self.assertRaisesRegex(Exception, "Unknown resource type"): + with pytest.raises(Exception, match="Unknown resource type"): serializer.serialize(object()) def test_simple_message(self): input = """\ foo = Foo """ - self.assertEqual(self.pretty_ftl(input), dedent_ftl(input)) + assert self.pretty_ftl(input) == dedent_ftl(input) def test_simple_term(self): input = """\ -foo = Foo """ - self.assertEqual(self.pretty_ftl(input), dedent_ftl(input)) + assert self.pretty_ftl(input) == dedent_ftl(input) def test_two_simple_messages(self): input = """\ foo = Foo bar = Bar """ - self.assertEqual(self.pretty_ftl(input), dedent_ftl(input)) + assert self.pretty_ftl(input) == dedent_ftl(input) def test_block_multiline(self): input = """\ @@ -47,7 +47,7 @@ def test_block_multiline(self): Foo Bar """ - self.assertEqual(self.pretty_ftl(input), dedent_ftl(input)) + assert self.pretty_ftl(input) == dedent_ftl(input) def test_inline_multiline(self): input = """\ @@ -59,43 +59,43 @@ def test_inline_multiline(self): Foo Bar """ - self.assertEqual(self.pretty_ftl(input), dedent_ftl(output)) + assert self.pretty_ftl(input) == dedent_ftl(output) def test_message_reference(self): input = """\ foo = Foo { bar } """ - self.assertEqual(self.pretty_ftl(input), dedent_ftl(input)) + assert self.pretty_ftl(input) == dedent_ftl(input) def test_term_reference(self): input = """\ foo = Foo { -bar } """ - self.assertEqual(self.pretty_ftl(input), dedent_ftl(input)) + assert self.pretty_ftl(input) == dedent_ftl(input) def test_variable_reference(self): input = """\ foo = Foo { $bar } """ - self.assertEqual(self.pretty_ftl(input), dedent_ftl(input)) + assert self.pretty_ftl(input) == dedent_ftl(input) def test_number_element(self): input = """\ foo = Foo { 1 } """ - self.assertEqual(self.pretty_ftl(input), dedent_ftl(input)) + assert self.pretty_ftl(input) == dedent_ftl(input) def test_string_element(self): input = """\ foo = Foo { "bar" } """ - self.assertEqual(self.pretty_ftl(input), dedent_ftl(input)) + assert self.pretty_ftl(input) == dedent_ftl(input) def test_attribute_expression(self): input = """\ foo = Foo { bar.baz } """ - self.assertEqual(self.pretty_ftl(input), dedent_ftl(input)) + assert self.pretty_ftl(input) == dedent_ftl(input) def test_comment_resource(self): input = """\ @@ -104,7 +104,7 @@ def test_comment_resource(self): foo = Foo """ - self.assertEqual(self.pretty_ftl(input), dedent_ftl(input)) + assert self.pretty_ftl(input) == dedent_ftl(input) def test_comment_message(self): input = """\ @@ -114,7 +114,7 @@ def test_comment_message(self): # bar = Bar """ - self.assertEqual(self.pretty_ftl(input), dedent_ftl(input)) + assert self.pretty_ftl(input) == dedent_ftl(input) def test_comment_group(self): input = """\ @@ -131,7 +131,7 @@ def test_comment_group(self): baz = Baz """ - self.assertEqual(self.pretty_ftl(input), dedent_ftl(input)) + assert self.pretty_ftl(input) == dedent_ftl(input) def test_comment_standalone(self): input = """\ @@ -141,7 +141,7 @@ def test_comment_standalone(self): bar = Bar """ - self.assertEqual(self.pretty_ftl(input), dedent_ftl(input)) + assert self.pretty_ftl(input) == dedent_ftl(input) def test_multiline_starting_inline(self): input = """\ @@ -153,14 +153,14 @@ def test_multiline_starting_inline(self): Foo Bar """ - self.assertEqual(self.pretty_ftl(input), dedent_ftl(output)) + assert self.pretty_ftl(input) == dedent_ftl(output) def test_multiline_starting_inline_with_special_char(self): input = """\ foo = *Foo Bar """ - self.assertEqual(self.pretty_ftl(input), dedent_ftl(input)) + assert self.pretty_ftl(input) == dedent_ftl(input) def test_multiline_with_placeable(self): input = """\ @@ -168,14 +168,14 @@ def test_multiline_with_placeable(self): Foo { bar } Baz """ - self.assertEqual(self.pretty_ftl(input), dedent_ftl(input)) + assert self.pretty_ftl(input) == dedent_ftl(input) def test_attribute(self): input = """\ foo = .attr = Foo Attr """ - self.assertEqual(self.pretty_ftl(input), dedent_ftl(input)) + assert self.pretty_ftl(input) == dedent_ftl(input) def test_attribute_multiline(self): input = """\ @@ -184,7 +184,7 @@ def test_attribute_multiline(self): Foo Attr Continued """ - self.assertEqual(self.pretty_ftl(input), dedent_ftl(input)) + assert self.pretty_ftl(input) == dedent_ftl(input) def test_two_attributes(self): input = """\ @@ -192,7 +192,7 @@ def test_two_attributes(self): .attr-a = Foo Attr A .attr-b = Foo Attr B """ - self.assertEqual(self.pretty_ftl(input), dedent_ftl(input)) + assert self.pretty_ftl(input) == dedent_ftl(input) def test_value_and_attributes(self): input = """\ @@ -200,7 +200,7 @@ def test_value_and_attributes(self): .attr-a = Foo Attr A .attr-b = Foo Attr B """ - self.assertEqual(self.pretty_ftl(input), dedent_ftl(input)) + assert self.pretty_ftl(input) == dedent_ftl(input) def test_multiline_value_and_attributes(self): input = """\ @@ -210,7 +210,7 @@ def test_multiline_value_and_attributes(self): .attr-a = Foo Attr A .attr-b = Foo Attr B """ - self.assertEqual(self.pretty_ftl(input), dedent_ftl(input)) + assert self.pretty_ftl(input) == dedent_ftl(input) def test_select_expression(self): input = """\ @@ -220,7 +220,7 @@ def test_select_expression(self): [b] B } """ - self.assertEqual(self.pretty_ftl(input), dedent_ftl(input)) + assert self.pretty_ftl(input) == dedent_ftl(input) def test_variant_multiline(self): input = """\ @@ -231,7 +231,7 @@ def test_variant_multiline(self): BBB } """ - self.assertEqual(self.pretty_ftl(input), dedent_ftl(input)) + assert self.pretty_ftl(input) == dedent_ftl(input) def test_variant_multiline_first_inline(self): input = """\ @@ -249,7 +249,7 @@ def test_variant_multiline_first_inline(self): BBB } """ - self.assertEqual(self.pretty_ftl(input), dedent_ftl(output)) + assert self.pretty_ftl(input) == dedent_ftl(output) def test_variant_key_number(self): input = """\ @@ -258,7 +258,7 @@ def test_variant_key_number(self): *[1] 1 } """ - self.assertEqual(self.pretty_ftl(input), dedent_ftl(input)) + assert self.pretty_ftl(input) == dedent_ftl(input) def test_select_expression_in_block_value(self): input = """\ @@ -268,7 +268,7 @@ def test_select_expression_in_block_value(self): [b] B } """ - self.assertEqual(self.pretty_ftl(input), dedent_ftl(input)) + assert self.pretty_ftl(input) == dedent_ftl(input) def test_select_expression_in_inline_value(self): input = """\ @@ -284,7 +284,7 @@ def test_select_expression_in_inline_value(self): [b] B } """ - self.assertEqual(self.pretty_ftl(input), dedent_ftl(output)) + assert self.pretty_ftl(input) == dedent_ftl(output) def test_select_expression_in_inline_value_starting_with_special_char(self): input = """\ @@ -293,7 +293,7 @@ def test_select_expression_in_inline_value_starting_with_special_char(self): [b] B } """ - self.assertEqual(self.pretty_ftl(input), dedent_ftl(input)) + assert self.pretty_ftl(input) == dedent_ftl(input) def test_select_expression_in_multi_multiline(self): input = """\ @@ -304,7 +304,7 @@ def test_select_expression_in_multi_multiline(self): [b] B } """ - self.assertEqual(self.pretty_ftl(input), dedent_ftl(input)) + assert self.pretty_ftl(input) == dedent_ftl(input) def test_select_expression_nested(self): input = """\ @@ -316,7 +316,7 @@ def test_select_expression_nested(self): } } """ - self.assertEqual(self.pretty_ftl(input), dedent_ftl(input)) + assert self.pretty_ftl(input) == dedent_ftl(input) def test_selector_variable_reference(self): input = """\ @@ -325,7 +325,7 @@ def test_selector_variable_reference(self): *[a] A } """ - self.assertEqual(self.pretty_ftl(input), dedent_ftl(input)) + assert self.pretty_ftl(input) == dedent_ftl(input) def test_selector_number_expression(self): input = """\ @@ -334,7 +334,7 @@ def test_selector_number_expression(self): *[a] A } """ - self.assertEqual(self.pretty_ftl(input), dedent_ftl(input)) + assert self.pretty_ftl(input) == dedent_ftl(input) def test_selector_string_expression(self): input = """\ @@ -343,7 +343,7 @@ def test_selector_string_expression(self): *[a] A } """ - self.assertEqual(self.pretty_ftl(input), dedent_ftl(input)) + assert self.pretty_ftl(input) == dedent_ftl(input) def test_selector_attribute_expression(self): input = """\ @@ -352,100 +352,100 @@ def test_selector_attribute_expression(self): *[a] A } """ - self.assertEqual(self.pretty_ftl(input), dedent_ftl(input)) + assert self.pretty_ftl(input) == dedent_ftl(input) def test_call_expression(self): input = """\ foo = { FOO() } """ - self.assertEqual(self.pretty_ftl(input), dedent_ftl(input)) + assert self.pretty_ftl(input) == dedent_ftl(input) def test_call_expression_with_string_expression(self): input = """\ foo = { FOO("bar") } """ - self.assertEqual(self.pretty_ftl(input), dedent_ftl(input)) + assert self.pretty_ftl(input) == dedent_ftl(input) def test_call_expression_with_number_expression(self): input = """\ foo = { FOO(1) } """ - self.assertEqual(self.pretty_ftl(input), dedent_ftl(input)) + assert self.pretty_ftl(input) == dedent_ftl(input) def test_call_expression_with_message_reference(self): input = """\ foo = { FOO(bar) } """ - self.assertEqual(self.pretty_ftl(input), dedent_ftl(input)) + assert self.pretty_ftl(input) == dedent_ftl(input) def test_call_expression_with_variable_reference(self): input = """\ foo = { FOO($bar) } """ - self.assertEqual(self.pretty_ftl(input), dedent_ftl(input)) + assert self.pretty_ftl(input) == dedent_ftl(input) def test_call_expression_with_two_positional_arguments(self): input = """\ foo = { FOO(bar, baz) } """ - self.assertEqual(self.pretty_ftl(input), dedent_ftl(input)) + assert self.pretty_ftl(input) == dedent_ftl(input) def test_call_expression_with_named_argument_number(self): input = """\ foo = { FOO(bar: 1) } """ - self.assertEqual(self.pretty_ftl(input), dedent_ftl(input)) + assert self.pretty_ftl(input) == dedent_ftl(input) def test_call_expression_with_named_argument_string(self): input = """\ foo = { FOO(bar: "bar") } """ - self.assertEqual(self.pretty_ftl(input), dedent_ftl(input)) + assert self.pretty_ftl(input) == dedent_ftl(input) def test_call_expression_with_two_named_arguments(self): input = """\ foo = { FOO(bar: "bar", baz: "baz") } """ - self.assertEqual(self.pretty_ftl(input), dedent_ftl(input)) + assert self.pretty_ftl(input) == dedent_ftl(input) def test_call_expression_with_positional_and_named_arguments(self): input = """\ foo = { FOO(bar, 1, baz: "baz") } """ - self.assertEqual(self.pretty_ftl(input), dedent_ftl(input)) + assert self.pretty_ftl(input) == dedent_ftl(input) def test_macro_call(self): input = """\ foo = { -term() } """ - self.assertEqual(self.pretty_ftl(input), dedent_ftl(input)) + assert self.pretty_ftl(input) == dedent_ftl(input) def test_nested_placeables(self): input = """\ foo = {{ FOO() }} """ - self.assertEqual(self.pretty_ftl(input), dedent_ftl(input)) + assert self.pretty_ftl(input) == dedent_ftl(input) def test_backslash_in_text(self): input = """\ foo = \\{ placeable } """ - self.assertEqual(self.pretty_ftl(input), dedent_ftl(input)) + assert self.pretty_ftl(input) == dedent_ftl(input) def test_escaped_special_in_string_literal(self): input = """\ foo = { "Escaped \\" quote" } """ - self.assertEqual(self.pretty_ftl(input), dedent_ftl(input)) + assert self.pretty_ftl(input) == dedent_ftl(input) def test_escaped_unicode_sequence(self): input = """\ foo = { "\\u0065" } """ - self.assertEqual(self.pretty_ftl(input), dedent_ftl(input)) + assert self.pretty_ftl(input) == dedent_ftl(input) -class TestSerializeExpression(unittest.TestCase): +class TestSerializeExpression: @staticmethod def pretty_expr(text): parser = FluentParser() @@ -454,47 +454,47 @@ def pretty_expr(text): return serialize_expression(expr) def test_invalid_expression(self): - with self.assertRaisesRegex(Exception, "Unknown expression type"): + with pytest.raises(Exception, match="Unknown expression type"): serialize_expression(None) - with self.assertRaisesRegex(Exception, "Unknown expression type"): + with pytest.raises(Exception, match="Unknown expression type"): serialize_expression(object()) def test_string_expression(self): input = """\ foo = { "str" } """ - self.assertEqual(self.pretty_expr(input), '"str"') + assert self.pretty_expr(input) == '"str"' def test_number_expression(self): input = """\ foo = { 3 } """ - self.assertEqual(self.pretty_expr(input), "3") + assert self.pretty_expr(input) == "3" def test_message_reference(self): input = """\ foo = { msg } """ - self.assertEqual(self.pretty_expr(input), "msg") + assert self.pretty_expr(input) == "msg" def test_variable_reference(self): input = """\ foo = { $ext } """ - self.assertEqual(self.pretty_expr(input), "$ext") + assert self.pretty_expr(input) == "$ext" def test_attribute_expression(self): input = """\ foo = { msg.attr } """ - self.assertEqual(self.pretty_expr(input), "msg.attr") + assert self.pretty_expr(input) == "msg.attr" def test_call_expression(self): input = """\ foo = { BUILTIN(3.14, kwarg: "value") } """ - self.assertEqual(self.pretty_expr(input), 'BUILTIN(3.14, kwarg: "value")') + assert self.pretty_expr(input) == 'BUILTIN(3.14, kwarg: "value")' def test_select_expression(self): input = """\ @@ -503,10 +503,10 @@ def test_select_expression(self): *[one] One } """ - self.assertEqual(self.pretty_expr(input), "$num ->\n *[one] One\n") + assert self.pretty_expr(input) == "$num ->\n *[one] One\n" -class TestSerializeVariantKey(unittest.TestCase): +class TestSerializeVariantKey: @staticmethod def pretty_variant_key(text, index): parser = FluentParser() @@ -515,10 +515,10 @@ def pretty_variant_key(text, index): return serialize_variant_key(variants[index].key) def test_invalid_expression(self): - with self.assertRaisesRegex(Exception, "Unknown variant key type"): + with pytest.raises(Exception, match="Unknown variant key type"): serialize_variant_key(None) - with self.assertRaisesRegex(Exception, "Unknown variant key type"): + with pytest.raises(Exception, match="Unknown variant key type"): serialize_variant_key(object()) def test_identifiers(self): @@ -528,8 +528,8 @@ def test_identifiers(self): *[other] Other } """ - self.assertEqual(self.pretty_variant_key(input, 0), "one") - self.assertEqual(self.pretty_variant_key(input, 1), "other") + assert self.pretty_variant_key(input, 0) == "one" + assert self.pretty_variant_key(input, 1) == "other" def test_number_literals(self): input = """\ @@ -540,7 +540,7 @@ def test_number_literals(self): [007] James } """ - self.assertEqual(self.pretty_variant_key(input, 0), "-123456789") - self.assertEqual(self.pretty_variant_key(input, 1), "0") - self.assertEqual(self.pretty_variant_key(input, 2), "3.14") - self.assertEqual(self.pretty_variant_key(input, 3), "007") + assert self.pretty_variant_key(input, 0) == "-123456789" + assert self.pretty_variant_key(input, 1) == "0" + assert self.pretty_variant_key(input, 2) == "3.14" + assert self.pretty_variant_key(input, 3) == "007" diff --git a/fluent.syntax/tests/syntax/test_stream.py b/fluent.syntax/tests/syntax/test_stream.py index d8bef4bb..dff2a040 100644 --- a/fluent.syntax/tests/syntax/test_stream.py +++ b/fluent.syntax/tests/syntax/test_stream.py @@ -1,98 +1,95 @@ -import unittest - from fluent.syntax.stream import ParserStream -class TestParserStream(unittest.TestCase): - +class TestParserStream: def test_next(self): ps = ParserStream("abcd") - self.assertEqual("a", ps.current_char) - self.assertEqual(0, ps.index) + assert "a" == ps.current_char + assert 0 == ps.index - self.assertEqual("b", ps.next()) - self.assertEqual("b", ps.current_char) - self.assertEqual(1, ps.index) + assert "b" == ps.next() + assert "b" == ps.current_char + assert 1 == ps.index - self.assertEqual("c", ps.next()) - self.assertEqual("c", ps.current_char) - self.assertEqual(2, ps.index) + assert "c" == ps.next() + assert "c" == ps.current_char + assert 2 == ps.index - self.assertEqual("d", ps.next()) - self.assertEqual("d", ps.current_char) - self.assertEqual(3, ps.index) + assert "d" == ps.next() + assert "d" == ps.current_char + assert 3 == ps.index - self.assertEqual(None, ps.next()) - self.assertEqual(None, ps.current_char) - self.assertEqual(4, ps.index) + assert ps.next() is None + assert ps.current_char is None + assert 4 == ps.index def test_peek(self): ps = ParserStream("abcd") - self.assertEqual("a", ps.current_peek) - self.assertEqual(0, ps.peek_offset) + assert "a" == ps.current_peek + assert 0 == ps.peek_offset - self.assertEqual("b", ps.peek()) - self.assertEqual("b", ps.current_peek) - self.assertEqual(1, ps.peek_offset) + assert "b" == ps.peek() + assert "b" == ps.current_peek + assert 1 == ps.peek_offset - self.assertEqual("c", ps.peek()) - self.assertEqual("c", ps.current_peek) - self.assertEqual(2, ps.peek_offset) + assert "c" == ps.peek() + assert "c" == ps.current_peek + assert 2 == ps.peek_offset - self.assertEqual("d", ps.peek()) - self.assertEqual("d", ps.current_peek) - self.assertEqual(3, ps.peek_offset) + assert "d" == ps.peek() + assert "d" == ps.current_peek + assert 3 == ps.peek_offset - self.assertEqual(None, ps.peek()) - self.assertEqual(None, ps.current_peek) - self.assertEqual(4, ps.peek_offset) + assert ps.peek() is None + assert ps.current_peek is None + assert 4 == ps.peek_offset def test_peek_and_next(self): ps = ParserStream("abcd") - self.assertEqual("b", ps.peek()) - self.assertEqual(1, ps.peek_offset) - self.assertEqual(0, ps.index) + assert "b" == ps.peek() + assert 1 == ps.peek_offset + assert 0 == ps.index - self.assertEqual("b", ps.next()) - self.assertEqual(0, ps.peek_offset) - self.assertEqual(1, ps.index) + assert "b" == ps.next() + assert 0 == ps.peek_offset + assert 1 == ps.index - self.assertEqual("c", ps.peek()) - self.assertEqual(1, ps.peek_offset) - self.assertEqual(1, ps.index) + assert "c" == ps.peek() + assert 1 == ps.peek_offset + assert 1 == ps.index - self.assertEqual("c", ps.next()) - self.assertEqual(0, ps.peek_offset) - self.assertEqual(2, ps.index) - self.assertEqual("c", ps.current_char) - self.assertEqual("c", ps.current_peek) + assert "c" == ps.next() + assert 0 == ps.peek_offset + assert 2 == ps.index + assert "c" == ps.current_char + assert "c" == ps.current_peek - self.assertEqual("d", ps.peek()) - self.assertEqual(1, ps.peek_offset) - self.assertEqual(2, ps.index) + assert "d" == ps.peek() + assert 1 == ps.peek_offset + assert 2 == ps.index - self.assertEqual("d", ps.next()) - self.assertEqual(0, ps.peek_offset) - self.assertEqual(3, ps.index) - self.assertEqual("d", ps.current_char) - self.assertEqual("d", ps.current_peek) + assert "d" == ps.next() + assert 0 == ps.peek_offset + assert 3 == ps.index + assert "d" == ps.current_char + assert "d" == ps.current_peek - self.assertEqual(None, ps.peek()) - self.assertEqual(1, ps.peek_offset) - self.assertEqual(3, ps.index) - self.assertEqual("d", ps.current_char) - self.assertEqual(None, ps.current_peek) + assert ps.peek() is None + assert 1 == ps.peek_offset + assert 3 == ps.index + assert "d" == ps.current_char + assert ps.current_peek is None - self.assertEqual(None, ps.peek()) - self.assertEqual(2, ps.peek_offset) - self.assertEqual(3, ps.index) + assert ps.peek() is None + assert 2 == ps.peek_offset + assert 3 == ps.index - self.assertEqual(None, ps.next()) - self.assertEqual(0, ps.peek_offset) - self.assertEqual(4, ps.index) + assert ps.next() is None + assert 0 == ps.peek_offset + assert 4 == ps.index def test_skip_to_peek(self): ps = ParserStream("abcd") @@ -102,24 +99,24 @@ def test_skip_to_peek(self): ps.skip_to_peek() - self.assertEqual("c", ps.current_char) - self.assertEqual("c", ps.current_peek) - self.assertEqual(0, ps.peek_offset) - self.assertEqual(2, ps.index) + assert "c" == ps.current_char + assert "c" == ps.current_peek + assert 0 == ps.peek_offset + assert 2 == ps.index ps.peek() - self.assertEqual("c", ps.current_char) - self.assertEqual("d", ps.current_peek) - self.assertEqual(1, ps.peek_offset) - self.assertEqual(2, ps.index) + assert "c" == ps.current_char + assert "d" == ps.current_peek + assert 1 == ps.peek_offset + assert 2 == ps.index ps.next() - self.assertEqual("d", ps.current_char) - self.assertEqual("d", ps.current_peek) - self.assertEqual(0, ps.peek_offset) - self.assertEqual(3, ps.index) + assert "d" == ps.current_char + assert "d" == ps.current_peek + assert 0 == ps.peek_offset + assert 3 == ps.index def test_reset_peek(self): ps = ParserStream("abcd") @@ -129,33 +126,33 @@ def test_reset_peek(self): ps.peek() ps.reset_peek() - self.assertEqual("b", ps.current_char) - self.assertEqual("b", ps.current_peek) - self.assertEqual(0, ps.peek_offset) - self.assertEqual(1, ps.index) + assert "b" == ps.current_char + assert "b" == ps.current_peek + assert 0 == ps.peek_offset + assert 1 == ps.index ps.peek() - self.assertEqual("b", ps.current_char) - self.assertEqual("c", ps.current_peek) - self.assertEqual(1, ps.peek_offset) - self.assertEqual(1, ps.index) + assert "b" == ps.current_char + assert "c" == ps.current_peek + assert 1 == ps.peek_offset + assert 1 == ps.index ps.peek() ps.peek() ps.peek() ps.reset_peek() - self.assertEqual("b", ps.current_char) - self.assertEqual("b", ps.current_peek) - self.assertEqual(0, ps.peek_offset) - self.assertEqual(1, ps.index) + assert "b" == ps.current_char + assert "b" == ps.current_peek + assert 0 == ps.peek_offset + assert 1 == ps.index - self.assertEqual("c", ps.peek()) - self.assertEqual("b", ps.current_char) - self.assertEqual("c", ps.current_peek) - self.assertEqual(1, ps.peek_offset) - self.assertEqual(1, ps.index) + assert "c" == ps.peek() + assert "b" == ps.current_char + assert "c" == ps.current_peek + assert 1 == ps.peek_offset + assert 1 == ps.index - self.assertEqual("d", ps.peek()) - self.assertEqual(None, ps.peek()) + assert "d" == ps.peek() + assert ps.peek() is None diff --git a/fluent.syntax/tests/syntax/test_structure.py b/fluent.syntax/tests/syntax/test_structure.py index 8d7254b3..2004ed7f 100644 --- a/fluent.syntax/tests/syntax/test_structure.py +++ b/fluent.syntax/tests/syntax/test_structure.py @@ -1,71 +1,53 @@ -import json -import os -import unittest +from json import loads +from os import listdir +from os.path import dirname, join, splitext + +import pytest from fluent.syntax import parse +fixtures_dir = join(dirname(__file__), "fixtures_structure") +fixture_names = [ + fp[0] for fn in listdir(fixtures_dir) if (fp := splitext(fn))[1] == ".ftl" +] + + +@pytest.mark.parametrize("name", fixture_names) +def test_structure_with_spans(name): + source, expected = read_fixtures(name) + ast = parse(source, with_spans=True) + assert ast.to_json() == expected + + +@pytest.mark.parametrize("name", fixture_names) +def test_structure_without_spans(name): + source, expected = read_fixtures(name) + ast = parse(source, with_spans=False) + assert ast.to_json() == without_spans(expected) + + +def read_fixtures(name: str): + ftl_path = join(fixtures_dir, name + ".ftl") + ast_path = join(fixtures_dir, name + ".json") -def read_file(path): - with open(path, "r", encoding="utf-8", newline="\n") as file: - text = file.read() - return text + with open(ftl_path, "r", encoding="utf-8", newline="\n") as file: + source = file.read() + with open(ast_path, "r", encoding="utf-8", newline="\n") as file: + expected = loads(file.read()) + + return source, expected def without_spans(expected): """ - Given an expected JSON fragment with span information, recursively replace all of the spans - with None. + Recursively replace all of the spans with None. """ if isinstance(expected, dict): - result = {} - for key, value in expected.items(): - if key == "span": - result[key] = None - else: - result[key] = without_spans(value) - - return result + return { + key: None if key == "span" else without_spans(value) + for key, value in expected.items() + } elif isinstance(expected, list): return [without_spans(item) for item in expected] else: - # We have been passed something which would not have span information in it return expected - - -fixtures = os.path.join(os.path.dirname(__file__), "fixtures_structure") - - -class TestStructureMeta(type): - def __new__(mcs, name, bases, attrs): - - def gen_test(file_name, with_spans): - def test(self): - ftl_path = os.path.join(fixtures, file_name + ".ftl") - ast_path = os.path.join(fixtures, file_name + ".json") - - source = read_file(ftl_path) - expected = json.loads(read_file(ast_path)) - - if not with_spans: - expected = without_spans(expected) - - ast = parse(source, with_spans=with_spans) - - self.assertEqual(ast.to_json(), expected) - - return test - - for f in os.listdir(fixtures): - file_name, ext = os.path.splitext(f) - - if ext != ".ftl": - continue - - attrs[f"test_{file_name}_with_spans"] = gen_test(file_name, with_spans=True) - attrs[f"test_{file_name}_without_spans"] = gen_test(file_name, with_spans=False) - - return type.__new__(mcs, name, bases, attrs) - - -class TestStructure(unittest.TestCase, metaclass=TestStructureMeta): - maxDiff = None diff --git a/fluent.syntax/tests/syntax/test_visitor.py b/fluent.syntax/tests/syntax/test_visitor.py index 1b3f4f16..b08dfb94 100644 --- a/fluent.syntax/tests/syntax/test_visitor.py +++ b/fluent.syntax/tests/syntax/test_visitor.py @@ -1,9 +1,9 @@ -import unittest from collections import defaultdict from fluent.syntax import ast, visitor from fluent.syntax.parser import FluentParser -from tests.syntax import dedent_ftl + +from . import dedent_ftl class MockVisitor(visitor.Visitor): @@ -19,55 +19,52 @@ def visit_Pattern(self, node): self.pattern_calls += 1 -class TestVisitor(unittest.TestCase): +class TestVisitor: def test_resource(self): resource = FluentParser().parse( dedent_ftl( """\ - one = Message - # Comment - two = Messages - three = Messages with - .an = Attribute - """ + one = Message + # Comment + two = Messages + three = Messages with + .an = Attribute + """ ) ) mv = MockVisitor() mv.visit(resource) - self.assertEqual(mv.pattern_calls, 4) - self.assertDictEqual( - mv.calls, - { - "Resource": 1, - "Comment": 1, - "Message": 3, - "Identifier": 4, - "Attribute": 1, - "Span": 10, - }, - ) - - -class TestTransformer(unittest.TestCase): - def test(self): + assert mv.pattern_calls == 4 + assert mv.calls == { + "Resource": 1, + "Comment": 1, + "Message": 3, + "Identifier": 4, + "Attribute": 1, + "Span": 10, + } + + +class TestTransformer: + def test_transformer(self): resource = FluentParser().parse( dedent_ftl( """\ - one = Message - two = Messages - three = Has a - .an = Message string in the Attribute - """ + one = Message + two = Messages + three = Has a + .an = Message string in the Attribute + """ ) ) prior_res_id = id(resource) prior_msg_id = id(resource.body[1].value) backup = resource.clone() transformed = ReplaceTransformer("Message", "Term").visit(resource) - self.assertEqual(prior_res_id, id(transformed)) - self.assertEqual(prior_msg_id, id(transformed.body[1].value)) - self.assertFalse(transformed.equals(backup)) - self.assertEqual(transformed.body[1].value.elements[0].value, "Terms") + assert prior_res_id == id(transformed) + assert prior_msg_id == id(transformed.body[1].value) + assert not transformed.equals(backup) + assert transformed.body[1].value.elements[0].value == "Terms" class WordCounter: diff --git a/pyproject.toml b/pyproject.toml index 8ff24004..7d9088c8 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -8,6 +8,7 @@ dependencies = [ "flake8 ~= 7.0", "isort ~= 5.0", "mypy ~= 1.0", + "pytest>=8.4.2", "types-babel", "types-pytz", ] diff --git a/uv.lock b/uv.lock index 44fcc13e..b7bcf2af 100644 --- a/uv.lock +++ b/uv.lock @@ -110,6 +110,18 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" }, ] +[[package]] +name = "exceptiongroup" +version = "1.3.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions", marker = "python_full_version < '3.13'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/50/79/66800aadf48771f6b62f7eb014e352e5d06856655206165d775e675a02c9/exceptiongroup-1.3.1.tar.gz", hash = "sha256:8b412432c6055b0b7d14c310000ae93352ed6754f70fa8f7c34141f91c4e3219", size = 30371, upload-time = "2025-11-21T23:01:54.787Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8a/0e/97c33bf5009bdbac74fd2beace167cab3f978feb69cc36f1ef79360d6c4e/exceptiongroup-1.3.1-py3-none-any.whl", hash = "sha256:a7a39a3bd276781e98394987d3a5701d0c4edffb633bb7a5144577f82c773598", size = 16740, upload-time = "2025-11-21T23:01:53.443Z" }, +] + [[package]] name = "flake8" version = "7.3.0" @@ -148,6 +160,30 @@ name = "fluent-syntax" version = "0.19.0" source = { editable = "fluent.syntax" } +[[package]] +name = "iniconfig" +version = "2.1.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.10'", +] +sdist = { url = "https://files.pythonhosted.org/packages/f2/97/ebf4da567aa6827c909642694d71c9fcf53e5b504f2d96afea02718862f3/iniconfig-2.1.0.tar.gz", hash = "sha256:3abbd2e30b36733fee78f9c7f7308f2d0050e88f0087fd25c2645f63c773e1c7", size = 4793, upload-time = "2025-03-19T20:09:59.721Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2c/e1/e6716421ea10d38022b952c159d5161ca1193197fb744506875fbb87ea7b/iniconfig-2.1.0-py3-none-any.whl", hash = "sha256:9deba5723312380e77435581c6bf4935c94cbfab9b1ed33ef8d238ea168eb760", size = 6050, upload-time = "2025-03-19T20:10:01.071Z" }, +] + +[[package]] +name = "iniconfig" +version = "2.3.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.10'", +] +sdist = { url = "https://files.pythonhosted.org/packages/72/34/14ca021ce8e5dfedc35312d08ba8bf51fdd999c576889fc2c24cb97f4f10/iniconfig-2.3.0.tar.gz", hash = "sha256:c76315c77db068650d49c5b56314774a7804df16fee4402c1f19d6d15d8c4730", size = 20503, upload-time = "2025-10-18T21:55:43.219Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/cb/b1/3846dd7f199d53cb17f49cba7e651e9ce294d8497c8c150530ed11865bb8/iniconfig-2.3.0-py3-none-any.whl", hash = "sha256:f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12", size = 7484, upload-time = "2025-10-18T21:55:41.639Z" }, +] + [[package]] name = "isort" version = "5.13.2" @@ -352,6 +388,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/cb/28/3bfe2fa5a7b9c46fe7e13c97bda14c895fb10fa2ebf1d0abb90e0cea7ee1/platformdirs-4.5.1-py3-none-any.whl", hash = "sha256:d03afa3963c806a9bed9d5125c8f4cb2fdaf74a55ab60e5d59b3fde758104d31", size = 18731, upload-time = "2025-12-05T13:52:56.823Z" }, ] +[[package]] +name = "pluggy" +version = "1.6.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f9/e2/3e91f31a7d2b083fe6ef3fa267035b518369d9511ffab804f839851d2779/pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3", size = 69412, upload-time = "2025-05-15T12:30:07.975Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746", size = 20538, upload-time = "2025-05-15T12:30:06.134Z" }, +] + [[package]] name = "pycodestyle" version = "2.14.0" @@ -370,6 +415,57 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/c2/2f/81d580a0fb83baeb066698975cb14a618bdbed7720678566f1b046a95fe8/pyflakes-3.4.0-py2.py3-none-any.whl", hash = "sha256:f742a7dbd0d9cb9ea41e9a24a918996e8170c799fa528688d40dd582c8265f4f", size = 63551, upload-time = "2025-06-20T18:45:26.937Z" }, ] +[[package]] +name = "pygments" +version = "2.19.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b0/77/a5b8c569bf593b0140bde72ea885a803b82086995367bf2037de0159d924/pygments-2.19.2.tar.gz", hash = "sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887", size = 4968631, upload-time = "2025-06-21T13:39:12.283Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c7/21/705964c7812476f378728bdf590ca4b771ec72385c533964653c68e86bdc/pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b", size = 1225217, upload-time = "2025-06-21T13:39:07.939Z" }, +] + +[[package]] +name = "pytest" +version = "8.4.2" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.10'", +] +dependencies = [ + { name = "colorama", marker = "python_full_version < '3.10' and sys_platform == 'win32'" }, + { name = "exceptiongroup", marker = "python_full_version < '3.10'" }, + { name = "iniconfig", version = "2.1.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, + { name = "packaging", marker = "python_full_version < '3.10'" }, + { name = "pluggy", marker = "python_full_version < '3.10'" }, + { name = "pygments", marker = "python_full_version < '3.10'" }, + { name = "tomli", marker = "python_full_version < '3.10'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a3/5c/00a0e072241553e1a7496d638deababa67c5058571567b92a7eaa258397c/pytest-8.4.2.tar.gz", hash = "sha256:86c0d0b93306b961d58d62a4db4879f27fe25513d4b969df351abdddb3c30e01", size = 1519618, upload-time = "2025-09-04T14:34:22.711Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a8/a4/20da314d277121d6534b3a980b29035dcd51e6744bd79075a6ce8fa4eb8d/pytest-8.4.2-py3-none-any.whl", hash = "sha256:872f880de3fc3a5bdc88a11b39c9710c3497a547cfa9320bc3c5e62fbf272e79", size = 365750, upload-time = "2025-09-04T14:34:20.226Z" }, +] + +[[package]] +name = "pytest" +version = "9.0.2" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.10'", +] +dependencies = [ + { name = "colorama", marker = "python_full_version >= '3.10' and sys_platform == 'win32'" }, + { name = "exceptiongroup", marker = "python_full_version == '3.10.*'" }, + { name = "iniconfig", version = "2.3.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, + { name = "packaging", marker = "python_full_version >= '3.10'" }, + { name = "pluggy", marker = "python_full_version >= '3.10'" }, + { name = "pygments", marker = "python_full_version >= '3.10'" }, + { name = "tomli", marker = "python_full_version == '3.10.*'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/d1/db/7ef3487e0fb0049ddb5ce41d3a49c235bf9ad299b6a25d5780a89f19230f/pytest-9.0.2.tar.gz", hash = "sha256:75186651a92bd89611d1d9fc20f0b4345fd827c41ccd5c299a868a05d70edf11", size = 1568901, upload-time = "2025-12-06T21:30:51.014Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3b/ab/b3226f0bd7cdcf710fbede2b3548584366da3b19b5021e74f5bde2a8fa3f/pytest-9.0.2-py3-none-any.whl", hash = "sha256:711ffd45bf766d5264d487b917733b453d917afd2b0ad65223959f59089f875b", size = 374801, upload-time = "2025-12-06T21:30:49.154Z" }, +] + [[package]] name = "python-fluent" version = "0" @@ -380,6 +476,8 @@ dependencies = [ { name = "fluent-syntax" }, { name = "isort" }, { name = "mypy" }, + { name = "pytest", version = "8.4.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, + { name = "pytest", version = "9.0.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, { name = "types-babel" }, { name = "types-pytz" }, ] @@ -391,6 +489,7 @@ requires-dist = [ { name = "fluent-syntax", editable = "fluent.syntax" }, { name = "isort", specifier = "~=5.0" }, { name = "mypy", specifier = "~=1.0" }, + { name = "pytest", specifier = ">=8.4.2" }, { name = "types-babel" }, { name = "types-pytz" }, ]