Issue #27186: Update os.fspath()/PyOS_FSPath() to check the return
type of __fspath__().

As part of this change, also make sure that the pure Python
implementation of os.fspath() is tested.
diff --git a/Lib/os.py b/Lib/os.py
index 67e1992..c31ecb2 100644
--- a/Lib/os.py
+++ b/Lib/os.py
@@ -881,14 +881,11 @@
         On Windows, use 'strict' error handler if the file system encoding is
         'mbcs' (which is the default encoding).
         """
-        filename = fspath(filename)
-        if isinstance(filename, bytes):
-            return filename
-        elif isinstance(filename, str):
+        filename = fspath(filename)  # Does type-checking of `filename`.
+        if isinstance(filename, str):
             return filename.encode(encoding, errors)
         else:
-            raise TypeError("expected str, bytes or os.PathLike object, not "
-                            + type(filename).__name__)
+            return filename
 
     def fsdecode(filename):
         """Decode filename (an os.PathLike, bytes, or str) from the filesystem
@@ -896,14 +893,11 @@
         Windows, use 'strict' error handler if the file system encoding is
         'mbcs' (which is the default encoding).
         """
-        filename = fspath(filename)
-        if isinstance(filename, str):
-            return filename
-        elif isinstance(filename, bytes):
+        filename = fspath(filename)  # Does type-checking of `filename`.
+        if isinstance(filename, bytes):
             return filename.decode(encoding, errors)
         else:
-            raise TypeError("expected str, bytes or os.PathLike object, not "
-                            + type(filename).__name__)
+            return filename
 
     return fsencode, fsdecode
 
@@ -1102,27 +1096,44 @@
     import io
     return io.open(fd, *args, **kwargs)
 
-# Supply os.fspath() if not defined in C
+
+# For testing purposes, make sure the function is available when the C
+# implementation exists.
+def _fspath(path):
+    """Return the path representation of a path-like object.
+
+    If str or bytes is passed in, it is returned unchanged. Otherwise the
+    os.PathLike interface is used to get the path representation. If the
+    path representation is not str or bytes, TypeError is raised. If the
+    provided path is not str, bytes, or os.PathLike, TypeError is raised.
+    """
+    if isinstance(path, (str, bytes)):
+        return path
+
+    # Work from the object's type to match method resolution of other magic
+    # methods.
+    path_type = type(path)
+    try:
+        path_repr = path_type.__fspath__(path)
+    except AttributeError:
+        if hasattr(path_type, '__fspath__'):
+            raise
+        else:
+            raise TypeError("expected str, bytes or os.PathLike object, "
+                            "not " + path_type.__name__)
+    if isinstance(path_repr, (str, bytes)):
+        return path_repr
+    else:
+        raise TypeError("expected {}.__fspath__() to return str or bytes, "
+                        "not {}".format(path_type.__name__,
+                                        type(path_repr).__name__))
+
+# If there is no C implementation, make the pure Python version the
+# implementation as transparently as possible.
 if not _exists('fspath'):
-    def fspath(path):
-        """Return the string representation of the path.
+    fspath = _fspath
+    fspath.__name__ = "fspath"
 
-        If str or bytes is passed in, it is returned unchanged.
-        """
-        if isinstance(path, (str, bytes)):
-            return path
-
-        # Work from the object's type to match method resolution of other magic
-        # methods.
-        path_type = type(path)
-        try:
-            return path_type.__fspath__(path)
-        except AttributeError:
-            if hasattr(path_type, '__fspath__'):
-                raise
-
-            raise TypeError("expected str, bytes or os.PathLike object, not "
-                            + path_type.__name__)
 
 class PathLike(abc.ABC):