Skip to content

Commit

Permalink
test: Fix test failures & add Windows CI tests (#206)
Browse files Browse the repository at this point in the history
  • Loading branch information
studyingegret authored Feb 7, 2025
1 parent df5ef40 commit fbf8b3f
Show file tree
Hide file tree
Showing 4 changed files with 110 additions and 37 deletions.
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)
42 changes: 42 additions & 0 deletions fluent.runtime/tests/test_utils.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
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)

with self.assertRaises(ValueError):
os.path.isfile("en/")
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 to exist and contents to match
"""
if expect_contents is None:
self.assertFalse(os.path.isfile(filename),
f"Expected {filename} to not exist.")
else:
self.assertTrue(os.path.isfile(filename),
f"Expected {filename} to exist.")
with codecs.open(filename, "r", "utf-8") as f:
self.assertEqual(f.read(), expect_contents)
45 changes: 45 additions & 0 deletions fluent.runtime/tests/utils.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,50 @@
"""Utilities for testing."""

import textwrap
from pathlib import PureWindowsPath, PurePosixPath
from unittest import mock
from io import StringIO
import functools


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


# Needed in test_falllback.py because it uses dict + string compare to make a virtual file structure
def _normalize_file_path(path):
"""Note: Does not support absolute paths or paths that
contain '.' or '..' parts."""
# Cannot use os.path or PurePath, because they only recognize
# one kind of path separator
if PureWindowsPath(path).is_absolute() or PurePosixPath(path).is_absolute():
raise ValueError(f"Unsupported path: {path}")
parts = path.replace("\\", "/").split("/")
if "." in parts or ".." in parts:
raise ValueError(f"Unsupported path: {path}")
if parts and parts[-1] == "":
# path ends with a trailing pathsep
raise ValueError(f"Path appears to be a directory, not a file: {path}")
return "/".join(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.
"""

# Here it is possible to validate file names, but skipped

def then(func):
@mock.patch("os.path.isfile", side_effect=lambda p: _normalize_file_path(p) in files)
@mock.patch("codecs.open", side_effect=lambda p, _, __: StringIO(files[_normalize_file_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

0 comments on commit fbf8b3f

Please sign in to comment.