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