1from __future__ import print_function, division, absolute_import
2
3from ufo2ft.filters import (
4    getFilterClass, BaseFilter, loadFilters, UFO2FT_FILTERS_KEY, logger)
5
6from fontTools.misc.py23 import SimpleNamespace
7from fontTools.misc.loggingTools import CapturingLogHandler
8
9import pytest
10from ..testSupport import _TempModule
11
12
13class FooBarFilter(BaseFilter):
14    """A filter that does nothing."""
15
16    _args = ("a", "b")
17    _kwargs = {"c": 0}
18
19    def filter(self, glyph):
20        return False
21
22
23@pytest.fixture(scope="module", autouse=True)
24def fooBar():
25    """Make a temporary 'ufo2ft.filters.fooBar' module containing a
26    'FooBarFilter' class for testing the filter loading machinery.
27    """
28    with _TempModule("ufo2ft.filters.fooBar") as temp_module:
29        temp_module.module.__dict__["FooBarFilter"] = FooBarFilter
30        yield
31
32
33def test_getFilterClass():
34    assert getFilterClass("Foo Bar") == FooBarFilter
35    assert getFilterClass("FooBar") == FooBarFilter
36    assert getFilterClass("fooBar") == FooBarFilter
37    with pytest.raises(ImportError):
38        getFilterClass("Baz")
39
40    with _TempModule("myfilters"), \
41            _TempModule("myfilters.fooBar") as temp_module:
42
43        with pytest.raises(AttributeError):
44            # this fails because `myfilters.fooBar` module does not
45            # have a `FooBarFilter` class
46            getFilterClass("Foo Bar", pkg="myfilters")
47
48        temp_module.module.__dict__['FooBarFilter'] = FooBarFilter
49
50        # this will attempt to import the `FooBarFilter` class from the
51        # `myfilters.fooBar` module
52        assert getFilterClass("Foo Bar", pkg="myfilters") == FooBarFilter
53
54
55class MockFont(SimpleNamespace):
56    pass
57
58
59class MockGlyph(SimpleNamespace):
60    pass
61
62
63def test_loadFilters_empty():
64    ufo = MockFont(lib={})
65    assert UFO2FT_FILTERS_KEY not in ufo.lib
66    assert loadFilters(ufo) == ([], [])
67
68
69@pytest.fixture
70def ufo():
71    ufo = MockFont(lib={})
72    ufo.lib[UFO2FT_FILTERS_KEY] = [{
73        "name": "Foo Bar",
74        "args": ["foo", "bar"],
75    }]
76    return ufo
77
78
79def test_loadFilters_pre(ufo):
80    ufo.lib[UFO2FT_FILTERS_KEY][0]["pre"] = True
81    pre, post = loadFilters(ufo)
82    assert len(pre) == 1
83    assert not post
84    assert isinstance(pre[0], FooBarFilter)
85
86
87def test_loadFilters_custom_namespace(ufo):
88    ufo.lib[UFO2FT_FILTERS_KEY][0]["name"] = "Self Destruct"
89    ufo.lib[UFO2FT_FILTERS_KEY][0]["namespace"] = "my_dangerous_filters"
90
91    class SelfDestructFilter(FooBarFilter):
92        def filter(glyph):
93            # Don't try this at home!!! LOL :)
94            # shutil.rmtree(os.path.expanduser("~"))
95            return True
96
97    with _TempModule("my_dangerous_filters"), \
98            _TempModule("my_dangerous_filters.selfDestruct") as temp:
99        temp.module.__dict__["SelfDestructFilter"] = SelfDestructFilter
100
101        _, [filter_obj] = loadFilters(ufo)
102
103    assert isinstance(filter_obj, SelfDestructFilter)
104
105
106def test_loadFilters_args_missing(ufo):
107    del ufo.lib[UFO2FT_FILTERS_KEY][0]["args"]
108
109    with pytest.raises(TypeError) as exc_info:
110        loadFilters(ufo)
111
112    assert exc_info.match("missing")
113
114
115def test_loadFilters_args_unsupported(ufo):
116    ufo.lib[UFO2FT_FILTERS_KEY][0]["args"].append("baz")
117
118    with pytest.raises(TypeError) as exc_info:
119        loadFilters(ufo)
120
121    assert exc_info.match('unsupported')
122
123
124def test_loadFilters_include_all(ufo):
125    _, [filter_obj] = loadFilters(ufo)
126
127    assert filter_obj.include(MockGlyph(name="hello"))
128    assert filter_obj.include(MockGlyph(name="world"))
129
130
131def test_loadFilters_include_list(ufo):
132    ufo.lib[UFO2FT_FILTERS_KEY][0]["include"] = ["a", "b"]
133
134    _, [filter_obj] = loadFilters(ufo)
135
136    assert filter_obj.include(MockGlyph(name="a"))
137    assert filter_obj.include(MockGlyph(name="b"))
138    assert not filter_obj.include(MockGlyph(name="c"))
139
140
141def test_loadFilters_exclude_list(ufo):
142    ufo.lib[UFO2FT_FILTERS_KEY][0]["exclude"] = ["a", "b"]
143
144    _, [filter_obj] = loadFilters(ufo)
145
146    assert not filter_obj.include(MockGlyph(name="a"))
147    assert not filter_obj.include(MockGlyph(name="b"))
148    assert filter_obj.include(MockGlyph(name="c"))
149
150
151def test_loadFilters_both_include_exclude(ufo):
152    ufo.lib[UFO2FT_FILTERS_KEY][0]["include"] = ["a", "b"]
153    ufo.lib[UFO2FT_FILTERS_KEY][0]["exclude"] = ["c", "d"]
154
155    with pytest.raises(ValueError) as exc_info:
156        loadFilters(ufo)
157
158    assert exc_info.match("arguments are mutually exclusive")
159
160
161def test_loadFilters_failed(ufo):
162    ufo.lib[UFO2FT_FILTERS_KEY].append(dict(name="Non Existent"))
163
164    with CapturingLogHandler(logger, level="ERROR") as captor:
165        loadFilters(ufo)
166
167    captor.assertRegex("Failed to load filter")
168
169
170def test_loadFilters_kwargs_unsupported(ufo):
171    ufo.lib[UFO2FT_FILTERS_KEY][0]["kwargs"] = {}
172    ufo.lib[UFO2FT_FILTERS_KEY][0]["kwargs"]["c"] = 1
173    ufo.lib[UFO2FT_FILTERS_KEY][0]["kwargs"]["d"] = 2  # unknown
174
175    with pytest.raises(TypeError) as exc_info:
176        loadFilters(ufo)
177
178    assert exc_info.match("got an unsupported keyword")
179
180
181def test_BaseFilter_repr():
182    class NoArgFilter(BaseFilter):
183        pass
184
185    assert repr(NoArgFilter()) == "NoArgFilter()"
186
187    assert repr(FooBarFilter("a", "b", c=1)) == (
188        "FooBarFilter('a', 'b', c=1)")
189
190    assert repr(FooBarFilter("c", "d", include=["x", "y"])) == \
191        "FooBarFilter('c', 'd', c=0, include=['x', 'y'])"
192
193    assert repr(FooBarFilter("e", "f", c=2.0, exclude=("z",))) == \
194        "FooBarFilter('e', 'f', c=2.0, exclude=('z',))"
195
196    f = lambda g: False
197    assert repr(FooBarFilter("g", "h", include=f)) == \
198        "FooBarFilter('g', 'h', c=0, include={})".format(repr(f))
199
200
201if __name__ == "__main__":
202    sys.exit(pytest.main(sys.argv))
203