Commits

Anonymous committed 6f480b0

Add tests for packaging.util and packaging.mkcfg => fixes

I love tests ;P

  • Participants
  • Parent commits 433e453

Comments (0)

Files changed (4)

File Lib/packaging/mkcfg.py

 import sys
 import re
 import shutil
-import glob
-import re
 import time
 import sysconfig
 import configparser as CP
 import tempfile
-from operator import itemgetter
-from hashlib import md5
-from functools import partial
 
 from packaging.util import load_distutils_setup_py
 
                  + (default and '[%s] ' % str(', '.join(default)) or '')
         answers = []
         while True:
-            anwser = raw_input(prompt + ': ')
+            answer = raw_input(prompt + ': ')
             if answer == '?':
                 self.show_help(step)
                 continue

File Lib/packaging/tests/test_mkcfg.py

 # -*- coding: utf-8 -*-
-"""Tests for packaging.mkcfg."""
+"""Tests for distutils.mkcfg."""
 import os
+import os.path as osp
 import sys
+import re
 import io
 import sysconfig
+from textwrap import dedent
+from operator import itemgetter
 
-from textwrap import dedent
-from packaging.mkcfg import MkCfg, ask_yn, ask, main
 from packaging.tests import run_unittest, support, unittest
+import packaging.mkcfg as mkcfg
+from packaging.mkcfg import Wizard
 
 
-class MkcfgTestCase(support.TempdirManager,
-                    unittest.TestCase):
+def create_files(root, tree):
+    for name, content in tree.items():
+        assert isinstance(content, (str, dict))
+        path = os.path.join(root, name)
+        if isinstance(content, str):
+            if not os.path.exists(root):
+                os.makedirs(root)
+            fobj = open(path, 'w')
+            try:
+                fobj.write(content)
+            finally:
+                fobj.close()
+        elif isinstance(content, dict):
+            create_files(path, content)
 
+class MkcfgWizardTests(support.TempdirManager,
+                       unittest.TestCase):
+    # output (stdout) tests are case/space-insensitive.
     def setUp(self):
-        super(MkcfgTestCase, self).setUp()
+        super(MkcfgWizardTests, self).setUp()
         self._stdin = sys.stdin
         self._stdout = sys.stdout
         sys.stdin = io.StringIO()
         self._cwd = os.getcwd()
         self.wdir = self.mkdtemp()
         os.chdir(self.wdir)
-        # patch sysconfig
-        self._old_get_paths = sysconfig.get_paths
-        sysconfig.get_paths = lambda *args, **kwargs: {
-                'man': sys.prefix + '/share/man',
-                'doc': sys.prefix + '/share/doc/pyxfoil', }
 
     def tearDown(self):
-        super(MkcfgTestCase, self).tearDown()
+        super(MkcfgWizardTests, self).tearDown()
         sys.stdin = self._stdin
         sys.stdout = self._stdout
         os.chdir(self._cwd)
-        sysconfig.get_paths = self._old_get_paths
 
-    def test_ask_yn(self):
-        sys.stdin.write('y\n')
+    def test_warn(self):
+        wizard = Wizard()
+        message = re.sub('\s', '', 'my name is babar'.lower())
+        wizard.warn(message)
+        sys.stdout.seek(0)
+        out = re.sub('\s', '', sys.stdout.read().lower())
+        self.assertIn(message, out)
+
+    def test_show_help(self):
+        wizard = Wizard()
+        for step, info in Wizard.STEPS.items():
+            expected = re.sub('\s', '', info['help'].lower())
+            wizard.show_help(step)
+            sys.stdout.seek(0)
+            out = re.sub('\s', '', sys.stdout.read().lower())
+            self.assertIn(expected, out)
+
+    def test_ask_simple(self):
+        wizard = Wizard()
+        for step, info in Wizard.STEPS.items():
+            if mkcfg.MULTIPLE in info['types']:
+                continue
+            expected = re.sub('\s', '', 'babar'.lower())
+            out = sys.stdin.write('babar\n\n')
+            sys.stdin.seek(0)
+            out = wizard.ask_simple(step)
+            sys.stdout.seek(0)
+            self.assertIn(expected, out)
+
+    def test_ask_simple_show_help(self):
+        wizard = Wizard()
+        for step, info in Wizard.STEPS.items():
+            if mkcfg.MULTIPLE in info['types']:
+                continue
+            expected = re.sub('\s', '', info['help'].lower())
+            sys.stdin.write('?\nbabar\n')
+            sys.stdin.seek(0)
+            out = wizard.ask_simple(step)
+            sys.stdout.seek(0)
+            out = re.sub('\s', '', sys.stdout.read().lower())
+            self.assertIn(expected, out)
+
+    def test_ask_simple_default(self):
+        wizard = Wizard()
+        for step, info in Wizard.STEPS.items():
+            if mkcfg.MULTIPLE in info['types']:
+                continue
+            expected = re.sub('\s', '', 'babar'.lower())
+            out = sys.stdin.write('\n\n')
+            sys.stdin.seek(0)
+            out = wizard.ask_simple(step, expected)
+            sys.stdout.seek(0)
+            self.assertIn(expected, out)
+
+    def test_ask_simple_mandatory(self):
+        wizard = Wizard()
+        for step, info in Wizard.STEPS.items():
+            if not mkcfg.MANDATORY in info['types']:
+                continue
+            out = sys.stdin.write('  \nbabar')
+            sys.stdin.seek(0)
+            ret = wizard.ask_simple(step, '')
+            sys.stdout.seek(0)
+            out = re.sub('\s', '', sys.stdout.read().lower())
+            self.assertIn('mandatory', out)
+            self.assertEqual('babar', ret)
+
+    def test_ask_multiple(self):
+        wizard = Wizard()
+        for step, info in Wizard.STEPS.items():
+            if not mkcfg.MULTIPLE in info['types']:
+                continue
+            expected = ['babar', 'alexander']
+            out = sys.stdin.write('babar\nalexander\n\n')
+            sys.stdin.seek(0)
+            out = wizard.ask_multiple(step)
+            sys.stdout.seek(0)
+            self.assertEqual(expected, out)
+
+    def test_ask_multiple_show_help(self):
+        wizard = Wizard()
+        for step, info in Wizard.STEPS.items():
+            if not mkcfg.MULTIPLE in info['types']:
+                continue
+            expected = re.sub('\s', '', info['help'].lower())
+            sys.stdin.write('?\nbabar\n\n')
+            sys.stdin.seek(0)
+            out = wizard.ask_multiple(step)
+            sys.stdout.seek(0)
+            out = re.sub('\s', '', sys.stdout.read().lower())
+            self.assertIn(expected, out)
+
+    def test_ask_multiple_default(self):
+        wizard = Wizard()
+        for step, info in Wizard.STEPS.items():
+            if mkcfg.MULTIPLE in info['types']:
+                continue
+            expected = ['babar', 'alexander']
+            sys.stdin.write('\n\n')
+            sys.stdin.seek(0)
+            out = wizard.ask_multiple(step, expected)
+            sys.stdout.seek(0)
+            self.assertEqual(expected, out)
+
+    def test_ask_choice_display(self):
+        choices = ['pom', 'flora', 'alexander']
+        message = 'babar'
+        wizard = Wizard()
+        sys.stdin.write('?\nq\n')
         sys.stdin.seek(0)
-        self.assertEqual('y', ask_yn('is this a test'))
+        wizard.ask_choice(message, choices)
+        sys.stdout.seek(0)
+        out = re.sub('\s', '', sys.stdout.read().lower())
+        for data in (choices + [message]):
+            self.assertIn(re.sub('\s', '', data.lower()), out)
 
-    def test_ask(self):
-        sys.stdin.write('a\n')
-        sys.stdin.write('b\n')
+    def test_ask_choice(self):
+        wizard = Wizard()
+        choices = ['pom', 'flora', 'alexander']
+        sys.stdin.write('l\n1\n')
         sys.stdin.seek(0)
-        self.assertEqual('a', ask('is this a test'))
-        self.assertEqual('b', ask(str(list(range(0, 70))), default='c',
-                                  lengthy=True))
-
-    def test_set_multi(self):
-        main = MkCfg()
-        sys.stdin.write('aaaaa\n')
-        sys.stdin.seek(0)
-        main.data['author'] = []
-        main._set_multi('_set_multi test', 'author')
-        self.assertEqual(['aaaaa'], main.data['author'])
+        out = wizard.ask_choice('babar', choices)
+        self.assertEqual(out, choices[1])
 
     def test_find_files(self):
-        # making sure we scan a project dir correctly
-        main = MkCfg()
+        tree = {
+            'package_one':{
+                '__init__.py':'"""package_one"""',
+                'subpackage_one':{
+                    '__init__.py':'"""subpackage_one"""',
+                    'submodule.py':'"""submodule.py"""',
+                    'data_file.txt':'data_file',},
+                'info_file.info':'info_file',
+                'stdlib':{
+                    'deap_package':{
+                        '__init__.py':'"""deap_package"""',},
+                    'isolated_module.py':'"""Isolated_module"""',},},
+            'mymodule.py':'"""mymodule"""',
+            'README':'my readme',
+            'doc':{
+                'Main':{
+                    'index.rst':'Some info',
+                    'usage.rst':'Some other info',},
+                'others':{
+                    'index.rst':'Some info',
+                    'usage.rst':'Some other info',},},
+            'man':{
+                'project.1':'#manpage',},
+            'appdata':{
+                'no_module.py':'"""no_module"""',
+                'dat':{
+                    'shared_data.dat':'data',
+                    'dot':{
+                        'shared_doto.dot':'doto',},},},}
+        create_files(self.wdir, tree)
+        wizard = Wizard()
+        config = wizard.find_files(self.wdir)
+        self.assertSetEqual(
+                {'packages', 'modules', 'extra_files', 'resources'},
+                set(config.keys()))
+        self.assertSetEqual(
+                {'package_one', 'package_one.subpackage_one',
+                 'package_one.stdlib.deap_package', },
+                set(config['packages']))
+        self.assertSetEqual(
+                {'mymodule', 'package_one.stdlib.isolated_module'},
+                set(config['modules']))
+        self.assertSetEqual(
+                {'package_one/info_file.info',
+                 'package_one/subpackage_one/data_file.txt', 'README',},
+                set(config['extra_files']))
+        self.assertSetEqual(
+                {('doc/Main/*', '{doc}/Main'),
+                 ('doc/others/*', '{doc}/others'),
+                 ('man/*', '{man}'),
+                 ('appdata/*', '{appdata}'),
+                 ('appdata/dat/*', '{appdata}/dat'),
+                 ('appdata/dat/dot/*', '{appdata}/dat/dot')},
+                set(config['resources']))
 
-        # building the structure
-        tempdir = self.wdir
-        dirs = ['pkg1', 'data', 'pkg2', 'pkg2/sub']
-        files = ['README', 'setup.cfg', 'foo.py',
-                 'pkg1/__init__.py', 'pkg1/bar.py',
-                 'data/data1', 'pkg2/__init__.py',
-                 'pkg2/sub/__init__.py']
+class MkcfgLoadDefaultSetupCfgTests(support.TempdirManager,
+                       unittest.TestCase):
+    pass
 
-        for dir_ in dirs:
-            os.mkdir(os.path.join(tempdir, dir_))
+class MkcfgWriteCfg(support.TempdirManager,
+                    unittest.TestCase):
 
-        for file_ in files:
-            path = os.path.join(tempdir, file_)
-            self.write_file(path, 'xxx')
+    def setUp(self):
+        super(MkcfgWriteCfg, self).setUp()
+        self._cwd = os.getcwd()
+        self.wdir = self.mkdtemp()
+        os.chdir(self.wdir)
 
-        main._find_files()
+    def tearDown(self):
+        super(MkcfgWriteCfg, self).tearDown()
+        os.chdir(self._cwd)
 
-        main.data['packages'].sort()
-        # do we have what we want ?
-        self.assertEqual(main.data['packages'], ['pkg1', 'pkg2', 'pkg2.sub'])
-        self.assertEqual(main.data['modules'], ['foo'])
-        data_fn = os.path.join('data', 'data1')
-        self.assertEqual(set(main.data['extra_files']),
-                         set(['setup.cfg', 'README', data_fn]))
+    def test_write_config_file(self):
+        config = {
+                'name': 'pyxfoil',
+                'version': '0.2',
+                'summary': 'Python bindings',
+                'description': ('My super Death-scription\n'
+                                'barbar is now on the public domain,\n'
+                                'ho, baby !'),
+                'maintainer': 'Andre Espaze',
+                'maintainer_email': 'andre.espaze@logilab.fr',
+                'home_page': 'http://www.python-science.org/project/pyxfoil',
+                'license':'GPLv2',
+                'packages': ['pyxfoil', 'babar', 'me'],
+                'resources': [('README.rst', '{doc}'), ('pyxfoil.1', '{man}')],
+                'modules': ['my_lib', 'mymodule'],
+                'extra_files': ['Martinique/Lamentin/dady',
+                                'Martinique/Lamentin/mumy',
+                                'Martinique/Lamentin/sys',
+                                'Martinique/Lamentin/bro',
+                                'setup.py',
+                                'README',
+                                'Pom',
+                                'Flora',
+                                'Alexander',
+                                'pyxfoil/fengine.so'],
+                'scripts': ['my_script', 'bin/run'],
+                }
+        mkcfg.write_config_file(config, 'setup.cfg')
+        fid = open(osp.join(self.wdir, 'setup.cfg'))
+        lines = set([line.rstrip() for line in fid])
+        fid.close()
 
-    @unittest.skip("causes failures")
-    def test_convert_setup_py_to_cfg(self):
-        self.write_file((self.wdir, 'setup.py'),
-                        dedent("""
-        # -*- coding: utf-8 -*-
-        from distutils.core import setup
-        lg_dsc = '''My super Death-scription
-        barbar is now on the public domain,
-        ho, baby !'''
-        setup(name='pyxfoil',
-              version='0.2',
-              description='Python bindings for the Xfoil engine',
-              long_description = lg_dsc,
-              maintainer='André Espaze',
-              maintainer_email='andre.espaze@logilab.fr',
-              url='http://www.python-science.org/project/pyxfoil',
-              license='GPLv2',
-              packages=['pyxfoil', 'babar', 'me'],
-              data_files=[('share/doc/pyxfoil', ['README.rst']),
-                          ('share/man', ['pyxfoil.1']),
-                         ],
-              py_modules = ['my_lib', 'mymodule'],
-              package_dir = {'babar' : '',
-                             'me' : 'Martinique/Lamentin',
-                            },
-              package_data = {'babar': ['Pom', 'Flora', 'Alexander'],
-                              'me': ['dady', 'mumy', 'sys', 'bro'],
-                              '':  ['setup.py', 'README'],
-                              'pyxfoil' : ['fengine.so'],
-                             },
-              scripts = ['my_script', 'bin/run'],
-              )
-        """))
-        sys.stdin.write('y\n')
-        sys.stdin.seek(0)
-        main()
-        fp = open(os.path.join(self.wdir, 'setup.cfg'))
-        try:
-            lines = set([line.rstrip() for line in fp])
-        finally:
-            fp.close()
-        self.assertEqual(lines, set(['',
+        self.assertSetEqual(lines, 
+           {'',
             '[metadata]',
             'version = 0.2',
             'name = pyxfoil',
-            'maintainer = André Espaze',
+            'maintainer = Andre Espaze',
             'description = My super Death-scription',
-            '       |barbar is now on the public domain,',
-            '       |ho, baby !',
+            '\t|barbar is now on the public domain,',
+            '\t|ho, baby !',
             'maintainer_email = andre.espaze@logilab.fr',
             'home_page = http://www.python-science.org/project/pyxfoil',
             'download_url = UNKNOWN',
-            'summary = Python bindings for the Xfoil engine',
+            'summary = Python bindings',
             '[files]',
             'modules = my_lib',
-            '    mymodule',
+            '\tmymodule',
             'packages = pyxfoil',
-            '    babar',
-            '    me',
+            '\tbabar',
+            '\tme',
             'extra_files = Martinique/Lamentin/dady',
-            '    Martinique/Lamentin/mumy',
-            '    Martinique/Lamentin/sys',
-            '    Martinique/Lamentin/bro',
-            '    Pom',
-            '    Flora',
-            '    Alexander',
-            '    setup.py',
-            '    README',
-            '    pyxfoil/fengine.so',
+            '\tMartinique/Lamentin/mumy',
+            '\tMartinique/Lamentin/sys',
+            '\tMartinique/Lamentin/bro',
+            '\tPom',
+            '\tFlora',
+            '\tAlexander',
+            '\tsetup.py',
+            '\tREADME',
+            '\tpyxfoil/fengine.so',
             'scripts = my_script',
-            '    bin/run',
-            'resources =',
-            '    README.rst = {doc}',
-            '    pyxfoil.1 = {man}',
-        ]))
-
-    @unittest.skip("causes failures")
-    def test_convert_setup_py_to_cfg_with_description_in_readme(self):
-        self.write_file((self.wdir, 'setup.py'),
-                        dedent("""
-        # -*- coding: utf-8 -*-
-        from distutils.core import setup
-        lg_dsc = open('README.txt').read()
-        setup(name='pyxfoil',
-              version='0.2',
-              description='Python bindings for the Xfoil engine',
-              long_description=lg_dsc,
-              maintainer='André Espaze',
-              maintainer_email='andre.espaze@logilab.fr',
-              url='http://www.python-science.org/project/pyxfoil',
-              license='GPLv2',
-              packages=['pyxfoil'],
-              package_data={'pyxfoil' : ['fengine.so', 'babar.so']},
-              data_files=[
-                ('share/doc/pyxfoil', ['README.rst']),
-                ('share/man', ['pyxfoil.1']),
-              ],
-        )
-        """))
-        self.write_file((self.wdir, 'README.txt'),
-                        dedent('''
-My super Death-scription
-barbar is now on the public domain,
-ho, baby !
-                        '''))
-        sys.stdin.write('y\n')
-        sys.stdin.seek(0)
-        # FIXME Out of memory error.
-        main()
-        fp = open(os.path.join(self.wdir, 'setup.cfg'))
-        try:
-            lines = set([line.rstrip() for line in fp])
-        finally:
-            fp.close()
-        self.assertEqual(lines, set(['',
-            '[metadata]',
-            'version = 0.2',
-            'name = pyxfoil',
-            'maintainer = André Espaze',
-            'maintainer_email = andre.espaze@logilab.fr',
-            'home_page = http://www.python-science.org/project/pyxfoil',
-            'download_url = UNKNOWN',
-            'summary = Python bindings for the Xfoil engine',
-            'description-file = README.txt',
-            '[files]',
-            'packages = pyxfoil',
-            'extra_files = pyxfoil/fengine.so',
-            '    pyxfoil/babar.so',
-            'resources =',
-            '    README.rst = {doc}',
-            '    pyxfoil.1 = {man}',
-        ]))
+            '\tbin/run',
+            'resources = README.rst {doc}',
+            '\tpyxfoil.1 {man}',
+           })
 
 
 def test_suite():

File Lib/packaging/tests/test_util.py

 from io import StringIO
 import subprocess
 import time
+from textwrap import dedent
+import sysconfig
+from operator import itemgetter
 
 from packaging.tests import captured_stdout, unittest, support
 from packaging.errors import (PackagingPlatformError,
             content = f.read()
         self.assertEqual(content, WANTED)
 
+    def test_convert_D1_to_d2(self):
+        self.write_file((self.tmp_dir, 'setup.py'),
+                        dedent("""
+        # -*- coding: utf-8 -*-
+        from distutils.core import setup
+        lg_dsc = '''My super Death-scription
+        yaouuuu'''
+        setup(name='pyxfoil',
+              version='0.2',
+              description='Python bindings',
+              long_description = lg_dsc,
+              maintainer='Andre Espaze',
+              maintainer_email='andre.espaze@logilab.fr',
+              url='http://www.python-science.org/project/pyxfoil',
+              license='GPLv2',
+              packages=['pyxfoil', 'babar', 'me'],
+              data_files=[('share/doc/pyxfoil', ['README.rst']),
+                          ('share/man', ['pyxfoil.1']),
+                         ],
+              py_modules = ['my_lib', 'mymodule'],
+              package_dir = {'babar' : '',
+                             'me' : 'Martinique/Lamentin',
+                            },
+              package_data = {'babar': ['Pom', 'Flora', 'Alexander'],
+                              'me': ['dady', 'mumy', 'sys', 'bro'],
+                              '':  ['setup.py', 'README'],
+                              'pyxfoil' : ['fengine.so'],
+                             },
+              scripts = ['my_script', 'bin/run'],
+              )
+        """))
+        config = util.load_distutils_setup_py(
+                                       os.path.join(self.tmp_dir, 'setup.py'))
+        self.maxDiff = None
+        resources = []
+        vars = {'distribution.name':config['name']}
+        path_tokens = sysconfig.get_paths(vars=vars).items()
+        path_tokens = sorted(path_tokens, reverse=True, key=itemgetter(1))
+        data_files=[('share/doc/pyxfoil', ['README.rst']),
+                    ('share/man', ['pyxfoil.1']),]
+        for dest, srcs in (data_files or []):
+            dest = os.path.join(sys.prefix, dest)
+            for tok, path in path_tokens:
+                if dest.startswith(path):
+                    dest = ('{%s}' % tok) + dest[len(path):]
+                    files = [('/ '.join(src.rsplit('/', 1)), dest)
+                             for src in srcs]
+                    resources.extend(files)
+                    continue
+        self.assertDictEqual({
+                'name': 'pyxfoil',
+                'version': '0.2',
+                'summary': 'Python bindings',
+                'description': 'My super Death-scription\nyaouuuu',
+                'maintainer': 'Andre Espaze',
+                'maintainer_email': 'andre.espaze@logilab.fr',
+                'home_page': 'http://www.python-science.org/project/pyxfoil',
+                'license':'GPLv2',
+                'packages': ['pyxfoil', 'babar', 'me'],
+                'resources': resources,
+                'modules': ['my_lib', 'mymodule'],
+                'extra_files': ['Martinique/Lamentin/dady',
+                                'Martinique/Lamentin/mumy',
+                                'Martinique/Lamentin/sys',
+                                'Martinique/Lamentin/bro',
+                                'setup.py',
+                                'README',
+                                'Pom',
+                                'Flora',
+                                'Alexander',
+                                'pyxfoil/fengine.so'],
+                'scripts': ['my_script', 'bin/run'],
+                },
+                             config)
+
+    def test_convert_d1_to_d2_with_description_in_readme(self):
+        self.write_file((self.tmp_dir, 'setup.py'),
+                        dedent("""
+        # -*- coding: utf-8 -*-
+        from distutils.core import setup
+        fob = open('README.txt')
+        lg_dsc = fob.read()
+        fob.close()
+        setup(name='pyxfoil',
+              version='0.2',
+              description='Python bindings for the Xfoil engine',
+              long_description=lg_dsc,
+              maintainer='Andr\xc3\xa9 Espaze',
+              maintainer_email='andre.espaze@logilab.fr',
+              url='http://www.python-science.org/project/pyxfoil',
+              license='GPLv2',
+              packages=['pyxfoil'],
+              package_data={'pyxfoil' : ['fengine.so']},
+              data_files=[
+                ('share/doc/pyxfoil', ['README.rst']),
+                ('share/man', ['pyxfoil.1']),
+              ],
+        )
+        """))
+        self.write_file((self.tmp_dir, 'README.txt'),
+                        dedent('''
+My super Death-scription
+barbar is now on the public domain,
+ho, baby !
+                        '''))
+        config = util.load_distutils_setup_py(
+                                         os.path.join(self.tmp_dir, 'setup.py'))
+        self.assertEqual({
+                'description-file': 'README.txt',
+                'extra_files': ['pyxfoil/fengine.so'],
+                'home_page': 'http://www.python-science.org/project/pyxfoil',
+                'maintainer': 'Andr\xc3\xa9 Espaze',
+                'maintainer_email': 'andre.espaze@logilab.fr',
+                'license':'GPLv2',
+                'name': 'pyxfoil',
+                'packages': ['pyxfoil'],
+                'resources': [('README.rst', '{doc}'), ('pyxfoil.1', '{man}')],
+                'summary': 'Python bindings for the Xfoil engine',
+                'version': '0.2'},
+                         config)
+
 
 class GlobTestCaseBase(support.TempdirManager,
                        support.LoggingCatcher,

File Lib/packaging/util.py

     # Note: the mutable config dict allows to extract data from the setup.py
     #       processing.
     # Use the distutils v1 processings to correctly parse metadata.
-    #XXX we could also use the setuptools distibution ???
+    #XXX we could also use the setuptools distribution ???
     from distutils.dist import Distribution
     dist = Distribution(attrs)
     dist.parse_config_files()
         config['extra_files'].extend(fils)
     # Use README* file if its content is the desciption
     if "description" in config:
-        ref = md5(re.sub('\s', '', 
+        ref = md5(re.sub('\s', '',
                   config['description']).lower().encode('utf-8'))
         ref = ref.digest()
         for readme in std_iglob('README*'):
-            fob = open(readme)
-            val = md5(re.sub('\s', '', 
+            with open(readme) as fob:
+                val = md5(re.sub('\s', '',
                              fob.read()).lower().encode('utf-8')).digest()
-            fob.close()
             if val == ref:
                 del config['description']
                 config['description-file'] = readme