[Inteproxy-commits] r168 - in trunk: . inteproxy test

scm-commit@wald.intevation.org scm-commit at wald.intevation.org
Tue Nov 25 17:59:04 CET 2008


Author: bh
Date: 2008-11-25 17:59:04 +0100 (Tue, 25 Nov 2008)
New Revision: 168

Added:
   trunk/inteproxy/config.py
   trunk/test/filesupport.py
   trunk/test/test_config.py
Modified:
   trunk/ChangeLog
   trunk/test/support.py
Log:
* inteproxy/config.py: New module to read the config file.  The
config file now contains optional proxy settings

* test/test_config.py: New.  Test cases for the config file
reading code

* test/filesupport.py: New.  Support code for tests involving
files.

* test/support.py (AttributeTestMixin): New mixin to help test
attributes of an object.


Modified: trunk/ChangeLog
===================================================================
--- trunk/ChangeLog	2008-11-24 14:36:30 UTC (rev 167)
+++ trunk/ChangeLog	2008-11-25 16:59:04 UTC (rev 168)
@@ -1,3 +1,17 @@
+2008-11-25  Bernhard Herzog  <bh at intevation.de>
+
+	* inteproxy/config.py: New module to read the config file.  The
+	config file now contains optional proxy settings
+
+	* test/test_config.py: New.  Test cases for the config file
+	reading code
+
+	* test/filesupport.py: New.  Support code for tests involving
+	files.
+
+	* test/support.py (AttributeTestMixin): New mixin to help test
+	attributes of an object.
+
 2008-09-30  Bernhard Herzog  <bh at intevation.de>
 
 	* website/index-de.htm4, website/index.htm4: Updated Intevation's

Added: trunk/inteproxy/config.py
===================================================================
--- trunk/inteproxy/config.py	2008-11-24 14:36:30 UTC (rev 167)
+++ trunk/inteproxy/config.py	2008-11-25 16:59:04 UTC (rev 168)
@@ -0,0 +1,120 @@
+# Copyright (C) 2007, 2008 by Intevation GmbH
+# Authors:
+# Bernhard Herzog <bh at intevation.de>
+#
+# This program is free software under the GPL (>=v2)
+# Read the file COPYING coming with the software for details.
+
+"""Read the InteProxy config file."""
+
+import sys
+from ConfigParser import SafeConfigParser, NoOptionError, NoSectionError
+
+
+# special object to indicate no default value
+nodefault = object()
+
+class Struct(object):
+
+    def __init__(self, **kw):
+        self.__dict__.update(**kw)
+
+
+class Option(object):
+
+    def __init__(self, name, default=nodefault, converter=lambda x: x,
+                 attribute=None):
+        self.name = name
+        self.default = default
+        self.converter = converter
+        if not attribute:
+            attribute = name
+        self.attribute = attribute
+
+    def has_default(self):
+        return self.default is not nodefault
+
+
+class HostEntry(object):
+
+    def __init__(self, host, path, classname):
+        self.host = host
+        self.path = path
+        self.classname = classname
+
+    def __repr__(self):
+        return "Host(%r, %r, %r)" % (self.host, self.path, self.classname)
+
+    def __eq__(self, other):
+        return (self.host == other.host
+                and self.path == other.path
+                and self.classname == other.classname)
+
+    def __lt__(self, other):
+        return (self.host < other.host
+                or self.path < other.path
+                or self.classname < other.classname)
+
+
+inteproxy_desc = [Option("http_proxy", default=None),
+                  Option("https_proxy", default=None)]
+
+proxy_desc = [Option("host"),
+              Option("port", default=80, converter=int),
+              Option("username", default=None),
+              Option("password", default=None)]
+
+host_desc = [Option("host"),
+             Option("path"),
+             Option("class", attribute="classname")]
+
+
+def read_config_section(parser, section, item_desc, defaults=None,
+                        settings_class=Struct):
+    if defaults is None:
+        defaults = dict()
+    options = dict()
+    for item in item_desc:
+        try:
+            value = item.converter(parser.get(section, item.name,
+                                              vars=defaults))
+        except (NoOptionError, NoSectionError):
+            if item.has_default():
+                value = item.default
+            else:
+                print >>sys.stderr, "Missing option %r in section %r" \
+                      % (item.name, section)
+                sys.exit(1)
+
+        options[item.attribute] = value
+
+    return settings_class(**options)
+
+
+def read_config(filename):
+    """Reads the InteProxy configuration from the file given by filename.
+    """
+    parser = SafeConfigParser()
+    parser.read([filename])
+
+    remote_hosts = []
+    config = None
+
+    sections = set(parser.sections())
+    config = read_config_section(parser, "inteproxy", inteproxy_desc)
+    sections.discard("inteproxy")
+
+    for proxy_attr in ("http_proxy", "https_proxy"):
+        section = getattr(config, proxy_attr)
+        if section is not None:
+            setattr(config, proxy_attr, read_config_section(parser, section,
+                                                            proxy_desc))
+            sections.discard(section)
+
+    config.hosts = []
+    for section in sections:
+        config.hosts.append(read_config_section(parser, section, host_desc,
+                                                settings_class=HostEntry))
+
+
+    return config


Property changes on: trunk/inteproxy/config.py
___________________________________________________________________
Name: svn:keywords
   + Id Revision
Name: svn:eol-style
   + native

Added: trunk/test/filesupport.py
===================================================================
--- trunk/test/filesupport.py	2008-11-24 14:36:30 UTC (rev 167)
+++ trunk/test/filesupport.py	2008-11-25 16:59:04 UTC (rev 168)
@@ -0,0 +1,145 @@
+# Copyright (C) 2007, 2008 by Intevation GmbH
+# Authors:
+# Bernhard Herzog <bh at intevation.de>
+
+"""Support code for the test cases"""
+
+import os
+import tempfile
+import shutil
+
+
+def writefile(filename, contents, permissions=None):
+    """Write contents to filename in an atomic way.
+
+    The contents are first written to a temporary file in the same
+    directory as filename.  Once the contents are written, the temporary
+    file is closed and renamed to filename.
+
+    The optional parameter permissions, if given, are the permissions
+    for the new file.  By default, or if the parameter is None, the
+    default permissions set by the tempfile.mkstemp are used which means
+    that the file is only readable for the user that created the file.
+    The permissions value is used as the second parameter to os.chmod.
+    """
+    dirname, basename = os.path.split(filename)
+    fileno, tempname = tempfile.mkstemp("", basename, dirname)
+    try:
+        os.write(fileno, contents)
+        if not contents.endswith("\n"):
+            os.write(fileno, "\n")
+        os.close(fileno)
+        if permissions is not None:
+            os.chmod(tempname, permissions)
+        os.rename(tempname, filename)
+    finally:
+        if os.path.exists(tempname):
+            os.remove(tempname)
+
+
+def create_temp_dir():
+    """Create a temporary directory for the test-suite and return its name.
+
+    The temporary directory is always called temp and is created in the
+    directory where the support module is located.
+
+    If the temp directory already exists, just return the name.
+    """
+    name = os.path.abspath(os.path.join(os.path.dirname(__file__), "temp"))
+
+    # if the directory already exists, we're done
+    if os.path.isdir(name):
+        return name
+
+    # create the directory
+    os.mkdir(name)
+    return name
+
+
+class FileTestMixin:
+
+    """Mixin class for tests that use temporary files.
+
+    Instances of this class create a test-specific sub-directory under
+    the main test temp directory.  The name of the sub-directory is the
+    return value of the test's id() method.
+    """
+
+    _test_specific_directory_created = False
+
+    def create_test_specific_temp_dir(self):
+        """Creates the test specific directory and returns its absolute name.
+        When this method is called for the first time and the directory
+        exists, if is removed, so that a specific test instance always
+        starts with an empty directory.
+        """
+        dirname = os.path.join(create_temp_dir(), self.id())
+        if not self._test_specific_directory_created:
+            if os.path.exists(dirname):
+                shutil.rmtree(dirname)
+            os.mkdir(dirname)
+            self._test_specific_directory_created = True
+        return dirname
+
+    def temp_file_name(self, basename):
+        """Returns the full name of the file named basename in the temp. dir.
+        """
+        return os.path.join(self.create_test_specific_temp_dir(), basename)
+
+    def create_temp_file(self, basename, contents, permissions=None):
+        """Creates a file in the temp directory with the given contents.
+        The optional parameter permissions should either be None (the
+        default) or an int specifying the file permissions (same format
+        as the second parameter of os.chmod).  The method returns the
+        absolute name of the created file.
+        """
+        filename = self.temp_file_name(basename)
+        file = open(filename, "w")
+        file.write(contents)
+        file.close()
+        if permissions is not None:
+            os.chmod(filename, permissions)
+        return filename
+
+    def create_temp_dir(self, basename):
+        """Creates the directory basename in the temporary directory.
+        The method returns the absolute name of the created directory.
+        """
+        dirname = self.temp_file_name(basename)
+        os.mkdir(dirname)
+        return dirname
+
+    def create_files(self, directory, filedesc):
+        """Creates a hierarchy of directories and files in directory.
+        The filedesc parameter should be a sequence of (name, contents)
+        pairs or (name, permissions, contents) triples.  Each item of
+        the sequence describes one entry of the directory.  If contents
+        is an instance of list, the entry is a subdirectory and the
+        contents is a list in the same format as filedesc and passed
+        recursively to the create_files method.  If contents is a
+        string, the new directory entry is a normal file and contents is
+        the contents of the file.  The permissions if present, should be
+        an int specifying the files permissions (setting permissions
+        doesn't work yet for directories).
+
+        The method returns the absolute name of the toplevel directory
+        created by the method.
+        """
+        directory = self.temp_file_name(directory)
+        os.makedirs(directory)
+        for item in filedesc:
+            if len(item) == 3:
+                name, permissions, contents = item
+            else:
+                name, contents = item
+                permissions = None # use default permissions
+            if isinstance(contents, list):
+                # a list as contents indicates a directory
+                self.create_files(os.path.join(directory, name), contents)
+            else:
+                writefile(os.path.join(directory, name), contents, permissions)
+        return directory
+
+    def check_file_contents(self, filename, contents):
+        """check the contents of a file"""
+        self.assertEquals(open(filename).read(), contents)


Property changes on: trunk/test/filesupport.py
___________________________________________________________________
Name: svn:keywords
   + Id Revision
Name: svn:eol-style
   + native

Modified: trunk/test/support.py
===================================================================
--- trunk/test/support.py	2008-11-24 14:36:30 UTC (rev 167)
+++ trunk/test/support.py	2008-11-25 16:59:04 UTC (rev 168)
@@ -1,4 +1,4 @@
-# Copyright (C) 2007 by Intevation GmbH
+# Copyright (C) 2007, 2008 by Intevation GmbH
 # Authors:
 # Bernhard Herzog <bh at intevation.de>
 #
@@ -46,3 +46,10 @@
         if mode is not None:
             os.chmod(filename, mode)
         return filename
+
+
+class AttributeTestMixin:
+
+    def check_attributes(self, obj, **attrs):
+        for key, value in attrs.items():
+            self.assertEquals(getattr(obj, key), value)

Added: trunk/test/test_config.py
===================================================================
--- trunk/test/test_config.py	2008-11-24 14:36:30 UTC (rev 167)
+++ trunk/test/test_config.py	2008-11-25 16:59:04 UTC (rev 168)
@@ -0,0 +1,122 @@
+# Copyright (C) 2008 by Intevation GmbH
+# Authors:
+# Bernhard Herzog <bh at intevation.de>
+#
+# This program is free software under the GPL (>=v2)
+# Read the file COPYING coming with the software for details.
+
+"""Tests for the inteproxy.config module"""
+
+import sys
+import os
+import operator
+import unittest
+
+from filesupport import FileTestMixin
+from support import AttributeTestMixin
+
+from inteproxy.config import read_config, HostEntry
+
+
+class ReadConfigTest(unittest.TestCase, FileTestMixin, AttributeTestMixin):
+
+    def setUp(self):
+        self.config_file = self.create_temp_file("test.cfg",
+                                                 self.config_contents)
+
+
+class TestReadConfigEmptyInteproxySection(ReadConfigTest):
+
+    config_contents = """\
+[inteproxy]
+"""
+
+    def test_readconfig(self):
+        self.check_attributes(read_config(self.config_file),
+                              http_proxy=None,
+                              https_proxy=None)
+
+
+class TestReadConfigNoInteproxySection(ReadConfigTest):
+
+    config_contents = """\
+"""
+
+    def test_readconfig(self):
+        self.check_attributes(read_config(self.config_file),
+                              http_proxy=None,
+                              https_proxy=None)
+
+
+class TestReadConfigHttpProxyNoPortNoAuth(ReadConfigTest):
+
+    config_contents = """\
+[inteproxy]
+http_proxy=local_proxy
+
+[local_proxy]
+host=localhost
+"""
+
+    def test_readconfig(self):
+        config = read_config(self.config_file)
+        self.check_attributes(config, https_proxy=None)
+        self.check_attributes(config.http_proxy,
+                              host="localhost", port=80,
+                              username=None, password=None)
+
+
+class TestReadConfigHttpsProxyWithPortAndAuth(ReadConfigTest):
+
+    config_contents = """\
+[inteproxy]
+https_proxy=https_proxy
+
+[https_proxy]
+host=localhost
+port=8080
+username=john
+password=secret
+"""
+
+    def test_readconfig(self):
+        config = read_config(self.config_file)
+        self.check_attributes(config, http_proxy=None)
+        self.check_attributes(config.https_proxy,
+                              host="localhost", port=8080,
+                              username="john", password="secret")
+
+class TestReadConfigProxyAndHosts(ReadConfigTest):
+
+    config_contents = """\
+[inteproxy]
+https_proxy=https_proxy
+
+[https_proxy]
+host=localhost
+port=8080
+username=john
+password=secret
+
+[inteproxy-demo.intevation.org]
+host=inteproxy-demo.intevation.org
+path=/cgi-bin/frida-wms
+class=owsproxy
+
+[wms.example.org]
+host=wms.example.org
+path=/wms
+class=basicauth
+"""
+
+    def test_readconfig(self):
+        config = read_config(self.config_file)
+        self.check_attributes(config, http_proxy=None)
+        self.check_attributes(config.https_proxy,
+                              host="localhost", port=8080,
+                              username="john", password="secret")
+        self.assertEquals(sorted(config.hosts),
+                          [HostEntry("inteproxy-demo.intevation.org",
+                                     "/cgi-bin/frida-wms", "owsproxy"),
+                           HostEntry("wms.example.org", "/wms", "basicauth"),
+                           ])


Property changes on: trunk/test/test_config.py
___________________________________________________________________
Name: svn:keywords
   + Id Revision
Name: svn:eol-style
   + native



More information about the Inteproxy-commits mailing list