diff --git a/tests/test_admin.py b/tests/test_admin.py new file mode 100644 index 0000000..bdcd201 --- /dev/null +++ b/tests/test_admin.py @@ -0,0 +1,239 @@ +from __future__ import print_function + +from builtins import str +from builtins import range +import os +import sys +import unittest +from unittest.mock import MagicMock, patch +import time +import email +import errno +import pickle +from email.generator import Generator +try: + from Mailman import __init__ +except ImportError: + import paths + +from Mailman import mm_cfg +from Mailman.MailList import MailList +from Mailman.Message import Message +from Mailman import Errors +from Mailman import Pending +from Mailman.Queue.Switchboard import Switchboard + +# Add the parent directory to the Python path so we can import Mailman modules +sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '..'))) + +# Create a mock email module +class MockMessage: + def __init__(self): + self.headers = {} + self.body = '' + + def get(self, key, default=None): + return self.headers.get(key, default) + + def __getitem__(self, key): + return self.headers[key] + + def __setitem__(self, key, value): + self.headers[key] = value + + def get_payload(self): + return self.body + + def set_payload(self, payload): + self.body = payload + +class MockUtils: + @staticmethod + def unquote(value): + return value + + @staticmethod + def parseaddr(addr): + return ('', addr) + + @staticmethod + def formataddr(pair): + return pair[1] + +class MockEmailModule: + message = MagicMock() + message.Message = MockMessage + utils = MockUtils + +sys.modules['email'] = MockEmailModule +sys.modules['email.message'] = MockEmailModule.message +sys.modules['email.utils'] = MockEmailModule.utils + +# Create a mock mm_cfg module +class MockMMCfg: + def __init__(self): + # Basic configuration options + self.Radio = 1 + self.Toggle = 2 + self.Number = 3 + self.ADMIN_CATEGORIES = ['general', 'privacy', 'members'] + + # Additional required attributes + self.MEMBER_PASSWORD_LENGTH = 10 + self.DEFAULT_SERVER_LANGUAGE = 'en' + self.WEB_HEADER_COLOR = '#ffffff' + self.WEB_ADMINITEM_COLOR = '#eeeeee' + self.WEB_ERROR_COLOR = '#ff0000' + self.WEB_HIGHLIGHT_COLOR = '#dddddd' + self.MAILMAN_SITE_LIST = 'mailman' + self.OWNERS_CAN_DELETE_THEIR_OWN_LISTS = True + self.DEFAULT_EMAIL_HOST = 'example.com' + self.ACCEPTABLE_LISTNAME_CHARACTERS = '[-_.=a-z0-9]' + self.SYNC_AFTER_WRITE = True + self.ALLOW_OPEN_SUBSCRIBE = True + self.AuthListAdmin = 1 + self.AuthSiteAdmin = 2 + self.OPTINFO = { + 'hide': True, + 'nomail': True, + 'ack': True, + 'notmetoo': True, + 'nodupes': True, + 'plain': True + } + +sys.modules['Mailman.mm_cfg'] = MockMMCfg() + +# Mock the Utils module +class MockMailmanUtils: + @staticmethod + def list_exists(name): + return False + + @staticmethod + def ValidateEmail(addr): + return True + + @staticmethod + def GetLanguageDescr(lang): + return 'English' if lang == 'en' else 'Unknown' + +sys.modules['Mailman.Utils'] = MockMailmanUtils + +from Mailman.Cgi import admin +from Mailman.htmlformat import Document +from Mailman.Message import Message + +class TestAdmin(unittest.TestCase): + def setUp(self): + # Create a mock MailList object + self.mlist = MagicMock() + self.doc = Document() + + # Set up basic mlist attributes + self.mlist.real_name = '' + self.mlist.description = '' + self.mlist.info = '' + self.mlist.subscribe_policy = 0 + self.mlist.advertised = False + self.mlist.preferred_language = 'en' + + # Mock GetConfigCategories to return our test categories + class MockGui: + def GetConfigInfo(self, mlist, category): + if category == 'general': + return [ + ('real_name', 1, 30, None, 'List name', None), + ('description', 1, 30, None, 'List description', None), + ('info', 1, 30, None, 'List info', None), + ('subscribe_policy', 1, None, None, 'Subscribe policy', None), + ('advertised', 2, None, None, 'List advertised', None) + ] + return [] + + class MockCategoryDict(dict): + def __init__(self): + super().__init__() + self['general'] = ('General Options', MockGui()) + self['privacy'] = ('Privacy Options', MockGui()) + self['members'] = ('Member Options', MockGui()) + + def keys(self): + return ['general', 'privacy', 'members'] + + self.mlist.GetConfigCategories.return_value = MockCategoryDict() + + def test_change_options_basic(self): + """Test basic option changing functionality""" + # Create a mock CGI data dictionary + cgidata = { + 'real_name': ['Test List'], + 'description': ['A test mailing list'], + 'info': ['List information'], + } + + # Call change_options with the general category + result = admin.change_options(self.mlist, 'general', None, cgidata, self.doc) + + # Verify the changes were made + self.assertEqual(self.mlist.real_name, 'Test List') + self.assertEqual(self.mlist.description, 'A test mailing list') + self.assertEqual(self.mlist.info, 'List information') + + def test_change_options_radio(self): + """Test changing radio button options""" + # Create a mock CGI data dictionary with a radio button option + cgidata = { + 'subscribe_policy': ['2'] # 2 = require approval + } + + # Call change_options + result = admin.change_options(self.mlist, 'general', None, cgidata, self.doc) + + # Verify the change was made + self.assertEqual(self.mlist.subscribe_policy, 2) + + def test_change_options_toggle(self): + """Test changing toggle options""" + # Create a mock CGI data dictionary with a toggle option + cgidata = { + 'advertised': ['1'] # 1 = True + } + + # Call change_options + result = admin.change_options(self.mlist, 'general', None, cgidata, self.doc) + + # Verify the change was made + self.assertTrue(self.mlist.advertised) + + def test_change_options_invalid_category(self): + """Test handling of invalid category""" + # Create a mock CGI data dictionary + cgidata = { + 'real_name': ['Test List'] + } + + # Call change_options with an invalid category + result = admin.change_options(self.mlist, 'nonexistent', None, cgidata, self.doc) + + # Verify no changes were made + self.assertNotEqual(self.mlist.real_name, 'Test List') + + def test_config_categories_handling(self): + """Test that GetConfigCategories handling works correctly""" + # Get the categories + categories = self.mlist.GetConfigCategories() + + # Test that we can access each category + for category in categories: + # Create a simple CGI data dictionary + cgidata = {'real_name': ['Test List']} + + try: + # This should not raise an AttributeError + result = admin.change_options(self.mlist, category, None, cgidata, self.doc) + except AttributeError as e: + self.fail(f"Got AttributeError when processing category {category}: {str(e)}") + +if __name__ == '__main__': + unittest.main() \ No newline at end of file diff --git a/tests/test_bounce_reminders.py b/tests/test_bounce_reminders.py index 9f9b598..607152e 100644 --- a/tests/test_bounce_reminders.py +++ b/tests/test_bounce_reminders.py @@ -134,8 +134,8 @@ class TestMixin(BounceMixin): def suite(): suite = unittest.TestSuite() - suite.addTest(unittest.makeSuite(ReminderBounceTest)) - suite.addTest(unittest.makeSuite(ReminderBounceQueueTest)) + suite.addTest(unittest.defaultTestLoader.loadTestsFromTestCase(ReminderBounceTest)) + suite.addTest(unittest.defaultTestLoader.loadTestsFromTestCase(ReminderBounceQueueTest)) return suite diff --git a/tests/test_bounces.py b/tests/test_bounces.py index c03f786..5c51a8e 100644 --- a/tests/test_bounces.py +++ b/tests/test_bounces.py @@ -252,7 +252,7 @@ def test_microsoft(self): def suite(): suite = unittest.TestSuite() - suite.addTest(unittest.makeSuite(BounceTest)) + suite.addTest(unittest.defaultTestLoader.loadTestsFromTestCase(BounceTest)) return suite diff --git a/tests/test_handlers.py b/tests/test_handlers.py index 2e2a8bd..9948237 100644 --- a/tests/test_handlers.py +++ b/tests/test_handlers.py @@ -2156,24 +2156,24 @@ def test_to_usenet(self): def suite(): suite = unittest.TestSuite() - suite.addTest(unittest.makeSuite(TestAcknowledge)) - suite.addTest(unittest.makeSuite(TestAfterDelivery)) - suite.addTest(unittest.makeSuite(TestApprove)) - suite.addTest(unittest.makeSuite(TestCalcRecips)) - suite.addTest(unittest.makeSuite(TestCleanse)) - suite.addTest(unittest.makeSuite(TestCookHeaders)) - suite.addTest(unittest.makeSuite(TestDecorate)) - suite.addTest(unittest.makeSuite(TestFileRecips)) - suite.addTest(unittest.makeSuite(TestHold)) - suite.addTest(unittest.makeSuite(TestMimeDel)) - suite.addTest(unittest.makeSuite(TestModerate)) - suite.addTest(unittest.makeSuite(TestReplybot)) - suite.addTest(unittest.makeSuite(TestSpamDetect)) - suite.addTest(unittest.makeSuite(TestTagger)) - suite.addTest(unittest.makeSuite(TestToArchive)) - suite.addTest(unittest.makeSuite(TestToDigest)) - suite.addTest(unittest.makeSuite(TestToOutgoing)) - suite.addTest(unittest.makeSuite(TestToUsenet)) + suite.addTest(unittest.defaultTestLoader.loadTestsFromTestCase(TestAcknowledge)) + suite.addTest(unittest.defaultTestLoader.loadTestsFromTestCase(TestAfterDelivery)) + suite.addTest(unittest.defaultTestLoader.loadTestsFromTestCase(TestApprove)) + suite.addTest(unittest.defaultTestLoader.loadTestsFromTestCase(TestCalcRecips)) + suite.addTest(unittest.defaultTestLoader.loadTestsFromTestCase(TestCleanse)) + suite.addTest(unittest.defaultTestLoader.loadTestsFromTestCase(TestCookHeaders)) + suite.addTest(unittest.defaultTestLoader.loadTestsFromTestCase(TestDecorate)) + suite.addTest(unittest.defaultTestLoader.loadTestsFromTestCase(TestFileRecips)) + suite.addTest(unittest.defaultTestLoader.loadTestsFromTestCase(TestHold)) + suite.addTest(unittest.defaultTestLoader.loadTestsFromTestCase(TestMimeDel)) + suite.addTest(unittest.defaultTestLoader.loadTestsFromTestCase(TestModerate)) + suite.addTest(unittest.defaultTestLoader.loadTestsFromTestCase(TestReplybot)) + suite.addTest(unittest.defaultTestLoader.loadTestsFromTestCase(TestSpamDetect)) + suite.addTest(unittest.defaultTestLoader.loadTestsFromTestCase(TestTagger)) + suite.addTest(unittest.defaultTestLoader.loadTestsFromTestCase(TestToArchive)) + suite.addTest(unittest.defaultTestLoader.loadTestsFromTestCase(TestToDigest)) + suite.addTest(unittest.defaultTestLoader.loadTestsFromTestCase(TestToOutgoing)) + suite.addTest(unittest.defaultTestLoader.loadTestsFromTestCase(TestToUsenet)) return suite diff --git a/tests/test_list_locks.py b/tests/test_list_locks.py new file mode 100755 index 0000000..f0563f1 --- /dev/null +++ b/tests/test_list_locks.py @@ -0,0 +1,283 @@ +#!/usr/bin/env python3 +# Copyright (C) 2024 by the Free Software Foundation, Inc. +# +# 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +"""Test script for list locking functionality using installed Mailman paths.""" + +import os +import sys +import random +import time +import unittest +import socket +import logging +from pathlib import Path + +# Remove current directory from sys.path to avoid importing local Mailman +if '' in sys.path: + sys.path.remove('') +if '.' in sys.path: + sys.path.remove('.') + +# Add the installed Mailman path to sys.path +INSTALLED_MAILMAN_PATH = '/usr/local/mailman' +if INSTALLED_MAILMAN_PATH not in sys.path: + sys.path.insert(0, INSTALLED_MAILMAN_PATH) + +# Import Mailman modules +try: + from Mailman import mm_cfg + from Mailman import MailList + from Mailman import Utils + from Mailman import LockFile + from Mailman.Errors import MMListError + from Mailman.Logging.Syslog import syslog +except ImportError as e: + print(f"Error importing Mailman modules: {e}") + print(f"sys.path = {sys.path}") + raise + +# Enable lock debugging +mm_cfg.LIST_LOCK_DEBUGGING = True + +class TestListLocks(unittest.TestCase): + """Test list locking functionality.""" + + def setUp(self): + """Set up test environment.""" + # Get all existing lists + self.lists_dir = os.path.join(INSTALLED_MAILMAN_PATH, 'lists') + self.locks_dir = mm_cfg.LOCK_DIR + print(f"Using lock directory: {self.locks_dir}") + + self.available_lists = [d for d in os.listdir(self.lists_dir) + if os.path.isdir(os.path.join(self.lists_dir, d))] + if not self.available_lists: + self.skipTest("No lists found in the Mailman installation") + self.test_list = random.choice(self.available_lists) + print(f"Testing with list: {self.test_list}") + print(f"Current user: {os.getuid()}") + print(f"Current group: {os.getgid()}") + print(f"Locks directory permissions: {oct(os.stat(self.locks_dir).st_mode)}") + + def test_direct_lock_creation(self): + """Test lock creation directly using LockFile.""" + # Create a lock file path + lock_file = os.path.join(self.locks_dir, f"{self.test_list}.lock") + + # Create a LockFile instance with logging enabled + lock = LockFile.LockFile(lock_file, lifetime=15, withlogging=True) + + # Ensure lock file doesn't exist initially + if os.path.exists(lock_file): + os.unlink(lock_file) + + print(f"Attempting to create lock file at {lock_file}") + try: + # Try to acquire the lock + lock.lock() + print("Lock acquired successfully") + + # Create the temp file first + temp_file = f"{lock_file}.{socket.gethostname()}.{os.getpid()}.0" + print(f"Creating temp file: {temp_file}") + with open(temp_file, 'w') as f: + f.write(temp_file) + + # Verify temp file exists + self.assertTrue(os.path.exists(temp_file), + f"Temp file {temp_file} was not created") + + # Now verify lock file exists + self.assertTrue(os.path.exists(lock_file), + f"Lock file {lock_file} was not created") + + # Check lock file contents + if os.path.exists(lock_file): + with open(lock_file) as f: + contents = f.read().strip() + print(f"Lock file contents: {contents}") + print(f"Lock file permissions: {oct(os.stat(lock_file).st_mode)}") + + # Verify lock file format (should be hostname.pid) + expected_prefix = f"{socket.gethostname()}.{os.getpid()}" + self.assertTrue(contents.startswith(expected_prefix), + f"Lock file contents {contents} don't match expected format") + + # Try to unlock + print("Attempting to unlock") + lock.unlock() + print("Unlock completed") + + # Verify both files were removed + self.assertFalse(os.path.exists(lock_file), + f"Lock file {lock_file} was not removed after unlock") + self.assertFalse(os.path.exists(temp_file), + f"Temp file {temp_file} was not removed after unlock") + + except Exception as e: + print(f"Error during lock test: {e}") + if os.path.exists(lock_file): + print(f"Lock file exists after error") + try: + with open(lock_file) as f: + print(f"Lock file contents: {f.read()}") + except: + print("Could not read lock file") + if os.path.exists(temp_file): + print(f"Temp file exists after error") + try: + with open(temp_file) as f: + print(f"Temp file contents: {f.read()}") + except: + print("Could not read temp file") + raise + finally: + # Clean up if needed + try: + lock.unlock(unconditionally=True) + except: + pass + for f in [lock_file, temp_file]: + if os.path.exists(f): + try: + os.unlink(f) + except: + pass + + def test_list_lock_creation(self): + """Test that a list lock is created when locking a list.""" + # Get the list object + mlist = MailList.MailList(self.test_list, lock=False) + + # Get the expected lock file path + lock_file = os.path.join(self.locks_dir, f"{self.test_list}.lock") + temp_file = f"{lock_file}.{socket.gethostname()}.{os.getpid()}.0" + + # Ensure files don't exist initially + for f in [lock_file, temp_file]: + if os.path.exists(f): + os.unlink(f) + + # Lock the list + print(f"Attempting to lock list {self.test_list}") + try: + mlist.Lock() + print(f"Lock method completed successfully") + except Exception as e: + print(f"Error during lock: {e}") + raise + + # Verify lock file was created + print(f"Checking for lock file at {lock_file}") + print(f"Lock file exists: {os.path.exists(lock_file)}") + print(f"Temp file exists: {os.path.exists(temp_file)}") + + if os.path.exists(lock_file): + print(f"Lock file contents: {open(lock_file).read()}") + print(f"Lock file permissions: {oct(os.stat(lock_file).st_mode)}") + + if os.path.exists(temp_file): + print(f"Temp file contents: {open(temp_file).read()}") + print(f"Temp file permissions: {oct(os.stat(temp_file).st_mode)}") + + self.assertTrue(os.path.exists(lock_file), + f"Lock file {lock_file} was not created") + + # Read lock file contents + with open(lock_file) as f: + lock_contents = f.read().strip() + + # Verify lock file contains expected format (hostname.pid) + expected_prefix = f"{socket.gethostname()}.{os.getpid()}" + self.assertTrue(lock_contents.startswith(expected_prefix), + f"Lock file contents {lock_contents} don't match expected format") + + # Unlock the list + print(f"Attempting to unlock list {self.test_list}") + try: + mlist.Unlock() + print(f"Unlock method completed successfully") + except Exception as e: + print(f"Error during unlock: {e}") + raise + + # Verify files were removed + print(f"Checking if files were removed") + print(f"Lock file exists: {os.path.exists(lock_file)}") + print(f"Temp file exists: {os.path.exists(temp_file)}") + self.assertFalse(os.path.exists(lock_file), + f"Lock file {lock_file} was not removed after unlock") + self.assertFalse(os.path.exists(temp_file), + f"Temp file {temp_file} was not removed after unlock") + + def test_list_lock_timeout(self): + """Test that a list lock times out after the specified lifetime.""" + # Get the list object with a short lock lifetime + mlist = MailList.MailList(self.test_list, lock=False) + + # Get the expected lock file path + lock_file = os.path.join(self.locks_dir, f"{self.test_list}.lock") + temp_file = f"{lock_file}.{socket.gethostname()}.{os.getpid()}.0" + + # Create a direct lock with a short lifetime + lock = LockFile.LockFile(lock_file, lifetime=5, withlogging=True) + + # Ensure files don't exist initially + for f in [lock_file, temp_file]: + if os.path.exists(f): + os.unlink(f) + + # Lock the file + print(f"Attempting to create lock with 5 second lifetime") + try: + lock.lock() + print(f"Lock created successfully") + except Exception as e: + print(f"Error during lock creation: {e}") + raise + + # Verify lock file was created + print(f"Checking for lock file at {lock_file}") + print(f"Lock file exists: {os.path.exists(lock_file)}") + print(f"Temp file exists: {os.path.exists(temp_file)}") + + if os.path.exists(lock_file): + print(f"Lock file contents: {open(lock_file).read()}") + print(f"Lock file permissions: {oct(os.stat(lock_file).st_mode)}") + + if os.path.exists(temp_file): + print(f"Temp file contents: {open(temp_file).read()}") + print(f"Temp file permissions: {oct(os.stat(temp_file).st_mode)}") + + self.assertTrue(os.path.exists(lock_file), + f"Lock file {lock_file} was not created") + + # Wait for lock to timeout (lifetime is 5 seconds) + print("Waiting for lock to timeout...") + time.sleep(7) # Wait a bit longer than the lifetime + + # Verify files were automatically removed + print(f"Checking if files were automatically removed") + print(f"Lock file exists: {os.path.exists(lock_file)}") + print(f"Temp file exists: {os.path.exists(temp_file)}") + self.assertFalse(os.path.exists(lock_file), + f"Lock file {lock_file} was not automatically removed after timeout") + self.assertFalse(os.path.exists(temp_file), + f"Temp file {temp_file} was not automatically removed after timeout") + +if __name__ == '__main__': + unittest.main() \ No newline at end of file diff --git a/tests/test_lockfile.py b/tests/test_lockfile.py index 9c4c2b7..372c4cc 100644 --- a/tests/test_lockfile.py +++ b/tests/test_lockfile.py @@ -40,7 +40,7 @@ def test_two_lockfiles_same_proc(self): def suite(): suite = unittest.TestSuite() - suite.addTest(unittest.makeSuite(TestLockFile)) + suite.addTest(unittest.defaultTestLoader.loadTestsFromTestCase(TestLockFile)) return suite diff --git a/tests/test_membership.py b/tests/test_membership.py index aec2bd8..0997aa5 100644 --- a/tests/test_membership.py +++ b/tests/test_membership.py @@ -376,8 +376,8 @@ class Info: pass def suite(): suite = unittest.TestSuite() - suite.addTest(unittest.makeSuite(TestNoMembers)) - suite.addTest(unittest.makeSuite(TestMembers)) + suite.addTest(unittest.defaultTestLoader.loadTestsFromTestCase(TestNoMembers)) + suite.addTest(unittest.defaultTestLoader.loadTestsFromTestCase(TestMembers)) return suite diff --git a/tests/test_message.py b/tests/test_message.py index c275ab0..3771870 100644 --- a/tests/test_message.py +++ b/tests/test_message.py @@ -99,9 +99,9 @@ def test_bounce_message(self): def suite(x): suite = unittest.TestSuite() if x == '1': - suite.addTest(unittest.makeSuite(TestSentMessage1)) + suite.addTest(unittest.defaultTestLoader.loadTestsFromTestCase(TestSentMessage1)) elif x == '2': - suite.addTest(unittest.makeSuite(TestSentMessage2)) + suite.addTest(unittest.defaultTestLoader.loadTestsFromTestCase(TestSentMessage2)) return suite diff --git a/tests/test_runners.py b/tests/test_runners.py index d76dcc8..9d7b777 100644 --- a/tests/test_runners.py +++ b/tests/test_runners.py @@ -115,7 +115,7 @@ def test_munge_duplicates(self): def suite(): suite = unittest.TestSuite() - suite.addTest(unittest.makeSuite(TestPrepMessage)) + suite.addTest(unittest.defaultTestLoader.loadTestsFromTestCase(TestPrepMessage)) return suite diff --git a/tests/test_safedict.py b/tests/test_safedict.py index d80db1e..61717e2 100644 --- a/tests/test_safedict.py +++ b/tests/test_safedict.py @@ -96,8 +96,8 @@ def test_copy(self): def suite(): suite = unittest.TestSuite() - suite.addTest(unittest.makeSuite(TestSafeDict)) - suite.addTest(unittest.makeSuite(TestMsgSafeDict)) + suite.addTest(unittest.defaultTestLoader.loadTestsFromTestCase(TestSafeDict)) + suite.addTest(unittest.defaultTestLoader.loadTestsFromTestCase(TestMsgSafeDict)) return suite diff --git a/tests/test_security_mgr.py b/tests/test_security_mgr.py index 79a1a21..09358eb 100644 --- a/tests/test_security_mgr.py +++ b/tests/test_security_mgr.py @@ -273,9 +273,9 @@ def test_not_a_user(self): def suite(): suite = unittest.TestSuite() - suite.addTest(unittest.makeSuite(TestSecurityManager)) - suite.addTest(unittest.makeSuite(TestAuthenticate)) - suite.addTest(unittest.makeSuite(TestWebAuthenticate)) + suite.addTest(unittest.defaultTestLoader.loadTestsFromTestCase(TestSecurityManager)) + suite.addTest(unittest.defaultTestLoader.loadTestsFromTestCase(TestAuthenticate)) + suite.addTest(unittest.defaultTestLoader.loadTestsFromTestCase(TestWebAuthenticate)) return suite diff --git a/tests/test_smtp.py b/tests/test_smtp.py index bc3e770..126ec65 100644 --- a/tests/test_smtp.py +++ b/tests/test_smtp.py @@ -66,7 +66,7 @@ def test_disconnect_midsession(self): def suite(): suite = unittest.TestSuite() - #suite.addTest(unittest.makeSuite(TestSMTPDirect)) + #suite.addTest(unittest.defaultTestLoader.loadTestsFromTestCase(TestSMTPDirect)) return suite