Commits

Ronald Oussoren committed 3141c24

Commit the code that is actually used

  • Participants
  • Parent commits e75633c

Comments (0)

Files changed (13)

+import os
+
+_basedir = os.path.dirname(os.path.abspath(__file__))
+STATE_DIR=os.path.join(_basedir, "state")
+HTML_DIR=os.path.join(_basedir, "html")
+TMPL_DIR=os.path.join(_basedir, "templates")
+STATIC_DIR=os.path.join(_basedir, "static")
+
+
+PREFIXES={}
+
+_framework="/Library/Frameworks/PythonDev.framework"
+for version in os.listdir(os.path.join(_framework, 'Versions')):
+    if version == 'Current':
+        continue
+
+    PREFIXES[version] = os.path.join(_framework, 'Versions', version)
+
+SOURCE_DIR="/Users/ronald/Projects/pyobjc-hg"
+
+PROJECTS=[
+    p for p in os.listdir(os.path.join(SOURCE_DIR, 'pyobjc'))
+      if p.startswith('pyobjc-') 
+         and os.path.isdir(os.path.join(SOURCE_DIR, 'pyobjc', p))
+]

File genreport.py

+#!/usr/bin/python
+import os
+import json
+import jinja2
+import shutil
+import collections
+
+import config
+
+def create_output_directory():
+    """ Recreate the output directory and add static resources """
+    if os.path.exists(config.HTML_DIR):
+        shutil.rmtree(config.HTML_DIR)
+
+    if not os.path.exists(os.path.dirname(config.HTML_DIR)):
+        os.makedirs(os.path.dirname(config.HTML_DIR))
+
+    shutil.copytree(config.STATIC_DIR, config.HTML_DIR)
+
+def parse_test_results():
+    """ Parse the tree with test results """
+    result = {}
+    
+    for osx_release in os.listdir(config.STATE_DIR):
+        osx_data = result[osx_release] = {}
+
+        with open(os.path.join(config.STATE_DIR, osx_release, 'repository.state'), 'rU') as fp:
+            osx_data.update(json.load(fp))
+
+        osx_data['python'] = {}
+        
+        topdir = os.path.join(config.STATE_DIR, osx_release)
+        for python in os.listdir(topdir):
+            if python == 'repository.state':
+                continue
+
+            version, arch = python.split('-', 1)
+            if version in osx_data['python']:
+                python_info = osx_data['python'][version]
+            else:
+                python_info = osx_data['python'][version] = {}
+
+            python_info[arch] = {}
+            for fn in os.listdir(os.path.join(topdir, python)):
+                if not fn.endswith('status'): continue
+                
+                with open(os.path.join(topdir, python, fn), 'rU') as fp:
+                    status = json.load(fp)
+
+                python_info[arch][fn[:-7]] = {
+                    'status': status,
+                    'stderr': os.path.join(topdir, python, fn[:-7]+ '.stderr'),
+                    'stdout': os.path.join(topdir, python, fn[:-7]+ '.stdout'),
+                }
+
+    return result
+
+
+def add_summary(testresults):
+    """ add summary information to the tree """
+    testresults['archs'] = set()
+    testresults['pyver'] = set()
+    testresults['byproject'] = projsummary = {}
+
+    for osx_release in testresults:
+        if not osx_release[0].isdigit(): continue
+        testresults[osx_release]['archs'] = set()
+        testresults[osx_release]['projnames'] = set()
+
+        for pyver in testresults[osx_release]['python']:
+            
+            pyinfo = testresults[osx_release]['python'][pyver]
+            testresults[osx_release]['archs'].update(pyinfo.keys())
+            testresults['archs'].update(pyinfo.keys())
+            testresults['pyver'].add(pyver)
+
+            pysummary = collections.defaultdict(int)
+
+
+            for arch in pyinfo:
+                summary = collections.defaultdict(int)
+                for proj in pyinfo[arch]:
+                    testresults[osx_release]['projnames'].add(proj)
+                    if proj not in projsummary:
+                        projsummary[proj] = {}
+
+                    if osx_release not in projsummary[proj]:
+                        projsummary[proj][osx_release] = {}
+
+                    if pyver not in projsummary[proj][osx_release]:
+                        projsummary[proj][osx_release][pyver] = collections.defaultdict(int)
+
+                    for key, val in pyinfo[arch][proj]['status'].items():
+                        if not isinstance(val, int): continue
+                        if key == 'exitcode':
+                            if val != 0:
+                                summary['crash'] += 1
+                                pysummary['crash'] += 1
+                                projsummary[proj][osx_release][pyver]['crash'] += 1
+                                pyinfo[arch][proj]['status']['crash'] = 1
+                                pyinfo[arch][proj]['status']['count'] = 1
+                        else:
+                            summary[key] += val
+                            pysummary[key] += val
+                            projsummary[proj][osx_release][pyver][key] += val
+
+                    if pyinfo[arch][proj]['status'].get('crash'):
+                        pyinfo[arch][proj]['status']['status'] = 'crash'
+
+                    elif pyinfo[arch][proj]['status'].get('fails'):
+                        pyinfo[arch][proj]['status']['status'] = 'fail'
+
+                    elif pyinfo[arch][proj]['status'].get('errors'):
+                        pyinfo[arch][proj]['status']['status'] = 'fail'
+
+                    elif pyinfo[arch][proj]['status'].get('xpass'):
+                        pyinfo[arch][proj]['status']['status'] = 'fail'
+
+                    else:
+                        pyinfo[arch][proj]['status']['status'] = 'pass'
+                     
+                if summary['crash'] != 0:
+                    summary['status'] = 'crash'
+
+                elif summary['fails'] != 0:
+                    summary['status'] = 'fail'
+
+                elif summary['errors'] != 0:
+                    summary['status'] = 'fail'
+
+                elif summary['xpass'] != 0:
+                    summary['status'] = 'fail'
+
+                else:
+                    summary['status'] = 'pass'
+
+                pyinfo[arch]['summary']  = dict(summary)
+
+            if pysummary['crash'] != 0:
+                pysummary['status'] = 'crash'
+
+            elif pysummary['fails'] != 0:
+                pysummary['status'] = 'fail'
+
+            elif pysummary['errors'] != 0:
+                pysummary['status'] = 'fail'
+
+            else:
+                pysummary['status'] = 'pass'
+            
+            pyinfo['summary']  = dict(pysummary)
+
+        for proj in projsummary:
+            for osx_release in projsummary[proj]:
+                for pyver, stats in projsummary[proj][osx_release].items():
+                    if stats['crash'] != 0:
+                        stats['status'] = 'crash'
+
+                    elif stats['fails'] != 0:
+                        stats['status'] = 'fail'
+
+                    elif stats['errors'] != 0:
+                        stats['status'] = 'fail'
+
+                    else:
+                        stats['status'] = 'pass'
+                  
+
+def emit_start_page(testresults):
+    with open(os.path.join(config.TMPL_DIR, 'toplevel-index.html'), 'rU') as fp:
+        template = jinja2.Template(fp.read())
+    
+    with open(os.path.join(config.HTML_DIR, 'index.html'), 'w') as fp:
+        fp.write(template.render(
+            results=testresults,
+            sorted=sorted,
+            len=len,
+        ))
+
+def emit_osx_page(osx_release, results):
+    with open(os.path.join(config.TMPL_DIR, 'osx-index.html'), 'rU') as fp:
+        template = jinja2.Template(fp.read())
+
+
+    dirname = os.path.join(config.HTML_DIR, "osx-%s"%(osx_release,))
+    os.mkdir(dirname)
+    
+    with open(os.path.join(dirname, 'index.html'), 'w') as fp:
+        fp.write(template.render(
+            osx_release=osx_release,
+            results=results,
+            sorted=sorted,
+            len=len,
+        ))
+
+    for proj in results['projnames']:
+        for arch in results['archs']:
+            for pyver in results['python']:
+                for kind in ('stdout', 'stderr'):
+                    in_path = results['python'][pyver][arch][proj][kind]
+                    out_path = os.path.join(dirname, "%s-%s-%s-%s.txt"%(proj, pyver, arch, kind))
+                    shutil.copy(in_path, out_path)
+
+
+
+
+def main():
+    create_output_directory()
+    
+    test_results = parse_test_results()
+    add_summary(test_results)
+
+    emit_start_page(test_results)
+    for osx_release in sorted(test_results):
+        if not osx_release[0].isdigit(): continue
+        emit_osx_page(osx_release, test_results[osx_release])
+
+if __name__ == "__main__":
+    
+    #import pprint
+    #test_results = parse_test_results()
+    #add_summary(test_results)
+    #pprint.pprint(test_results)
+
+    main()

File installpkgs.py

+
+"""
+Helper script for running the PyObjC test suite
+
+TODO:
+* Generalize: same structure can also be used for the py2app tests
+* Auto detect architectures supported by python binaries
+* Configuration from ini-file
+"""
+import os
+import subprocess
+import contextlib
+import logging
+import shutil
+import sys
+import platform
+import json
+
+import config
+from runtests import sort_framework_wrappers, build_project
+
+def save_site_packages(prefix):
+    version = os.path.basename(prefix)
+    libdir = os.path.join(prefix, 'lib', 'python'+version)
+
+    site_packages = os.path.join(libdir, 'site-packages')
+
+    if os.path.exists(site_packages):
+        os.rename(site_packages, site_packages + '.saved')
+
+    shutil.copytree(site_packages + '.saved', site_packages)
+
+def restore_site_packages(prefix):
+    version = os.path.basename(prefix)
+    libdir = os.path.join(prefix, 'lib', 'python'+version)
+
+    site_packages = os.path.join(libdir, 'site-packages')
+            
+    if os.path.exists(site_packages + ".saved"):
+        if os.path.exists(site_packages):
+            shutil.rmtree(site_packages)
+        os.rename(site_packages + ".saved", site_packages)
+
+
+def main():
+    logging.basicConfig(file=sys.stdout, level=logging.DEBUG)
+
+    osx_release = platform.mac_ver()[0]
+
+    osx_dir = os.path.join(config.STATE_DIR, ".".join(osx_release.split(".")[:2]))
+
+    if len(sys.argv) != 2:
+        print 'Usage: %s [cleanup|install]'%(sys.argv[0],)
+        sys.exit(1)
+
+    elif sys.argv[1] == 'cleanup':
+        for py_ver in sorted(config.PREFIXES):
+            py_prefix = config.PREFIXES[py_ver]
+            restore_site_packages(py_prefix)
+
+    elif sys.argv[1] == 'install':
+        build_order = ['altgraph', 'modulegraph', 'macholib', 'py2app', 'pyobjc-core'] + sort_framework_wrappers()
+        for py_ver in sorted(config.PREFIXES):
+            py_prefix = config.PREFIXES[py_ver]
+            save_site_packages(py_prefix)
+
+            interpreter = os.path.join(py_prefix, 'bin', 'python')
+            if not os.path.exists(interpreter):
+                interpreter += '3'
+
+            for project in build_order:
+                build_project(interpreter, project)
+
+    else:
+        print 'Usage: %s [cleanup|install]'%(sys.argv[0],)
+        print
+        print "install - build and install pyobjc + related packages in clean site-packages"
+        print "cleanup - reset site-packages"
+        sys.exit(1)
+
+if __name__ == "__main__":
+    main()

File objective/__init__.py

Empty file removed.

File objective/__init__.pyc

Binary file removed.

File objective/testharness/__init__.py

-"""
-Test harness for running pyobjc tests
-"""

File objective/testharness/config.py

-import os
-
-_basedir = os.path.dirname(os.path.abspath(__file__))
-STATE_DIR=os.path.join(_basedir, "state")
-HTML_DIR=os.path.join(_basedir, "html")
-TMPL_DIR=os.path.join(_basedir, "templates")
-STATIC_DIR=os.path.join(_basedir, "static")
-
-
-PREFIXES={}
-
-_framework="/Library/Frameworks/PythonDev.framework"
-for version in os.listdir(os.path.join(_framework, 'Versions')):
-    if version == 'Current':
-        continue
-
-    PREFIXES[version] = os.path.join(_framework, 'Versions', version)
-
-SOURCE_DIR="/Users/ronald/Projects/pyobjc-hg/pyobjc"
-
-PROJECTS=[
-    p for p in os.listdir(SOURCE_DIR)
-      if p.startswith('pyobjc-') 
-         and os.path.isdir(os.path.join(SOURCE_DIR, p))
-]

File objective/testharness/genhtml.py

-#!/usr/bin/python
-import os
-import json
-import jinja2
-import shutil
-import collections
-
-import config
-
-def create_output_directory():
-    """ Recreate the output directory and add static resources """
-    if os.path.exists(config.HTML_DIR):
-        shutil.rmtree(config.HTML_DIR)
-
-    if not os.path.exists(os.path.dirname(config.HTML_DIR)):
-        os.makedirs(os.path.dirname(config.HTML_DIR))
-
-    shutil.copytree(config.STATIC_DIR, config.HTML_DIR)
-
-def parse_test_results():
-    """ Parse the tree with test results """
-    result = {}
-    
-    for osx_release in os.listdir(config.STATE_DIR):
-        osx_data = result[osx_release] = {}
-
-        with open(os.path.join(config.STATE_DIR, osx_release, 'repository.state'), 'rU') as fp:
-            osx_data.update(json.load(fp))
-
-        osx_data['python'] = {}
-        
-        topdir = os.path.join(config.STATE_DIR, osx_release)
-        for python in os.listdir(topdir):
-            if python == 'repository.state':
-                continue
-
-            version, arch = python.split('-', 1)
-            if version in osx_data['python']:
-                python_info = osx_data['python'][version]
-            else:
-                python_info = osx_data['python'][version] = {}
-
-            python_info[arch] = {}
-            for fn in os.listdir(os.path.join(topdir, python)):
-                if not fn.endswith('status'): continue
-                
-                with open(os.path.join(topdir, python, fn), 'rU') as fp:
-                    status = json.load(fp)
-
-                python_info[arch][fn[:-7]] = {
-                    'status': status,
-                    'stderr': os.path.join(topdir, python, fn[:-7]+ '.stderr'),
-                    'stdout': os.path.join(topdir, python, fn[:-7]+ '.stdout'),
-                }
-
-    return result
-
-
-def add_summary(testresults):
-    """ add summary information to the tree """
-    testresults['archs'] = set()
-    testresults['pyver'] = set()
-    testresults['byproject'] = projsummary = {}
-
-    for osx_release in testresults:
-        if not osx_release[0].isdigit(): continue
-        testresults[osx_release]['archs'] = set()
-        testresults[osx_release]['projnames'] = set()
-
-        for pyver in testresults[osx_release]['python']:
-            
-            pyinfo = testresults[osx_release]['python'][pyver]
-            testresults[osx_release]['archs'].update(pyinfo.keys())
-            testresults['archs'].update(pyinfo.keys())
-            testresults['pyver'].add(pyver)
-
-            pysummary = collections.defaultdict(int)
-
-
-            for arch in pyinfo:
-                summary = collections.defaultdict(int)
-                for proj in pyinfo[arch]:
-                    testresults[osx_release]['projnames'].add(proj)
-                    if proj not in projsummary:
-                        projsummary[proj] = {}
-
-                    if osx_release not in projsummary[proj]:
-                        projsummary[proj][osx_release] = {}
-
-                    if pyver not in projsummary[proj][osx_release]:
-                        projsummary[proj][osx_release][pyver] = collections.defaultdict(int)
-
-                    for key, val in pyinfo[arch][proj]['status'].items():
-                        if not isinstance(val, int): continue
-                        if key == 'exitcode':
-                            if val != 0:
-                                summary['crash'] += 1
-                                pysummary['crash'] += 1
-                                projsummary[proj][osx_release][pyver]['crash'] += 1
-                                pyinfo[arch][proj]['status']['crash'] = 1
-                                pyinfo[arch][proj]['status']['count'] = 1
-                        else:
-                            summary[key] += val
-                            pysummary[key] += val
-                            projsummary[proj][osx_release][pyver][key] += val
-
-                    if pyinfo[arch][proj]['status'].get('crash'):
-                        pyinfo[arch][proj]['status']['status'] = 'crash'
-
-                    elif pyinfo[arch][proj]['status'].get('fails'):
-                        pyinfo[arch][proj]['status']['status'] = 'fail'
-
-                    elif pyinfo[arch][proj]['status'].get('errors'):
-                        pyinfo[arch][proj]['status']['status'] = 'fail'
-
-                    elif pyinfo[arch][proj]['status'].get('xpass'):
-                        pyinfo[arch][proj]['status']['status'] = 'fail'
-
-                    else:
-                        pyinfo[arch][proj]['status']['status'] = 'pass'
-                     
-                if summary['crash'] != 0:
-                    summary['status'] = 'crash'
-
-                elif summary['fails'] != 0:
-                    summary['status'] = 'fail'
-
-                elif summary['errors'] != 0:
-                    summary['status'] = 'fail'
-
-                elif summary['xpass'] != 0:
-                    summary['status'] = 'fail'
-
-                else:
-                    summary['status'] = 'pass'
-
-                pyinfo[arch]['summary']  = dict(summary)
-
-            if pysummary['crash'] != 0:
-                pysummary['status'] = 'crash'
-
-            elif pysummary['fails'] != 0:
-                pysummary['status'] = 'fail'
-
-            elif pysummary['errors'] != 0:
-                pysummary['status'] = 'fail'
-
-            else:
-                pysummary['status'] = 'pass'
-            
-            pyinfo['summary']  = dict(pysummary)
-
-        for proj in projsummary:
-            for osx_release in projsummary[proj]:
-                for pyver, stats in projsummary[proj][osx_release].items():
-                    if stats['crash'] != 0:
-                        stats['status'] = 'crash'
-
-                    elif stats['fails'] != 0:
-                        stats['status'] = 'fail'
-
-                    elif stats['errors'] != 0:
-                        stats['status'] = 'fail'
-
-                    else:
-                        stats['status'] = 'pass'
-                  
-
-def emit_start_page(testresults):
-    with open(os.path.join(config.TMPL_DIR, 'toplevel-index.html'), 'rU') as fp:
-        template = jinja2.Template(fp.read())
-    
-    with open(os.path.join(config.HTML_DIR, 'index.html'), 'w') as fp:
-        fp.write(template.render(
-            results=testresults,
-            sorted=sorted,
-            len=len,
-        ))
-
-def emit_osx_page(osx_release, results):
-    with open(os.path.join(config.TMPL_DIR, 'osx-index.html'), 'rU') as fp:
-        template = jinja2.Template(fp.read())
-
-
-    dirname = os.path.join(config.HTML_DIR, "osx-%s"%(osx_release,))
-    os.mkdir(dirname)
-    
-    with open(os.path.join(dirname, 'index.html'), 'w') as fp:
-        fp.write(template.render(
-            osx_release=osx_release,
-            results=results,
-            sorted=sorted,
-            len=len,
-        ))
-
-    for proj in results['projnames']:
-        for arch in results['archs']:
-            for pyver in results['python']:
-                for kind in ('stdout', 'stderr'):
-                    in_path = results['python'][pyver][arch][proj][kind]
-                    out_path = os.path.join(dirname, "%s-%s-%s-%s.txt"%(proj, pyver, arch, kind))
-                    shutil.copy(in_path, out_path)
-
-
-
-
-def main():
-    create_output_directory()
-    
-    test_results = parse_test_results()
-    add_summary(test_results)
-
-    emit_start_page(test_results)
-    for osx_release in sorted(test_results):
-        if not osx_release[0].isdigit(): continue
-        emit_osx_page(osx_release, test_results[osx_release])
-
-if __name__ == "__main__":
-    
-    #import pprint
-    #test_results = parse_test_results()
-    #add_summary(test_results)
-    #pprint.pprint(test_results)
-
-    main()

File objective/testharness/runtests.py

-"""
-Helper script for running the PyObjC test suite
-
-TODO:
-* Generalize: same structure can also be used for the py2app tests
-* Auto detect architectures supported by python binaries
-* Configuration from ini-file
-"""
-import os
-import subprocess
-import contextlib
-import logging
-import shutil
-import sys
-import platform
-import json
-
-import config
-from topsort import topological_sort
-
-
-@contextlib.contextmanager
-def saved_site_packges(prefix):
-    lg = logging.getLogger("saved_site_packages")
-
-    version = os.path.basename(prefix)
-    libdir = os.path.join(prefix, 'lib', 'python'+version)
-
-    site_packages = os.path.join(libdir, 'site-packages')
-
-    if os.path.exists(site_packages):
-        lg.info("save existing site_packages for %r", prefix)
-        os.rename(site_packages, site_packages + '.saved')
-
-    try:
-        shutil.copytree(site_packages + '.saved', site_packages)
-
-        yield
-
-
-    finally:
-        if os.path.exists(site_packages):
-            shutil.rmtree(site_packages)
-            
-        if os.path.exists(site_packages + ".saved"):
-            lg.info("restore existing site_packages for %r", prefix)
-            os.rename(site_packages + ".saved", site_packages)
-
-
-def sort_framework_wrappers():
-    """
-    Returns a list of framework wrappers in the order they should
-    be build in.
-    """
-    frameworks = []
-    partial_order = []
-
-    for subdir in config.PROJECTS:
-        if not subdir.startswith('pyobjc-framework-'): continue
-
-        setup = os.path.join(config.SOURCE_DIR, subdir, 'setup.py')
-
-        requires = None
-        with open(setup) as fp:
-            for ln in fp:
-                if requires is None:
-                    if ln.strip().startswith('install_requires'):
-                        requires = []
-                else:
-                    if ln.strip().startswith(']'):
-                        break
-
-                    dep = ln.strip()[1:-1]
-                    if dep.startswith('pyobjc-framework'):
-                        dep = dep.split('>')[0]
-                        requires.append(dep)
-
-        frameworks.append(subdir)
-        for dep in requires:
-            partial_order.append((dep, subdir))
-
-    frameworks = topological_sort(frameworks, partial_order)
-    return frameworks
-
-
-def build_project(interpreter, project):
-    lg = logging.getLogger("build_project")
-
-    proj_dir = os.path.join(config.SOURCE_DIR, project)
-
-    # First ask distutils to clean up
-    lg.info("cleaning %r using %r", project, interpreter)
-    status = subprocess.call(
-            [interpreter, "setup.py", "clean"],
-            cwd=proj_dir)
-    if status != 0:
-        lg.warning("clean %r failed (status %s)", project, status)
-        return False
-
-    # Drop the build directory, just in case...
-    #  (not strictly necessary, but can avoid issues when
-    #   using 2to3)
-    if os.path.exists(os.path.join(proj_dir, 'build')):
-        shutil.rmtree(os.path.join(proj_dir, 'build'))
-
-
-    # Finally perform the actual build
-    lg.info("building %r using %r", project, interpreter)
-    status = subprocess.call(
-            [interpreter, "setup.py", "install"],
-            cwd=proj_dir)
-    if status != 0:
-        lg.warning("build %r failed (status %s)", project, status)
-        return False
-
-    return True
-      
-
-def run_tests(interpreter, arch, project, state_dir):
-    lg = logging.getLogger("run_tests")
-
-    proj_dir = os.path.join(config.SOURCE_DIR, project)
-    
-    lg.info("testsing %r using %r (%7s)", project, interpreter, arch)
-    p = subprocess.Popen(
-            ["/usr/bin/arch", "-%s"%(arch,), interpreter, "setup.py", "test", "--verbosity=3"],
-            cwd=proj_dir,
-            stdout=subprocess.PIPE,
-            stderr=subprocess.PIPE,
-    )
-    stdout, stderr = p.communicate()
-    exitcode = p.wait()
-
-    status_line = stdout.decode('utf-8').rsplit('\n',2)[-2]
-    if not status_line.startswith('SUMMARY'):
-        status = {
-            'message': 'No status line at end',
-        }
-    else:
-        status = eval(status_line.split(None, 1)[1])
-
-    status['exitcode'] = exitcode
-
-    if not os.path.exists(state_dir):
-        os.makedirs(state_dir)
-
-    with open(os.path.join(state_dir, project + '.status'), 'wb') as fp:
-        json.dump(status, fp)
-
-    with open(os.path.join(state_dir, project + '.stdout'), 'wb') as fp:
-        fp.write(stdout)
-
-    with open(os.path.join(state_dir, project + '.stderr'), 'wb') as fp:
-        fp.write(stderr)
-
-
-def mercurial_state(source_root):
-    stdout = subprocess.check_output(
-            ['hg', 'summary'],
-            cwd=source_root)
-
-    result = {}
-    for ln in stdout.splitlines():
-        if ln.startswith(' '): continue
-        key, value = (s.strip() for s in ln.split(':', 1))
-
-        if key == 'parent':
-            result['revision'] = value.split()[0].split(':')[1]
-
-        elif key == 'branch':
-            result['branch'] = value
-
-        elif key == 'commit':
-            if value == '(clean)':
-                result['state'] = 'clean'
-            else:
-                result['state'] = 'dirty'
-    return result
-
-def main():
-    lg = logging.getLogger("runtests")
-
-    osx_release = platform.mac_ver()[0]
-
-    lg.info("running PyObjC tests on OSX %s", osx_release)
-    osx_dir = os.path.join(config.STATE_DIR, ".".join(osx_release.split(".")[:2]))
-
-    build_order = ['pyobjc-core'] + sort_framework_wrappers()
-
-    # XXX: test using smaller subset
-    #build_order = build_order[:2]
-
-    # Store state of the repository for reporting
-    repo_state = mercurial_state(config.SOURCE_DIR)
-    with open(os.path.join(osx_dir, 'repository.state'), 'w') as fp:
-        json.dump(repo_state, fp)
-
-    # Run tests
-    for py_ver in sorted(config.PREFIXES):
-        py_prefix = config.PREFIXES[py_ver]
-
-        with saved_site_packges(py_prefix):
-            # First install all packages
-            interpreter = os.path.join(py_prefix, 'bin', 'python')
-            if not os.path.exists(interpreter):
-                interpreter += '3'
-
-            for project in build_order:
-                build_project(interpreter, project)
-
-            # Then run tests for all supported architectures
-            for arch in ('i386', 'x86_64'): # FIXME: should detect these
-                lg.info("running with Python %s (%7s) in %s", py_ver, arch, py_prefix)
-                state_dir = os.path.join(osx_dir, "%s-%s"%(py_ver, arch))
-
-                for project in build_order:
-                    run_tests(interpreter, arch, project, state_dir)
-        
-    lg.info("done")
-
-if __name__ == "__main__":
-    main()

File objective/testharness/script.py

-from __future__ import absolute_import
-
-import argparse
-import logging
-import sys
-
-from . import runtests
-from . import genhtml
-
-parser = argparse.ArgumentParser(prog="objective-testharnass")
-parser.add_argument("--verbose", action="store_true", help="print progress information", default=False)
-#parser.add_argument("--ini-file", metavar="FILE", help="configuration file", default="test.ini")
-
-subparsers = parser.add_subparsers(dest="command")
-
-run_command = subparsers.add_parser("run", help="Run tests")
-#run_command.add_argument("--sdk-root",
-             #   help="Use the given SDK", metavar="DIR", default="/")
-
-html_command = subparsers.add_parser("html", help="Generate HTML report")
-
-def main():
-    args = parser.parse_args()
-
-    if args.verbose:
-        logging.basicConfig(level=logging.DEBUG, file=sys.stderr)
-    else:
-        logging.basicConfig(level=logging.WARNING, file=sys.stderr)
-
-    if args.command == 'run':
-        runtests.main()
-
-    elif args.command == 'html':
-        genhtml.main()
-
-    else:
-        raise RuntimeError("unhandled command: %s"%(args.command,))

File objective/testharness/topsort.py

-"""
-Module defining a topological sort function, see 
-<http://www.bitformation.com/art/python_toposort.html> for more
-information.
-
-Original topological sort code written by Ofer Faigon (www.bitformation.com) and used with permission
-"""
-
-def topological_sort(items, partial_order):
-    """
-    Perform topological sort.
-    items is a list of items to be sorted.
-    partial_order is a list of pairs. If pair (a,b) is in it, it means
-    that item a should appear before item b.
-    Returns a list of the items in one of the possible orders, or None
-    if partial_order contains a loop.
-    """
-
-    def add_node(graph, node):
-        """Add a node to the graph if not already exists."""
-        if node not in graph:
-            graph[node] = [0] # 0 = number of arcs coming into this node.
-
-    def add_arc(graph, fromnode, tonode):
-        """Add an arc to a graph. Can create multiple arcs.
-           The end nodes must already exist."""
-        graph[fromnode].append(tonode)
-        # Update the count of incoming arcs in tonode.
-        graph[tonode][0] += 1
-
-    # step 1 - create a directed graph with an arc a->b for each input
-    # pair (a,b).
-    # The graph is represented by a dictionary. The dictionary contains
-    # a pair item:list for each node in the graph. /item/ is the value
-    # of the node. /list/'s 1st item is the count of incoming arcs, and
-    # the rest are the destinations of the outgoing arcs. For example:
-    #           {'a':[0,'b','c'], 'b':[1], 'c':[1]}
-    # represents the graph:   c <-- a --> b
-    # The graph may contain loops and multiple arcs.
-    # Note that our representation does not contain reference loops to
-    # cause GC problems even when the represented graph contains loops,
-    # because we keep the node names rather than references to the nodes.
-    graph = {}
-    for v in items:
-        add_node(graph, v)
-    for a,b in partial_order:
-        add_arc(graph, a, b)
-
-    # Step 2 - find all roots (nodes with zero incoming arcs).
-    roots = [node for (node,nodeinfo) in graph.items() if nodeinfo[0] == 0]
-
-    # step 3 - repeatedly emit a root and remove it from the graph. Removing
-    # a node may convert some of the node's direct children into roots.
-    # Whenever that happens, we append the new roots to the list of
-    # current roots.
-    sorted = []
-    while len(roots) != 0:
-        # If len(roots) is always 1 when we get here, it means that
-        # the input describes a complete ordering and there is only
-        # one possible output.
-        # When len(roots) > 1, we can choose any root to send to the
-        # output; this freedom represents the multiple complete orderings
-        # that satisfy the input restrictions. We arbitrarily take one of
-        # the roots using pop(). Note that for the algorithm to be efficient,
-        # this operation must be done in O(1) time.
-        root = roots.pop()
-        sorted.append(root)
-        for child in graph[root][1:]:
-            graph[child][0] = graph[child][0] - 1
-            if graph[child][0] == 0:
-                roots.append(child)
-        del graph[root]
-    if len(graph.items()) != 0:
-        # There is a loop in the input.
-        return None
-    return sorted
+"""
+Helper script for running the PyObjC test suite
+
+TODO:
+* Generalize: same structure can also be used for the py2app tests
+* Auto detect architectures supported by python binaries
+* Configuration from ini-file
+"""
+import os
+import subprocess
+import contextlib
+import logging
+import shutil
+import sys
+import platform
+import json
+
+import config
+from topsort import topological_sort
+
+
+@contextlib.contextmanager
+def saved_site_packges(prefix):
+    lg = logging.getLogger("saved_site_packages")
+
+    version = os.path.basename(prefix)
+    libdir = os.path.join(prefix, 'lib', 'python'+version)
+
+    site_packages = os.path.join(libdir, 'site-packages')
+
+    if os.path.exists(site_packages):
+        lg.info("save existing site_packages for %r", prefix)
+        os.rename(site_packages, site_packages + '.saved')
+
+    try:
+        shutil.copytree(site_packages + '.saved', site_packages)
+
+        yield
+
+
+    finally:
+        if os.path.exists(site_packages):
+            shutil.rmtree(site_packages)
+            
+        if os.path.exists(site_packages + ".saved"):
+            lg.info("restore existing site_packages for %r", prefix)
+            os.rename(site_packages + ".saved", site_packages)
+
+
+def sort_framework_wrappers():
+    """
+    Returns a list of framework wrappers in the order they should
+    be build in.
+    """
+    frameworks = []
+    partial_order = []
+
+    for subdir in config.PROJECTS:
+        if not subdir.startswith('pyobjc-framework-'): continue
+
+        setup = os.path.join(config.SOURCE_DIR, 'pyobjc', subdir, 'setup.py')
+
+        requires = None
+        with open(setup) as fp:
+            for ln in fp:
+                if requires is None:
+                    if ln.strip().startswith('install_requires'):
+                        requires = []
+                else:
+                    if ln.strip().startswith(']'):
+                        break
+
+                    dep = ln.strip()[1:-1]
+                    if dep.startswith('pyobjc-framework'):
+                        dep = dep.split('>')[0]
+                        requires.append(dep)
+
+        frameworks.append(subdir)
+        for dep in requires:
+            partial_order.append((dep, subdir))
+
+    frameworks = topological_sort(frameworks, partial_order)
+    return frameworks
+
+
+def build_project(interpreter, project):
+    lg = logging.getLogger("build_project")
+
+    proj_dir = os.path.join(config.SOURCE_DIR, project)
+    if not os.path.exists(proj_dir):
+        proj_dir = os.path.join(config.SOURCE_DIR, "pyobjc", project)
+
+    # First ask distutils to clean up
+    lg.info("cleaning %r using %r", project, interpreter)
+    status = subprocess.call(
+            [interpreter, "setup.py", "clean"],
+            cwd=proj_dir)
+    if status != 0:
+        lg.warning("clean %r failed (status %s)", project, status)
+        return False
+
+    # Drop the build directory, just in case...
+    #  (not strictly necessary, but can avoid issues when
+    #   using 2to3)
+    if os.path.exists(os.path.join(proj_dir, 'build')):
+        shutil.rmtree(os.path.join(proj_dir, 'build'))
+
+
+    # Finally perform the actual build
+    lg.info("building %r using %r", project, interpreter)
+    status = subprocess.call(
+            [interpreter, "setup.py", "install"],
+            cwd=proj_dir)
+    if status != 0:
+        lg.warning("build %r failed (status %s)", project, status)
+        return False
+
+    return True
+      
+
+def run_tests(interpreter, arch, project, state_dir):
+    lg = logging.getLogger("run_tests")
+
+    proj_dir = os.path.join(config.SOURCE_DIR, project)
+    if not os.path.exists(proj_dir):
+        proj_dir = os.path.join(config.SOURCE_DIR, "pyobjc", project)
+        
+    
+    lg.info("testing %r using %r (%7s)", project, interpreter, arch)
+    p = subprocess.Popen(
+            ["/usr/bin/arch", "-%s"%(arch,), interpreter, "setup.py", "test", "--verbosity=3"],
+            cwd=proj_dir,
+            stdout=subprocess.PIPE,
+            stderr=subprocess.PIPE,
+    )
+    stdout, stderr = p.communicate()
+    exitcode = p.wait()
+
+    status_line = stdout.decode('utf-8').rsplit('\n',2)[-2]
+    if not status_line.startswith('SUMMARY'):
+        status = {
+            'message': 'No status line at end',
+        }
+    else:
+        status = eval(status_line.split(None, 1)[1])
+
+    status['exitcode'] = exitcode
+
+    if not os.path.exists(state_dir):
+        os.makedirs(state_dir)
+
+    with open(os.path.join(state_dir, project + '.status'), 'wb') as fp:
+        json.dump(status, fp)
+
+    with open(os.path.join(state_dir, project + '.stdout'), 'wb') as fp:
+        fp.write(stdout)
+
+    with open(os.path.join(state_dir, project + '.stderr'), 'wb') as fp:
+        fp.write(stderr)
+
+
+def mercurial_state(source_root):
+    stdout = subprocess.check_output(
+            ['hg', 'summary'],
+            cwd=source_root)
+
+    result = {}
+    for ln in stdout.splitlines():
+        if ln.startswith(' '): continue
+        key, value = (s.strip() for s in ln.split(':', 1))
+
+        if key == 'parent':
+            result['revision'] = value.split()[0].split(':')[1]
+
+        elif key == 'branch':
+            result['branch'] = value
+
+        elif key == 'commit':
+            if value == '(clean)':
+                result['state'] = 'clean'
+            else:
+                result['state'] = 'dirty'
+    return result
+
+def main():
+    logging.basicConfig(file=sys.stdout, level=logging.DEBUG)
+    lg = logging.getLogger("runtests")
+
+    osx_release = platform.mac_ver()[0]
+
+    lg.info("running PyObjC tests on OSX %s", osx_release)
+    osx_dir = os.path.join(config.STATE_DIR, ".".join(osx_release.split(".")[:2]))
+    if not os.path.exists(osx_dir):
+        os.makedirs(osx_dir)
+
+    build_order = ['altgraph', 'modulegraph', 'macholib', 'py2app', 'pyobjc-core'] + sort_framework_wrappers()
+
+    # Store state of the repository for reporting
+    # XXX: also store repo state for altgraph, ....
+    repo_state = mercurial_state(os.path.join(config.SOURCE_DIR, "pyobjc"))
+    with open(os.path.join(osx_dir, 'repository.state'), 'w') as fp:
+        json.dump(repo_state, fp)
+
+    # Run tests
+    for py_ver in sorted(config.PREFIXES):
+        py_prefix = config.PREFIXES[py_ver]
+
+        with saved_site_packges(py_prefix):
+            # First install all packages
+            interpreter = os.path.join(py_prefix, 'bin', 'python')
+            if not os.path.exists(interpreter):
+                interpreter += '3'
+
+            for project in build_order:
+                build_project(interpreter, project)
+
+            # Then run tests for all supported architectures
+            for arch in ('i386', 'x86_64'): # FIXME: should detect these
+                lg.info("running with Python %s (%7s) in %s", py_ver, arch, py_prefix)
+                state_dir = os.path.join(osx_dir, "%s-%s"%(py_ver, arch))
+
+                for project in build_order:
+                    run_tests(interpreter, arch, project, state_dir)
+        
+    lg.info("done")
+
+if __name__ == "__main__":
+    main()
+"""
+Module defining a topological sort function, see 
+<http://www.bitformation.com/art/python_toposort.html> for more
+information.
+
+Original topological sort code written by Ofer Faigon (www.bitformation.com) and used with permission
+"""
+
+def topological_sort(items, partial_order):
+    """
+    Perform topological sort.
+    items is a list of items to be sorted.
+    partial_order is a list of pairs. If pair (a,b) is in it, it means
+    that item a should appear before item b.
+    Returns a list of the items in one of the possible orders, or None
+    if partial_order contains a loop.
+    """
+
+    def add_node(graph, node):
+        """Add a node to the graph if not already exists."""
+        if node not in graph:
+            graph[node] = [0] # 0 = number of arcs coming into this node.
+
+    def add_arc(graph, fromnode, tonode):
+        """Add an arc to a graph. Can create multiple arcs.
+           The end nodes must already exist."""
+        graph[fromnode].append(tonode)
+        # Update the count of incoming arcs in tonode.
+        graph[tonode][0] += 1
+
+    # step 1 - create a directed graph with an arc a->b for each input
+    # pair (a,b).
+    # The graph is represented by a dictionary. The dictionary contains
+    # a pair item:list for each node in the graph. /item/ is the value
+    # of the node. /list/'s 1st item is the count of incoming arcs, and
+    # the rest are the destinations of the outgoing arcs. For example:
+    #           {'a':[0,'b','c'], 'b':[1], 'c':[1]}
+    # represents the graph:   c <-- a --> b
+    # The graph may contain loops and multiple arcs.
+    # Note that our representation does not contain reference loops to
+    # cause GC problems even when the represented graph contains loops,
+    # because we keep the node names rather than references to the nodes.
+    graph = {}
+    for v in items:
+        add_node(graph, v)
+    for a,b in partial_order:
+        add_arc(graph, a, b)
+
+    # Step 2 - find all roots (nodes with zero incoming arcs).
+    roots = [node for (node,nodeinfo) in graph.items() if nodeinfo[0] == 0]
+
+    # step 3 - repeatedly emit a root and remove it from the graph. Removing
+    # a node may convert some of the node's direct children into roots.
+    # Whenever that happens, we append the new roots to the list of
+    # current roots.
+    sorted = []
+    while len(roots) != 0:
+        # If len(roots) is always 1 when we get here, it means that
+        # the input describes a complete ordering and there is only
+        # one possible output.
+        # When len(roots) > 1, we can choose any root to send to the
+        # output; this freedom represents the multiple complete orderings
+        # that satisfy the input restrictions. We arbitrarily take one of
+        # the roots using pop(). Note that for the algorithm to be efficient,
+        # this operation must be done in O(1) time.
+        root = roots.pop()
+        sorted.append(root)
+        for child in graph[root][1:]:
+            graph[child][0] = graph[child][0] - 1
+            if graph[child][0] == 0:
+                roots.append(child)
+        del graph[root]
+    if len(graph.items()) != 0:
+        # There is a loop in the input.
+        return None
+    return sorted