1#Copyright ReportLab Europe Ltd. 2000-2017
2#see license.txt for license details
3"""Tests for reportlab.lib.utils
4"""
5__version__='3.3.0'
6from reportlab.lib.testutils import setOutDir,makeSuiteForClasses, printLocation
7setOutDir(__name__)
8import os, time, sys
9import reportlab
10from reportlab import rl_config
11import unittest
12from reportlab.lib import colors
13from reportlab.lib.utils import recursiveImport, recursiveGetAttr, recursiveSetAttr, rl_isfile, \
14                                isCompactDistro, isPy3, isPyPy, TimeStamp, rl_get_module, \
15                                recursiveGetAttr, recursiveSetAttr, recursiveDelAttr, \
16                                asUnicode, asUnicodeEx, asBytes
17
18def _rel_open_and_read(fn):
19    from reportlab.lib.utils import open_and_read
20    from reportlab.lib.testutils import testsFolder
21    cwd = os.getcwd()
22    os.chdir(testsFolder)
23    try:
24        return open_and_read(fn)
25    finally:
26        os.chdir(cwd)
27
28class ImporterTestCase(unittest.TestCase):
29    "Test import utilities"
30
31    @classmethod
32    def setUpClass(cls):
33        from reportlab.lib.utils import get_rl_tempdir
34        cls._value = float(repr(time.time()))
35        s = int(cls._value)
36        cls._tempdir = get_rl_tempdir('reportlab_test','tmp_%s' % s)
37        if not os.path.isdir(cls._tempdir):
38            os.makedirs(cls._tempdir,0o700)
39        _testmodulename = os.path.join(cls._tempdir,'test_module_%s.py' % s)
40        with open(_testmodulename,'w') as f:
41            f.write('__all__=[]\nvalue=%s\n' % repr(cls._value))
42        if sys.platform=='darwin' and isPy3:
43            time.sleep(0.3)
44        cls._testmodulename = os.path.splitext(os.path.basename(_testmodulename))[0]
45
46    @classmethod
47    def tearDownClass(cls):
48        from shutil import rmtree
49        rmtree(cls._tempdir,1)
50
51    def myAssertRaisesRegex(self,*args,**kwds):
52        return getattr(self,'assertRaisesRegex' if isPy3 else 'assertRaisesRegexp')(*args,**kwds)
53
54    def test1(self):
55        "try stuff known to be in the path"
56        m1 = recursiveImport('reportlab.pdfgen.canvas')
57        import reportlab.pdfgen.canvas
58        assert m1 == reportlab.pdfgen.canvas
59
60    def test2(self):
61        "try under a well known directory NOT on the path"
62        from reportlab.lib.testutils import testsFolder
63        D = os.path.join(testsFolder,'..','tools','pythonpoint')
64        fn = os.path.join(D,'stdparser.py')
65        if rl_isfile(fn) or rl_isfile(fn+'c') or rl_isfile(fn+'o'):
66            m1 = recursiveImport('stdparser', baseDir=D)
67
68    def test3(self):
69        "ensure CWD is on the path"
70        try:
71            cwd = os.getcwd()
72            os.chdir(self._tempdir)
73            m1 = recursiveImport(self._testmodulename)
74        finally:
75            os.chdir(cwd)
76
77    def test4(self):
78        "ensure noCWD removes current dir from path"
79        try:
80            cwd = os.getcwd()
81            os.chdir(self._tempdir)
82            import sys
83            try:
84                del sys.modules[self._testmodulename]
85            except KeyError:
86                pass
87            self.assertRaises(ImportError,
88                              recursiveImport,
89                              self._testmodulename,
90                              noCWD=1)
91        finally:
92            os.chdir(cwd)
93
94    def test5(self):
95        "recursive attribute setting/getting on modules"
96        import reportlab.lib.units
97        inch = recursiveGetAttr(reportlab, 'lib.units.inch')
98        assert inch == 72
99
100        recursiveSetAttr(reportlab, 'lib.units.cubit', 18*inch)
101        cubit = recursiveGetAttr(reportlab, 'lib.units.cubit')
102        assert cubit == 18*inch
103
104    def test6(self):
105        "recursive attribute setting/getting on drawings"
106        from reportlab.graphics.charts.barcharts import sampleH1
107        drawing = sampleH1()
108        recursiveSetAttr(drawing, 'barchart.valueAxis.valueMax', 72)
109        theMax = recursiveGetAttr(drawing, 'barchart.valueAxis.valueMax')
110        assert theMax == 72
111
112    def test7(self):
113        "test open and read of a simple relative file"
114        b = _rel_open_and_read('../docs/images/Edit_Prefs.gif')
115
116    def test8(self):
117        "test open and read of a relative file: URL"
118        b = _rel_open_and_read('file:../docs/images/Edit_Prefs.gif')
119
120    def test9(self):
121        "test open and read of an http: URL"
122        from reportlab.lib.utils import open_and_read
123        b = open_and_read('http://www.reportlab.com/rsrc/encryption.gif')
124
125    def test10(self):
126        "test open and read of a simple relative file"
127        from reportlab.lib.utils import open_and_read, getBytesIO
128        b = getBytesIO(_rel_open_and_read('../docs/images/Edit_Prefs.gif'))
129        b = open_and_read(b)
130
131    def test11(self):
132        "test open and read of an RFC 2397 data URI with base64 encoding"
133        result = _rel_open_and_read('data:image/gif;base64,R0lGODdhAQABAIAAAP///////ywAAAAAAQABAAACAkQBADs=')
134        self.assertEqual(result,b'GIF87a\x01\x00\x01\x00\x80\x00\x00\xff\xff\xff\xff\xff\xff,\x00\x00\x00\x00\x01\x00\x01\x00\x00\x02\x02D\x01\x00;')
135
136    def test12(self):
137        "test open and read of an RFC 2397 data URI without an encoding"
138        result = _rel_open_and_read('data:text/plain;,Hello%20World')
139        self.assertEqual(result,b'Hello World')
140
141    def testRecursiveImportErrors(self):
142        "check we get useful error messages"
143        try:
144            m1 = recursiveImport('reportlab.pdfgen.brush')
145            self.fail("Imported a nonexistent module")
146        except ImportError as e:
147            self.assertIn('reportlab.pdfgen.brush',str(e))
148
149        try:
150            m1 = recursiveImport('totally.non.existent')
151            self.fail("Imported a nonexistent module")
152        except ImportError as e:
153            self.assertIn('totally',str(e))
154
155        try:
156            #import a module in the 'tests' directory with a bug
157            m1 = recursiveImport('unimportable')
158            self.fail("Imported a buggy module")
159        except Exception as e:
160            self.assertIn(("integer division by zeroException raised while importing 'unimportable': integer division by zero"
161                            if isPyPy
162                            else ('division by zero' if isPy3
163                                 else 'integer division or modulo by zero'))
164                        ,str(e))
165    def test14(self):
166        "test the TimeStamp behaviour"
167        oinvariant = rl_config.invariant
168        sden = 'SOURCE_DATE_EPOCH'
169        if sden in os.environ:
170            sde = os.environ[sden]
171            del os.environ[sden]
172        else:
173            sde = self
174
175        try:
176            rl_config.invariant = False
177            t = time.time()
178            ts = TimeStamp()
179            self.assertTrue(abs(t-ts.t)<1)
180            ts = TimeStamp(invariant=True)
181            self.assertEqual(ts.t,946684800.0)
182            self.assertEqual(ts.YMDhms,(2000, 1, 1, 0, 0, 0))
183            self.assertEqual(ts.tzname,'UTC')
184            os.environ[sden] = '1490003100'
185            ts = TimeStamp(invariant=True)  #debian variable takes precedence here
186            self.assertEqual(ts.t,1490003100)
187            self.assertEqual(ts.YMDhms,(2017, 3, 20, 9, 45, 0))
188            self.assertEqual(ts.tzname,'UTC')
189            rl_config.invariant = True
190            ts = TimeStamp()                #still takes precedence
191            self.assertEqual(ts.t,1490003100)
192            self.assertEqual(ts.YMDhms,(2017, 3, 20, 9, 45, 0))
193            self.assertEqual(ts.tzname,'UTC')
194            del os.environ[sden]
195            ts = TimeStamp()                #now rl_config takes precedence
196            self.assertEqual(ts.t,946684800.0)
197            self.assertEqual(ts.YMDhms,(2000, 1, 1, 0, 0, 0))
198            self.assertEqual(ts.tzname,'UTC')
199        finally:
200            if sde is not self:
201                os.environ[sden] = sde
202            rl_config.invariant = oinvariant
203
204    def test15(self):
205        m = rl_get_module(self._testmodulename,self._tempdir)
206        self.assertEqual(self._value,m.value)
207
208    def test16(self):
209        from reportlab.lib import fontfinder
210        ff = fontfinder.FontFinder(useCache=False,recur=True)
211        ff.addDirectories(rl_config.T1SearchPath + rl_config.TTFSearchPath)
212        ff.search()
213        ff.getFamilyNames()
214
215    def test17(self):
216        self.assertEqual(asUnicode(u'abc'),u'abc')
217        self.assertEqual(asUnicode(b'abc'),u'abc')
218        self.assertRaises(AttributeError,asUnicode,['abc'])
219        self.myAssertRaisesRegex(AttributeError,r"asUnicode\(.*'list' object has no attribute 'decode'", asUnicode,['abc'])
220        self.assertEqual(asUnicodeEx(u'abc'),u'abc')
221        self.assertEqual(asUnicodeEx(b'abc'),u'abc')
222        self.assertEqual(asUnicodeEx(123),u'123')
223        self.assertEqual(asBytes(u'abc'),b'abc')
224        self.assertEqual(asBytes(b'abc'),b'abc')
225        self.assertRaises(AttributeError,asBytes,['abc'])
226        self.myAssertRaisesRegex(AttributeError,"asBytes\(.*'list' object has no attribute 'encode'", asBytes,['abc'])
227
228class RaccessTest:
229    l = [1, 2]
230    a = [0, [1, 2, [3,4]]]
231    b = {'x': {'y': 'y'}, 'z': [1, 2]}
232    z = 'z'
233
234class RaccessPerson:
235    settings = {
236        'autosave': True,
237        'style': {
238            'height': 30,
239            'width': 200
240        },
241        'themes': ['light', 'dark']
242    }
243    def __init__(self, name, age, friends):
244        self.name = name
245        self.age = age
246        self.friends = friends
247
248class RaccessTestCase(unittest.TestCase):
249    "Test recursive access functions"
250    def test1(self):
251        def innerTest(k,v):
252            obj = RaccessTest()
253            obj.t = obj
254            obj.a.append(obj)
255            obj.b['w'] = obj
256            self.assertEqual(recursiveGetAttr(obj,k),v,"error getattr(obj,%r)==%r" % (k,v))
257        for k,v in [
258            ('l', RaccessTest.l),
259            ('t.t.t.t.z', 'z'),
260            ('a[0]', 0),
261            ('a[1][0]', 1),
262            ('a[1][2]', [3,4]),
263            ('b["x"]', {'y': 'y'}),
264            ('b["x"]["y"]', 'y'),
265            ('b["z"]', [1,2]),
266            ('b["z"][1]', 2),
267            ('b["w"].z', 'z'),
268            ('b["w"].t.l', [1, 2]),
269            ('a[-1].z', 'z'),
270            ('l[-1]', 2),
271            ('a[2].t.a[-1].z', 'z'),
272            ('a[2].t.b["z"][0]', 1),
273            ('a[-1].t.z', 'z'),
274            ]:
275            innerTest(k,v)
276
277    def test_person_example(self):
278        bob = RaccessPerson(name="Bob", age=31, friends=[])
279        jill = RaccessPerson(name="Jill", age=29, friends=[bob])
280        jack = RaccessPerson(name="Jack", age=28, friends=[bob, jill])
281
282        # Nothing new
283        self.assertEqual(recursiveGetAttr(bob, 'age') ,31)
284
285        # Lists
286        self.assertEqual(recursiveGetAttr(jill, 'friends[0].name') ,'Bob')
287        self.assertEqual(recursiveGetAttr(jack, 'friends[-1].age') ,29)
288
289        # Dict lookups
290        self.assertEqual(recursiveGetAttr(jack, 'settings["style"]["width"]') ,200)
291
292        # Combination of lookups
293        self.assertEqual(recursiveGetAttr(jack, 'settings["themes"][-2]') ,'light')
294        self.assertEqual(recursiveGetAttr(jack, 'friends[-1].settings["themes"][1]') ,'dark')
295
296        # Setattr
297        #recursiveSetAttr(bob, 'settings["style"]["width"]', 400)
298        #self.assertEqual(recursiveGetAttr(bob, 'settings["style"]["width"]') ,400)
299
300        # Nested objects
301        recursiveSetAttr(bob, 'friends', [jack, jill])
302        self.assertEqual(recursiveGetAttr(jack, 'friends[0].friends[0]') ,jack)
303
304        recursiveSetAttr(jill, 'friends[0].age', 32)
305        self.assertEqual(bob.age ,32)
306
307        # Deletion
308        #recursiveDelAttr(jill, 'friends[0]')
309        #self.assertEqual(len(jill.friends) ,0)
310
311        recursiveDelAttr(jill, 'age')
312        assert not hasattr(jill, 'age')
313
314        recursiveDelAttr(bob, 'friends[0].age')
315        assert not hasattr(jack, 'age')
316
317        # Unsupported
318        #with self.assertRaises(NotImplementedError) as e:
319        #   recursiveGetAttr(bob, 'friends[0+1]')
320
321        # Nice try, function calls are not allowed
322        #with self.assertRaises(ValueError):
323        #   recursiveGetAttr(bob, 'friends.pop(0)')
324
325        # Must be an expression
326        with self.assertRaises(SyntaxError):
327            recursiveGetAttr(bob, 'friends = []')
328
329        # Must be an expression
330        with self.assertRaises(SyntaxError):
331            recursiveGetAttr(bob, 'friends..')
332
333        # Must be an expression
334        with self.assertRaises(KeyError):
335            recursiveGetAttr(bob, 'settings["DoesNotExist"]')
336
337        # Must be an expression
338        with self.assertRaises(IndexError):
339            recursiveGetAttr(bob, 'friends[100]')
340
341    def test_empty(self):
342        obj = RaccessTest()
343        with self.assertRaises(ValueError):
344           recursiveGetAttr(obj,"  ")
345
346        with self.assertRaises(ValueError):
347            recursiveGetAttr(obj,"")
348
349        with self.assertRaises(TypeError):
350            recursiveGetAttr(obj, 0)
351
352        with self.assertRaises(TypeError):
353            recursiveGetAttr(obj, None)
354
355        with self.assertRaises(TypeError):
356            recursiveGetAttr(obj, obj)
357
358def makeSuite():
359    return makeSuiteForClasses(ImporterTestCase,RaccessTestCase)
360
361if __name__ == "__main__": #noruntests
362    unittest.TextTestRunner().run(makeSuite())
363    printLocation()
364