Index: main/waeup.sirp/trunk/src/waeup/sirp/utils/tests/test_logger.py
===================================================================
--- main/waeup.sirp/trunk/src/waeup/sirp/utils/tests/test_logger.py	(revision 6502)
+++ main/waeup.sirp/trunk/src/waeup/sirp/utils/tests/test_logger.py	(revision 6578)
@@ -1,44 +1,193 @@
-##
-## test_logger.py
-## Login : <uli@pu.smp.net>
-## Started on  Mon Jun 13 14:40:04 2011 Uli Fouquet
-## $Id$
-## 
-## Copyright (C) 2011 Uli Fouquet
-## This program is free software; you can redistribute it and/or modify
-## it under the terms of the GNU General Public License as published by
-## the Free Software Foundation; either version 2 of the License, or
-## (at your option) any later version.
-## 
-## This program is distributed in the hope that it will be useful,
-## but WITHOUT ANY WARRANTY; without even the implied warranty of
-## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-## GNU General Public License for more details.
-## 
-## You should have received a copy of the GNU General Public License
-## along with this program; if not, write to the Free Software
-## Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
-##
+# Tests for waeup.sirp.utils.logger
 import logging
 import os
-import re
 import shutil
 import tempfile
-from zope.app.testing.functional import FunctionalTestCase
 from zope.component import queryUtility
 from zope.component.hooks import setSite, clearSite
 from zope.interface.verify import verifyClass, verifyObject
 from waeup.sirp.app import University
-from waeup.sirp.interfaces import ISiteLoggers, ILogger
-from waeup.sirp.testing import FunctionalLayer, doctestsuite_for_module
+from waeup.sirp.testing import (
+    FunctionalLayer, FunctionalTestCase, get_all_loggers,
+    remove_new_loggers, remove_logger)
+
 from waeup.sirp.utils.logger import (
-    SiteLoggers, WAeUPLogger, SiteLoggersRepository)
-
-class SiteLoggersTests(FunctionalTestCase):
+    Logger, MAX_BYTES, BACKUP_COUNT, ILoggerCollector, LoggerCollector,
+    ILogger)
+
+class FakeComponent(object):
+    # A component that can use the Logger mixin-class
+    def do_something(self):
+        self.logger.info('msg from do_something')
+
+class LoggingComponent(FakeComponent, Logger):
+    # A component that supports logging by mixing in Logger
+    logger_name = 'sample.${sitename}'
+    logger_filename = 'sample.log'
+
+class LoggersTests(FunctionalTestCase):
 
     layer = FunctionalLayer
 
     def setUp(self):
-        super(SiteLoggersTests, self).setUp()
+        super(LoggersTests, self).setUp()
+        self.old_loggers = get_all_loggers()
+        # Setup a sample site for each test
+        app = University()
+        self.dc_root = tempfile.mkdtemp()
+        self.dc_root2 = None
+        app['datacenter'].setStoragePath(self.dc_root)
+
+        # Prepopulate the ZODB...
+        self.getRootFolder()['app'] = app
+        self.app = self.getRootFolder()['app']
+        self.workdir = tempfile.mkdtemp()
+        return
+
+    def tearDown(self):
+        super(LoggersTests, self).tearDown()
+        remove_new_loggers(self.old_loggers)
+        shutil.rmtree(self.workdir)
+        shutil.rmtree(self.dc_root)
+        clearSite()
+        return
+
+    def clear_logger(self, logger):
+        del_list = [x for x in logger.handlers]
+        for handler in del_list:
+            logger.removeHandler(handler)
+        return
+
+    def test_iface(self):
+        setSite(self.app)
+        logger = LoggingComponent()
+        verifyClass(ILogger, Logger)
+        verifyObject(ILogger, logger)
+        return
+
+    def test_logger_prop_no_site(self):
+        # If not in a site, we will get a logger without handlers
+        logger = Logger().logger
+        self.assertTrue(isinstance(logger, logging.Logger))
+        self.assertEqual(len(logger.handlers), 0)
+        return
+
+    def test_site_no_site_name(self):
+        # If a site has not name we will get a logger anyway
+        setSite(self.app)
+        del self.app.__name__
+        logger = Logger().logger
+        self.assertTrue(logger is not None)
+        return
+
+    def test_get_logdir(self):
+        # We can get a logdir
+        setSite(self.app)
+        logger = Logger()
+        logdir = logger.logger_get_logdir()
+        expected_logdir = os.path.join(
+            self.app['datacenter'].storage, 'logs')
+        self.assertEqual(logdir, expected_logdir)
+        return
+
+    def test_get_logdir_no_site(self):
+        # We cannot get a logdir without a site
+        logger = Logger()
+        logdir = logger.logger_get_logdir()
+        self.assertTrue(logdir is None)
+        return
+
+    def test_get_logdir_no_datacenter(self):
+        # Having a site, is not enough. We also need a datacenter in it.
+        setSite(self.app)
+        del self.app['datacenter']
+        logger = Logger()
+        logdir = logger.logger_get_logdir()
+        self.assertTrue(logdir is None)
+        return
+
+    def test_get_logdir_create_dir(self):
+        # If the logdirectory does not exist, we will create it
+        setSite(self.app)
+        logger = Logger()
+        expected_logdir = os.path.join(
+            self.app['datacenter'].storage, 'logs')
+        if os.path.exists(expected_logdir):
+            shutil.rmtree(expected_logdir)
+        logdir = logger.logger_get_logdir()
+        exists_after = os.path.exists(expected_logdir)
+        self.assertEqual(logdir, expected_logdir)
+        self.assertTrue(exists_after is True)
+        return
+
+    def test_get_logfile(self):
+        # We can get a logfile
+        setSite(self.app)
+        logger = Logger()
+        expected_filepath = os.path.join(
+            self.app['datacenter'].storage, 'logs', 'main.log')
+        path = logger.logger_get_logfile_path()
+        self.assertEqual(expected_filepath, path)
+        return
+
+    def test_get_logfile_no_dir(self):
+        # We cannot get a logfilepath if there is no dir for it
+        setSite(self.app)
+        logger = Logger()
+        del self.app['datacenter']
+        path = logger.logger_get_logfile_path()
+        self.assertTrue(path is None)
+        return
+
+    def test_setup(self):
+        # We can setup a logger.
+        mylogger = logging.getLogger('test.sample')
+        setSite(self.app)
+        logger = Logger()
+        result = logger.logger_setup(mylogger)
+        self.assertEqual(len(result.handlers), 1)
+        handler = result.handlers[0]
+        self.assertEqual(handler.maxBytes, MAX_BYTES)
+        self.assertEqual(handler.backupCount, BACKUP_COUNT)
+        self.assertTrue(result.propagate is False)
+        return
+
+    def test_setup_no_site(self):
+        # Without a site we get no logger from setup
+        mylogger = logging.getLogger('test.sample')
+        logger = Logger()
+        result = logger.logger_setup(mylogger)
+        self.assertTrue(result is None)
+        return
+
+    def test_logfile_change(self):
+        # When the logfile location changes, logger_logfile_changed can react
+        setSite(self.app)
+        logger = Logger()
+        logger.logger.warn('Warning 1')  # Log something to old logfile
+        filename1 = logger.logger.handlers[0].baseFilename
+        content1 = open(filename1, 'r').read()
+        # Now move the logfile
+        self.dc_root2 = tempfile.mkdtemp()
+        self.app['datacenter'].setStoragePath(self.dc_root2)
+        logger.logger_logfile_changed()
+        logger.logger.warn('Warning 2')  # Log something to new logfile
+        filename2 = logger.logger.handlers[0].baseFilename
+        content2 = open(filename2, 'r').read()
+        self.assertTrue('Warning 1' in content1)
+        self.assertTrue('Warning 2' in content2)
+        self.assertTrue('Warning 1' not in content2)
+        self.assertTrue('Warning 2' not in content1)
+        self.assertTrue(filename1 != filename2)
+        return
+
+
+class LoggersFunctionalTests(FunctionalTestCase):
+    # Check loggers with real components using them
+
+    layer = FunctionalLayer
+
+    def setUp(self):
+        super(LoggersFunctionalTests, self).setUp()
 
         # Setup a sample site for each test
@@ -50,11 +199,15 @@
         self.getRootFolder()['app'] = app
         self.app = self.getRootFolder()['app']
-
         self.workdir = tempfile.mkdtemp()
-        SiteLoggersRepository.clear()
+
+        self.app['mycomponent'] = LoggingComponent()
+        self.component = self.app['mycomponent']
+        setSite(self.app)
+        self.old_loggers = get_all_loggers()
         return
 
     def tearDown(self):
-        super(SiteLoggersTests, self).tearDown()
+        super(LoggersFunctionalTests, self).tearDown()
+        remove_new_loggers(self.old_loggers)
         shutil.rmtree(self.workdir)
         shutil.rmtree(self.dc_root)
@@ -62,75 +215,39 @@
         return
 
-    def test_iface(self):
-        loggers = SiteLoggers()
-        verifyClass(ISiteLoggers, SiteLoggers)
-        verifyObject(ISiteLoggers, loggers)
-        return
-
-    def test_loggers(self):
-        loggers = SiteLoggers()
-        loggers.register('sample1')
-        loggers.register('sample2')
-        result = loggers.loggers
-        assert len(result) == 2
-
-    def test_register(self):
-        loggers = SiteLoggers()
-        loggers.register('sample')
-        assert 'sample' in loggers._loggers.keys()
-
-    def test_register_duplicate(self):
-        loggers = SiteLoggers()
-        loggers.register('sample')
-        self.assertRaises(
-            KeyError,
-            loggers.register, 'sample')
-
-    def test_unregister(self):
-        loggers = SiteLoggers()
-        loggers.register('sample')
-        loggers.unregister('sample')
-        self.assertEqual(loggers._loggers, {})
-
-    def test_utility_no_site(self):
-        # make sure we get loggers even if no site is defined/set
-        util = queryUtility(ISiteLoggers)
-        loggers = util.getLoggers()
-        assert util is not None
-        assert loggers is not None
-        assert None in SiteLoggersRepository.keys()
-
-    def test_utility(self):
-        # make sute we get loggers for a certain site
-        setSite(self.app)
-        util = queryUtility(ISiteLoggers)
-        loggers = util.getLoggers()
-        assert util is not None
-        assert loggers is not None
-        assert self.app in SiteLoggersRepository.keys()
-
-    def test_utility_multiple(self):
-        # make sure we get the same loggers for same site
-        setSite(self.app)
-        self.assertEqual(SiteLoggersRepository.keys(), [])
-        util = queryUtility(ISiteLoggers)
-        loggers1 = util.getLoggers()
-        loggers2 = util.getLoggers()
-        assert loggers1 is loggers2
-
-    def test_utility_given_site(self):
-        # make sure a passed site is respected
-        util = queryUtility(ISiteLoggers)
-        loggers = util.getLoggers(site=self.app)
-        assert loggers is not None
-        assert self.app in SiteLoggersRepository.keys()
-
-class LoggerTests(FunctionalTestCase):
+    def test_component_can_get_logger(self):
+        # Components can get a logger
+        logger = self.component.logger
+        self.assertTrue(logger is not None)
+        self.assertEqual(len(logger.handlers), 1)
+        self.assertEqual(logger.name, 'sample.app')
+        return
+
+    def test_component_handler_setup(self):
+        # handlers of components are setup
+        handler = self.component.logger.handlers[0]
+        self.assertTrue(isinstance(
+                handler, logging.handlers.RotatingFileHandler))
+        self.assertEqual(handler.maxBytes, MAX_BYTES)
+        self.assertEqual(handler.backupCount, BACKUP_COUNT)
+        self.assertTrue(handler.baseFilename.endswith('logs/sample.log'))
+        return
+
+    def test_component_can_log(self):
+        # a method of some 'loggerized' instance can log like this.
+        self.component.logger.setLevel(logging.INFO)
+        self.component.do_something()
+        handler = logging.getLogger('sample.app').handlers[0]
+        logfile = handler.baseFilename
+        content = open(logfile, 'rb').read()
+        self.assertTrue(content.endswith(' - INFO - msg from do_something\n'))
+        return
+
+class LoggerCollectorTests(FunctionalTestCase):
 
     layer = FunctionalLayer
 
     def setUp(self):
-        super(LoggerTests, self).setUp()
-
+        super(LoggerCollectorTests, self).setUp()
+        self.old_loggers = get_all_loggers()
         # Setup a sample site for each test
         app = University()
@@ -141,10 +258,18 @@
         self.getRootFolder()['app'] = app
         self.app = self.getRootFolder()['app']
-
         self.workdir = tempfile.mkdtemp()
+
+        self.app['mycomponent'] = LoggingComponent()
+        self.component = self.app['mycomponent']
+        setSite(self.app)
+
+        self.component = LoggingComponent()
+
+
         return
 
     def tearDown(self):
-        super(LoggerTests, self).tearDown()
+        super(LoggerCollectorTests, self).tearDown()
+        remove_new_loggers(self.old_loggers)
         shutil.rmtree(self.workdir)
         shutil.rmtree(self.dc_root)
@@ -152,81 +277,113 @@
         return
 
-    def test_iface(self):
-        setSite(self.app)
-        logger = WAeUPLogger('sample')
-        verifyClass(ILogger, WAeUPLogger)
-        verifyObject(ILogger, logger)
-        return
-
-    def test_logger_attr(self):
-        setSite(self.app)
-        logger = WAeUPLogger('sample')
-        raw_logger = logger.logger
-        assert isinstance(raw_logger, logging.Logger)
-
-    def test_site_name(self):
-        setSite(self.app)
-        logger = WAeUPLogger('waeup.sirp.%s.sample', site=self.app)
-        self.assertEqual(logger.logger.name, 'waeup.sirp.app.sample')
-
-    def test_site_no_name(self):
-        # While a site has no name, we get __app as placeholder
-        setSite(self.app)
-        self.app.__name__ = None
-        logger = WAeUPLogger('waeup.sirp.%s.sample', site=self.app)
-        self.assertEqual(logger.logger.name, 'waeup.sirp.__app.sample')
-
-    def test_site_changing_name(self):
-        # Changing the sitename (from None to a value) will be reflected
-        setSite(self.app)
-        self.app.__name__ = None
-        logger = WAeUPLogger('waeup.sirp.%s.sample', site=self.app)
-        self.assertEqual(logger.logger.name, 'waeup.sirp.__app.sample')
-        self.app.__name__ = 'myapp'
-        self.assertEqual(logger.logger.name, 'waeup.sirp.myapp.sample')
-        assert len(logger.logger.handlers) > 0
-
-    def test_loggername_expansion(self):
-        setSite(self.app)
-        logger = WAeUPLogger('sample', site=self.app)
-        logger.logger # Trigger setup
-        self.assertEqual(logger._loggername, 'waeup.sirp.app.sample')
-
-    def test_filename(self):
-        setSite(self.app)
-        logger = WAeUPLogger('sample')
-        logger.logger # Trigger setup
-        self.assertEqual(logger.filename, 'sample.log')
-
-    def test_filename_from_dotted_name(self):
-        setSite(self.app)
-        logger = WAeUPLogger('waeup.sirp.%s.sample')
-        logger.logger # Trigger setup
-        logfile = os.path.join(self.dc_root, 'logs', 'sample.log')
-        self.assertEqual(logger.filename, 'sample.log')
-        assert os.path.isfile(logfile)
-
-    def test_option_filename(self):
-        setSite(self.app)
-        logger = WAeUPLogger('sample', filename='my.log')
-        logger.logger # Trigger setup
-        logfile = os.path.join(self.dc_root, 'logs', 'my.log')
-        self.assertEqual(logger.filename, 'my.log')
-        assert os.path.isfile(logfile)
-
-    def test_option_propagate(self):
-        logger = WAeUPLogger('sample', site=self.app)
-        assert logger.logger.propagate is False  # default
-        logger = WAeUPLogger('sample', site=self.app, propagate=True)
-        assert logger.logger.propagate is True
-        logger = WAeUPLogger('sample', site=self.app, propagate=False)
-        assert logger.logger.propagate is False
-
-    def test_logging(self):
-        logger = WAeUPLogger('sample', site=self.app)
-        logger.logger.info('Hi there!')
-        logfile = os.path.join(self.dc_root, 'logs', 'sample.log')
-        assert os.path.isfile(logfile)
-        contents = open(logfile, 'rb').read()
-        self.assertTrue(re.match(
-                '^....-..-.. ..:..:..,... - INFO - Hi there!\n$', contents))
+    def test_ifaces(self):
+        collector = LoggerCollector()
+        verifyClass(ILoggerCollector, LoggerCollector)
+        verifyObject(ILoggerCollector, collector)
+        return
+
+    def test_get_utility(self):
+        # We can get a logger collector via utility lookup
+        util1 = queryUtility(ILoggerCollector, default=None)
+        util2 = queryUtility(ILoggerCollector, default=None)
+        self.assertTrue(util1 is util2)
+        self.assertTrue(isinstance(util1, LoggerCollector))
+        return
+
+    def test_get_loggers(self):
+        # We can get loggers from a logger collector
+        collector = LoggerCollector()
+        result0 = collector.getLoggers(None)
+        result1 = collector.getLoggers('not-a-site')
+        result2 = collector.getLoggers(self.app)
+        collector.registerLogger(self.app, self.component)
+        result3 = collector.getLoggers(self.app)
+        self.assertEqual(result0, [])
+        self.assertEqual(result1, [])
+        self.assertEqual(result2, [])
+        self.assertEqual(result3, [self.component])
+        self.assertTrue(result3[0] is self.component)
+        return
+
+    def test_register_logger(self):
+        # We can register loggers by name
+        collector = LoggerCollector()
+        collector.registerLogger(None, self.component) # Should have no effect
+        collector.registerLogger(object(), self.component) # Same here
+        collector.registerLogger(self.app, self.component)
+        self.assertEqual(['app'], collector.keys())
+        return
+
+    def test_register_logger_double(self):
+        # Registering the same component twice gives one entry, not two
+        collector = LoggerCollector()
+        collector.registerLogger(self.app, self.component)
+        collector.registerLogger(self.app, self.component)
+        self.assertEqual(len(collector['app']), 1)
+        return
+
+    def test_unregister_logger(self):
+        # We can also unregister loggers
+        collector = LoggerCollector()
+        collector.registerLogger(self.app, self.component)
+        collector.unregisterLogger(self.app, self.component)
+        result = collector.getLoggers(self.app)
+        self.assertEqual(result, [])
+        return
+
+    def test_unregister_logger_invalid(self):
+        # We can cope with unregistration of non-existing entries.
+        collector = LoggerCollector()
+        collector.registerLogger(self.app, self.component)
+        collector.unregisterLogger(None, self.component)
+        collector.unregisterLogger(self.app, 'nonsense')
+        self.assertEqual(len(collector), 1)
+        return
+
+class LogfileChangeTests(FunctionalTestCase):
+
+    layer = FunctionalLayer
+
+    def setUp(self):
+        super(LogfileChangeTests, self).setUp()
+        remove_logger('waeup.sirp.app') # In case it lingers around
+        self.old_loggers = get_all_loggers()
+        # Setup a sample site for each test
+        app = University()
+        self.dc_root = tempfile.mkdtemp()
+        self.dc_root_new = None
+        app['datacenter'].setStoragePath(self.dc_root)
+
+        # Prepopulate the ZODB...
+        self.getRootFolder()['app'] = app
+        self.app = self.getRootFolder()['app']
+        self.workdir = tempfile.mkdtemp()
+
+        self.app['mycomponent'] = LoggingComponent()
+        self.component = self.app['mycomponent']
+        setSite(self.app)
+
+        self.component = LoggingComponent()
+        return
+
+    def tearDown(self):
+        super(LogfileChangeTests, self).tearDown()
+        remove_new_loggers(self.old_loggers)
+        shutil.rmtree(self.workdir)
+        shutil.rmtree(self.dc_root)
+        if self.dc_root_new is not None:
+            shutil.rmtree(self.dc_root_new)
+        clearSite()
+        return
+
+    def test_storage_move(self):
+        # When a datacenter storage (its path) changes, we have to
+        # move the logfiles as well.
+        #return
+        filename1 = self.component.logger.handlers[0].baseFilename
+        self.dc_root_new = tempfile.mkdtemp()
+        self.app['datacenter'].setStoragePath(self.dc_root_new)
+        filename2 = self.component.logger.handlers[0].baseFilename
+        self.assertTrue(filename1 != filename2)
+        self.assertTrue(filename2.startswith(self.dc_root_new))
+        return
