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