Skip to content
Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,10 @@ jobs:
- run: python -m flake8
- run: python -m mypy fluent.syntax/fluent fluent.runtime/fluent
test:
runs-on: ubuntu-latest
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [ubuntu-22.04, windows-2022]
python-version: [3.7, 3.8, 3.9, "3.10", 3.11, 3.12, pypy3.9, pypy3.10]
steps:
- uses: actions/checkout@v4
Expand Down
57 changes: 21 additions & 36 deletions fluent.runtime/tests/test_fallback.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,8 @@
import io
import os
import unittest
from unittest import mock
from .utils import patch_files

from fluent.runtime import FluentLocalization, FluentResourceLoader

ISFILE = os.path.isfile


class TestLocalization(unittest.TestCase):
def test_init(self):
Expand All @@ -15,21 +11,18 @@ def test_init(self):
)
self.assertTrue(callable(l10n.format_value))

@mock.patch("os.path.isfile")
@mock.patch("codecs.open")
def test_bundles(self, codecs_open, isfile):
data = {
"de/one.ftl": "one = in German",
"de/two.ftl": "two = in German",
"fr/two.ftl": "three = in French",
"en/one.ftl": "four = exists",
"en/two.ftl": "five = exists",
}
isfile.side_effect = lambda p: p in data or ISFILE(p)
codecs_open.side_effect = lambda p, _, __: io.StringIO(data[p])
@patch_files({
"de/one.ftl": "one = in German",
"de/two.ftl": "two = in German",
"fr/two.ftl": "three = in French",
"en/one.ftl": "four = exists",
"en/two.ftl": "five = exists",
})
def test_bundles(self):
l10n = FluentLocalization(
["de", "fr", "en"], ["one.ftl", "two.ftl"], FluentResourceLoader("{locale}")
)
# Curious
bundles_gen = l10n._bundles()
bundle_de = next(bundles_gen)
self.assertEqual(bundle_de.locales[0], "de")
Expand All @@ -49,38 +42,30 @@ def test_bundles(self, codecs_open, isfile):
self.assertEqual(l10n.format_value("five"), "exists")


@mock.patch("os.path.isfile")
@mock.patch("codecs.open")
class TestResourceLoader(unittest.TestCase):
def test_all_exist(self, codecs_open, isfile):
data = {
"en/one.ftl": "one = exists",
"en/two.ftl": "two = exists",
}
isfile.side_effect = lambda p: p in data
codecs_open.side_effect = lambda p, _, __: io.StringIO(data[p])
@patch_files({
"en/one.ftl": "one = exists",
"en/two.ftl": "two = exists",
})
def test_all_exist(self):
loader = FluentResourceLoader("{locale}")
resources_list = list(loader.resources("en", ["one.ftl", "two.ftl"]))
self.assertEqual(len(resources_list), 1)
resources = resources_list[0]
self.assertEqual(len(resources), 2)

def test_one_exists(self, codecs_open, isfile):
data = {
"en/two.ftl": "two = exists",
}
isfile.side_effect = lambda p: p in data
codecs_open.side_effect = lambda p, _, __: io.StringIO(data[p])
@patch_files({
"en/two.ftl": "two = exists",
})
def test_one_exists(self):
loader = FluentResourceLoader("{locale}")
resources_list = list(loader.resources("en", ["one.ftl", "two.ftl"]))
self.assertEqual(len(resources_list), 1)
resources = resources_list[0]
self.assertEqual(len(resources), 1)

def test_none_exist(self, codecs_open, isfile):
data = {}
isfile.side_effect = lambda p: p in data
codecs_open.side_effect = lambda p, _, __: io.StringIO(data[p])
@patch_files({})
def test_none_exist(self):
loader = FluentResourceLoader("{locale}")
resources_list = list(loader.resources("en", ["one.ftl", "two.ftl"]))
self.assertEqual(len(resources_list), 0)
39 changes: 39 additions & 0 deletions fluent.runtime/tests/test_utils.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import unittest
from .utils import patch_files
import os
import codecs


class TestFileSimulate(unittest.TestCase):
def test_basic(self):
@patch_files({
"the.txt": "The",
"en/one.txt": "One",
"en/two.txt": "Two"
})
def patch_me(a, b):
self.assertEqual(a, 10)
self.assertEqual(b, "b")
self.assertFileIs(os.path.basename(__file__), None)
self.assertFileIs("the.txt", "The")
self.assertFileIs("en/one.txt", "One")
self.assertFileIs("en\\one.txt", "One")
self.assertFileIs("en/two.txt", "Two")
self.assertFileIs("en\\two.txt", "Two")
self.assertFileIs("en/three.txt", None)
self.assertFileIs("en\\three.txt", None)
patch_me(10, "b")

def assertFileIs(self, filename, expect_contents):
"""
expect_contents is None: Expect file does not exist
expect_contents is a str: Expect file contents to match
"""
if expect_contents is None:
self.assertFalse(os.path.isfile(filename),
"Expected " + filename + " to not exist.")
else:
self.assertTrue(os.path.isfile(filename),
"Expected " + filename + " to exist.")
self.assertEqual(codecs.open(filename, "r", "utf-8").read(),
expect_contents)
53 changes: 53 additions & 0 deletions fluent.runtime/tests/utils.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,58 @@
"""Utilities for testing."""

import textwrap
from pathlib import PurePath
from unittest import mock
from io import StringIO
import functools


def dedent_ftl(text):
return textwrap.dedent(f"{text.rstrip()}\n")


# Unify path separator, default path separator on Windows is \ not /
# Supports only relative paths
# Needed in test_falllback.py because it uses dict + string compare to make a virtual file structure
def _normalize_path(path):
path = PurePath(path)
if path.is_absolute():
raise ValueError("Absolute paths are not supported in file simulation yet. ("
+ str(path) + ")")
if "." not in path.parts and ".." not in path.parts:
return "/".join(PurePath(path).parts)
else:
res_parts = []
length = len(path.parts)
i = 0
while i < length:
if path.parts[i] == ".":
i += 1
elif i < length - 1 and path.parts[i+1] == "..":
i += 2
else:
res_parts.append(path.parts[i])
i += 1
return "/".join(res_parts)


def patch_files(files: dict):
"""Decorate a function to simulate files ``files`` during the function.
The keys of ``files`` are file names and must use '/' for path separator.
The values are file contents. Directories or relative paths are not supported.
Example: ``{"en/one.txt": "One", "en/two.txt": "Two"}``
The implementation may be changed to match the mechanism used.
"""
if files is None:
files = {}

def then(func):
@mock.patch("os.path.isfile", side_effect=lambda p: _normalize_path(p) in files)
@mock.patch("codecs.open", side_effect=lambda p, _, __: StringIO(files[_normalize_path(p)]))
@functools.wraps(func) # Make ret look like func to later decorators
def ret(*args, **kwargs):
func(*args[:-2], **kwargs)
return ret
return then