mathandy / svgpathtools

A collection of tools for manipulating and analyzing SVG Path objects and Bezier curves.
MIT License
557 stars 142 forks source link

Test suite fails depending on CPU (micro)architecture #183

Open mia-0 opened 2 years ago

mia-0 commented 2 years ago

The test suite fails on all non-x86_64 architectures and some x86_64 CPUs. I ran into this problem while trying to package it for openSUSE. Here’s a log excerpt of a failed aarch64 build:

[   27s] ============================= test session starts ==============================
[   27s] platform linux -- Python 3.8.13, pytest-7.1.1, pluggy-1.0.0 -- /usr/bin/python3.8
[   27s] cachedir: .pytest_cache
[   27s] rootdir: /home/abuild/rpmbuild/BUILD/svgpathtools-1.5.1
[   28s] collecting ... collected 90 items
[   28s] 
[   28s] test/test_bezier.py::TestBezier2Polynomial::test_bezier2polynomial PASSED [  1%]
[   28s] test/test_bezier.py::TestPolynomial2Bezier::test_polynomial2bezier PASSED [  2%]
[   28s] test/test_document.py::TestDocument::test_from_file_object PASSED        [  3%]
[   28s] test/test_document.py::TestDocument::test_from_file_path PASSED          [  4%]
[   28s] test/test_document.py::TestDocument::test_from_file_path_string PASSED   [  5%]
[   28s] test/test_document.py::TestDocument::test_from_string PASSED             [  6%]
[   28s] test/test_document.py::TestDocument::test_from_stringio PASSED           [  7%]
[   28s] test/test_generation.py::TestGeneration::test_normalizing PASSED         [  8%]
[   28s] test/test_generation.py::TestGeneration::test_path_parsing FAILED        [ 10%]
[   28s] test/test_groups.py::TestGroups::test_add_group PASSED                   [ 11%]
[   28s] test/test_groups.py::TestGroups::test_group_flatten PASSED               [ 12%]
[   28s] test/test_groups.py::TestGroups::test_nested_group PASSED                [ 13%]
[   28s] test/test_parsing.py::TestParser::test_errors PASSED                     [ 14%]
[   28s] test/test_parsing.py::TestParser::test_issue_99 PASSED                   [ 15%]
[   28s] test/test_parsing.py::TestParser::test_negative PASSED                   [ 16%]
[   28s] test/test_parsing.py::TestParser::test_numbers PASSED                    [ 17%]
[   28s] test/test_parsing.py::TestParser::test_others PASSED                     [ 18%]
[   28s] test/test_parsing.py::TestParser::test_pathd_init PASSED                 [ 20%]
[   28s] test/test_parsing.py::TestParser::test_svg_examples PASSED               [ 21%]
[   28s] test/test_parsing.py::TestParser::test_transform PASSED                  [ 22%]
[   28s] test/test_path.py::LineTest::test_equality PASSED                        [ 23%]
[   28s] test/test_path.py::LineTest::test_lines PASSED                           [ 24%]
[   29s] test/test_path.py::LineTest::test_point_to_t PASSED                      [ 25%]
[   29s] test/test_path.py::LineTest::test_radialrange PASSED                     [ 26%]
[   29s] test/test_path.py::CubicBezierTest::test_approx_circle PASSED            [ 27%]
[   29s] test/test_path.py::CubicBezierTest::test_equality PASSED                 [ 28%]
[   29s] test/test_path.py::CubicBezierTest::test_length PASSED                   [ 30%]
[   29s] test/test_path.py::CubicBezierTest::test_svg_examples PASSED             [ 31%]
[   29s] test/test_path.py::QuadraticBezierTest::test_equality PASSED             [ 32%]
[   29s] test/test_path.py::QuadraticBezierTest::test_length PASSED               [ 33%]
[   29s] test/test_path.py::QuadraticBezierTest::test_svg_examples PASSED         [ 34%]
[   30s] test/test_path.py::ArcTest::test_approx_cubic PASSED                     [ 35%]
[   30s] test/test_path.py::ArcTest::test_approx_quad PASSED                      [ 36%]
[   30s] test/test_path.py::ArcTest::test_equality PASSED                         [ 37%]
[   30s] test/test_path.py::ArcTest::test_length PASSED                           [ 38%]
[   30s] test/test_path.py::ArcTest::test_point PASSED                            [ 40%]
[   32s] test/test_path.py::ArcTest::test_point_to_t PASSED                       [ 41%]
[   32s] test/test_path.py::ArcTest::test_trusting_acos PASSED                    [ 42%]
[   32s] test/test_path.py::TestPath::test_circle PASSED                          [ 43%]
[   32s] test/test_path.py::TestPath::test_continuous_subpaths PASSED             [ 44%]
[   32s] test/test_path.py::TestPath::test_cropped PASSED                         [ 45%]
[   32s] test/test_path.py::TestPath::test_d PASSED                               [ 46%]
[   32s] test/test_path.py::TestPath::test_equality PASSED                        [ 47%]
[   32s] test/test_path.py::TestPath::test_hash FAILED                            [ 48%]
[   32s] test/test_path.py::TestPath::test_repr PASSED                            [ 50%]
[   32s] test/test_path.py::TestPath::test_svg_specs PASSED                       [ 51%]
[   32s] test/test_path.py::TestPath::test_transform_scale PASSED                 [ 52%]
[   33s] test/test_path.py::Test_ilength::test_ilength_arcs PASSED                [ 53%]
[   33s] test/test_path.py::Test_ilength::test_ilength_cubics PASSED              [ 54%]
[   33s] test/test_path.py::Test_ilength::test_ilength_exceptions PASSED          [ 55%]
[   33s] test/test_path.py::Test_ilength::test_ilength_lines PASSED               [ 56%]
[   34s] test/test_path.py::Test_ilength::test_ilength_paths PASSED               [ 57%]
[   34s] test/test_path.py::Test_ilength::test_ilength_quadratics PASSED          [ 58%]
[   34s] test/test_path.py::Test_intersect::test_arc_arc_0 PASSED                 [ 60%]
[   34s] test/test_path.py::Test_intersect::test_arc_arc_1 PASSED                 [ 61%]
[   34s] test/test_path.py::Test_intersect::test_arc_arc_2 PASSED                 [ 62%]
[   34s] test/test_path.py::Test_intersect::test_arc_arc_same_circle PASSED       [ 63%]
[   34s] test/test_path.py::Test_intersect::test_arc_arc_tangent_circles_inside PASSED [ 64%]
[   34s] test/test_path.py::Test_intersect::test_arc_arc_tangent_circles_outside PASSED [ 65%]
[   37s] test/test_path.py::Test_intersect::test_arc_line PASSED                  [ 66%]
[   38s] test/test_path.py::Test_intersect::test_intersect PASSED                 [ 67%]
[   38s] test/test_path.py::Test_intersect::test_intersect_arc_line_1 PASSED      [ 68%]
[   38s] test/test_path.py::Test_intersect::test_intersect_arc_line_2 PASSED      [ 70%]
[   38s] test/test_path.py::Test_intersect::test_intersect_arc_line_3 PASSED      [ 71%]
[   38s] test/test_path.py::Test_intersect::test_intersect_arc_line_disjoint_bboxes PASSED [ 72%]
[   38s] test/test_path.py::Test_intersect::test_line_line_0 PASSED               [ 73%]
[   38s] test/test_path.py::Test_intersect::test_line_line_1 PASSED               [ 74%]
[   38s] test/test_path.py::TestPathTools::test_bpoints2bezier PASSED             [ 75%]
[   38s] test/test_path.py::TestPathTools::test_closest_point_in_path PASSED      [ 76%]
[   38s] test/test_path.py::TestPathTools::test_farthest_point_in_path PASSED     [ 77%]
[   38s] test/test_path.py::TestPathTools::test_is_bezier_path PASSED             [ 78%]
[   38s] test/test_path.py::TestPathTools::test_is_bezier_segment PASSED          [ 80%]
[   38s] test/test_path.py::TestPathTools::test_is_contained_by PASSED            [ 81%]
[   38s] test/test_path.py::TestPathTools::test_path_area PASSED                  [ 82%]
[   38s] test/test_path.py::TestPathTools::test_path_encloses_pt PASSED           [ 83%]
[   38s] test/test_path.py::TestPathTools::test_polynomial2bezier PASSED          [ 84%]
[   38s] test/test_path.py::TestPathBugs::test_issue_113 PASSED                   [ 85%]
[   38s] test/test_path.py::TestPathBugs::test_issue_71 PASSED                    [ 86%]
[   38s] test/test_path.py::TestPathBugs::test_issue_94 PASSED                    [ 87%]
[   38s] test/test_path.py::TestPathBugs::test_issue_95 PASSED                    [ 88%]
[   38s] test/test_polytools.py::Test_polytools::test_rational_limit PASSED       [ 90%]
[   38s] test/test_sax_groups.py::TestSaxGroups::test_parse_display PASSED        [ 91%]
[   38s] test/test_svg2paths.py::TestSVG2Paths::test_from_file_object PASSED      [ 92%]
[   38s] test/test_svg2paths.py::TestSVG2Paths::test_from_file_path PASSED        [ 93%]
[   38s] test/test_svg2paths.py::TestSVG2Paths::test_from_file_path_string PASSED [ 94%]
[   38s] test/test_svg2paths.py::TestSVG2Paths::test_from_string PASSED           [ 95%]
[   38s] test/test_svg2paths.py::TestSVG2Paths::test_from_stringio PASSED         [ 96%]
[   38s] test/test_svg2paths.py::TestSVG2Paths::test_rect2pathd PASSED            [ 97%]
[   38s] test/test_svg2paths.py::TestSVG2Paths::test_svg2paths_ellipses PASSED    [ 98%]
[   38s] test/test_svg2paths.py::TestSVG2Paths::test_svg2paths_polygons PASSED    [100%]
[   38s] 
[   38s] =================================== FAILURES ===================================
[   38s] _______________________ TestGeneration.test_path_parsing _______________________
[   38s] 
[   38s] self = <test_generation.TestGeneration testMethod=test_path_parsing>
[   38s] 
[   38s]     def test_path_parsing(self):
[   38s]         """Examples from the SVG spec"""
[   38s]         paths = [
[   38s]             'M 100,100 L 300,100 L 200,300 Z',
[   38s]             'M 0,0 L 50,20 M 100,100 L 300,100 L 200,300 Z',
[   38s]             'M 100,100 L 200,200',
[   38s]             'M 100,200 L 200,100 L -100,-200',
[   38s]             'M 100,200 C 100,100 250,100 250,200 S 400,300 400,200',
[   38s]             'M 100,200 C 100,100 400,100 400,200',
[   38s]             'M 100,500 C 25,400 475,400 400,500',
[   38s]             'M 100,800 C 175,700 325,700 400,800',
[   38s]             'M 600,200 C 675,100 975,100 900,200',
[   38s]             'M 600,500 C 600,350 900,650 900,500',
[   38s]             'M 600,800 C 625,700 725,700 750,800 S 875,900 900,800',
[   38s]             'M 200,300 Q 400,50 600,300 T 1000,300',
[   38s]             'M -3.4E+38,3.4E+38 L -3.4E-38,3.4E-38',
[   38s]             'M 0,0 L 50,20 M 50,20 L 200,100 Z',
[   38s]             'M 600,350 L 650,325 A 25,25 -30 0,1 700,300 L 750,275',
[   38s]         ]
[   38s]         float_paths = [
[   38s]             'M 100.0,100.0 L 300.0,100.0 L 200.0,300.0 L 100.0,100.0',
[   38s]             'M 0.0,0.0 L 50.0,20.0 M 100.0,100.0 L 300.0,100.0 L 200.0,300.0 L 100.0,100.0',
[   38s]             'M 100.0,100.0 L 200.0,200.0',
[   38s]             'M 100.0,200.0 L 200.0,100.0 L -100.0,-200.0',
[   38s]             'M 100.0,200.0 C 100.0,100.0 250.0,100.0 250.0,200.0 C 250.0,300.0 400.0,300.0 400.0,200.0',
[   38s]             'M 100.0,200.0 C 100.0,100.0 400.0,100.0 400.0,200.0',
[   38s]             'M 100.0,500.0 C 25.0,400.0 475.0,400.0 400.0,500.0',
[   38s]             'M 100.0,800.0 C 175.0,700.0 325.0,700.0 400.0,800.0',
[   38s]             'M 600.0,200.0 C 675.0,100.0 975.0,100.0 900.0,200.0',
[   38s]             'M 600.0,500.0 C 600.0,350.0 900.0,650.0 900.0,500.0',
[   38s]             'M 600.0,800.0 C 625.0,700.0 725.0,700.0 750.0,800.0 C 775.0,900.0 875.0,900.0 900.0,800.0',
[   38s]             'M 200.0,300.0 Q 400.0,50.0 600.0,300.0 Q 800.0,550.0 1000.0,300.0',
[   38s]             'M -3.4e+38,3.4e+38 L -3.4e-38,3.4e-38',
[   38s]             'M 0.0,0.0 L 50.0,20.0 L 200.0,100.0 L 50.0,20.0',
[   38s]             ('M 600.0,350.0 L 650.0,325.0 A 27.9508497187,27.9508497187 -30.0 0,1 700.0,300.0 L 750.0,275.0',  # Python 2
[   38s]              'M 600.0,350.0 L 650.0,325.0 A 27.95084971874737,27.95084971874737 -30.0 0,1 700.0,300.0 L 750.0,275.0')  # Python 3
[   38s]         ]
[   38s]     
[   38s]         for path, flpath in zip(paths[::-1], float_paths[::-1]):
[   38s]             # Note:  Python 3 and Python 2 differ in the number of digits
[   38s]             # truncated when returning a string representation of a float
[   38s]             parsed_path = parse_path(path)
[   38s]             res = parsed_path.d()
[   38s]             if isinstance(flpath, tuple):
[   38s]                 option3 = res == flpath[1]  # Python 3
[   38s]                 flpath = flpath[0]
[   38s]             else:
[   38s]                 option3 = False
[   38s]             option1 = res == path
[   38s]             option2 = res == flpath
[   38s]     
[   38s]             msg = ('\npath =\n {}\nflpath =\n {}\nparse_path(path).d() =\n {}'
[   38s]                    ''.format(path, flpath, res))
[   38s] >           self.assertTrue(option1 or option2 or option3, msg=msg)
[   38s] E           AssertionError: False is not true : 
[   38s] E           path =
[   38s] E            M 600,350 L 650,325 A 25,25 -30 0,1 700,300 L 750,275
[   38s] E           flpath =
[   38s] E            M 600.0,350.0 L 650.0,325.0 A 27.9508497187,27.9508497187 -30.0 0,1 700.0,300.0 L 750.0,275.0
[   38s] E           parse_path(path).d() =
[   38s] E            M 600.0,350.0 L 650.0,325.0 A 27.950849718747367,27.950849718747367 -30.0 0,1 700.0,300.0 L 750.0,275.0
[   38s] 
[   38s] test/test_generation.py:63: AssertionError
[   38s] ______________________________ TestPath.test_hash ______________________________
[   38s] 
[   38s] self = <test_path.TestPath testMethod=test_hash>
[   38s] 
[   38s]     def test_hash(self):
[   38s]         line1 = Line(600.5 + 350.5j, 650.5 + 325.5j)
[   38s]         arc1 = Arc(650 + 325j, 25 + 25j, -30, 0, 1, 700 + 300j)
[   38s]         arc2 = Arc(650 + 325j, 30 + 25j, -30, 0, 0, 700 + 300j)
[   38s]         cub1 = CubicBezier(650 + 325j, 25 + 25j, -30, 700 + 300j)
[   38s]         cub2 = CubicBezier(700 + 300j, 800 + 400j, 750 + 200j, 600 + 100j)
[   38s]         quad3 = QuadraticBezier(600 + 100j, 600, 600 + 300j)
[   38s]         linez = Line(600 + 300j, 600 + 350j)
[   38s]     
[   38s]         bezpath = Path(line1, cub1, cub2, quad3)
[   38s]         bezpathz = Path(line1, cub1, cub2, quad3, linez)
[   38s]         path = Path(line1, arc1, cub2, quad3)
[   38s]         pathz = Path(line1, arc1, cub2, quad3, linez)
[   38s]         lpath = Path(linez)
[   38s]         qpath = Path(quad3)
[   38s]         cpath = Path(cub1)
[   38s]         apath = Path(arc1, arc2)
[   38s]     
[   38s]         test_curves = [bezpath, bezpathz, path, pathz, lpath, qpath, cpath,
[   38s]                        apath, line1, arc1, arc2, cub1, cub2, quad3, linez]
[   38s]     
[   38s]         # this is necessary due to changes to the builtin `hash` function
[   38s]         user_hash_seed = os.environ.get("PYTHONHASHSEED", "")
[   38s]         os.environ["PYTHONHASHSEED"] = "314"
[   38s]         if version_info >= (3, 8):
[   38s]             expected_hashes = [
[   38s]                 -6073024107272494569, -2519772625496438197, 8726412907710383506,
[   38s]                 2132930052750006195, 3112548573593977871, 991446120749438306,
[   38s]                 -5589397644574569777, -4438808571483114580, -3125333407400456536,
[   38s]                 -4418099728831808951, 702646573139378041, -6331016786776229094,
[   38s]                 5053050772929443013, 6102272282813527681, -5385294438006156225
[   38s]             ]
[   38s]         elif (3, 2) <= version_info < (3, 8):
[   38s]             expected_hashes = [
[   38s]                 -5662973462929734898, 5166874115671195563, 5223434942701471389,
[   38s]                 -7224979960884350294, -5178990533869800243, -4003140762934044601,
[   38s]                 8575549467429100514, -6692132994808317852, 1594848578230132678,
[   38s]                 -6374833902132909499, 4188352014604112779, -5090374009174854814,
[   38s]                 -7093907105533857815, 2036243740727202243, -8108488067585685407
[   38s]             ]
[   38s]         else:
[   38s]     
[   38s]             expected_hashes = [
[   38s]                 -5762846476463470127, -138736730317965290, -2005041722222729058,
[   38s]                 8448700906794235291, -5178990533869800243, -4003140762934044601,
[   38s]                 8575549467429100514, 5166859065265868968, 1373103287265872323,
[   38s]                 -1022491904150314631, 4188352014604112779, -5090374009174854814,
[   38s]                 -7093907105533857815, 2036243740727202243, -8108488067585685407
[   38s]             ]
[   38s]     
[   38s]         if version_info.major == 2 and os.name == 'nt':
[   38s]             # the expected hash values for 2.7 apparently differed on Windows
[   38s]             # if you work in Windows and want to fix this test, please do
[   38s]             return
[   38s]     
[   38s]         for c, h in zip(test_curves, expected_hashes):
[   38s] >           self.assertTrue(hash(c) == h, msg="hash {} was expected for curve = {}".format(h, c))
[   38s] E           AssertionError: False is not true : hash 8726412907710383506 was expected for curve = Path(Line(start=(600.5+350.5j), end=(650.5+325.5j)),
[   38s] E                Arc(start=(650+325j), radius=(27.950849718747367+27.950849718747367j), rotation=-30, large_arc=False, sweep=True, end=(700+300j)),
[   38s] E                CubicBezier(start=(700+300j), control1=(800+400j), control2=(750+200j), end=(600+100j)),
[   38s] E                QuadraticBezier(start=(600+100j), control=600, end=(600+300j)))
[   38s] 
[   38s] test/test_path.py:778: AssertionError
[   38s] =============================== warnings summary ===============================
[   38s] test/test_path.py::QuadraticBezierTest::test_length
[   38s] test/test_path.py::TestPath::test_transform_scale
[   38s] test/test_path.py::Test_ilength::test_ilength_paths
[   38s] test/test_path.py::Test_ilength::test_ilength_quadratics
[   38s] test/test_path.py::TestPathBugs::test_issue_113
[   38s]   /home/abuild/rpmbuild/BUILDROOT/python-svgpathtools-1.5.1-13.1.aarch64/usr/lib/python3.8/site-packages/svgpathtools/path.py:942: RuntimeWarning: divide by zero encountered in double_scalars
[   38s]     logarand = (sqrt(c2) * (t1 + beta) + dq1_mag) / \
[   38s] 
[   38s] test/test_path.py::QuadraticBezierTest::test_length
[   38s] test/test_path.py::TestPath::test_transform_scale
[   38s] test/test_path.py::Test_ilength::test_ilength_paths
[   38s] test/test_path.py::Test_ilength::test_ilength_quadratics
[   38s] test/test_path.py::TestPathBugs::test_issue_113
[   38s]   /home/abuild/rpmbuild/BUILDROOT/python-svgpathtools-1.5.1-13.1.aarch64/usr/lib/python3.8/site-packages/svgpathtools/path.py:945: RuntimeWarning: invalid value encountered in double_scalars
[   38s]     gamma * sqrt(c2) * log(logarand)
[   38s] 
[   38s] test/test_path.py::QuadraticBezierTest::test_length
[   38s] test/test_path.py::Test_ilength::test_ilength_paths
[   38s] test/test_path.py::Test_ilength::test_ilength_quadratics
[   38s] test/test_path.py::TestPathBugs::test_issue_113
[   38s]   /home/abuild/rpmbuild/BUILDROOT/python-svgpathtools-1.5.1-13.1.aarch64/usr/lib/python3.8/site-packages/svgpathtools/path.py:942: RuntimeWarning: invalid value encountered in double_scalars
[   38s]     logarand = (sqrt(c2) * (t1 + beta) + dq1_mag) / \
[   38s] 
[   38s] test/test_path.py::Test_ilength::test_ilength_paths
[   38s] test/test_path.py::Test_ilength::test_ilength_quadratics
[   38s] test/test_path.py::TestPathBugs::test_issue_113
[   38s]   /home/abuild/rpmbuild/BUILDROOT/python-svgpathtools-1.5.1-13.1.aarch64/usr/lib/python3.8/site-packages/svgpathtools/path.py:945: RuntimeWarning: invalid value encountered in log
[   38s]     gamma * sqrt(c2) * log(logarand)
[   38s] 
[   38s] test/test_path.py::TestPathTools::test_path_area
[   38s]   /home/abuild/rpmbuild/BUILD/svgpathtools-1.5.1/test/test_path.py:1981: UserWarning: Skipping `test_path_area` as RUN_SLOW_TESTS is false.
[   38s]     warnings.warn("Skipping `test_path_area` as RUN_SLOW_TESTS is false.")
[   38s] 
[   38s] test/test_path.py::TestPathBugs::test_issue_113
[   38s]   /home/abuild/rpmbuild/BUILDROOT/python-svgpathtools-1.5.1-13.1.aarch64/usr/lib/python3.8/site-packages/svgpathtools/path.py:940: RuntimeWarning: invalid value encountered in sqrt
[   38s]     dq1_mag = sqrt(c2 * t1 ** 2 + c1 * t1 + c0)
[   38s] 
[   38s] test/test_path.py::TestPathBugs::test_issue_71
[   38s]   /home/abuild/rpmbuild/BUILDROOT/python-svgpathtools-1.5.1-13.1.aarch64/usr/lib/python3.8/site-packages/svgpathtools/path.py:2624: UserWarning: This attribute is deprecated, consider using isclosed() method instead.
[   38s]   
[   38s]   This attribute is kept for compatibility with scripts created using svg.path (v2.0). You can prevent this warning in the future by setting CLOSED_WARNING_ON=False.
[   38s]     warn(mes)
[   38s] 
[   38s] -- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
[   38s] =========================== short test summary info ============================
[   38s] FAILED test/test_generation.py::TestGeneration::test_path_parsing - Assertion...
[   38s] FAILED test/test_path.py::TestPath::test_hash - AssertionError: False is not ...
[   38s] ================== 2 failed, 88 passed, 20 warnings in 11.10s ==================
mathandy commented 2 years ago

I'm usually on an apple SoC so I know this isn't all non-x86 architectures.

There's a known issue regarding the hash method being OS (and maybe also architecture) dependent. I'm not surprised to see fails related to precision when it comes to converting floats to strings on different systems.

I'd like to add GitHub CI actions to test for architecture-level issues like these if that's possible.

Any addition information you could give about when these tests fail is appreciated.

And of course, feel free to make a pull request if there are fixes you have in mind.