1#!/pxrpythonsubst
2#
3# Copyright 2016 Pixar
4#
5# Licensed under the Apache License, Version 2.0 (the "Apache License")
6# with the following modification; you may not use this file except in
7# compliance with the Apache License and the following modification to it:
8# Section 6. Trademarks. is deleted and replaced with:
9#
10# 6. Trademarks. This License does not grant permission to use the trade
11#    names, trademarks, service marks, or product names of the Licensor
12#    and its affiliates, except as required to comply with Section 4(c) of
13#    the License and to reproduce the content of the NOTICE file.
14#
15# You may obtain a copy of the Apache License at
16#
17#     http://www.apache.org/licenses/LICENSE-2.0
18#
19# Unless required by applicable law or agreed to in writing, software
20# distributed under the Apache License with the above modification is
21# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
22# KIND, either express or implied. See the Apache License for the specific
23# language governing permissions and limitations under the Apache License.
24#
25
26from __future__ import print_function
27
28import sys
29from pxr import Tf
30
31import unittest
32
33status = 0
34
35f1called = False
36def f1():
37    global f1called
38    print('called to python!')
39    f1called = True
40
41def f2():
42    return 'called to python, return string!'
43
44def f3(arg1, arg2):
45    print('args', arg1, arg2)
46
47def f4(stringArg):
48    return 'got string ' + stringArg
49
50class MyBase(Tf._TestBase):
51    def __init__(self):
52        Tf._TestBase.__init__(self)
53    def Virtual(self):
54        return 'python base'
55    def Virtual2(self):
56        print('python base v2')
57    def Virtual3(self, arg):
58        print('python base v3', arg)
59    def UnwrappedVirtual(self):
60        return 'unwrapped virtual'
61
62class MyDerived(Tf._TestDerived):
63    def Virtual(self):
64        return 'python derived'
65    def Virtual2(self):
66        print('python derived v2')
67    def Virtual3(self, arg):
68        print('python derived v3', arg)
69    def Virtual4(self):
70        return 'python derived v4'
71
72class Raiser(Tf._TestBase):
73    def __init__(self):
74        Tf._TestBase.__init__(self)
75    def Virtual(self):
76        raise RuntimeError('testing exception stuff')
77    def Virtual2(self):
78        print('raiser v2')
79    def Virtual3(self, arg):
80        print('py virtual 3', arg)
81
82class TestPython(unittest.TestCase):
83
84    def test_BaseDerived(self):
85        mb = MyBase()
86        d = Tf._TestDerived()
87        md = MyDerived()
88
89        self.assertEqual('unwrapped virtual', mb.TestCallVirtual())
90
91        self.assertEqual('cpp base', mb.Virtual4())
92        self.assertEqual('python derived v4', md.Virtual4())
93
94        self.assertEqual((False, 'python base'), Tf._TakesBase(mb))
95        self.assertEqual((True, 'cpp derived'), Tf._TakesBase(d))
96        self.assertEqual((True, 'python derived'), Tf._TakesBase(md))
97
98        self.assertEqual('python base', Tf._TakesConstBase(mb))
99        self.assertEqual('cpp derived', Tf._TakesConstBase(d))
100        self.assertEqual('python derived', Tf._TakesConstBase(md))
101
102        self.assertEqual('cpp derived', Tf._TakesDerived(d))
103        self.assertEqual('python derived', Tf._TakesDerived(md))
104
105        self.assertIs(Tf._ReturnsConstBase(md), md)
106        self.assertIs(Tf._ReturnsBase(md), md)
107        self.assertIs(Tf._ReturnsBaseRefPtr(md), md)
108
109        try:
110            Tf._TestBase().Virtual()
111            assert False, 'calling pure virtual'
112        except:
113            pass
114
115
116    def test_Factory(self):
117        df = Tf._DerivedFactory()
118        self.assertIsInstance(df, Tf._TestDerived)
119        self.assertTrue(hasattr(df, '__owner'))
120        self.assertEqual((True, 'cpp derived'), Tf._TakesBase(df))
121        Tf._TakesReference(df)
122        self.assertFalse(hasattr(df, '__owner'))
123
124        self.assertIs(Tf._DerivedNullFactory(), None)
125
126
127    def test_ErrorException(self):
128        with self.assertRaises(RuntimeError):
129            Tf._TakesBase(Raiser())
130
131        with self.assertRaises(Tf.ErrorException) as cm:
132            Tf._mightRaise(True)
133        for x in cm.exception.args:
134            self.assertTrue(len(repr(x)))
135
136        with self.assertRaises(Tf.ErrorException):
137            Tf.RaiseCodingError("some error")
138
139        with self.assertRaises(Tf.ErrorException):
140            Tf.RaiseRuntimeError("some error")
141
142        with self.assertRaises(Tf.ErrorException):
143            Tf._doErrors()
144
145    def test_CppException(self):
146        with self.assertRaises(Tf.CppException) as cm:
147            Tf._ThrowTest('hello')
148        print(cm.exception)
149
150        with self.assertRaises(Tf.CppException) as cm:
151            Tf._CallThrowTest(lambda : Tf._ThrowTest('py-to-cpp-to-py'))
152        print(cm.exception)
153
154    def test_StaticMethodPosting(self):
155        with self.assertRaises(Tf.ErrorException):
156            Tf._TestStaticMethodError.Error()
157
158        Tf.Warn("expected warning, for coverage")
159
160        Tf.Status("expected status message, for coverage")
161
162    def test_NoticeListener(self):
163        global noticesHandled
164        noticesHandled = 0
165
166        def HandleNotice(notice, sender):
167            global noticesHandled
168            noticesHandled += 1
169
170        listener = Tf.Notice.RegisterGlobally('TfNotice', HandleNotice)
171        self.assertEqual(0, noticesHandled)
172        Tf.Notice().SendGlobally()
173        self.assertEqual(1, noticesHandled)
174        listener.Revoke()
175        Tf.Notice().SendGlobally()
176        self.assertEqual(1, noticesHandled)
177
178        # Raise in notice listener
179        def RaiseOnNotice(notice, sender):
180            raise RuntimeError('got notice!')
181
182        class CustomTestNotice(Tf.Notice):
183            pass
184        Tf.Type.Define(CustomTestNotice)
185
186        listener = Tf.Notice.RegisterGlobally(CustomTestNotice, RaiseOnNotice)
187        with self.assertRaises(RuntimeError):
188            CustomTestNotice().SendGlobally()
189
190        # Register a bad notice
191        with self.assertRaises(TypeError):
192            listener = Tf.Notice.RegisterGlobally('BogusNotice', HandleNotice)
193
194
195    def test_Enums(self):
196        Tf._takesTfEnum(Tf._Alpha)
197        Tf._takesTfEnum(Tf._Delta)
198
199        self.assertEqual(Tf._Delta, Tf._returnsTfEnum(Tf._Delta))
200        self.assertIs(Tf._returnsTfEnum(Tf._Delta), Tf._Delta)
201
202        Tf._takesTfEnum(Tf._Enum.One)
203
204        Tf._takesTestEnum(Tf._Alpha)
205        Tf._takesTestEnum2(Tf._Enum.One)
206
207        self.assertEqual(Tf._TestScopedEnum.Boron,
208                         Tf._TestScopedEnum.GetValueFromName('Boron'))
209        self.assertEqual(Tf._TestScopedEnum.Hydrogen,
210                         Tf._TestScopedEnum.GetValueFromName('Hydrogen'))
211        self.assertNotEqual(Tf._TestScopedEnum.Hydrogen,
212                            Tf._TestScopedEnum.GetValueFromName('Boron'))
213
214        def testRepr(s):
215            self.assertEqual(s, repr(eval(s)))
216
217        testRepr("Tf._Alpha")
218        testRepr("Tf._TestScopedEnum.Hydrogen")
219        testRepr("Tf._Enum.One")
220        testRepr("Tf._Enum.TestScopedEnum.Alef")
221
222
223
224    def test_EnumComparisons(self):
225        self.assertTrue(Tf._Alpha < Tf._Bravo < Tf._Charlie < Tf._Delta)
226        self.assertTrue(Tf._Delta > Tf._Charlie > Tf._Bravo > Tf._Alpha)
227        self.assertTrue(Tf._Alpha != Tf._Bravo != Tf._Charlie != Tf._Delta)
228        self.assertTrue(Tf._Alpha != Tf._Enum.One)
229        self.assertTrue(Tf._Alpha > Tf._Enum.One)
230        self.assertTrue(Tf._Alpha >= Tf._Alpha)
231        self.assertTrue(Tf._Alpha <= Tf._Delta)
232        self.assertTrue(Tf._Charlie >= Tf._Bravo)
233
234
235    def test_EnumValuesRemovedFromTypeScope(self):
236        with self.assertRaises(AttributeError):
237            Tf._takesTestEnum(Tf._TestEnum._Alpha)
238
239        self.assertEqual((Tf._Alpha, Tf._Bravo, Tf._Charlie, Tf._Delta),
240                Tf._TestEnum.allValues)
241
242        with self.assertRaises(TypeError):
243            Tf._takesTestEnum(Tf._Enum.One)
244        with self.assertRaises(TypeError):
245            Tf._takesTestEnum2(Tf._Alpha)
246
247        self.assertEqual((Tf._Enum.One, Tf._Enum.Two, Tf._Enum.Three),
248                Tf._Enum.TestEnum2.allValues)
249
250        self.assertEqual((Tf._Enum.TestScopedEnum.Alef,
251                          Tf._Enum.TestScopedEnum.Bet,
252                          Tf._Enum.TestScopedEnum.Gimel),
253                         Tf._Enum.TestScopedEnum.allValues)
254
255        self.assertEqual(1, Tf._Enum.One.value)
256        self.assertEqual(2, Tf._Enum.Two.value)
257        self.assertEqual(3, Tf._Alpha.value)
258        self.assertEqual('A', Tf._Alpha.displayName)
259        self.assertEqual('B', Tf._Bravo.displayName)
260        self.assertEqual(Tf._Alpha, Tf.Enum.GetValueFromFullName(Tf._Alpha.fullName))
261        self.assertEqual(None, Tf.Enum.GetValueFromFullName("invalid_enum_name"))
262
263        self.assertTrue(Tf._Enum.One == 1)
264        self.assertTrue(Tf._Enum.Two == 2)
265        self.assertTrue(Tf._Alpha == 3)
266
267        self.assertTrue(1 == Tf._Enum.One)
268        self.assertTrue(2 == Tf._Enum.Two)
269        self.assertTrue(3 == Tf._Alpha)
270
271        self.assertTrue(Tf._Alpha | Tf._Alpha is Tf._Alpha)
272        self.assertTrue(Tf._Alpha & Tf._Alpha is Tf._Alpha)
273        self.assertTrue(Tf._Alpha == 3)
274        self.assertTrue(Tf._Alpha | 1 is Tf._Alpha)
275        self.assertTrue(2 | Tf._Alpha is Tf._Alpha)
276        self.assertTrue(4 | Tf._Alpha == 7)
277
278        self.assertTrue(Tf._Alpha & 3 is Tf._Alpha)
279        self.assertTrue(3 & Tf._Alpha is Tf._Alpha)
280        self.assertTrue(2 & Tf._Alpha == 2)
281
282        self.assertTrue(Tf._Enum.One ^ Tf._Enum.Two == 3)
283        self.assertTrue(4 ^ Tf._Alpha == 7)
284        self.assertTrue(Tf._Alpha ^ 4 == 7)
285
286
287    def test_EnumRegistrationCollision(self):
288        with self.assertRaises(Tf.ErrorException):
289            Tf._registerInvalidEnum(Tf)
290
291
292    def test_EnumInvalidBitwiseOperations(self):
293        '''Bitwise operations are not permitted between enum values of different types.'''
294        with self.assertRaises(TypeError):
295            Tf._Alpha & Tf._Enum.Two
296            assert False, "Should not permit bitwise AND between different enum types"
297
298        with self.assertRaises(TypeError):
299            Tf._Alpha | Tf._Enum.Two
300
301        with self.assertRaises(TypeError):
302            Tf._Alpha ^ Tf._Enum.Two
303
304    def test_EnumOneObjectPerUniqueValue(self):
305        '''Only one object should be created for each unique value per type.'''
306        value1 = Tf._Alpha | Tf._Delta
307        value2 = Tf._Alpha | Tf._Delta
308        self.assertIs(value1, value2)
309
310    def test_EnumConversion(self):
311        value1 = Tf._Alpha | Tf._Delta
312        # Conversions of TfEnum objects to python should retain the correct type.'''
313        self.assertIs(Tf._returnsTfEnum(value1), value1)
314
315        # The auto-generated python object should be convertible to the original type.
316        Tf._takesTestEnum(value1)
317
318    def test_ByteArrayConversion(self):
319        '''Verify we can convert buffers to byte arrays.'''
320        ba = Tf._ConvertByteListToByteArray(['a', 'b', 'c'])
321        self.assertEqual(ba, bytearray([ord('a'), ord('b'), ord('c')]))
322
323        numbers = [str(x % 10) for x in range(256)]
324        ba = Tf._ConvertByteListToByteArray(numbers)
325        self.assertEqual(ba, bytearray([ord(x) for x in numbers]))
326
327        zeroes = ['\x00', '\x00', '\x00']
328        ba = Tf._ConvertByteListToByteArray(zeroes)
329        self.assertEqual(ba, bytearray([0, 0, 0]))
330
331        ba = Tf._ConvertByteListToByteArray([])
332        self.assertEqual(ba, bytearray())
333        self.assertTrue(isinstance(ba, bytearray))
334
335    def test_Callbacks(self):
336        global f1called
337        Tf._callback(f1)
338        self.assertTrue(f1called)
339
340        self.assertEqual('called to python, return string!', Tf._stringCallback(f2))
341
342        self.assertEqual('got string c++ is calling...', Tf._stringStringCallback(f4))
343
344        with self.assertRaises(TypeError):
345            Tf._callback(f3)
346        with self.assertRaises(TypeError):
347            Tf._stringCallback(f1)
348
349
350    def test_WeakStrongRefCallbacks(self):
351        class Foo(object):
352            def method(self):
353                return 'python method'
354        f = Foo()
355        m = f.method
356
357        # the callback is an instancemethod, it should not keep the object
358        # alive.
359        Tf._setTestCallback(m)
360        self.assertEqual('python method', Tf._invokeTestCallback())
361        del f
362        del m
363        print('expected warning...')
364        self.assertEqual('', Tf._invokeTestCallback())
365        print('end of expected warning')
366
367        l = lambda : 'python lambda'
368
369        # the callback is a lambda, it should stay alive (and will keep f alive)
370        Tf._setTestCallback(l)
371        self.assertEqual('python lambda', Tf._invokeTestCallback())
372        del l
373        self.assertEqual('python lambda', Tf._invokeTestCallback())
374
375        # Test unbound instance method.
376        self.assertEqual('test', Tf._callUnboundInstance(str.lower, "TEST"))
377
378        # the callback is a function, it should not stay alive
379        def func():
380            return 'python func'
381
382        Tf._setTestCallback(func)
383        self.assertEqual('python func', Tf._invokeTestCallback())
384        del func
385        print('expected warning...')
386        self.assertEqual('', Tf._invokeTestCallback())
387        print('end of expected warning')
388
389        del Foo
390
391
392    def test_Singleton(self):
393        class Foo(Tf.Singleton):
394            def init(self):
395                print('Initializing singleton')
396                self._value = 100
397            def GetValue(self):
398                return self._value
399            def SetValue(self, value):
400                self._value = value
401
402        # Get the singleton instance (first time causes creation)
403        f = Foo()
404
405        # Subsequent times do not cause creation
406        f = Foo()
407
408        # Always get same instance (there is only one)
409        f is Foo() and Foo() is Foo()
410
411        self.assertEqual(100, Foo().GetValue())
412
413        Foo().SetValue(123)
414
415        self.assertEqual(123, Foo().GetValue())
416
417
418    def test_TfPyClassMethod(self):
419        c = Tf._ClassWithClassMethod()
420
421        # Test classmethod invokation.
422        def _TestCallable():
423            return 123
424        self.assertEqual((Tf._ClassWithClassMethod, 123), c.Test(_TestCallable))
425
426        # Test classmethod error handling.
427        class _TestException(Exception):
428            '''A sample exception to raise.'''
429            pass
430        def _TestCallableWithException():
431            raise _TestException()
432
433        with self.assertRaises(_TestException):
434            c.Test(_TestCallableWithException)
435
436
437    def test_Debug(self):
438        # should allow setting TfDebug's output file to either stdout or
439        # stderr, but not other files.
440        Tf.Debug.SetOutputFile(sys.__stdout__)
441        Tf.Debug.SetOutputFile(sys.__stderr__)
442
443        # other files not allowed.
444        import tempfile
445        with tempfile.NamedTemporaryFile() as f:
446            with self.assertRaises(Tf.ErrorException):
447                Tf.Debug.SetOutputFile(f.file)
448
449        # argument checking.
450        # Will raise Tf.ErrorException.
451        with self.assertRaises(Tf.ErrorException):
452            Tf.Debug.SetOutputFile(1234)
453
454
455    def test_ExceptionWithoutCurrentThreadState(self):
456        with self.assertRaises(RuntimeError):
457            Tf._ThrowCppException()
458
459
460    def test_TakeVectorOfVectorOfStrings(self):
461        self.assertEqual(4, Tf._TakesVecVecString([['1', '2', '3'], ['4', '5'], [], ['6']]))
462
463
464    def test_TfPyObjWrapper(self):
465        self.assertEqual('a', Tf._RoundTripWrapperTest('a'))
466        self.assertEqual(1234, Tf._RoundTripWrapperTest(1234))
467        self.assertEqual([], Tf._RoundTripWrapperTest([]))
468        self.assertEqual(None, Tf._RoundTripWrapperTest(None))
469
470        self.assertEqual('a', Tf._RoundTripWrapperCallTest(lambda: 'a'))
471        self.assertEqual(1234, Tf._RoundTripWrapperCallTest(lambda: 1234))
472        self.assertEqual([], Tf._RoundTripWrapperCallTest(lambda: []))
473        self.assertEqual(None, Tf._RoundTripWrapperCallTest(lambda: None))
474
475        self.assertEqual('a', Tf._RoundTripWrapperIndexTest(['a','b'], 0))
476        self.assertEqual('b', Tf._RoundTripWrapperIndexTest(['a','b'], 1))
477        self.assertEqual(4, Tf._RoundTripWrapperIndexTest([1,2,3,4], -1))
478
479    def test_TfMakePyConstructorWithVarArgs(self):
480        with self.assertRaises(TypeError):
481            Tf._ClassWithVarArgInit()
482
483        def CheckResults(c, allowExtraArgs, args, kwargs):
484            self.assertEqual(c.allowExtraArgs, allowExtraArgs)
485            self.assertEqual(c.args, args)
486            self.assertEqual(c.kwargs, kwargs)
487
488        def StandardTests(allowExtraArgs):
489            CheckResults(Tf._ClassWithVarArgInit(allowExtraArgs),
490                        allowExtraArgs, (), {})
491            CheckResults(Tf._ClassWithVarArgInit(allowExtraArgs, 1),
492                        allowExtraArgs, (), {'a':1})
493            CheckResults(Tf._ClassWithVarArgInit(allowExtraArgs, 1, 2, 3),
494                        allowExtraArgs, (), {'a':1, 'b':2, 'c':3})
495            CheckResults(Tf._ClassWithVarArgInit(allowExtraArgs, 1, 2, c=3),
496                        allowExtraArgs, (), {'a':1, 'b':2, 'c':3})
497
498        # Tests with extra arguments disallowed.
499        StandardTests(allowExtraArgs=False)
500
501        # These cases should emit an error because there are unexpected
502        # arguments
503        with self.assertRaises(TypeError):
504            Tf._ClassWithVarArgInit(False, 1, 2, 3, 4)
505
506        with self.assertRaises(TypeError):
507            Tf._ClassWithVarArgInit(False, d=4)
508
509        # This should emit an error because we have multiple values for a single
510        # arg.
511        with self.assertRaises(TypeError):
512            Tf._ClassWithVarArgInit(False, 1, 2, 3, b=4)
513
514        # Tests with extra arguments allowed.
515        StandardTests(allowExtraArgs=True)
516
517        CheckResults(Tf._ClassWithVarArgInit(True, 1, 2, 3, 4, 5),
518                    True, (4,5), {'a':1, 'b':2, 'c':3})
519        CheckResults(Tf._ClassWithVarArgInit(True, 1, 2, c=3, d=6, f=8),
520                    True, (), {'a':1, 'b':2, 'c':3, 'd':6, 'f':8})
521        CheckResults(Tf._ClassWithVarArgInit(True, 1, 2, 3, 4, d=8),
522                    True, (4,), {'a':1, 'b':2, 'c':3, 'd':8})
523
524if __name__ == '__main__':
525    unittest.main()
526