xref: /aosp_15_r20/external/fonttools/Tests/misc/py23_test.py (revision e1fe3e4ad2793916b15cccdc4a7da52a7e1dd0e9)
1from fontTools.misc.py23 import tobytes
2from fontTools.misc.textTools import deHexStr
3import filecmp
4from io import StringIO
5import tempfile
6from subprocess import check_call
7import sys
8import os
9import unittest
10
11from fontTools.misc.py23 import (
12    round2,
13    round3,
14    isclose,
15    redirect_stdout,
16    redirect_stderr,
17)
18
19
20PIPE_SCRIPT = """\
21import sys
22binary_stdin = open(sys.stdin.fileno(), mode='rb', closefd=False)
23binary_stdout = open(sys.stdout.fileno(), mode='wb', closefd=False)
24binary_stdout.write(binary_stdin.read())
25"""
26
27# the string contains a mix of line endings, plus the Win "EOF" charater (0x1A)
28# 'hello\rworld\r\n\x1a\r\n'
29TEST_BIN_DATA = deHexStr("68 65 6c 6c 6f 0d 77 6f 72 6c 64 0d 0a 1a 0d 0a")
30
31
32class OpenFuncWrapperTest(unittest.TestCase):
33    @staticmethod
34    def make_temp(data):
35        with tempfile.NamedTemporaryFile(delete=False) as f:
36            f.write(tobytes(data))
37        return f.name
38
39    def diff_piped(self, data, import_statement):
40        script = self.make_temp("\n".join([import_statement, PIPE_SCRIPT]))
41        datafile = self.make_temp(data)
42        try:
43            with open(datafile, "rb") as infile, tempfile.NamedTemporaryFile(
44                delete=False
45            ) as outfile:
46                env = dict(os.environ)
47                env["PYTHONPATH"] = os.pathsep.join(sys.path)
48                check_call(
49                    [sys.executable, script], stdin=infile, stdout=outfile, env=env
50                )
51            result = not filecmp.cmp(infile.name, outfile.name, shallow=False)
52        finally:
53            os.remove(script)
54            os.remove(datafile)
55            os.remove(outfile.name)
56        return result
57
58    def test_binary_pipe_py23_open_wrapper(self):
59        if self.diff_piped(TEST_BIN_DATA, "from fontTools.misc.py23 import open"):
60            self.fail("Input and output data differ!")
61
62    def test_binary_pipe_built_in_io_open(self):
63        if sys.version_info.major < 3 and sys.platform == "win32":
64            # On Windows Python 2.x, the piped input and output data are
65            # expected to be different when using io.open, because of issue
66            # https://bugs.python.org/issue10841.
67            expected = True
68        else:
69            expected = False
70        result = self.diff_piped(TEST_BIN_DATA, "from io import open")
71        self.assertEqual(result, expected)
72
73
74class Round2Test(unittest.TestCase):
75    """
76    Test cases taken from cpython 2.7 test suite:
77
78    https://github.com/python/cpython/blob/2.7/Lib/test/test_float.py#L748
79
80    Excludes the test cases that are not supported when using the `decimal`
81    module's `quantize` method.
82    """
83
84    def test_second_argument_type(self):
85        # floats should be illegal
86        self.assertRaises(TypeError, round2, 3.14159, 2.0)
87
88    def test_halfway_cases(self):
89        # Halfway cases need special attention, since the current
90        # implementation has to deal with them specially.  Note that
91        # 2.x rounds halfway values up (i.e., away from zero) while
92        # 3.x does round-half-to-even.
93        self.assertAlmostEqual(round2(0.125, 2), 0.13)
94        self.assertAlmostEqual(round2(0.375, 2), 0.38)
95        self.assertAlmostEqual(round2(0.625, 2), 0.63)
96        self.assertAlmostEqual(round2(0.875, 2), 0.88)
97        self.assertAlmostEqual(round2(-0.125, 2), -0.13)
98        self.assertAlmostEqual(round2(-0.375, 2), -0.38)
99        self.assertAlmostEqual(round2(-0.625, 2), -0.63)
100        self.assertAlmostEqual(round2(-0.875, 2), -0.88)
101
102        self.assertAlmostEqual(round2(0.25, 1), 0.3)
103        self.assertAlmostEqual(round2(0.75, 1), 0.8)
104        self.assertAlmostEqual(round2(-0.25, 1), -0.3)
105        self.assertAlmostEqual(round2(-0.75, 1), -0.8)
106
107        self.assertEqual(round2(-6.5, 0), -7.0)
108        self.assertEqual(round2(-5.5, 0), -6.0)
109        self.assertEqual(round2(-1.5, 0), -2.0)
110        self.assertEqual(round2(-0.5, 0), -1.0)
111        self.assertEqual(round2(0.5, 0), 1.0)
112        self.assertEqual(round2(1.5, 0), 2.0)
113        self.assertEqual(round2(2.5, 0), 3.0)
114        self.assertEqual(round2(3.5, 0), 4.0)
115        self.assertEqual(round2(4.5, 0), 5.0)
116        self.assertEqual(round2(5.5, 0), 6.0)
117        self.assertEqual(round2(6.5, 0), 7.0)
118
119        # same but without an explicit second argument; in 3.x these
120        # will give integers
121        self.assertEqual(round2(-6.5), -7.0)
122        self.assertEqual(round2(-5.5), -6.0)
123        self.assertEqual(round2(-1.5), -2.0)
124        self.assertEqual(round2(-0.5), -1.0)
125        self.assertEqual(round2(0.5), 1.0)
126        self.assertEqual(round2(1.5), 2.0)
127        self.assertEqual(round2(2.5), 3.0)
128        self.assertEqual(round2(3.5), 4.0)
129        self.assertEqual(round2(4.5), 5.0)
130        self.assertEqual(round2(5.5), 6.0)
131        self.assertEqual(round2(6.5), 7.0)
132
133        self.assertEqual(round2(-25.0, -1), -30.0)
134        self.assertEqual(round2(-15.0, -1), -20.0)
135        self.assertEqual(round2(-5.0, -1), -10.0)
136        self.assertEqual(round2(5.0, -1), 10.0)
137        self.assertEqual(round2(15.0, -1), 20.0)
138        self.assertEqual(round2(25.0, -1), 30.0)
139        self.assertEqual(round2(35.0, -1), 40.0)
140        self.assertEqual(round2(45.0, -1), 50.0)
141        self.assertEqual(round2(55.0, -1), 60.0)
142        self.assertEqual(round2(65.0, -1), 70.0)
143        self.assertEqual(round2(75.0, -1), 80.0)
144        self.assertEqual(round2(85.0, -1), 90.0)
145        self.assertEqual(round2(95.0, -1), 100.0)
146        self.assertEqual(round2(12325.0, -1), 12330.0)
147        self.assertEqual(round2(0, -1), 0.0)
148
149        self.assertEqual(round2(350.0, -2), 400.0)
150        self.assertEqual(round2(450.0, -2), 500.0)
151
152        self.assertAlmostEqual(round2(0.5e21, -21), 1e21)
153        self.assertAlmostEqual(round2(1.5e21, -21), 2e21)
154        self.assertAlmostEqual(round2(2.5e21, -21), 3e21)
155        self.assertAlmostEqual(round2(5.5e21, -21), 6e21)
156        self.assertAlmostEqual(round2(8.5e21, -21), 9e21)
157
158        self.assertAlmostEqual(round2(-1.5e22, -22), -2e22)
159        self.assertAlmostEqual(round2(-0.5e22, -22), -1e22)
160        self.assertAlmostEqual(round2(0.5e22, -22), 1e22)
161        self.assertAlmostEqual(round2(1.5e22, -22), 2e22)
162
163
164class Round3Test(unittest.TestCase):
165    """Same as above but results adapted for Python 3 round()"""
166
167    def test_second_argument_type(self):
168        # floats should be illegal
169        self.assertRaises(TypeError, round3, 3.14159, 2.0)
170
171        # None should be allowed
172        self.assertEqual(round3(1.0, None), 1)
173        # the following would raise an error with the built-in Python3.5 round:
174        # TypeError: 'NoneType' object cannot be interpreted as an integer
175        self.assertEqual(round3(1, None), 1)
176
177    def test_halfway_cases(self):
178        self.assertAlmostEqual(round3(0.125, 2), 0.12)
179        self.assertAlmostEqual(round3(0.375, 2), 0.38)
180        self.assertAlmostEqual(round3(0.625, 2), 0.62)
181        self.assertAlmostEqual(round3(0.875, 2), 0.88)
182        self.assertAlmostEqual(round3(-0.125, 2), -0.12)
183        self.assertAlmostEqual(round3(-0.375, 2), -0.38)
184        self.assertAlmostEqual(round3(-0.625, 2), -0.62)
185        self.assertAlmostEqual(round3(-0.875, 2), -0.88)
186
187        self.assertAlmostEqual(round3(0.25, 1), 0.2)
188        self.assertAlmostEqual(round3(0.75, 1), 0.8)
189        self.assertAlmostEqual(round3(-0.25, 1), -0.2)
190        self.assertAlmostEqual(round3(-0.75, 1), -0.8)
191
192        self.assertEqual(round3(-6.5, 0), -6.0)
193        self.assertEqual(round3(-5.5, 0), -6.0)
194        self.assertEqual(round3(-1.5, 0), -2.0)
195        self.assertEqual(round3(-0.5, 0), 0.0)
196        self.assertEqual(round3(0.5, 0), 0.0)
197        self.assertEqual(round3(1.5, 0), 2.0)
198        self.assertEqual(round3(2.5, 0), 2.0)
199        self.assertEqual(round3(3.5, 0), 4.0)
200        self.assertEqual(round3(4.5, 0), 4.0)
201        self.assertEqual(round3(5.5, 0), 6.0)
202        self.assertEqual(round3(6.5, 0), 6.0)
203
204        # same but without an explicit second argument; in 2.x these
205        # will give floats
206        self.assertEqual(round3(-6.5), -6)
207        self.assertEqual(round3(-5.5), -6)
208        self.assertEqual(round3(-1.5), -2.0)
209        self.assertEqual(round3(-0.5), 0)
210        self.assertEqual(round3(0.5), 0)
211        self.assertEqual(round3(1.5), 2)
212        self.assertEqual(round3(2.5), 2)
213        self.assertEqual(round3(3.5), 4)
214        self.assertEqual(round3(4.5), 4)
215        self.assertEqual(round3(5.5), 6)
216        self.assertEqual(round3(6.5), 6)
217
218        # no ndigits and input is already an integer: output == input
219        rv = round3(1)
220        self.assertEqual(rv, 1)
221        self.assertTrue(isinstance(rv, int))
222        rv = round3(1.0)
223        self.assertEqual(rv, 1)
224        self.assertTrue(isinstance(rv, int))
225
226        self.assertEqual(round3(-25.0, -1), -20.0)
227        self.assertEqual(round3(-15.0, -1), -20.0)
228        self.assertEqual(round3(-5.0, -1), 0.0)
229        self.assertEqual(round3(5.0, -1), 0.0)
230        self.assertEqual(round3(15.0, -1), 20.0)
231        self.assertEqual(round3(25.0, -1), 20.0)
232        self.assertEqual(round3(35.0, -1), 40.0)
233        self.assertEqual(round3(45.0, -1), 40.0)
234        self.assertEqual(round3(55.0, -1), 60.0)
235        self.assertEqual(round3(65.0, -1), 60.0)
236        self.assertEqual(round3(75.0, -1), 80.0)
237        self.assertEqual(round3(85.0, -1), 80.0)
238        self.assertEqual(round3(95.0, -1), 100.0)
239        self.assertEqual(round3(12325.0, -1), 12320.0)
240        self.assertEqual(round3(0, -1), 0.0)
241
242        self.assertEqual(round3(350.0, -2), 400.0)
243        self.assertEqual(round3(450.0, -2), 400.0)
244
245        self.assertAlmostEqual(round3(0.5e21, -21), 0.0)
246        self.assertAlmostEqual(round3(1.5e21, -21), 2e21)
247        self.assertAlmostEqual(round3(2.5e21, -21), 2e21)
248        self.assertAlmostEqual(round3(5.5e21, -21), 6e21)
249        self.assertAlmostEqual(round3(8.5e21, -21), 8e21)
250
251        self.assertAlmostEqual(round3(-1.5e22, -22), -2e22)
252        self.assertAlmostEqual(round3(-0.5e22, -22), 0.0)
253        self.assertAlmostEqual(round3(0.5e22, -22), 0.0)
254        self.assertAlmostEqual(round3(1.5e22, -22), 2e22)
255
256
257NAN = float("nan")
258INF = float("inf")
259NINF = float("-inf")
260
261
262class IsCloseTests(unittest.TestCase):
263    """
264    Tests taken from Python 3.5 test_math.py:
265    https://hg.python.org/cpython/file/v3.5.2/Lib/test/test_math.py
266    """
267
268    isclose = staticmethod(isclose)
269
270    def assertIsClose(self, a, b, *args, **kwargs):
271        self.assertTrue(
272            self.isclose(a, b, *args, **kwargs),
273            msg="%s and %s should be close!" % (a, b),
274        )
275
276    def assertIsNotClose(self, a, b, *args, **kwargs):
277        self.assertFalse(
278            self.isclose(a, b, *args, **kwargs),
279            msg="%s and %s should not be close!" % (a, b),
280        )
281
282    def assertAllClose(self, examples, *args, **kwargs):
283        for a, b in examples:
284            self.assertIsClose(a, b, *args, **kwargs)
285
286    def assertAllNotClose(self, examples, *args, **kwargs):
287        for a, b in examples:
288            self.assertIsNotClose(a, b, *args, **kwargs)
289
290    def test_negative_tolerances(self):
291        # ValueError should be raised if either tolerance is less than zero
292        with self.assertRaises(ValueError):
293            self.assertIsClose(1, 1, rel_tol=-1e-100)
294        with self.assertRaises(ValueError):
295            self.assertIsClose(1, 1, rel_tol=1e-100, abs_tol=-1e10)
296
297    def test_identical(self):
298        # identical values must test as close
299        identical_examples = [
300            (2.0, 2.0),
301            (0.1e200, 0.1e200),
302            (1.123e-300, 1.123e-300),
303            (12345, 12345.0),
304            (0.0, -0.0),
305            (345678, 345678),
306        ]
307        self.assertAllClose(identical_examples, rel_tol=0.0, abs_tol=0.0)
308
309    def test_eight_decimal_places(self):
310        # examples that are close to 1e-8, but not 1e-9
311        eight_decimal_places_examples = [
312            (1e8, 1e8 + 1),
313            (-1e-8, -1.000000009e-8),
314            (1.12345678, 1.12345679),
315        ]
316        self.assertAllClose(eight_decimal_places_examples, rel_tol=1e-8)
317        self.assertAllNotClose(eight_decimal_places_examples, rel_tol=1e-9)
318
319    def test_near_zero(self):
320        # values close to zero
321        near_zero_examples = [(1e-9, 0.0), (-1e-9, 0.0), (-1e-150, 0.0)]
322        # these should not be close to any rel_tol
323        self.assertAllNotClose(near_zero_examples, rel_tol=0.9)
324        # these should be close to abs_tol=1e-8
325        self.assertAllClose(near_zero_examples, abs_tol=1e-8)
326
327    def test_identical_infinite(self):
328        # these are close regardless of tolerance -- i.e. they are equal
329        self.assertIsClose(INF, INF)
330        self.assertIsClose(INF, INF, abs_tol=0.0)
331        self.assertIsClose(NINF, NINF)
332        self.assertIsClose(NINF, NINF, abs_tol=0.0)
333
334    def test_inf_ninf_nan(self):
335        # these should never be close (following IEEE 754 rules for equality)
336        not_close_examples = [
337            (NAN, NAN),
338            (NAN, 1e-100),
339            (1e-100, NAN),
340            (INF, NAN),
341            (NAN, INF),
342            (INF, NINF),
343            (INF, 1.0),
344            (1.0, INF),
345            (INF, 1e308),
346            (1e308, INF),
347        ]
348        # use largest reasonable tolerance
349        self.assertAllNotClose(not_close_examples, abs_tol=0.999999999999999)
350
351    def test_zero_tolerance(self):
352        # test with zero tolerance
353        zero_tolerance_close_examples = [(1.0, 1.0), (-3.4, -3.4), (-1e-300, -1e-300)]
354        self.assertAllClose(zero_tolerance_close_examples, rel_tol=0.0)
355
356        zero_tolerance_not_close_examples = [
357            (1.0, 1.000000000000001),
358            (0.99999999999999, 1.0),
359            (1.0e200, 0.999999999999999e200),
360        ]
361        self.assertAllNotClose(zero_tolerance_not_close_examples, rel_tol=0.0)
362
363    def test_assymetry(self):
364        # test the assymetry example from PEP 485
365        self.assertAllClose([(9, 10), (10, 9)], rel_tol=0.1)
366
367    def test_integers(self):
368        # test with integer values
369        integer_examples = [(100000001, 100000000), (123456789, 123456788)]
370
371        self.assertAllClose(integer_examples, rel_tol=1e-8)
372        self.assertAllNotClose(integer_examples, rel_tol=1e-9)
373
374    def test_decimals(self):
375        # test with Decimal values
376        from decimal import Decimal
377
378        decimal_examples = [
379            (Decimal("1.00000001"), Decimal("1.0")),
380            (Decimal("1.00000001e-20"), Decimal("1.0e-20")),
381            (Decimal("1.00000001e-100"), Decimal("1.0e-100")),
382        ]
383        self.assertAllClose(decimal_examples, rel_tol=1e-8)
384        self.assertAllNotClose(decimal_examples, rel_tol=1e-9)
385
386    def test_fractions(self):
387        # test with Fraction values
388        from fractions import Fraction
389
390        # could use some more examples here!
391        fraction_examples = [(Fraction(1, 100000000) + 1, Fraction(1))]
392        self.assertAllClose(fraction_examples, rel_tol=1e-8)
393        self.assertAllNotClose(fraction_examples, rel_tol=1e-9)
394
395
396class TestRedirectStream:
397    redirect_stream = None
398    orig_stream = None
399
400    def test_no_redirect_in_init(self):
401        orig_stdout = getattr(sys, self.orig_stream)
402        self.redirect_stream(None)
403        self.assertIs(getattr(sys, self.orig_stream), orig_stdout)
404
405    def test_redirect_to_string_io(self):
406        f = StringIO()
407        msg = "Consider an API like help(), which prints directly to stdout"
408        orig_stdout = getattr(sys, self.orig_stream)
409        with self.redirect_stream(f):
410            print(msg, file=getattr(sys, self.orig_stream))
411        self.assertIs(getattr(sys, self.orig_stream), orig_stdout)
412        s = f.getvalue().strip()
413        self.assertEqual(s, msg)
414
415    def test_enter_result_is_target(self):
416        f = StringIO()
417        with self.redirect_stream(f) as enter_result:
418            self.assertIs(enter_result, f)
419
420    def test_cm_is_reusable(self):
421        f = StringIO()
422        write_to_f = self.redirect_stream(f)
423        orig_stdout = getattr(sys, self.orig_stream)
424        with write_to_f:
425            print("Hello", end=" ", file=getattr(sys, self.orig_stream))
426        with write_to_f:
427            print("World!", file=getattr(sys, self.orig_stream))
428        self.assertIs(getattr(sys, self.orig_stream), orig_stdout)
429        s = f.getvalue()
430        self.assertEqual(s, "Hello World!\n")
431
432    def test_cm_is_reentrant(self):
433        f = StringIO()
434        write_to_f = self.redirect_stream(f)
435        orig_stdout = getattr(sys, self.orig_stream)
436        with write_to_f:
437            print("Hello", end=" ", file=getattr(sys, self.orig_stream))
438            with write_to_f:
439                print("World!", file=getattr(sys, self.orig_stream))
440        self.assertIs(getattr(sys, self.orig_stream), orig_stdout)
441        s = f.getvalue()
442        self.assertEqual(s, "Hello World!\n")
443
444
445class TestRedirectStdout(TestRedirectStream, unittest.TestCase):
446    redirect_stream = redirect_stdout
447    orig_stream = "stdout"
448
449
450class TestRedirectStderr(TestRedirectStream, unittest.TestCase):
451    redirect_stream = redirect_stderr
452    orig_stream = "stderr"
453
454
455if __name__ == "__main__":
456    sys.exit(unittest.main())
457