diff -r 9c8d31d69044 Lib/mimetypes.py --- a/Lib/mimetypes.py Mon Jan 06 11:10:08 2014 -0800 +++ b/Lib/mimetypes.py Mon Apr 14 08:54:34 2014 -0700 @@ -171,6 +171,7 @@ for ext in self.types_map_inv[False].get(type, []): if ext not in extensions: extensions.append(ext) + extensions.sort() return extensions def guess_extension(self, type, strict=True): diff -r 9c8d31d69044 Lib/test/test_mimetypes.py --- a/Lib/test/test_mimetypes.py Mon Jan 06 11:10:08 2014 -0800 +++ b/Lib/test/test_mimetypes.py Mon Apr 14 08:54:34 2014 -0700 @@ -6,16 +6,27 @@ from test import support -# Tell it we don't know about external files: -mimetypes.knownfiles = [] -mimetypes.inited = False -mimetypes._default_mime_types() +mimetypes_globals = ('suffix_map', 'types_map', 'encodings_map', + 'common_types', 'inited', '_db', 'knownfiles') class MimeTypesTestCase(unittest.TestCase): def setUp(self): + # Save mimetypes global module state + self.saved = {} + for mod_var in mimetypes_globals: + self.saved[mod_var] = getattr(mimetypes, mod_var) + # Tell it we don't know about external files: + mimetypes.knownfiles = [] + mimetypes.inited = False + mimetypes._default_mime_types() + self.db = mimetypes.MimeTypes() + def tearDown(self): + for mod_var in mimetypes_globals: + setattr(mimetypes, mod_var, self.saved[mod_var]) + def test_default_data(self): eq = self.assertEqual eq(self.db.guess_type("foo.html"), ("text/html", None)) @@ -56,7 +67,7 @@ # test_urllib2 is run before test_mimetypes, global state is modified # such that the 'all' set will have more items in it. all = set(self.db.guess_all_extensions('text/plain', strict=True)) - unless(all >= set(['.bat', '.c', '.h', '.ksh', '.pl', '.txt'])) + unless(len(all) >= len(set(['.bat', '.c', '.h', '.ksh', '.pl', '.txt']))) # And now non-strict all = self.db.guess_all_extensions('image/jpg', strict=False) all.sort() @@ -78,6 +89,38 @@ self.assertEqual(exts, ['.g3', '.g\xb3']) +class MimeTypesGlobalsTestCase(unittest.TestCase): + def setUp(self): + self.db = mimetypes.MimeTypes() + self.saved = {} + for mod_var in mimetypes_globals: + self.saved[mod_var] = getattr(mimetypes, mod_var) + + def tearDown(self): + for mod_var in mimetypes_globals: + setattr(mimetypes, mod_var, self.saved[mod_var]) + + def test_extensions_list_stable(self): + # Test that the sort of extensions is stable + # http://bugs.python.org/issue4963 + eq = self.assertEqual + + # Find a suitable mime type for tests (needs more than one suffix) + for test_type in mimetypes._db.types_map_inv[True]: + exts = mimetypes.guess_all_extensions(test_type) + if len(exts) > 1: + break + + results = set() + for i in range(0, 20): + # Note -- this isn't guaranteed to catch that extension order is + # unstable. It's just statistically likely. + results.add(tuple(mimetypes._db.guess_all_extensions( + test_type, strict=True))) + mimetypes.init() + eq(len(results), 1) + + @unittest.skipUnless(sys.platform.startswith("win"), "Windows only") class Win32MimeTypesTestCase(unittest.TestCase): def setUp(self): @@ -103,6 +146,7 @@ def test_main(): support.run_unittest(MimeTypesTestCase, + MimeTypesGlobalsTestCase, Win32MimeTypesTestCase )