Commits

Ronald Oussoren committed a609fcc

Improved testing for the lazy importer

  • Participants
  • Parent commits 70e82dd

Comments (0)

Files changed (2)

pyobjc-core/Lib/objc/_lazyimport.py

 import objc
 ModuleType = type(sys)
 
-
 def _loadBundle(frameworkName, frameworkIdentifier, frameworkPath):
     if frameworkIdentifier is None:
         bundle = loadBundle(
                 '_ObjCLazyModule__aliases',
             )
 
-    def __init__(self, name, frameworkIdentifier, frameworkPath, metadict, inline_list=None, initialdict={}, parents=()):
+    def __init__(self, name, frameworkIdentifier, frameworkPath, metadict=None, inline_list=None, initialdict=None, parents=()):
         super(ObjCLazyModule, self).__init__(name)
 
         if frameworkIdentifier is not None or frameworkPath is not None:
                 if sys.modules[nm] is not None:
                     self.__dict__[rest] = sys.modules[nm]
 
-        self.__dict__.update(initialdict)
+        if metadict is None:
+            metadict = {}
+
+        if initialdict:
+            self.__dict__.update(initialdict)
         self.__dict__.update(metadict.get('misc', {}))
         self.__parents = parents
         self.__varmap = metadict.get('constants')
 
             else:
                 self.__dict__[name] = value
+                if '__all__' in self.__dict__:
+                    del self.__dict__['__all__']
                 return value
 
         # Check if the name is a constant from
             pass
         else:
             self.__dict__[name] = value
+            if '__all__' in self.__dict__:
+                del self.__dict__['__all__']
             return value
 
         # Then check if the name is class
 
         else:
             self.__dict__[name] = value
+            if '__all__' in self.__dict__:
+                del self.__dict__['__all__']
             return value
 
         # Finally give up and raise AttributeError
         raise AttributeError(name)
 
     def __calc_all(self):
-        all = set()
 
         # Ensure that all dynamic entries get loaded
         if self.__varmap_dct:
-            if self.__varmap_dct:
-                tp = self.__varmap_dct.pop(name)
-                return objc._loadConstant(name, tp, False)
-                dct = {}
-                objc.loadBundleVariables(self.__bundle, dct,
-                        [ (nm, self.__varmap[nm]) for nm in self.__varmap_dct ])
-                for nm in dct:
-                    if nm not in self.__dict__:
-                        self.__dict__[nm] = dct[nm]
+            dct = {}
+            objc.loadBundleVariables(self.__bundle, dct,
+                    [ (nm, self.__varmap_dct[nm].encode('ascii')) for nm in self.__varmap_dct ])
+            for nm in dct:
+                if nm not in self.__dict__:
+                    self.__dict__[nm] = dct[nm]
 
-                self.__varmap_dct = {}
+            self.__varmap_dct = {}
 
         if self.__varmap:
             varmap = []
             for nm, tp in re.findall(r"\$([A-Z0-9a-z_]*)(@[^$]*)?(?=\$)", self.__varmap):
-                varmap.append((nm, b'@' if tp is None else tp))
+                varmap.append((nm, b'@' if not tp else tp.encode('ascii')))
 
             dct = {}
-            objc.loadBundleVariables(self.__bundle, dct,
-                    [ (nm, self.__varmap[nm]) for nm in self.__varmap_dct ])
+            objc.loadBundleVariables(self.__bundle, dct, varmap)
 
             for nm in dct:
                 if nm not in self.__dict__:
             self.__varmap = ""
 
         if self.__enummap:
-            for nm, val in re.findall(r"\$([A-Z0-9a-z_]*)@([^$])*(?=\$)", self.__enummap):
+            for nm, val in re.findall(r"\$([A-Z0-9a-z_]*)@([^$]*)(?=\$)", self.__enummap):
                 if nm not in self.__dict__:
                     self.__dict__[nm] = self.__prs_enum(val)
-                try:
-                    getattr(self, nm)
-                except AttributeError:
-                    pass
 
             self.__enummap = ""
 
         if self.__funcmap:
-            for nm in list(self.__funcmap):
-                try:
-                    getattr(self, nm)
-                except AttributeError:
-                    pass
-
             func_list = []
             for nm in self.__funcmap:
                 func_list.append((nm,) + self.__funcmap[nm])
                 except AttributeError:
                     pass
 
+        all_names = set()
+
         # Add all names that are already in our __dict__
-        all.update(self.__dict__)
+        all_names.update(self.__dict__)
 
         # Merge __all__of parents ('from parent import *')
         for p in self.__parents:
-            all.update(getattr(p, '__all__', ()))
+            try:
+                all_names.update(p.__all__)
+            except AttributeError:
+                all_names.update(dir(p))
 
         # Add all class names
-        all.update(cls.__name__ for cls in getClassList())
+        all_names.update(cls.__name__ for cls in getClassList())
 
-        return [ v for v in all if not v.startswith('_') ]
+        return [ v for v in all_names if not v.startswith('_') ]
 
     def __prs_enum(self, val):
         if val.startswith("'"):
-            if isinstance(val, bytes):
-                # Python 2.x
+            if isinstance(val, bytes): # pragma: no 3.x cover
                 val, = struct.unpack('>l', val[1:-1])
-            else:
-                # Python 3.x
+            else: # pragma: no 2.x cover
                 val, = struct.unpack('>l', val[1:-1].encode('latin1'))
 
-        elif '.' in val:
-            val = float(name, val)
+        elif '.' in val or 'e' in val:
+            val = float(val)
 
         else:
             val = int(val)
         if self.__varmap_dct:
             if name in self.__varmap_dct:
                 tp = self.__varmap_dct.pop(name)
-                return objc._loadConstant(name, tp, False)
+                result = objc._loadConstant(name, tp, False)
+                return result
 
         if self.__varmap:
             m = re.search(r"\$%s(@[^$]*)?\$"%(name,), self.__varmap)
             if m is not None:
                 tp = m.group(1)
-                if tp is None:
+                if not tp:
                     tp = '@'
                 else:
                     tp = tp[1:]
         if self.__enummap:
             m = re.search(r"\$%s@([^$]*)\$"%(name,), self.__enummap)
             if m is not None:
-                val = m.group(1)
-                val = self.__prs_enum(val)
-
-                return val
+                return self.__prs_enum(m.group(1))
 
         if self.__funcmap:
             if name in self.__funcmap:
                 info = self.__expressions.pop(name)
                 try:
                     return eval(info, {}, self.__expressions_mapping)
-                except NameError:
+                except: # Ignore all errors in evaluation the expression.
                     pass
 
         if self.__aliases:

pyobjc-core/PyObjCTest/test_lazy_import.py

 import objc._lazyimport as lazyimport
 import objc
 import sys
+import os
+import struct
 
 if sys.maxsize > 2 ** 32:
     def sel32or64(a, b): return b
         self.assertEqual(o.bundleIdentifier(), 'com.apple.AppKit')
         self.assertTrue(o.isLoaded())
 
-
         # Should not be loaded yet, hence fallback from identifier to path
         o = lazyimport._loadBundle('PreferencePanes', 'com.apple.frameworks.preferencepanes', '/System/Library/Frameworks/PreferencePanes.framework')
         o.load()
         self.assertTrue(o.isLoaded())
 
 
+    def test_all_types_without_all(self):
+        self.do_test_all_types(all=False)
 
-    def no_test_all_types(self):
+    def test_all_types_with_all(self):
+        self.do_test_all_types(all=True)
+
+    def do_test_all_types(self, all):
         # NOTE: Test isn't ready yet
         # Note: this test uses the real functions, other tests can (and will) mock
         #       functions in objc._objc to test failure condictions, but the full
         #       API stack should be used in at least one testcase.
         metadict = {
-            'constants': '$NSAFMCharacterSet$NSAFMDescender$',
-            'constants_dicts': {
-                'NSAFMAscender': b'@',
+            'constants': '$NSWorkspaceMoveOperation$NSWorkspaceCopyOperation@@$',
+            'constants_dict': {
+                'NSWorkspaceLinkOperation': '@',
             },
             'enums': '$NSAWTEventType@16$NSAboveBottom@4$NSAboveTop@1$',
 
                         }
                     }
                 ),
+                'FunctionThatDoesNotExist': (
+                    sel32or64(b'v^{_NSRect={_NSPoint=ff}{_NSSize=ff}}i', b'v^{CGRect={CGPoint=dd}{CGSize=dd}}q'),
+                    '', {}
+                ),
+                'NSAccessibilityActionDescription': (
+                    b'@@', '', {}
+                ),
             },
             'aliases': {
                 'doc_string': '__doc__',
+                'invalid_alias': 'does_not_exist',
             },
             'expressions': {
                 'mysum': 'NSAWTEventType + NSAboveBottom + 3',
+                'invalid_expression1': 'no_such_name + 1',
+                'invalid_expression2': 'NSAboveBottom + "b"',
             }
         }
 
                 initial_dict, ())
         self.assertIsInstance(mod, objc.ObjCLazyModule)
 
+        if all:
+            # Force precalculation of all attributes by accessing the __all__
+            # attribute
+            self.assertEqual(set(dir(mod)), set(mod.__all__))
+
         self.assertEqual(mod.__doc__, initial_dict['__doc__'])
         self.assertEqual(mod.doc_string, initial_dict['__doc__'])
-        self.assertIsInstance(mod.NSAFMCharacterSet, objc.objc_object)
-        self.assertIsInstance(mod.NSAFMDescender, objc.objc_object)
-        self.assertIsInstance(mod.NSAFMAscender, objc.objc_object)
+        self.assertRaises(AttributeError, getattr, mod, 'invalid_alias')
+        self.assertIsInstance(mod.NSWorkspaceMoveOperation, objc.pyobjc_unicode)
+        self.assertIsInstance(mod.NSWorkspaceCopyOperation, objc.pyobjc_unicode)
+        self.assertIsInstance(mod.NSWorkspaceLinkOperation, objc.pyobjc_unicode)
         self.assertEqual(mod.NSAWTEventType, 16)
         self.assertEqual(mod.NSAboveBottom, 4)
         self.assertEqual(mod.NSAboveTop, 1)
         self.assertIsInstance(mod.NSRectClipList, objc.function)
         self.assertEqual(mod.NSRectClipList.__name__, 'NSRectClipList')
-        self.assertArgLengthInArg(mod.NSRectClipList, 0, 1)
+        self.assertArgSizeInArg(mod.NSRectClipList, 0, 1)
+        self.assertRaises(AttributeError, getattr, mod, 'FunctionThatDoesNotExist')
         self.assertEqual(mod.mysum, mod.NSAWTEventType + mod.NSAboveBottom + 3)
+        self.assertRaises(AttributeError, getattr, mod, 'invalid_expression1')
+        self.assertRaises(AttributeError, getattr, mod, 'invalid_expression2')
+        self.assertIs(mod.NSURL, objc.lookUpClass('NSURL'))
+        self.assertRaises(AttributeError, getattr, mod, 'NSNonExistingClass')
 
 
-    def no_test_hack(self):
-        # This is not really a test, but ensures that the code is at
-        # least exercised a little.
-        # There still have to be real tests...
-        try:
-            import CoreFoundation
-            import Foundation
-            import AppKit
-        except ImportError:
-            return
 
-        CoreFoundation.__all__
-        Foundation.__all__
-        AppKit.__all__
-        dir(Foundation)
+        self.assertEqual(set(dir(mod)), set(mod.__all__))
+        self.assertIn('NSRectClipList', mod.__dict__)
+        self.assertIn('NSRectClipList', mod.__all__)
+        self.assertIn('NSAccessibilityActionDescription', mod.__all__)
+        self.assertIn('mysum', mod.__all__)
+        self.assertIn('NSWorkspaceMoveOperation', mod.__all__)
+        self.assertNotIn('__doc__', mod.__all__)
 
-        try:
-            import Quartz
-        except ImportError:
-            return
+    def test_without_framework(self):
+        initial_dict = {
+                '__doc__': 'rootless test module',
+        }
+        metadict = {
+            'constants': '$ABAddressBookErrorDomain$',
+            'constants_dict': {
+                'ABMultiValueIdentifiersErrorKey': '@',
+            },
+            'enums': '$NSAWTEventType@16$NSAboveBottom@4$NSAboveTop@1$',
 
-        Quartz.__all__
+            'functions': {
+                'NSRectClipList': (
+                    sel32or64(b'v^{_NSRect={_NSPoint=ff}{_NSSize=ff}}i', b'v^{CGRect={CGPoint=dd}{CGSize=dd}}q'),
+                    '',
+                    {
+                        'arguments': {
+                            0: {
+                                'c_array_length_in_arg': 1,
+                                'type_modifier': b'n'
+                            }
+                        }
+                    }
+                ),
+                'NSAccessibilityActionDescription': (
+                    b'@@', '', {}
+                ),
+            },
+            'aliases': {
+                'doc_string': '__doc__',
+            },
+            'expressions': {
+                'mysum': 'NSAWTEventType + NSAboveBottom + 3',
+            }
+        }
 
+        mod = objc.ObjCLazyModule ('RootLess', None, None, metadict, None,
+                initial_dict, ())
+
+        self.assertEqual(mod.__doc__, 'rootless test module')
+        self.assertEqual(mod.__doc__, mod.doc_string)
+        self.assertEqual(mod.NSAboveBottom, 4)
+        self.assertEqual(mod.mysum, mod.NSAWTEventType + mod.NSAboveBottom + 3)
+        self.assertRaises(AttributeError, getattr, mod, 'NSRectClipList')
+        self.assertRaises(AttributeError, getattr, mod, 'ABAddressBookErrorDomain')
+        self.assertRaises(AttributeError, getattr, mod, 'ABMultiValueIdentifiersErrorKey')
+
+
+    def test_with_parents(self):
+        mod = objc.ObjCLazyModule ('RootLess', None, None, None, None,
+                None, (sys, os))
+
+        self.assertEqual(mod.path, sys.path)
+        self.assertIn('path', mod.__dict__)
+        self.assertEqual(mod.unlink, os.unlink)
+        self.assertIn('unlink', mod.__dict__)
+
+        mod.__dict__['version_info'] = 42
+        self.assertEqual(mod.version_info, 42)
+
+        self.assertIn('walk', mod.__all__)
+        self.assertIn('version', mod.__all__)
+        self.assertNotIn('__doc__', mod.__all__)
+
+    def test_all_clearing(self):
+        metadict = {
+            'enums': '$NSAWTEventType@16$NSAboveBottom@4$NSAboveTop@1$',
+        }
+
+        initial_dict = {
+                '__doc__': 'AppKit test module',
+        }
+
+        mod = objc.ObjCLazyModule ('AppKit', None, '/System/Library/Frameworks/AppKit.framework', metadict, None,
+                initial_dict, (sys,))
+        self.assertIsInstance(mod, objc.ObjCLazyModule)
+
+        mod.__all__ = 42
+        self.assertIs(mod.path, sys.path)
+        self.assertNotIn('__all__', mod.__dict__)
+
+        mod.__all__ = 42
+        self.assertEqual(mod.NSAWTEventType, 16)
+        self.assertNotIn('__all__', mod.__dict__)
+
+        mod.__all__ = 42
+        self.assertIs(mod.NSObject, objc.lookUpClass('NSObject'))
+        self.assertNotIn('__all__', mod.__dict__)
+
+        self.assertTrue('NSAWTEventType' in mod.__all__)
+        self.assertTrue('NSAboveBottom' in mod.__all__)
+
+    def test_enum_formats(self):
+        metadict = {
+            'enums': '$intval@16$floatval@4.5$charval@\'1234\'$floatval2@1e3$',
+        }
+
+        initial_dict = {
+                '__doc__': 'AppKit test module',
+        }
+
+        mod = objc.ObjCLazyModule ('AppKit', None, '/System/Library/Frameworks/AppKit.framework', metadict, None,
+                initial_dict, ())
+        self.assertIsInstance(mod, objc.ObjCLazyModule)
+
+        self.assertEqual(mod.intval, 16)
+        self.assertEqual(mod.floatval, 4.5)
+        self.assertEqual(mod.charval, struct.unpack('>l', b'1234')[0])
+        self.assertEqual(mod.floatval2, 1.0e3)
+
+    def test_magic_aliases(self):
+        metadict = {
+            'aliases': {
+                'umax': 'ULONG_MAX',
+                'max': 'LONG_MAX',
+                'min': 'LONG_MIN',
+            }
+        }
+
+        initial_dict = {
+                '__doc__': 'AppKit test module',
+        }
+
+        mod = objc.ObjCLazyModule ('AppKit', None, '/System/Library/Frameworks/AppKit.framework', metadict, None,
+                initial_dict, ())
+        self.assertIsInstance(mod, objc.ObjCLazyModule)
+
+        if sys.maxsize > 2**32:
+            self.assertEqual(mod.umax, 2**64-1)
+        else:
+            self.assertEqual(mod.umax, 2**32-1)
+        self.assertEqual(mod.max, sys.maxsize)
+        self.assertEqual(mod.min, -sys.maxsize-1)
 
 
     # XXX: add tests for the rest of the module