Commits

Ronald Oussoren committed 29bf941

* Improved test coverage

* Start splitting metadata documentation from the
main documentation (and while doing that add more
information).

The metadata documentation is far from complete
right now.

  • Participants
  • Parent commits fc8686f

Comments (0)

Files changed (11)

pyobjc-core/Doc/index.rst

    fsref-fsspec
    xcode
    blocks
+   metadata/index
    lib/index
    dev/index
 

pyobjc-core/Doc/lib/module-objc.rst

 Framework wrappers
 ..................
 
-.. function:: setSignatureForSelector(class_name, selector, signature)
-
-   .. deprecated:: 2.3
-
-      Use the metadata system instead
-
-   Register a replacement signature for a specific selector. This can
-   be used to provide a more exact signature for a method.
-      
 .. function:: pyobjc_id(obj)
 
    Returns the address of the underlying object as an integer.
 
    .. todo:: insert reference to metadata documentation
 
-.. function:: parseBridgeSupport(xmldata, globals, frameworkName[, dylib_path[, inlineTab[, bundle]]])
-
-   Load a `BridgeSupport XML file <http://developer.apple.com/library/mac/#documentation/Darwin/Reference/ManPages/man5/BridgeSupport.5.html>`_
-   with metadata for a framework.
-
-   The definitions from the framework will be added to the *globals* dictionary. *Dylib_path* is an optional path to a shared library
-   ("dylib") with additional definitions, *inlineTab* is an optional capsule object with function definitions (see :func:`loadFunctionList` for
-   more information on the capsule). The *bundle* argument is used to load global variables.
-   
-   .. note::
-
-      This function is primarily present for backward compatibility and for users that need an easy way to wrap their own Objective-C code.
-      PyObjC itself uses a different metadata mechanism that's better tuned to the needs of PyObjC.
-
-   .. versionchanged:: 2.4
-      This function is not present in PyObjC 2.4
-
-   .. versionchanged:: 2.5
-      The function is available again in PyObjC 2.5, and adds the *bundle* argument.
-
-
 .. function:: registerListType(type)
 
    Register *type* as a list-like type that will be bridged to Objective-C as an NSArray subclass.
    Register *type* as a dict-like type that will be bridged to Objective-C as an NSDictionary subclass.
 
 
-.. function:: initFrameworkWrapper(frameworkName, frameworkPath, frameworkIdentifier, globals[, inlineTab [, scan_classes[, frameworkResourceName]]])
-
-   Load the named framework using the identifier if that has result otherwise
-   using the path. Also loads the information in the bridgesupport file (
-   either one embedded in the framework or one next to the module that
-   called :func:`initFrameworkWrapper`).
-
-   This can be used to create a wrapper module for an Objective-C framework::
-
-       import objc
-       __bundle__ = objc.initFrameworkWrapper(
-            "CFNetwork",
-            frameworkIdentifier="com.apple.CFNetwork",
-            frameworkPath=objc.pathForFramework(
-               "/System/Library/Frameworks/CoreServices.framework/Frameworks/CFNetwork.framework"),
-               globals=globals())
-
-   .. note::
-      
-      This mechanism is deprecated, PyObjC uses a more efficient mechanism for the framework wrappers that
-      are shipped by the project. See :class:`ObjCLazyModule` for more information.
-
 .. function:: addConvenienceForSelector(selector, methods)
 
     Add a list of method to every class that has *selector* as a selector.
       and has less oportunity to change things.
 
 
-.. class:: ObjCLazyModule(name, frameworkIdentifier, frameworkPath, metadict, [inline_list[, initialdict[, parents]]])
-
-   A subclass of the built-in :class:`module` type that adds lazy-loading of values defined
-   in PyObjC metadata.
-
-   :param frameworkIdentifier: the *bundle_identifier* argument for a call to :func:`loadBundle`
-   :param frameworkPath:       the *bundle_path* argument for a call to :func:`loadBundle`
-   :param metadict:            the dictionary with metadata, usually the \__dict__ of a module generated by 
-                               the metadata compiler.
-   :param inline_list:         a capsule object with function definitions, see :func:`loadFunctionList` for more information.
-   :param initial_dict:        additional values to add to the module dictionary
-   :param parents:             a list of parent modules, the module behaves as if those modules were imported using
-                               ``from parent parent import *``, but lazily fetches definitions on first access.
-
-   .. note::
-
-      This is the primary entry point for the framework wrappers shipped with PyObjC.
-
-   .. todo::
-
-      Add references to more information on the PyObjC metadata system, in particular the metadata format
-      and the collection and compilation tools.
-
 Types
 -----
 
    that it gets the right method signature in the Objective-C runtime.
 
    The conventions for accessor names that can be used with Key-Value Coding
-   is described in `the Apple documentation for Key-Value Coding`_
+   is described in the `Apple documentation for Key-Value Coding`_
 
    The table below describes the convention for methods for a property named '<property>',
    with a short description and notes. The `Apple documentation for Key-Value Coding`_ 
       Added support for unordered properties. Also fixed some issues for 64-bit
       builds.
 
-.. _`the Apple documentation for Key-Value Coding`: http://developer.apple.com/library/ios/#documentation/cocoa/conceptual/KeyValueCoding/Articles/AccessorConventions.html
+.. _`Apple documentation for Key-Value Coding`: http://developer.apple.com/library/ios/#documentation/cocoa/conceptual/KeyValueCoding/Articles/AccessorConventions.html
 
 .. function:: typedAccessor(valueType)
 
 
    .. note:: 
 
-      When you use a typed accessor you must also implement "setNilValueForKey_",
-      as described in `the Apple documentation for Key-Value Coding`_
+      When you use a typed accessor you must also implement "setNilValueForKey\_",
+      as described in the `Apple documentation for Key-Value Coding`_
 
 .. function:: typedSelector(signature)
 
 
    .. code-block:: python
 
-       @objc.callbackFor(NSArray.sortedArrayUsingFunction_context_)
+       @objc.callbackFor(NSArray.sortedArrayUsingFunction_context\_)
        def compare(left, right, context):
            return 1
 

pyobjc-core/Doc/metadata/bridgesupport.rst

+BridgeSupport XML files
+=======================
+
+.. py:currentmodule:: objc
+
+
+Introduction
+------------
+
+PyObjC 2.0 introduced a way to load enhanced API descriptions 
+from XML "bridgesupport" files. The format of this file is shared
+between a number of bridges, although later releases of PyObjC added
+more capabilities that aren't in the official format.
+
+As of PyObjC 2.4 [1]_ use of bridgesupport files is deprecated, the 
+:doc:`compiled metadata system <compiled>` allows for faster and lazy loading.
+
+Basic structure and use
+-----------------------
+
+Bridgesupport files are XML files with a root element "signatures" 
+that has child elements for various kind of API descriptions. The API
+descriptions contain information that cannot be extracted at runtime,
+such as the names and types of global variables (constants), enum labels,
+function prototypes and additional information about method signatures.
+
+PyObjC supports loading bridgesupport files when initializing a module
+using :func:`initFrameworkWrapper`, as well as parsing the contents of
+bridgesupport files using :func:`parseBridgeSupport`.
+
+The function :func:`initFrameworkWrapper` is basicly a one-step 
+solution for wrapping a framework: it loads the framework bundle
+and bridgesupport file and then initializes the contents of the wrapper
+module.   Bridgesupport files are located using a search path:
+
+ 1. "PyObjC.brigesupport" next to the module calling
+    :func:`initFrameworkWrapper`. 
+
+ 2. A resource file inside the framework itself (with suffix
+    ".bridgesupport" and the same basename as the framework,
+    located in subdirectory "BridgeSupport").
+
+ 3. A resource name with the same basename as the framework
+    and suffix ".bridgesupport" in "/System/Library/BridgeSupport"
+
+    .. note::
+
+       PyObjC will not load bridgesupport files from
+       "/Library/BridgeSupport" or "~/Library/BridgeSupport"
+       to avoid depending on system-specif files that could
+       make a bundled PyObjC application non-portable.
+
+When a bridgesupport is loaded from the last two location
+the function also looks for "PyObjCOverrides.bridgesupport" next
+to the module that called :func:`initFrameworkWrapper`.
+
+
+Creating a library wrapper
+--------------------------
+
+The easiest way to create a framework wrapper using 
+bridgesupport files is to use a python package where
+the "__init__.py" contains a call of :func:`initFrameworkWrapper`
+
+.. sourcecode:: python
+
+   import objc as _objc
+
+   __bundle__ = _objc.initFrameworkWrapper("FrameworkName",
+                        frameworkIdentifier="com.apple.Framework",
+                        frameworkPath=_objc.pathForFramework("/System/Library/Frameworks/FrameworkName.framework")
+                        globals=globals())
+
+The framework name, identifier and path should be replaced by the
+information for the framework you are wrapping.
+
+The :func:`initFrameworkWrapper`  will load bridgesupport files from 
+a file named "PyObjC.bridgesupport" next to the "__init__.py" when
+such a file exist, and otherwise from the default location in the
+framework itself.
+
+
+Detailed file structure
+-----------------------
+
+.. todo:: fully describe the metadata supported by PyObjC
+
+.. seealso::
+
+   `BridgeSupport(5) <http://developer.apple.com/library/mac/#documentation/Darwin/Reference/ManPages/man5/BridgeSupport.5.html>`__
+     Apple manual page describing the metadata format
+
+
+API description
+---------------
+
+.. function:: parseBridgeSupport(xmldata, globals, frameworkName[, dylib_path[, inlineTab[, bundle]]])
+
+   Load a `BridgeSupport XML file <http://developer.apple.com/library/mac/#documentation/Darwin/Reference/ManPages/man5/BridgeSupport.5.html>`_
+   with metadata for a framework.
+
+   The definitions from the framework will be added to the *globals* dictionary. *Dylib_path* is an optional path to a shared library
+   "dylib") with additional definitions, *inlineTab* is an optional capsule object with function definitions (see :func:`loadFunctionList` for
+   more information on the capsule). The *bundle* argument is used to load global variables.
+                  
+   .. note::
+
+      This function is primarily present for backward compatibility and for users that need an easy way to wrap their own Objective-C code.
+      PyObjC itself uses a different metadata mechanism that's better tuned to the needs of PyObjC.
+
+   .. versionchanged:: 2.4
+      This function is not present in PyObjC 2.4
+
+   .. versionchanged:: 2.5
+      The function is available again in PyObjC 2.5, and adds the *bundle* argument.
+
+
+
+.. function:: initFrameworkWrapper(frameworkName, frameworkPath, frameworkIdentifier, globals[, inlineTab [, scan_classes[, frameworkResourceName]]])
+
+   Load the named framework using the identifier if that has result otherwise
+   using the path. Also loads the information in the bridgesupport file (
+   either one embedded in the framework or one next to the module that
+   called :func:`initFrameworkWrapper`).
+
+   See `Basic structure and use`_ for more information on the way this
+   function loads for bridgesupport files.
+
+
+.. rubric:: Footnotes
+
+.. [1] Technically, deprecation started in PyObjC 2.5, the bridgesupport 
+       system was temporarily removed in PyObjC 2.4.

pyobjc-core/Doc/metadata/compiled.rst

+Compiled metadata system
+========================
+
+.. py:currentmodule:: objc
+
+
+Starting with version 2.4 PyObjC provides a more efficient, lazy loading
+metadata system. This can greatly reduce the memory use and startup time
+for PyObjC based applications while still providing full access to
+Cocoa APIs.
+
+Creating a framework wrapper
+----------------------------
+
+A framework wrapper with the new metadata system is always a 
+python package. The package contains an "__init__.py" file
+that creates the lazy loader, a "_metadata.py" file with the
+compiled metadata and optionally other modules and extensions.
+
+The general structure of the "__init__.py" file is:
+
+.. sourcecode:: python
+
+   import sys, objc
+   import Foundation
+   from . import _metadata
+
+   sys.modules['FrameworkName'] = objc.ObjCLazyModule('FrameworkName',
+        "com.apple.FrameworkName', 
+        objc.pathForFramework("/System/Library/Frameworks/FrameworkName.framework"),
+        _metadata.__dict__, None, {
+            '__doc__': __doc__,
+            'objc': objc,
+            '__path__': __path__,
+        }, (Foundation,))
+
+The framework name, identifier and path should be replaced by
+the correct values for the wrapped framework. The import of "Foundation"
+can be replaced by imports of other framework this framework relies on
+(also add those to the last argument of :class:`objc.ObjCLazyModule`).
+
+Contents of the "_metadata" module
+----------------------------------
+
+The exact contents of the "_metadata" module will be described later.
+
+Generating the "_metadata" module
+---------------------------------
+
+The "objective-metadata" project contains a tool for collecting information
+about frameworks and compiling that information and manual additions into
+a "_metadata" module.
+
+.. todo:: include reference to that project and its documentation
+
+API description
+---------------
+
+.. class:: ObjCLazyModule(name, frameworkIdentifier, frameworkPath, metadict, [inline_list[, initialdict[, parents]]])
+
+   A subclass of the built-in :class:`module` type that adds lazy-loading of values defined
+   in PyObjC metadata.
+
+   :param frameworkIdentifier: the *bundle_identifier* argument for a call to :func:`loadBundle`
+   :param frameworkPath:       the *bundle_path* argument for a call to :func:`loadBundle`
+   :param metadict:            the dictionary with metadata, usually the \__dict__ of a module generated by 
+                               the metadata compiler.
+   :param inline_list:         a capsule object with function definitions, see :func:`loadFunctionList` for more information.
+   :param initial_dict:        additional values to add to the module dictionary
+   :param parents:             a list of parent modules, the module behaves as if those modules were imported using
+                               ``from parent parent import *``, but lazily fetches definitions on first access.
+
+   .. note::
+
+      This is the primary entry point for the framework wrappers shipped with PyObjC.

pyobjc-core/Doc/metadata/index.rst

+PyObjC metadata system
+======================
+
+The Objective-C runtime does not expose 
+enough information to provide completely 
+automatic bindings of all APIs.  Because of 
+this PyObjC needs a way to load additional
+information about APIs.
+
+The additianal information is loaded through
+a metadata system, the documents below describe
+the methods by which the metadata can be loaded.
+  
+.. toctree::
+   :maxdepth: 1
+
+   bridgesupport
+   compiled
+   manual

pyobjc-core/Doc/metadata/manual.rst

+Manual metadata loading
+=======================
+
+.. py:currentmodule:: objc
+
+Introduction
+------------
+
+When the other two metadata systems aren't suitable it
+is also possible to load metadata through code. The other
+two systems use the functionality described in this section
+to actually load the metadata.
+
+.. seealso::
+
+   :doc:`bridgesupport`
+     Loading metadata from XML files
+
+   :doc:`compiled`
+     Loading metadata using compiled files
+
+
+API description
+---------------
+
+For now see :doc:`the main API description <lib/module-objc` for the
+functions that are used to load metadata.
+
+The contains of the metadata dictionary argument for 
+:func:`registerMetaDataForSelector` is not documented at the moment.
+
+
+Deprecated APIs
+---------------
+
+.. function:: setSignatureForSelector(class_name, selector, signature)
+
+   .. deprecated:: 2.3
+
+   Use the metadata system instead
+
+   Register a replacement signature for a specific selector. This can
+   be used to provide a more exact signature for a method.

pyobjc-core/Lib/PyObjCTools/TestSupport.py

     if not _cache:
 
         cflags = _get_config_var('CFLAGS')
-        m = _re.search('-isysroot ([^ ]*) ', cflags)
+        m = _re.search('-isysroot\s+([^ ]*)(\s|$)', cflags)
         if m is None:
+            _cache.append(None)
             return None
 
 
         path = m.group(1)
         if path == '/':
-            return tuple(map(int, os_release().split('.')))
+            result = tuple(map(int, os_release().split('.')))
+            _cache.append(result)
+            return result
 
         bn = _os.path.basename(path)
         version = bn[6:-4]
             version = version[:-1]
 
 
-        return tuple(map(int, version.split('.')))
+        result =  tuple(map(int, version.split('.')))
+        _cache.append(result)
+        return result
 
     return _cache[0]
 
     """
     Return True if we're running in 32-bit mode
     """
-    if hasattr(_sys, 'maxint'):
-        # Python 2.5 or earlier
-        if _sys.maxint > 2 ** 32:
-            return False
-    else:
-        if _sys.maxsize > 2 ** 32:
-            return False
+    if _sys.maxsize > 2 ** 32:
+        return False
     return True
 
 def onlyIf(expr, message=None):

pyobjc-core/Lib/objc/_bridgesupport.py

 # TODO: parseBridgeSupport (and its support class) is a 
 #       basic port from C, check if it can be simplified.
 
+# NOTE: This search path only contains system locations to
+# avoid accidently reiying on system-specific functionality.
+BRIDGESUPPORT_DIRECTORIES = [
+    '/System/Library/BridgeSupport',
+]
+
 _DEFAULT_SUGGESTION="don't use this method"
 _BOOLEAN_ATTRIBUTES=[
     "already_retained",
     # If there is no metadata there look for metadata in the standard Library
     # locations
     fn = frameworkName + '.bridgesupport'
-    for dn in _gBridgeSupportDirectories:
+    for dn in BRIDGESUPPORT_DIRECTORIES:
         path = os.path.join(dn, fn)
         if os.path.exists(path):
             data = open(path, 'rb').read()

pyobjc-core/Modules/objc/method-signature.m

 	}
 
 	if (meta != NULL && !PyDict_Check(meta)) {
-		PyErr_SetString(PyExc_TypeError, "invalid metadata");
+		PyObject* r = PyObject_Repr(meta);
+		if (r == NULL) {
+			return -1;
+		}
+		PyErr_Format(PyExc_TypeError, "metadata of type %s: %s",
+				Py_TYPE(meta)->tp_name,
+				PyText_AsString(r));
+		Py_DECREF(r);
+
 		return -1;
 	}
 

pyobjc-core/PyObjCTest/test_callbacks.py

     }
 )
 
+objc.registerMetaDataForSelector(
+    b"OC_CallbackTest", b"selWithCallback:", {
+        "arguments": {
+            2:  {
+                "callable": {
+                    "retval": { "type": b"q", },
+                    "arguments": {
+                        "0":    { "type": b"@", },
+                        "1":    { "type": b"i", },
+                    }
+                }
+            },
+        }
+    }
+)
+
+objc.registerMetaDataForSelector(
+    b"OC_CallbackTest", b"selWithCallback2:", {
+        "arguments": {
+            2:  {
+                "callable": {
+                    "retval": { "type": b"d", },
+                    "arguments": {
+                    }
+                }
+            },
+        }
+    }
+)
+
+objc.registerMetaDataForSelector(
+    b"OC_CallbackTest", b"selWithCallback:andCallback:", {
+        "arguments": {
+            2:  {
+                "callable": {
+                    "arguments": {
+                        "0":    { "type": b"@", },
+                    }
+                }
+            },
+            3:  {
+                "callable": {
+                    "retval":   { "type": b"@", },
+                    "arguments": {
+                        "0":    { "type": b"q", },
+                        "1":    { "type": b"^v", },
+                    }
+                }
+            },
+        }
+    }
+)
+
 class OC_CallbackTest (objc.lookUpClass("NSObject")):
     @objc.typedSelector(b"v@:" + objc._C_SEL + objc._C_SEL + b"@")
     def selWithSEL_SEL_andObject_(self, s1, s2, o):
         pass
 
+    @objc.typedSelector(b"v@:" + objc._C_SEL)
     def selWithoutSEL_(self, o):
         pass
 
+    @objc.typedSelector(b"v@:?")
+    def selWithCallback_(self, cb):
+        pass
+
+    @objc.typedSelector(b"v@:?")
+    def selWithCallback2_(self, cb):
+        pass
+
+    @objc.typedSelector(b"v@:??")
+    def selWithCallback_andCallback_(self, cb1, cb2):
+        pass
+
+
+
 
 class TestClosure (TestCase):
     # tests for objc._makeClosure
         self.assertRaises(ValueError, objc._makeClosure, function, OC_CallbackTest.selWithSEL_SEL_andObject_,  -1)
         self.assertRaises(ValueError, objc._makeClosure, function, OC_CallbackTest.selWithoutSEL_,  -1)
 
-    # create and use closure for 
-    # - function
-    # - selector
-    # - different kinds of arguments
+    def testCreatingCallbacks(self):
+        def function(*arg):
+            pass
+        cl = objc._makeClosure(function, OC_CallbackTest.selWithCallback_, -1)
+        self.assertIn(' "objc.__imp__" ', repr(cl))
+        cl = objc._makeClosure(function, OC_CallbackTest.selWithCallback2_, -1)
+        self.assertIn(' "objc.__imp__" ', repr(cl))
+        cl = objc._makeClosure(function, OC_CallbackTest.selWithCallback_andCallback_, -1)
+        self.assertIn(' "objc.__imp__" ', repr(cl))
+        cl = objc._makeClosure(function, OC_CallbackTest.selWithCallback_andCallback_, 2)
+        self.assertIn(' "objc.__imp__" ', repr(cl))
+        cl = objc._makeClosure(function, OC_CallbackTest.selWithCallback_andCallback_, 3)
+        self.assertIn(' "objc.__imp__" ', repr(cl))
+
+        self.assertRaises(objc.BadPrototypeError, objc._makeClosure, lambda a: None, OC_CallbackTest.selWithCallback_, -1)
+        objc._makeClosure(lambda a, b: None, OC_CallbackTest.selWithCallback_, -1)
+        self.assertRaises(objc.BadPrototypeError, objc._makeClosure, lambda a, b, c: None, OC_CallbackTest.selWithCallback_, -1)
+
+    # TODO: Verify that C code has proper coverage
 
 
 class TestCallbackFor (TestCase):
         self.assertRaises(ValueError, objc.callbackFor(OC_CallbackTest.selWithSEL_SEL_andObject_), function)
         self.assertRaises(ValueError, objc.callbackFor(OC_CallbackTest.selWithoutSEL_), function)
 
+    def testCreatingCallbacks(self):
+        def function(*arg):
+            pass
 
+        @objc.callbackFor(OC_CallbackTest.selWithCallback_)
+        def function(arg1, arg2):
+            pass
+        self.assertIn(' "objc.__imp__" ', repr(function.pyobjc_closure))
+
+        @objc.callbackFor(OC_CallbackTest.selWithCallback2_)
+        def function():
+            pass
+        self.assertIn(' "objc.__imp__" ', repr(function.pyobjc_closure))
+
+        @objc.callbackFor(OC_CallbackTest.selWithCallback_andCallback_, -1)
+        def function(a):
+            pass
+        self.assertIn(' "objc.__imp__" ', repr(function.pyobjc_closure))
+
+        @objc.callbackFor(OC_CallbackTest.selWithCallback_andCallback_, 2)
+        def function(a):
+            pass
+        self.assertIn(' "objc.__imp__" ', repr(function.pyobjc_closure))
+
+        @objc.callbackFor(OC_CallbackTest.selWithCallback_andCallback_, 3)
+        def function(a, b):
+            pass
+        self.assertIn(' "objc.__imp__" ', repr(function.pyobjc_closure))
+
+        self.assertRaises(objc.BadPrototypeError, objc.callbackFor(OC_CallbackTest.selWithCallback_), lambda a: None)
+        self.assertRaises(objc.BadPrototypeError, objc.callbackFor(OC_CallbackTest.selWithCallback_), lambda a,b,c: None)
+
+    # TODO: add tests that actually call the callback
 
 class TestSelectorFor (TestCase):
     # tests for objc.selectorFor

pyobjc-core/PyObjCTest/test_testsupport.py

 from PyObjCTools.TestSupport import *
 import unittest
 import objc
+import sys
+
+from PyObjCTools import TestSupport
 
 class Method(object):
     def __init__(self, argno, meta, selector=False):
 
 class TestTestSupport (TestCase):
 
+    def test_sdkForPython(self):
+        orig_get_config_var = TestSupport._get_config_var
+        try:
+            config_result = ''
+            def get_config_var(value):
+                if value != 'CFLAGS':
+                    raise KeyError(value)
+
+                return config_result
+
+            TestSupport._get_config_var = get_config_var
+            cache = sdkForPython.func_defaults[0]
+
+            config_result = ''
+            self.assertEqual(sdkForPython(), None)
+            self.assertEqual(cache, [None])
+            self.assertEqual(sdkForPython(), None)
+            self.assertEqual(cache, [None])
+
+            cache[:] = []
+
+            config_result = '-isysroot /Developer/SDKs/MacOSX10.6.sdk'
+            self.assertEqual(sdkForPython(), (10, 6))
+            self.assertEqual(cache, [(10,6)])
+            self.assertEqual(sdkForPython(), (10, 6))
+            self.assertEqual(cache, [(10,6)])
+
+            cache[:] = []
+
+            config_result = '-isysroot /'
+            os_rel = tuple(map(int, os_release().split('.')))
+            self.assertEqual(sdkForPython(), os_rel)
+            self.assertEqual(cache, [os_rel])
+            self.assertEqual(sdkForPython(), os_rel)
+            self.assertEqual(cache, [os_rel])
+
+            cache[:] = []
+
+            config_result = '-dynamic -isysroot /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.4u.sdk -arch i386 -arch x86_64'
+            self.assertEqual(sdkForPython(), (10,4))
+            self.assertEqual(cache, [(10,4)])
+            self.assertEqual(sdkForPython(), (10,4))
+            self.assertEqual(cache, [(10,4)])
+
+            cache[:] = []
+
+            config_result = '-dynamic -isysroot /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.10.sdk -arch i386 -arch x86_64'
+            self.assertEqual(sdkForPython(), (10,10))
+            self.assertEqual(cache, [(10,10)])
+            self.assertEqual(sdkForPython(), (10,10))
+            self.assertEqual(cache, [(10,10)])
+
+            cache[:] = []
+
+        finally:
+            TestSupport._get_config_var = orig_get_config_var
+
+    def test_os_release(self):
+        import platform
+        TestSupport._os_release = '10.10'
+        self.assertEqual(os_release(), '10.10')
+        TestSupport._os_release = None
+
+        self.assertEqual(TestSupport.os_release(), '.'.join(platform.mac_ver()[0].split('.')[:2]))
+
+    def test_fourcc(self):
+        import struct
+        self.assertEqual(fourcc('abcd'), struct.unpack('>i', 'abcd')[0])
+
+    def testIs32Bit(self):
+        orig = sys.maxsize
+        try:
+            sys.maxsize = 2 ** 31 -1
+            self.assertTrue(is32Bit())
+
+            sys.maxsize = 2 ** 63 -1
+            self.assertFalse(is32Bit())
+
+        finally:
+            sys.maxsize = orig
+
+
     def test_assert_opaque(self):
         self.assertRaises(AssertionError, self.assertIsOpaquePointer, long)