Issue #23731: Implement PEP 488.

The concept of .pyo files no longer exists. Now .pyc files have an
optional `opt-` tag which specifies if any extra optimizations beyond
the peepholer were applied.
diff --git a/Lib/test/support/__init__.py b/Lib/test/support/__init__.py
index 0495121..1d0f11f 100644
--- a/Lib/test/support/__init__.py
+++ b/Lib/test/support/__init__.py
@@ -376,36 +376,32 @@
         pass
 
 def make_legacy_pyc(source):
-    """Move a PEP 3147 pyc/pyo file to its legacy pyc/pyo location.
-
-    The choice of .pyc or .pyo extension is done based on the __debug__ flag
-    value.
+    """Move a PEP 3147/488 pyc file to its legacy pyc location.
 
     :param source: The file system path to the source file.  The source file
-        does not need to exist, however the PEP 3147 pyc file must exist.
+        does not need to exist, however the PEP 3147/488 pyc file must exist.
     :return: The file system path to the legacy pyc file.
     """
     pyc_file = importlib.util.cache_from_source(source)
     up_one = os.path.dirname(os.path.abspath(source))
-    legacy_pyc = os.path.join(up_one, source + ('c' if __debug__ else 'o'))
+    legacy_pyc = os.path.join(up_one, source + 'c')
     os.rename(pyc_file, legacy_pyc)
     return legacy_pyc
 
 def forget(modname):
     """'Forget' a module was ever imported.
 
-    This removes the module from sys.modules and deletes any PEP 3147 or
-    legacy .pyc and .pyo files.
+    This removes the module from sys.modules and deletes any PEP 3147/488 or
+    legacy .pyc files.
     """
     unload(modname)
     for dirname in sys.path:
         source = os.path.join(dirname, modname + '.py')
         # It doesn't matter if they exist or not, unlink all possible
-        # combinations of PEP 3147 and legacy pyc and pyo files.
+        # combinations of PEP 3147/488 and legacy pyc files.
         unlink(source + 'c')
-        unlink(source + 'o')
-        unlink(importlib.util.cache_from_source(source, debug_override=True))
-        unlink(importlib.util.cache_from_source(source, debug_override=False))
+        for opt in ('', 1, 2):
+            unlink(importlib.util.cache_from_source(source, optimization=opt))
 
 # Check whether a gui is actually available
 def _is_gui_available():
diff --git a/Lib/test/test_compile.py b/Lib/test/test_compile.py
index 6116676..3d33bb5 100644
--- a/Lib/test/test_compile.py
+++ b/Lib/test/test_compile.py
@@ -425,7 +425,7 @@
 
     def test_compile_ast(self):
         fname = __file__
-        if fname.lower().endswith(('pyc', 'pyo')):
+        if fname.lower().endswith('pyc'):
             fname = fname[:-1]
         with open(fname, 'r') as f:
             fcontents = f.read()
diff --git a/Lib/test/test_compileall.py b/Lib/test/test_compileall.py
index 7506c70..07756f6 100644
--- a/Lib/test/test_compileall.py
+++ b/Lib/test/test_compileall.py
@@ -101,16 +101,16 @@
     def test_optimize(self):
         # make sure compiling with different optimization settings than the
         # interpreter's creates the correct file names
-        optimize = 1 if __debug__ else 0
+        optimize, opt = (1, 1) if __debug__ else (0, '')
         compileall.compile_dir(self.directory, quiet=True, optimize=optimize)
         cached = importlib.util.cache_from_source(self.source_path,
-                                                  debug_override=not optimize)
+                                                  optimization=opt)
         self.assertTrue(os.path.isfile(cached))
         cached2 = importlib.util.cache_from_source(self.source_path2,
-                                                   debug_override=not optimize)
+                                                   optimization=opt)
         self.assertTrue(os.path.isfile(cached2))
         cached3 = importlib.util.cache_from_source(self.source_path3,
-                                                   debug_override=not optimize)
+                                                   optimization=opt)
         self.assertTrue(os.path.isfile(cached3))
 
     @mock.patch('compileall.ProcessPoolExecutor')
@@ -237,11 +237,11 @@
         self.assertNotIn(b'Listing ', quiet)
 
     # Ensure that the default behavior of compileall's CLI is to create
-    # PEP 3147 pyc/pyo files.
+    # PEP 3147/PEP 488 pyc files.
     for name, ext, switch in [
         ('normal', 'pyc', []),
-        ('optimize', 'pyo', ['-O']),
-        ('doubleoptimize', 'pyo', ['-OO']),
+        ('optimize', 'opt-1.pyc', ['-O']),
+        ('doubleoptimize', 'opt-2.pyc', ['-OO']),
     ]:
         def f(self, ext=ext, switch=switch):
             script_helper.assert_python_ok(*(switch +
@@ -258,13 +258,12 @@
 
     def test_legacy_paths(self):
         # Ensure that with the proper switch, compileall leaves legacy
-        # pyc/pyo files, and no __pycache__ directory.
+        # pyc files, and no __pycache__ directory.
         self.assertRunOK('-b', '-q', self.pkgdir)
         # Verify the __pycache__ directory contents.
         self.assertFalse(os.path.exists(self.pkgdir_cachedir))
-        opt = 'c' if __debug__ else 'o'
-        expected = sorted(['__init__.py', '__init__.py' + opt, 'bar.py',
-                           'bar.py' + opt])
+        expected = sorted(['__init__.py', '__init__.pyc', 'bar.py',
+                           'bar.pyc'])
         self.assertEqual(sorted(os.listdir(self.pkgdir)), expected)
 
     def test_multiple_runs(self):
diff --git a/Lib/test/test_imp.py b/Lib/test/test_imp.py
index 80b9ec3..47bf1de 100644
--- a/Lib/test/test_imp.py
+++ b/Lib/test/test_imp.py
@@ -111,7 +111,6 @@
             del sys.path[0]
             support.unlink(temp_mod_name + '.py')
             support.unlink(temp_mod_name + '.pyc')
-            support.unlink(temp_mod_name + '.pyo')
 
     def test_issue5604(self):
         # Test cannot cover imp.load_compiled function.
@@ -194,7 +193,7 @@
             self.assertEqual(package.b, 2)
         finally:
             del sys.path[0]
-            for ext in ('.py', '.pyc', '.pyo'):
+            for ext in ('.py', '.pyc'):
                 support.unlink(temp_mod_name + ext)
                 support.unlink(init_file_name + ext)
             support.rmtree(test_package_name)
@@ -346,56 +345,6 @@
                               'qux.{}.pyc'.format(self.tag))
         self.assertEqual(imp.cache_from_source(path, True), expect)
 
-    def test_cache_from_source_no_cache_tag(self):
-        # Non cache tag means NotImplementedError.
-        with support.swap_attr(sys.implementation, 'cache_tag', None):
-            with self.assertRaises(NotImplementedError):
-                imp.cache_from_source('whatever.py')
-
-    def test_cache_from_source_no_dot(self):
-        # Directory with a dot, filename without dot.
-        path = os.path.join('foo.bar', 'file')
-        expect = os.path.join('foo.bar', '__pycache__',
-                              'file{}.pyc'.format(self.tag))
-        self.assertEqual(imp.cache_from_source(path, True), expect)
-
-    def test_cache_from_source_optimized(self):
-        # Given the path to a .py file, return the path to its PEP 3147
-        # defined .pyo file (i.e. under __pycache__).
-        path = os.path.join('foo', 'bar', 'baz', 'qux.py')
-        expect = os.path.join('foo', 'bar', 'baz', '__pycache__',
-                              'qux.{}.pyo'.format(self.tag))
-        self.assertEqual(imp.cache_from_source(path, False), expect)
-
-    def test_cache_from_source_cwd(self):
-        path = 'foo.py'
-        expect = os.path.join('__pycache__', 'foo.{}.pyc'.format(self.tag))
-        self.assertEqual(imp.cache_from_source(path, True), expect)
-
-    def test_cache_from_source_override(self):
-        # When debug_override is not None, it can be any true-ish or false-ish
-        # value.
-        path = os.path.join('foo', 'bar', 'baz.py')
-        partial_expect = os.path.join('foo', 'bar', '__pycache__',
-                                      'baz.{}.py'.format(self.tag))
-        self.assertEqual(imp.cache_from_source(path, []), partial_expect + 'o')
-        self.assertEqual(imp.cache_from_source(path, [17]),
-                         partial_expect + 'c')
-        # However if the bool-ishness can't be determined, the exception
-        # propagates.
-        class Bearish:
-            def __bool__(self): raise RuntimeError
-        with self.assertRaises(RuntimeError):
-            imp.cache_from_source('/foo/bar/baz.py', Bearish())
-
-    @unittest.skipUnless(os.sep == '\\' and os.altsep == '/',
-                     'test meaningful only where os.altsep is defined')
-    def test_sep_altsep_and_sep_cache_from_source(self):
-        # Windows path and PEP 3147 where sep is right of altsep.
-        self.assertEqual(
-            imp.cache_from_source('\\foo\\bar\\baz/qux.py', True),
-            '\\foo\\bar\\baz\\__pycache__\\qux.{}.pyc'.format(self.tag))
-
     @unittest.skipUnless(sys.implementation.cache_tag is not None,
                          'requires sys.implementation.cache_tag to not be '
                          'None')
@@ -407,68 +356,6 @@
         expect = os.path.join('foo', 'bar', 'baz', 'qux.py')
         self.assertEqual(imp.source_from_cache(path), expect)
 
-    def test_source_from_cache_no_cache_tag(self):
-        # If sys.implementation.cache_tag is None, raise NotImplementedError.
-        path = os.path.join('blah', '__pycache__', 'whatever.pyc')
-        with support.swap_attr(sys.implementation, 'cache_tag', None):
-            with self.assertRaises(NotImplementedError):
-                imp.source_from_cache(path)
-
-    def test_source_from_cache_bad_path(self):
-        # When the path to a pyc file is not in PEP 3147 format, a ValueError
-        # is raised.
-        self.assertRaises(
-            ValueError, imp.source_from_cache, '/foo/bar/bazqux.pyc')
-
-    def test_source_from_cache_no_slash(self):
-        # No slashes at all in path -> ValueError
-        self.assertRaises(
-            ValueError, imp.source_from_cache, 'foo.cpython-32.pyc')
-
-    def test_source_from_cache_too_few_dots(self):
-        # Too few dots in final path component -> ValueError
-        self.assertRaises(
-            ValueError, imp.source_from_cache, '__pycache__/foo.pyc')
-
-    def test_source_from_cache_too_many_dots(self):
-        # Too many dots in final path component -> ValueError
-        self.assertRaises(
-            ValueError, imp.source_from_cache,
-            '__pycache__/foo.cpython-32.foo.pyc')
-
-    def test_source_from_cache_no__pycache__(self):
-        # Another problem with the path -> ValueError
-        self.assertRaises(
-            ValueError, imp.source_from_cache,
-            '/foo/bar/foo.cpython-32.foo.pyc')
-
-    def test_package___file__(self):
-        try:
-            m = __import__('pep3147')
-        except ImportError:
-            pass
-        else:
-            self.fail("pep3147 module already exists: %r" % (m,))
-        # Test that a package's __file__ points to the right source directory.
-        os.mkdir('pep3147')
-        sys.path.insert(0, os.curdir)
-        def cleanup():
-            if sys.path[0] == os.curdir:
-                del sys.path[0]
-            shutil.rmtree('pep3147')
-        self.addCleanup(cleanup)
-        # Touch the __init__.py file.
-        support.create_empty_file('pep3147/__init__.py')
-        importlib.invalidate_caches()
-        expected___file__ = os.sep.join(('.', 'pep3147', '__init__.py'))
-        m = __import__('pep3147')
-        self.assertEqual(m.__file__, expected___file__, (m.__file__, m.__path__, sys.path, sys.path_importer_cache))
-        # Ensure we load the pyc file.
-        support.unload('pep3147')
-        m = __import__('pep3147')
-        support.unload('pep3147')
-        self.assertEqual(m.__file__, expected___file__, (m.__file__, m.__path__, sys.path, sys.path_importer_cache))
-
 
 class NullImporterTests(unittest.TestCase):
     @unittest.skipIf(support.TESTFN_UNENCODABLE is None,
diff --git a/Lib/test/test_import/__init__.py b/Lib/test/test_import/__init__.py
index fd21fa2..5f87d89 100644
--- a/Lib/test/test_import/__init__.py
+++ b/Lib/test/test_import/__init__.py
@@ -32,7 +32,6 @@
 def remove_files(name):
     for f in (name + ".py",
               name + ".pyc",
-              name + ".pyo",
               name + ".pyw",
               name + "$py.class"):
         unlink(f)
@@ -84,7 +83,6 @@
         def test_with_extension(ext):
             # The extension is normally ".py", perhaps ".pyw".
             source = TESTFN + ext
-            pyo = TESTFN + ".pyo"
             if is_jython:
                 pyc = TESTFN + "$py.class"
             else:
@@ -115,7 +113,6 @@
                 forget(TESTFN)
                 unlink(source)
                 unlink(pyc)
-                unlink(pyo)
 
         sys.path.insert(0, os.curdir)
         try:
@@ -138,7 +135,7 @@
             f.write(']')
 
         try:
-            # Compile & remove .py file; we only need .pyc (or .pyo).
+            # Compile & remove .py file; we only need .pyc.
             # Bytecode must be relocated from the PEP 3147 bytecode-only location.
             py_compile.compile(filename)
         finally:
@@ -252,7 +249,7 @@
             importlib.invalidate_caches()
             mod = __import__(TESTFN)
             base, ext = os.path.splitext(mod.__file__)
-            self.assertIn(ext, ('.pyc', '.pyo'))
+            self.assertEqual(ext, '.pyc')
         finally:
             del sys.path[0]
             remove_files(TESTFN)
@@ -328,7 +325,7 @@
 
 @skip_if_dont_write_bytecode
 class FilePermissionTests(unittest.TestCase):
-    # tests for file mode on cached .pyc/.pyo files
+    # tests for file mode on cached .pyc files
 
     @unittest.skipUnless(os.name == 'posix',
                          "test meaningful only on posix systems")
@@ -339,7 +336,7 @@
             module = __import__(name)
             if not os.path.exists(cached_path):
                 self.fail("__import__ did not result in creation of "
-                          "either a .pyc or .pyo file")
+                          "a .pyc file")
             stat_info = os.stat(cached_path)
 
         # Check that the umask is respected, and the executable bits
@@ -358,7 +355,7 @@
             __import__(name)
             if not os.path.exists(cached_path):
                 self.fail("__import__ did not result in creation of "
-                          "either a .pyc or .pyo file")
+                          "a .pyc file")
             stat_info = os.stat(cached_path)
 
         self.assertEqual(oct(stat.S_IMODE(stat_info.st_mode)), oct(mode))
@@ -373,7 +370,7 @@
             __import__(name)
             if not os.path.exists(cached_path):
                 self.fail("__import__ did not result in creation of "
-                          "either a .pyc or .pyo file")
+                          "a .pyc file")
             stat_info = os.stat(cached_path)
 
         expected = mode | 0o200 # Account for fix for issue #6074
@@ -404,10 +401,7 @@
             unlink(path)
             unload(name)
             importlib.invalidate_caches()
-            if __debug__:
-                bytecode_only = path + "c"
-            else:
-                bytecode_only = path + "o"
+            bytecode_only = path + "c"
             os.rename(importlib.util.cache_from_source(path), bytecode_only)
             m = __import__(name)
             self.assertEqual(m.x, 'rewritten')
@@ -631,9 +625,7 @@
 
 
 class PycacheTests(unittest.TestCase):
-    # Test the various PEP 3147 related behaviors.
-
-    tag = sys.implementation.cache_tag
+    # Test the various PEP 3147/488-related behaviors.
 
     def _clean(self):
         forget(TESTFN)
@@ -658,9 +650,10 @@
         self.assertFalse(os.path.exists('__pycache__'))
         __import__(TESTFN)
         self.assertTrue(os.path.exists('__pycache__'))
-        self.assertTrue(os.path.exists(os.path.join(
-            '__pycache__', '{}.{}.py{}'.format(
-            TESTFN, self.tag, 'c' if __debug__ else 'o'))))
+        pyc_path = importlib.util.cache_from_source(self.source)
+        self.assertTrue(os.path.exists(pyc_path),
+                        'bytecode file {!r} for {!r} does not '
+                        'exist'.format(pyc_path, TESTFN))
 
     @unittest.skipUnless(os.name == 'posix',
                          "test meaningful only on posix systems")
@@ -673,8 +666,10 @@
         with temp_umask(0o222):
             __import__(TESTFN)
         self.assertTrue(os.path.exists('__pycache__'))
-        self.assertFalse(os.path.exists(os.path.join(
-            '__pycache__', '{}.{}.pyc'.format(TESTFN, self.tag))))
+        pyc_path = importlib.util.cache_from_source(self.source)
+        self.assertFalse(os.path.exists(pyc_path),
+                        'bytecode file {!r} for {!r} '
+                        'exists'.format(pyc_path, TESTFN))
 
     @skip_if_dont_write_bytecode
     def test_missing_source(self):
diff --git a/Lib/test/test_importlib/test_util.py b/Lib/test/test_importlib/test_util.py
index 5d35d14..69466b2 100644
--- a/Lib/test/test_importlib/test_util.py
+++ b/Lib/test/test_importlib/test_util.py
@@ -5,6 +5,7 @@
 importlib_util = util.import_importlib('importlib.util')
 
 import os
+import string
 import sys
 from test import support
 import types
@@ -562,7 +563,8 @@
         path = os.path.join('foo', 'bar', 'baz', 'qux.py')
         expect = os.path.join('foo', 'bar', 'baz', '__pycache__',
                               'qux.{}.pyc'.format(self.tag))
-        self.assertEqual(self.util.cache_from_source(path, True), expect)
+        self.assertEqual(self.util.cache_from_source(path, optimization=''),
+                         expect)
 
     def test_cache_from_source_no_cache_tag(self):
         # No cache tag means NotImplementedError.
@@ -575,43 +577,103 @@
         path = os.path.join('foo.bar', 'file')
         expect = os.path.join('foo.bar', '__pycache__',
                               'file{}.pyc'.format(self.tag))
-        self.assertEqual(self.util.cache_from_source(path, True), expect)
+        self.assertEqual(self.util.cache_from_source(path, optimization=''),
+                         expect)
 
-    def test_cache_from_source_optimized(self):
-        # Given the path to a .py file, return the path to its PEP 3147
-        # defined .pyo file (i.e. under __pycache__).
+    def test_cache_from_source_debug_override(self):
+        # Given the path to a .py file, return the path to its PEP 3147/PEP 488
+        # defined .pyc file (i.e. under __pycache__).
         path = os.path.join('foo', 'bar', 'baz', 'qux.py')
-        expect = os.path.join('foo', 'bar', 'baz', '__pycache__',
-                              'qux.{}.pyo'.format(self.tag))
-        self.assertEqual(self.util.cache_from_source(path, False), expect)
+        with warnings.catch_warnings():
+            warnings.simplefilter('ignore')
+            self.assertEqual(self.util.cache_from_source(path, False),
+                             self.util.cache_from_source(path, optimization=1))
+            self.assertEqual(self.util.cache_from_source(path, True),
+                             self.util.cache_from_source(path, optimization=''))
+        with warnings.catch_warnings():
+            warnings.simplefilter('error')
+            with self.assertRaises(DeprecationWarning):
+                self.util.cache_from_source(path, False)
+            with self.assertRaises(DeprecationWarning):
+                self.util.cache_from_source(path, True)
 
     def test_cache_from_source_cwd(self):
         path = 'foo.py'
         expect = os.path.join('__pycache__', 'foo.{}.pyc'.format(self.tag))
-        self.assertEqual(self.util.cache_from_source(path, True), expect)
+        self.assertEqual(self.util.cache_from_source(path, optimization=''),
+                         expect)
 
     def test_cache_from_source_override(self):
         # When debug_override is not None, it can be any true-ish or false-ish
         # value.
         path = os.path.join('foo', 'bar', 'baz.py')
-        partial_expect = os.path.join('foo', 'bar', '__pycache__',
-                                      'baz.{}.py'.format(self.tag))
-        self.assertEqual(self.util.cache_from_source(path, []), partial_expect + 'o')
-        self.assertEqual(self.util.cache_from_source(path, [17]),
-                         partial_expect + 'c')
         # However if the bool-ishness can't be determined, the exception
         # propagates.
         class Bearish:
             def __bool__(self): raise RuntimeError
-        with self.assertRaises(RuntimeError):
-            self.util.cache_from_source('/foo/bar/baz.py', Bearish())
+        with warnings.catch_warnings():
+            warnings.simplefilter('ignore')
+            self.assertEqual(self.util.cache_from_source(path, []),
+                             self.util.cache_from_source(path, optimization=1))
+            self.assertEqual(self.util.cache_from_source(path, [17]),
+                             self.util.cache_from_source(path, optimization=''))
+            with self.assertRaises(RuntimeError):
+                self.util.cache_from_source('/foo/bar/baz.py', Bearish())
+
+
+    def test_cache_from_source_optimization_empty_string(self):
+        # Setting 'optimization' to '' leads to no optimization tag (PEP 488).
+        path = 'foo.py'
+        expect = os.path.join('__pycache__', 'foo.{}.pyc'.format(self.tag))
+        self.assertEqual(self.util.cache_from_source(path, optimization=''),
+                         expect)
+
+    def test_cache_from_source_optimization_None(self):
+        # Setting 'optimization' to None uses the interpreter's optimization.
+        # (PEP 488)
+        path = 'foo.py'
+        optimization_level = sys.flags.optimize
+        almost_expect = os.path.join('__pycache__', 'foo.{}'.format(self.tag))
+        if optimization_level == 0:
+            expect = almost_expect + '.pyc'
+        elif optimization_level <= 2:
+            expect = almost_expect + '.opt-{}.pyc'.format(optimization_level)
+        else:
+            msg = '{!r} is a non-standard optimization level'.format(optimization_level)
+            self.skipTest(msg)
+        self.assertEqual(self.util.cache_from_source(path, optimization=None),
+                         expect)
+
+    def test_cache_from_source_optimization_set(self):
+        # The 'optimization' parameter accepts anything that has a string repr
+        # that passes str.alnum().
+        path = 'foo.py'
+        valid_characters = string.ascii_letters + string.digits
+        almost_expect = os.path.join('__pycache__', 'foo.{}'.format(self.tag))
+        got = self.util.cache_from_source(path, optimization=valid_characters)
+        # Test all valid characters are accepted.
+        self.assertEqual(got,
+                         almost_expect + '.opt-{}.pyc'.format(valid_characters))
+        # str() should be called on argument.
+        self.assertEqual(self.util.cache_from_source(path, optimization=42),
+                         almost_expect + '.opt-42.pyc')
+        # Invalid characters raise ValueError.
+        with self.assertRaises(ValueError):
+            self.util.cache_from_source(path, optimization='path/is/bad')
+
+    def test_cache_from_source_debug_override_optimization_both_set(self):
+        # Can only set one of the optimization-related parameters.
+        with warnings.catch_warnings():
+            warnings.simplefilter('ignore')
+            with self.assertRaises(TypeError):
+                self.util.cache_from_source('foo.py', False, optimization='')
 
     @unittest.skipUnless(os.sep == '\\' and os.altsep == '/',
                      'test meaningful only where os.altsep is defined')
     def test_sep_altsep_and_sep_cache_from_source(self):
         # Windows path and PEP 3147 where sep is right of altsep.
         self.assertEqual(
-            self.util.cache_from_source('\\foo\\bar\\baz/qux.py', True),
+            self.util.cache_from_source('\\foo\\bar\\baz/qux.py', optimization=''),
             '\\foo\\bar\\baz\\__pycache__\\qux.{}.pyc'.format(self.tag))
 
     @unittest.skipUnless(sys.implementation.cache_tag is not None,
@@ -649,7 +711,12 @@
             ValueError, self.util.source_from_cache, '__pycache__/foo.pyc')
 
     def test_source_from_cache_too_many_dots(self):
-        # Too many dots in final path component -> ValueError
+        with self.assertRaises(ValueError):
+            self.util.source_from_cache(
+                    '__pycache__/foo.cpython-32.opt-1.foo.pyc')
+
+    def test_source_from_cache_not_opt(self):
+        # Non-`opt-` path component -> ValueError
         self.assertRaises(
             ValueError, self.util.source_from_cache,
             '__pycache__/foo.cpython-32.foo.pyc')
@@ -660,6 +727,17 @@
             ValueError, self.util.source_from_cache,
             '/foo/bar/foo.cpython-32.foo.pyc')
 
+    def test_source_from_cache_optimized_bytecode(self):
+        # Optimized bytecode is not an issue.
+        path = os.path.join('__pycache__', 'foo.{}.opt-1.pyc'.format(self.tag))
+        self.assertEqual(self.util.source_from_cache(path), 'foo.py')
+
+    def test_source_from_cache_missing_optimization(self):
+        # An empty optimization level is a no-no.
+        path = os.path.join('__pycache__', 'foo.{}.opt-.pyc'.format(self.tag))
+        with self.assertRaises(ValueError):
+            self.util.source_from_cache(path)
+
 
 (Frozen_PEP3147Tests,
  Source_PEP3147Tests
diff --git a/Lib/test/test_py_compile.py b/Lib/test/test_py_compile.py
index 1abea27..bff35db 100644
--- a/Lib/test/test_py_compile.py
+++ b/Lib/test/test_py_compile.py
@@ -119,6 +119,10 @@
         self.assertTrue(os.path.exists(cache_path))
         self.assertFalse(os.path.exists(pyc_path))
 
+    def test_optimization_path(self):
+        # Specifying optimized bytecode should lead to a path reflecting that.
+        self.assertIn('opt-2', py_compile.compile(self.source_path, optimize=2))
+
 
 if __name__ == "__main__":
     unittest.main()
diff --git a/Lib/test/test_runpy.py b/Lib/test/test_runpy.py
index 786b813..3884734 100644
--- a/Lib/test/test_runpy.py
+++ b/Lib/test/test_runpy.py
@@ -269,7 +269,7 @@
             if verbose > 1: print(ex) # Persist with cleaning up
 
     def _fix_ns_for_legacy_pyc(self, ns, alter_sys):
-        char_to_add = "c" if __debug__ else "o"
+        char_to_add = "c"
         ns["__file__"] += char_to_add
         ns["__cached__"] = ns["__file__"]
         spec = ns["__spec__"]
diff --git a/Lib/test/test_trace.py b/Lib/test/test_trace.py
index 05bf274..2f2c584 100644
--- a/Lib/test/test_trace.py
+++ b/Lib/test/test_trace.py
@@ -13,8 +13,8 @@
 #------------------------------- Utilities -----------------------------------#
 
 def fix_ext_py(filename):
-    """Given a .pyc/.pyo filename converts it to the appropriate .py"""
-    if filename.endswith(('.pyc', '.pyo')):
+    """Given a .pyc filename converts it to the appropriate .py"""
+    if filename.endswith('.pyc'):
         filename = filename[:-1]
     return filename
 
diff --git a/Lib/test/test_tracemalloc.py b/Lib/test/test_tracemalloc.py
index 19d3fd8..9382c48 100644
--- a/Lib/test/test_tracemalloc.py
+++ b/Lib/test/test_tracemalloc.py
@@ -660,11 +660,9 @@
         self.assertFalse(fnmatch('abcdd', 'a*c*e'))
         self.assertFalse(fnmatch('abcbdefef', 'a*bd*eg'))
 
-        # replace .pyc and .pyo suffix with .py
+        # replace .pyc suffix with .py
         self.assertTrue(fnmatch('a.pyc', 'a.py'))
-        self.assertTrue(fnmatch('a.pyo', 'a.py'))
         self.assertTrue(fnmatch('a.py', 'a.pyc'))
-        self.assertTrue(fnmatch('a.py', 'a.pyo'))
 
         if os.name == 'nt':
             # case insensitive
@@ -674,7 +672,6 @@
             self.assertTrue(fnmatch('a.pyc', 'a.PY'))
             self.assertTrue(fnmatch('a.PYO', 'a.py'))
             self.assertTrue(fnmatch('a.py', 'a.PYC'))
-            self.assertTrue(fnmatch('a.PY', 'a.pyo'))
         else:
             # case sensitive
             self.assertFalse(fnmatch('aBC', 'ABc'))
@@ -683,7 +680,6 @@
             self.assertFalse(fnmatch('a.pyc', 'a.PY'))
             self.assertFalse(fnmatch('a.PYO', 'a.py'))
             self.assertFalse(fnmatch('a.py', 'a.PYC'))
-            self.assertFalse(fnmatch('a.PY', 'a.pyo'))
 
         if os.name == 'nt':
             # normalize alternate separator "/" to the standard separator "\"
diff --git a/Lib/test/test_zipfile.py b/Lib/test/test_zipfile.py
index 1b2dc85..aa8c463 100644
--- a/Lib/test/test_zipfile.py
+++ b/Lib/test/test_zipfile.py
@@ -689,7 +689,7 @@
         self.requiresWriteAccess(os.path.dirname(__file__))
         with TemporaryFile() as t, zipfile.PyZipFile(t, "w") as zipfp:
             fn = __file__
-            if fn.endswith('.pyc') or fn.endswith('.pyo'):
+            if fn.endswith('.pyc'):
                 path_split = fn.split(os.sep)
                 if os.altsep is not None:
                     path_split.extend(fn.split(os.altsep))
@@ -706,7 +706,7 @@
 
         with TemporaryFile() as t, zipfile.PyZipFile(t, "w") as zipfp:
             fn = __file__
-            if fn.endswith(('.pyc', '.pyo')):
+            if fn.endswith('.pyc'):
                 fn = fn[:-1]
 
             zipfp.writepy(fn, "testpackage")
@@ -762,10 +762,8 @@
         import email
         packagedir = os.path.dirname(email.__file__)
         self.requiresWriteAccess(packagedir)
-        # use .pyc if running test in optimization mode,
-        # use .pyo if running test in debug mode
         optlevel = 1 if __debug__ else 0
-        ext = '.pyo' if optlevel == 1 else '.pyc'
+        ext = '.pyc'
 
         with TemporaryFile() as t, \
              zipfile.PyZipFile(t, "w", optimize=optlevel) as zipfp:
@@ -839,11 +837,10 @@
                 self.assertIn("SyntaxError", s.getvalue())
 
                 # as it will not have compiled the python file, it will
-                # include the .py file not .pyc or .pyo
+                # include the .py file not .pyc
                 names = zipfp.namelist()
                 self.assertIn('mod1.py', names)
                 self.assertNotIn('mod1.pyc', names)
-                self.assertNotIn('mod1.pyo', names)
 
         finally:
             rmtree(TESTFN2)
diff --git a/Lib/test/test_zipimport.py b/Lib/test/test_zipimport.py
index 0a83841..a97a778 100644
--- a/Lib/test/test_zipimport.py
+++ b/Lib/test/test_zipimport.py
@@ -51,7 +51,7 @@
 TEMP_ZIP = os.path.abspath("junk95142.zip")
 
 pyc_file = importlib.util.cache_from_source(TESTMOD + '.py')
-pyc_ext = ('.pyc' if __debug__ else '.pyo')
+pyc_ext = '.pyc'
 
 
 class ImportHooksBaseTestCase(unittest.TestCase):