swc-installation-test: Consolidate and reorganize test scripts
authorW. Trevor King <wking@tremily.us>
Sun, 30 Dec 2012 12:19:15 +0000 (07:19 -0500)
committerW. Trevor King <wking@tremily.us>
Tue, 1 Jan 2013 14:49:40 +0000 (09:49 -0500)
swc-installation-test-1.py [new file with mode: 0755]
swc-installation-test-2.py [new file with mode: 0755]
swc-installation-test.py [deleted file]
workshop_checklist.py [deleted file]

diff --git a/swc-installation-test-1.py b/swc-installation-test-1.py
new file mode 100755 (executable)
index 0000000..148f614
--- /dev/null
@@ -0,0 +1,43 @@
+#!/usr/bin/env python
+
+"""Test script to check required Python version.
+
+Execute this code at the command line by typing:
+
+  python swc-installation-test-1.py
+
+How to get a command line:
+
+- On OSX run this with the Terminal application.
+
+- On Windows, go to the Start menu, select 'Run' and type 'cmd'
+(without the quotes) to run the 'cmd.exe' Windows Command Prompt.
+
+- On Linux, either use your login shell directly, or run one of a
+  number of graphical terminals (e.g. 'xterm', 'gnome-terminal', ...).
+
+Run the script and follow the instructions it prints at the end.
+
+This test is separate to avoid syntax errors parsing the more
+elaborate `swc-installation-test-2.py`.
+"""
+
+import sys as _sys
+
+
+__version__ = '0.1'
+
+
+def check():
+    if _sys.version_info < (2, 6):
+        print('check for Python version (python):')
+        print('outdated version of Python: ' + _sys.version)
+        return False
+    return True
+
+
+if __name__ == '__main__':
+    if check():
+        print('Passed')
+    else:
+        print('Failed')
diff --git a/swc-installation-test-2.py b/swc-installation-test-2.py
new file mode 100755 (executable)
index 0000000..ebca9b1
--- /dev/null
@@ -0,0 +1,310 @@
+#!/usr/bin/env python
+
+"""Test script to check for required functionality.
+
+Execute this code at the command line by typing:
+
+  python swc-installation-test-2.py
+
+How to get a command line:
+
+- On OSX run this with the Terminal application.
+
+- On Windows, go to the Start menu, select 'Run' and type 'cmd'
+(without the quotes) to run the 'cmd.exe' Windows Command Prompt.
+
+- On Linux, either use your login shell directly, or run one of a
+  number of graphical terminals (e.g. 'xterm', 'gnome-terminal', ...).
+
+Run the script and follow the instructions it prints at the end.
+
+This script requires at least Python 2.6.  You can check the version
+of Python that you have installed with 'swc-installation-test-1.py'.
+"""
+
+import importlib as _importlib
+import logging as _logging
+import os as _os
+import platform as _platform
+import re as _re
+import subprocess as _subprocess
+import sys as _sys
+
+
+__version__ = '0.1'
+
+# Comment out any entries you don't need
+CHECKS = [
+    'python',
+    'bash',
+    'easy_install',
+    'git',
+    'hg',              # Command line tool
+    'mercurial',       # Python package
+    'make',
+    'nosetests',       # Command line tool
+    'nose',            # Python package
+    'sqlite3',         # Command line tool
+    'sqlite3-python',  # Python package
+    'IPython',
+    'numpy',
+    'scipy',
+    'matplotlib',
+    'sympy',
+    'Cython',
+    'networkx',
+    'mayavi.mlab',
+    'setuptools',
+    ]
+
+CHECKER = {}
+
+
+class DependencyError (Exception):
+    def __init__(self, checker, message):
+        self.checker = checker
+        self.message = message
+
+    def __str__(self):
+        return 'check for {0} failed:\n{1}'.format(
+            self.checker.full_name(), self.message)
+
+
+def check(checks=None):
+    failures = []
+    if not checks:
+        checks = CHECKS
+    for check in checks:
+        checker = CHECKER[check]
+        _sys.stdout.write('check {0}...\t'.format(checker.full_name()))
+        try:
+            checker.check()
+        except DependencyError as e:
+            failures.append(e)
+            _sys.stdout.write('fail\n')
+        else:
+            _sys.stdout.write('pass\n')
+    if failures:
+        print('\nFailures:')
+        for failure in failures:
+            print()
+            print(failure)
+        return False
+    return True
+
+
+class Dependency (object):
+    def __init__(self, name, long_name=None, minimum_version=None,
+                 version_delimiter='.'):
+        self.name = name
+        self.long_name = long_name or name
+        self.minimum_version = minimum_version
+        self.version_delimiter = version_delimiter
+
+    def __str__(self):
+        return '<{0} {1}>'.format(type(self).__name__, self.name)
+
+    def full_name(self):
+        if self.name == self.long_name:
+            return self.name
+        else:
+            return '{0} ({1})'.format(self.long_name, self.name)
+
+    def check(self):
+        version = self._get_version()
+        parsed_version = None
+        if hasattr(self, '_get_parsed_version'):
+            parsed_version = self._get_parsed_version()
+        if self.minimum_version:
+            self._check_version(version=version, parsed_version=parsed_version)
+
+    def _get_version(self):
+        raise NotImplementedError(self)
+
+    def _check_version(self, version, parsed_version=None):
+        if not parsed_version:
+            parsed_version = self._parse_version(version=version)
+        if parsed_version < self.minimum_version:
+            raise DependencyError(
+                checker=self,
+                message='outdated version of {0}: {1} (need >= {2})'.format(
+                    self.full_name(), version,
+                    self.version_delimiter.join(
+                        str(part) for part in self.minimum_version)))
+
+    def _parse_version(self, version):
+        parsed_version = []
+        for part in version.split(self.version_delimiter):
+            try:
+                parsed_version.append(int(part))
+            except ValueError as e:
+                raise NotImplementedError((version, part)) from e
+        return tuple(parsed_version)
+
+
+class PythonDependency (Dependency):
+    def __init__(self, name='python', long_name='Python version',
+                 minimum_version=(2, 6), **kwargs):
+        super(PythonDependency, self).__init__(
+            name=name, long_name=long_name, minimum_version=minimum_version,
+            **kwargs)
+
+    def _get_version(self):
+        return _sys.version
+
+    def _get_parsed_version(self):
+        return _sys.version_info
+
+
+CHECKER['python'] = PythonDependency()
+
+
+class CommandDependency (Dependency):
+    def __init__(self, command, version_option='--version',
+                 version_regexp=None, version_stream='stdout', **kwargs):
+        if 'name' not in kwargs:
+            kwargs['name'] = command
+        super(CommandDependency, self).__init__(**kwargs)
+        self.command = command
+        self.version_option = version_option
+        if not version_regexp:
+            regexp = r'([\d][\d{0}]*[\d])'.format(self.version_delimiter)
+            version_regexp = _re.compile(regexp)
+        self.version_regexp = version_regexp
+        self.version_stream = version_stream
+
+    def _get_version_stream(self):
+        try:
+            p = _subprocess.Popen(
+                [self.command, self.version_option],
+                stdout=_subprocess.PIPE, stderr=_subprocess.PIPE,
+                close_fds=True, shell=False, universal_newlines=True)
+        except OSError as e:
+            raise DependencyError(
+                checker=self,
+                message="could not find '{0}' executable".format(self.command),
+                ) from e
+        stdout,stderr = p.communicate()
+        status = p.wait()
+        if status:
+            lines = [
+                "failed to execute '{0} {1}':".format(
+                    self.command, self.version_option),
+                'status: {0}'.format(status),
+                ]
+            for name,string in [('stdout', stdout), ('stderr', stderr)]:
+                if string:
+                    lines.extend([name + ':', string])
+            raise DependencyError(checker=self, message='\n'.join(lines))
+        for name,string in [('stdout', stdout), ('stderr', stderr)]:
+            if name == self.version_stream:
+                return string
+        raise NotImplementedError(self.version_stream)
+
+    def _get_version(self):
+        version_stream = self._get_version_stream()
+        match = self.version_regexp.search(version_stream)
+        if not match:
+            raise DependencyError(
+                checker=self,
+                message='no version string in output:\n{0}'.format(
+                    version_stream))
+        return match.group(1)
+
+
+for command,long_name,minimum_version in [
+        ('bash', 'Bourne Again Shell', (4, 0)),
+        ('easy_install', 'Setuptools easy_install', None),
+        ('git', 'Git', (1, 8, 0)),
+        ('hg', 'Mercurial', (2, 0, 0)),
+        ('make', None, None),
+        ('sqlite3', 'SQLite 3', None),
+        ('nosetests', 'Nose', (1, 0, 0)),
+        ]:
+    if not long_name:
+        long_name = command
+    CHECKER[command] = CommandDependency(
+        command=command, long_name=long_name, minimum_version=minimum_version)
+del command, long_name, minimum_version  # cleanup namespace
+
+
+class PythonPackageDependency (Dependency):
+    def __init__(self, package, **kwargs):
+        if 'name' not in kwargs:
+            kwargs['name'] = package
+        super(PythonPackageDependency, self).__init__(**kwargs)
+        self.package = package
+
+    def _get_version(self):
+        try:
+            package = _importlib.import_module(self.package)
+        except ImportError as e:
+            raise DependencyError(
+                checker=self,
+                message="could not import the '{0}' package".format(
+                    self.package),
+                ) from e
+        try:
+            version = package.__version__
+        except AttributeError:
+            version = 'unknown'
+        return version
+
+
+for package,name,long_name,minimum_version in [
+        ('mercurial', None, 'Mercurial Python package',
+         CHECKER['hg'].minimum_version),
+        ('nose', None, 'Nose Python package',
+         CHECKER['nosetests'].minimum_version),
+        ('sqlite3', 'sqlite3-python', 'SQLite Python package',
+         CHECKER['sqlite3'].minimum_version),
+        ('IPython', None, None, None),
+        ('numpy', None, 'NumPy', None),
+        ('scipy', None, 'SciPy', None),
+        ('matplotlib', None, 'Matplotlib', None),
+        ('sympy', None, 'SymPy', None),
+        ('Cython', None, None, None),
+        ('networkx', None, 'NetworkX', None),
+        ('mayavi.mlab', None, 'MayaVi', None),
+        ('setuptools', None, 'Setuptools', None),
+        ]:
+    if not name:
+        name = package
+    if not long_name:
+        long_name = name
+    CHECKER[name] = PythonPackageDependency(
+        package=package, name=name, long_name=long_name,
+        minimum_version=minimum_version)
+del package, name, long_name, minimum_version  # cleanup namespace
+
+
+def print_system_info():
+    print("If you do not understand why the above failures occurred,")
+    print("copy and send the *entire* output (all info above and summary")
+    print("below) to the instructor for help.")
+    print()
+    print('==================')
+    print('System information')
+    print('==================')
+    print('os.name      : {0}'.format(_os.name))
+    try:
+        print('os.uname     : {0}'.format(_os.uname()))
+    except:
+        pass
+    print('platform     : {0}'.format(_sys.platform))
+    print('platform+    : {0}'.format(_platform.platform()))
+    print('prefix       : {0}'.format(_sys.prefix))
+    print('exec_prefix  : {0}'.format(_sys.exec_prefix))
+    print('executable   : {0}'.format(_sys.executable))
+    print('version_info : {0}'.format(_sys.version_info))
+    print('version      : {0}'.format(_sys.version))
+    print('environment  :')
+    for key,value in sorted(_os.environ.items()):
+        print('  {0}={1}'.format(key, value))
+    print('==================')
+
+
+if __name__ == '__main__':
+    if not check(_sys.argv[1:]):
+        print()
+        print_system_info()
diff --git a/swc-installation-test.py b/swc-installation-test.py
deleted file mode 100644 (file)
index 4c76274..0000000
+++ /dev/null
@@ -1,60 +0,0 @@
-# Run this as
-#
-#  python swc_installation_test.py
-#
-# If if says nothing, everything is fine!
-
-import os
-
-python_modules = ['nose']
-
-tools = ['bash',
-         ('easy_install', 'Python setuptools'),
-         ('hg', 'Mercurial'),
-         'make',
-         ('nosetests', 'Python nose'),
-         'sqlite3']
-
-# Check Python modules/packages
-
-def check_python_modules(module_names):
-    for module_name in module_names:
-        try:
-            __import__(module_name)
-        except ImportError:
-            print "Python module '%s' is missing" % module_name
-
-
-# Check command line tools
-
-def check_command_line_tools(tools):
-    shell_path = os.environ['PATH'].split(':')
-    for tool in tools:
-        if isinstance(tool, basestring):
-            command = tool
-            package = None
-        else:
-            command, package = tool
-        found = False
-        for directory in shell_path:
-            filename = os.path.join(directory, command)
-            if os.access(filename, os.X_OK) and not os.path.isdir(filename):
-                found = True
-                break
-        if not found:
-            if package is None:
-                print "Command line tool '%s' is missing" % command
-            else:
-                print "Command line tool '%s' " \
-                    "from package '%s' is missing" % (command, package)
-
-
-# Run all the checks
-
-def main():
-    check_python_modules(python_modules)
-    check_command_line_tools(tools)
-
-
-if __name__ == '__main__':
-    main()
diff --git a/workshop_checklist.py b/workshop_checklist.py
deleted file mode 100644 (file)
index 8792498..0000000
+++ /dev/null
@@ -1,173 +0,0 @@
-#!/usr/bin/env python
-# -*- coding: utf-8 -*-
-"""Minimal test script to check for modules needed in python workshop.
-
-Execute this code at the command line by typing:
-
-python workshop_checklist.py
-
-How to get a command line:
-
-- On OSX run this with the Terminal application.
-
-- On Windows, go to the Start menu, select 'Run' and type 'cmd' (without the
-quotes) to run the cmd.exe Windows Command Prompt.
-
-Run the script and follow the instructions it prints at the end.
-"""
-
-##############################################################################
-# Config here
-
-# Modules whose imports we're going to validate
-MODULES = ['IPython',
-           'numpy',
-           'scipy',
-           'scipy.io',
-           'matplotlib','pylab',
-           # 'sympy', 'Cython', 'networkx', 'mayavi.mlab',
-           # 'setuptools',
-           ]
-
-##############################################################################
-# Code begins
-
-# Standard library imports
-import glob
-import os
-import platform
-import sys
-
-from StringIO import StringIO
-
-# Third-party imports
-import nose
-import nose.tools as nt
-
-#-----------------------------------------------------------------------------
-# Generic utility functions
-def sys_info():
-    """Summarize some info about the system"""
-
-    print '=================='
-    print 'System information'
-    print '=================='
-    print 'os.name      :',os.name
-    try:
-        print 'os.uname     :',os.uname()
-    except:
-        pass
-    print 'platform     :',sys.platform
-    print 'platform+    :',platform.platform()
-    print 'prefix       :',sys.prefix
-    print 'exec_prefix  :',sys.exec_prefix
-    print 'executable   :',sys.executable
-    print 'version_info :',sys.version_info
-    print 'version      :',sys.version
-    print '=================='
-
-
-#-----------------------------------------------------------------------------
-# Tests
-
-def check_import(mname):
-    "Check that the given name imports correctly"
-    exec "import %s as m" % mname
-
-    if mname == 'matplotlib':
-        m.use('Agg')
-        m.rcParams['figure.subplot.top']= 0.85
-    
-    try:
-        vinfo = m.__version__
-    except AttributeError:
-        vinfo = '*no info*'
-
-    print 'MOD: %s, version: %s' % (mname,vinfo)
-
-
-# Test generators are best written without docstrings, because nose can then
-# show the parameters being used.
-def test_imports():
-    for mname in MODULES:
-        yield check_import, mname
-
-
-# Test generator, don't put a docstring in it
-def test_loadtxt():
-    import numpy as np
-    import numpy.testing as npt
-
-    # Examples taken from the loadtxt docstring
-    array = np.array
-    
-    c = StringIO("0 1\n2 3")
-    a1 = np.loadtxt(c)
-    a2 = np.array([[ 0.,  1.],
-                   [ 2.,  3.]])
-    yield npt.assert_array_equal,a1,a2
-
-    d = StringIO("M 21 72\nF 35 58")
-    a1 = np.loadtxt(d, dtype={'names': ('gender', 'age', 'weight'),
-                         'formats': ('S1', 'i4', 'f4')})
-    
-    a2 = np.array([('M', 21, 72.0), ('F', 35, 58.0)],
-                  dtype=[('gender', '|S1'), ('age', '<i4'), ('weight', '<f4')])
-    yield npt.assert_array_equal,a1,a2
-
-    c = StringIO("1,0,2\n3,0,4")
-    x,y = np.loadtxt(c, delimiter=',', usecols=(0,2), unpack=True)
-    yield npt.assert_array_equal,x,np.array([ 1.,  3.])
-    yield npt.assert_array_equal,y,np.array([ 2.,  4.])
-
-
-def test_plot():
-    "Simple plot generation."
-    from matplotlib import pyplot as plt
-    plt.figure()
-    plt.plot([1,2,3])
-    plt.xlabel('some numbers')
-    plt.savefig('tmp_test_plot.png')
-
-
-def test_plot_math():
-    "Plots with math"
-    from matplotlib import pyplot as plt
-    plt.figure()
-    plt.plot([1,2,3],label='data')
-    t=(r'And X is $\sum_{i=0}^\infty \gamma_i + \frac{\alpha^{i^2}}{\gamma}'
-       r'+ \cos(2 \theta^2)$')
-    plt.title(t)
-    plt.legend()
-    plt.grid()
-    plt.savefig('tmp_test_plot_math.png')
-
-
-def cleanup_pngs():
-    """Remove temporary pngs made by our plotting tests"""
-
-    for f in glob.glob('tmp_test_plot*.png'):
-        try:
-            os.remove(f)
-        except OSError:
-            print '*** Error: could not remove file',f
-            
-
-#-----------------------------------------------------------------------------
-# Main routine, executed when this file is run as a script
-#
-if __name__ == '__main__':
-    print "Running tests:"
-    # This call form is ipython-friendly
-    nose.runmodule(argv=[__file__,'-vvs'],
-                   exit=False)
-    print """
-***************************************************************************
-                           TESTS FINISHED
-***************************************************************************
-
-If the printout above did not finish in 'OK' but instead says 'FAILED', copy
-and send the *entire* output (all info above and summary below) to the
-instructor for help.
-"""
-    sys_info()