From 62863a0b655451d8ae1657c118d6c3f62e641ce4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C3=A9n=C3=A9dikt=20Tran?= <10796600+picnixz@users.noreply.github.com> Date: Tue, 18 Mar 2025 12:53:55 +0100 Subject: [PATCH 1/8] fix `test_mimetypes.test_guess_type_conflicting_with_mimetypes` Using `run_python_until_end()` ignores `setUpModule()`. In particular, mocking `mimetypes.knownfiles` has no effect for the CLI tests and leads to issues on platforms defining non-standard MIME types such as macOS or openSUSE. --- Lib/mimetypes.py | 10 ++++++---- Lib/test/test_mimetypes.py | 25 +++++++++++++++++-------- 2 files changed, 23 insertions(+), 12 deletions(-) diff --git a/Lib/mimetypes.py b/Lib/mimetypes.py index 6b94fe3c4df756..85988ea00730e8 100644 --- a/Lib/mimetypes.py +++ b/Lib/mimetypes.py @@ -669,7 +669,7 @@ def _default_mime_types(): _default_mime_types() -def _main(): +def _main(args=None): """Run the mimetypes command-line interface.""" import sys from argparse import ArgumentParser @@ -686,7 +686,7 @@ def _main(): help='additionally search for common but non-standard types' ) parser.add_argument('type', nargs='+', help='a type to search') - args = parser.parse_args() + args = parser.parse_args(args) if args.extension: for gtype in args.type: @@ -694,14 +694,16 @@ def _main(): if guess: print(guess) else: - sys.exit(f"error: unknown type {gtype}") + print(f"error: unknown type {gtype}", file=sys.stderr) + sys.exit(1) else: for gtype in args.type: guess, encoding = guess_type(gtype, not args.lenient) if guess: print('type:', guess, 'encoding:', encoding) else: - sys.exit(f"error: media type unknown for {gtype}") + print(f"error: media type unknown for {gtype}", file=sys.stderr) + sys.exit(1) if __name__ == '__main__': diff --git a/Lib/test/test_mimetypes.py b/Lib/test/test_mimetypes.py index b5d1f50099e16a..99f6cbf7a3ca72 100644 --- a/Lib/test/test_mimetypes.py +++ b/Lib/test/test_mimetypes.py @@ -1,7 +1,9 @@ +import contextlib import io import mimetypes import os import sys +import tempfile import unittest.mock from os import linesep @@ -392,9 +394,19 @@ def test__all__(self): class MimetypesCliTestCase(unittest.TestCase): - def mimetypes_cmd(cls, *args, **kwargs): - result, _ = run_python_until_end('-m', 'mimetypes', *args) - return result.rc, result.out.decode(), result.err.decode() + def mimetypes_cmd(self, *args): + # We cannot use run_python_until_end() as the latter would not + # call setUpModule() which unsets mimetypes.knowfiles. Instead, + # we need to directly call the main() function in order to avoid + # re-initializing the database. + rc, out, err = 0, io.StringIO(), io.StringIO() + with contextlib.redirect_stdout(out), contextlib.redirect_stderr(err): + try: + mimetypes._main(args) + except SystemExit as exc: + self.assertIsInstance(exc.code, int) + rc = exc.code + return rc, out.getvalue(), err.getvalue() def test_help_option(self): retcode, out, err = self.mimetypes_cmd('-h') @@ -430,15 +442,12 @@ def test_guess_type(self): self.assertEqual(out, f'type: image/webp encoding: None{linesep}') self.assertEqual(err, '') - @unittest.skipIf( - sys.platform == 'darwin', - 'macOS lists common_types in mime.types thus making them always known' - ) - def test_guess_type_conflicting_with_mimetypes(self): + def test_z_guess_type_conflicting_with_mimetypes(self): retcode, out, err = self.mimetypes_cmd('foo.pic') self.assertEqual(retcode, 1) self.assertEqual(out, '') self.assertEqual(err, f'error: media type unknown for foo.pic{linesep}') + if __name__ == "__main__": unittest.main() From 7be23f021cd06c8e8cdefdcfa11dd1d7f7fac39f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C3=A9n=C3=A9dikt=20Tran?= <10796600+picnixz@users.noreply.github.com> Date: Tue, 18 Mar 2025 13:33:28 +0100 Subject: [PATCH 2/8] remove checks for linesep --- Lib/test/test_mimetypes.py | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/Lib/test/test_mimetypes.py b/Lib/test/test_mimetypes.py index 99f6cbf7a3ca72..ca6672e4b5ca27 100644 --- a/Lib/test/test_mimetypes.py +++ b/Lib/test/test_mimetypes.py @@ -5,7 +5,6 @@ import sys import tempfile import unittest.mock -from os import linesep from test import support from test.support import os_helper @@ -423,30 +422,30 @@ def test_invalid_option(self): def test_guess_extension(self): retcode, out, err = self.mimetypes_cmd('-l', '-e', 'image/jpg') self.assertEqual(retcode, 0) - self.assertEqual(out, f'.jpg{linesep}') + self.assertEqual(out, '.jpg\n') self.assertEqual(err, '') retcode, out, err = self.mimetypes_cmd('-e', 'image/jpg') self.assertEqual(retcode, 1) self.assertEqual(out, '') - self.assertEqual(err, f'error: unknown type image/jpg{linesep}') + self.assertEqual(err, 'error: unknown type image/jpg\n') retcode, out, err = self.mimetypes_cmd('-e', 'image/jpeg') self.assertEqual(retcode, 0) - self.assertEqual(out, f'.jpg{linesep}') + self.assertEqual(out, '.jpg\n') self.assertEqual(err, '') def test_guess_type(self): retcode, out, err = self.mimetypes_cmd('-l', 'foo.webp') self.assertEqual(retcode, 0) - self.assertEqual(out, f'type: image/webp encoding: None{linesep}') + self.assertEqual(out, 'type: image/webp encoding: None\n') self.assertEqual(err, '') def test_z_guess_type_conflicting_with_mimetypes(self): retcode, out, err = self.mimetypes_cmd('foo.pic') self.assertEqual(retcode, 1) self.assertEqual(out, '') - self.assertEqual(err, f'error: media type unknown for foo.pic{linesep}') + self.assertEqual(err, 'error: media type unknown for foo.pic\n') if __name__ == "__main__": From 01e89c35d8604a34ad9d962fad93e1b7b6757bfe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C3=A9n=C3=A9dikt=20Tran?= <10796600+picnixz@users.noreply.github.com> Date: Wed, 26 Mar 2025 10:28:19 +0100 Subject: [PATCH 3/8] use `parser.exit()` instead of print + sys.exit() --- Lib/mimetypes.py | 8 ++++---- Lib/test/test_mimetypes.py | 16 ++++++++-------- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/Lib/mimetypes.py b/Lib/mimetypes.py index 85988ea00730e8..44dde5ec91ffe5 100644 --- a/Lib/mimetypes.py +++ b/Lib/mimetypes.py @@ -694,16 +694,16 @@ def _main(args=None): if guess: print(guess) else: - print(f"error: unknown type {gtype}", file=sys.stderr) - sys.exit(1) + # do not use parser.error() as it prints a help message + parser.exit(1, f"error: unknown type {gtype}\n") else: for gtype in args.type: guess, encoding = guess_type(gtype, not args.lenient) if guess: print('type:', guess, 'encoding:', encoding) else: - print(f"error: media type unknown for {gtype}", file=sys.stderr) - sys.exit(1) + # do not use parser.error() as it prints a help message + parser.exit(1, f"error: media type unknown for {gtype}\n") if __name__ == '__main__': diff --git a/Lib/test/test_mimetypes.py b/Lib/test/test_mimetypes.py index ca6672e4b5ca27..aa44acf0d00a2d 100644 --- a/Lib/test/test_mimetypes.py +++ b/Lib/test/test_mimetypes.py @@ -5,11 +5,11 @@ import sys import tempfile import unittest.mock - +from os import linesep +from platform import win32_edition from test import support from test.support import os_helper from test.support.script_helper import run_python_until_end -from platform import win32_edition try: import _winapi @@ -422,30 +422,30 @@ def test_invalid_option(self): def test_guess_extension(self): retcode, out, err = self.mimetypes_cmd('-l', '-e', 'image/jpg') self.assertEqual(retcode, 0) - self.assertEqual(out, '.jpg\n') + self.assertEqual(out, f'.jpg{linesep}') self.assertEqual(err, '') retcode, out, err = self.mimetypes_cmd('-e', 'image/jpg') self.assertEqual(retcode, 1) self.assertEqual(out, '') - self.assertEqual(err, 'error: unknown type image/jpg\n') + self.assertEqual(err, f'error: unknown type image/jpg{linesep}') retcode, out, err = self.mimetypes_cmd('-e', 'image/jpeg') self.assertEqual(retcode, 0) - self.assertEqual(out, '.jpg\n') + self.assertEqual(out, f'.jpg{linesep}') self.assertEqual(err, '') def test_guess_type(self): retcode, out, err = self.mimetypes_cmd('-l', 'foo.webp') self.assertEqual(retcode, 0) - self.assertEqual(out, 'type: image/webp encoding: None\n') + self.assertEqual(out, f'type: image/webp encoding: None{linesep}') self.assertEqual(err, '') - def test_z_guess_type_conflicting_with_mimetypes(self): + def test_guess_type_conflicting_with_mimetypes(self): retcode, out, err = self.mimetypes_cmd('foo.pic') self.assertEqual(retcode, 1) self.assertEqual(out, '') - self.assertEqual(err, 'error: media type unknown for foo.pic\n') + self.assertEqual(err, f'error: media type unknown for foo.pic{linesep}') if __name__ == "__main__": From 1d29b5d82395b6c05265aec8afd2d92e277b1c33 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C3=A9n=C3=A9dikt=20Tran?= <10796600+picnixz@users.noreply.github.com> Date: Fri, 28 Mar 2025 15:21:01 +0100 Subject: [PATCH 4/8] fixup --- Lib/test/test_mimetypes.py | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/Lib/test/test_mimetypes.py b/Lib/test/test_mimetypes.py index aa44acf0d00a2d..9b7705a5a3411f 100644 --- a/Lib/test/test_mimetypes.py +++ b/Lib/test/test_mimetypes.py @@ -5,7 +5,6 @@ import sys import tempfile import unittest.mock -from os import linesep from platform import win32_edition from test import support from test.support import os_helper @@ -422,30 +421,30 @@ def test_invalid_option(self): def test_guess_extension(self): retcode, out, err = self.mimetypes_cmd('-l', '-e', 'image/jpg') self.assertEqual(retcode, 0) - self.assertEqual(out, f'.jpg{linesep}') + self.assertEqual(out, '.jpg\n') self.assertEqual(err, '') retcode, out, err = self.mimetypes_cmd('-e', 'image/jpg') self.assertEqual(retcode, 1) self.assertEqual(out, '') - self.assertEqual(err, f'error: unknown type image/jpg{linesep}') + self.assertEqual(err, 'error: unknown type image/jpg\n') retcode, out, err = self.mimetypes_cmd('-e', 'image/jpeg') self.assertEqual(retcode, 0) - self.assertEqual(out, f'.jpg{linesep}') + self.assertEqual(out, '.jpg\n') self.assertEqual(err, '') def test_guess_type(self): retcode, out, err = self.mimetypes_cmd('-l', 'foo.webp') self.assertEqual(retcode, 0) - self.assertEqual(out, f'type: image/webp encoding: None{linesep}') + self.assertEqual(out, 'type: image/webp encoding: None\n') self.assertEqual(err, '') def test_guess_type_conflicting_with_mimetypes(self): retcode, out, err = self.mimetypes_cmd('foo.pic') self.assertEqual(retcode, 1) self.assertEqual(out, '') - self.assertEqual(err, f'error: media type unknown for foo.pic{linesep}') + self.assertEqual(err, 'error: media type unknown for foo.pic\n') if __name__ == "__main__": From eeb563524299af1b983b44ad0516a5bf4b9ea1f2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C3=A9n=C3=A9dikt=20Tran?= <10796600+picnixz@users.noreply.github.com> Date: Sun, 30 Mar 2025 11:23:25 +0200 Subject: [PATCH 5/8] Update Lib/test/test_mimetypes.py --- Lib/test/test_mimetypes.py | 1 - 1 file changed, 1 deletion(-) diff --git a/Lib/test/test_mimetypes.py b/Lib/test/test_mimetypes.py index 9b7705a5a3411f..7f782b42d97920 100644 --- a/Lib/test/test_mimetypes.py +++ b/Lib/test/test_mimetypes.py @@ -3,7 +3,6 @@ import mimetypes import os import sys -import tempfile import unittest.mock from platform import win32_edition from test import support From d9d136e091a329e20033499312b28b08f82eef59 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C3=A9n=C3=A9dikt=20Tran?= <10796600+picnixz@users.noreply.github.com> Date: Tue, 8 Apr 2025 10:29:57 +0200 Subject: [PATCH 6/8] use Hugo's approach for testing `mimetypes` --- Lib/mimetypes.py | 27 +++++----- Lib/test/test_mimetypes.py | 101 +++++++++++++++++-------------------- 2 files changed, 61 insertions(+), 67 deletions(-) diff --git a/Lib/mimetypes.py b/Lib/mimetypes.py index 44dde5ec91ffe5..67660e4f0368ce 100644 --- a/Lib/mimetypes.py +++ b/Lib/mimetypes.py @@ -669,9 +669,7 @@ def _default_mime_types(): _default_mime_types() -def _main(args=None): - """Run the mimetypes command-line interface.""" - import sys +def _parse_args(args): from argparse import ArgumentParser parser = ArgumentParser(description='map filename extensions to MIME types') @@ -687,24 +685,29 @@ def _main(args=None): ) parser.add_argument('type', nargs='+', help='a type to search') args = parser.parse_args(args) + return args, parser.format_help() + + +def _main(args=None): + """Run the mimetypes command-line interface and return a text to print.""" + import sys + + args, help_text = _parse_args(args) if args.extension: for gtype in args.type: guess = guess_extension(gtype, not args.lenient) if guess: - print(guess) - else: - # do not use parser.error() as it prints a help message - parser.exit(1, f"error: unknown type {gtype}\n") + return str(guess) + sys.exit(f"error: unknown type {gtype}") else: for gtype in args.type: guess, encoding = guess_type(gtype, not args.lenient) if guess: - print('type:', guess, 'encoding:', encoding) - else: - # do not use parser.error() as it prints a help message - parser.exit(1, f"error: media type unknown for {gtype}\n") + return f"type: {guess} encoding: {encoding}" + sys.exit(f"error: media type unknown for {gtype}") + return parser.format_help() if __name__ == '__main__': - _main() + print(_main()) diff --git a/Lib/test/test_mimetypes.py b/Lib/test/test_mimetypes.py index 7f782b42d97920..b05c212a2a6b6b 100644 --- a/Lib/test/test_mimetypes.py +++ b/Lib/test/test_mimetypes.py @@ -2,6 +2,7 @@ import io import mimetypes import os +import shlex import sys import unittest.mock from platform import win32_edition @@ -389,61 +390,51 @@ def test__all__(self): support.check__all__(self, mimetypes) -class MimetypesCliTestCase(unittest.TestCase): - - def mimetypes_cmd(self, *args): - # We cannot use run_python_until_end() as the latter would not - # call setUpModule() which unsets mimetypes.knowfiles. Instead, - # we need to directly call the main() function in order to avoid - # re-initializing the database. - rc, out, err = 0, io.StringIO(), io.StringIO() - with contextlib.redirect_stdout(out), contextlib.redirect_stderr(err): - try: - mimetypes._main(args) - except SystemExit as exc: - self.assertIsInstance(exc.code, int) - rc = exc.code - return rc, out.getvalue(), err.getvalue() - - def test_help_option(self): - retcode, out, err = self.mimetypes_cmd('-h') - self.assertEqual(retcode, 0) - self.assertStartsWith(out, 'usage: ') - self.assertEqual(err, '') - - def test_invalid_option(self): - retcode, out, err = self.mimetypes_cmd('--invalid') - self.assertEqual(retcode, 2) - self.assertEqual(out, '') - self.assertStartsWith(err, 'usage: ') - - def test_guess_extension(self): - retcode, out, err = self.mimetypes_cmd('-l', '-e', 'image/jpg') - self.assertEqual(retcode, 0) - self.assertEqual(out, '.jpg\n') - self.assertEqual(err, '') - - retcode, out, err = self.mimetypes_cmd('-e', 'image/jpg') - self.assertEqual(retcode, 1) - self.assertEqual(out, '') - self.assertEqual(err, 'error: unknown type image/jpg\n') - - retcode, out, err = self.mimetypes_cmd('-e', 'image/jpeg') - self.assertEqual(retcode, 0) - self.assertEqual(out, '.jpg\n') - self.assertEqual(err, '') - - def test_guess_type(self): - retcode, out, err = self.mimetypes_cmd('-l', 'foo.webp') - self.assertEqual(retcode, 0) - self.assertEqual(out, 'type: image/webp encoding: None\n') - self.assertEqual(err, '') - - def test_guess_type_conflicting_with_mimetypes(self): - retcode, out, err = self.mimetypes_cmd('foo.pic') - self.assertEqual(retcode, 1) - self.assertEqual(out, '') - self.assertEqual(err, 'error: media type unknown for foo.pic\n') +class CommandLineTest(unittest.TestCase): + def test_parse_args(self): + args, help_text = mimetypes._parse_args("-h") + self.assertTrue(help_text.startswith("usage: ")) + + args, help_text = mimetypes._parse_args("--invalid") + self.assertTrue(help_text.startswith("usage: ")) + + args, _ = mimetypes._parse_args(shlex.split("-l -e image/jpg")) + self.assertTrue(args.extension) + self.assertTrue(args.lenient) + self.assertEqual(args.type, ["image/jpg"]) + + args, _ = mimetypes._parse_args(shlex.split("-e image/jpg")) + self.assertTrue(args.extension) + self.assertFalse(args.lenient) + self.assertEqual(args.type, ["image/jpg"]) + + args, _ = mimetypes._parse_args(shlex.split("-l foo.webp")) + self.assertFalse(args.extension) + self.assertTrue(args.lenient) + self.assertEqual(args.type, ["foo.webp"]) + + args, _ = mimetypes._parse_args(shlex.split("foo.pic")) + self.assertFalse(args.extension) + self.assertFalse(args.lenient) + self.assertEqual(args.type, ["foo.pic"]) + + + def test_invocation(self): + for command, expected in [ + ("-l -e image/jpg", ".jpg"), + ("-e image/jpeg", ".jpg"), + ("-l foo.webp", "type: image/webp encoding: None"), + ]: + self.assertEqual(mimetypes._main(shlex.split(command)), expected) + + + def test_invocation_error(self): + for command, expected in [ + ("-e image/jpg", "error: unknown type image/jpg"), + ("foo.pic", "error: media type unknown for foo.pic"), + ]: + with self.assertRaisesRegex(SystemExit, expected): + mimetypes._main(shlex.split(command)) if __name__ == "__main__": From b0c29fa202568887c05b1d7c243d3f0ae9b5513a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C3=A9n=C3=A9dikt=20Tran?= <10796600+picnixz@users.noreply.github.com> Date: Tue, 8 Apr 2025 10:37:41 +0200 Subject: [PATCH 7/8] Update Lib/test/test_mimetypes.py --- Lib/test/test_mimetypes.py | 1 - 1 file changed, 1 deletion(-) diff --git a/Lib/test/test_mimetypes.py b/Lib/test/test_mimetypes.py index b05c212a2a6b6b..2da1c13800e875 100644 --- a/Lib/test/test_mimetypes.py +++ b/Lib/test/test_mimetypes.py @@ -1,4 +1,3 @@ -import contextlib import io import mimetypes import os From 56bbd953f3ef6148048364ee7c5e8c126f384786 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C3=A9n=C3=A9dikt=20Tran?= <10796600+picnixz@users.noreply.github.com> Date: Tue, 8 Apr 2025 10:41:54 +0200 Subject: [PATCH 8/8] [empty commit to trigger CI]