# coding: utf-8
"""Test doctest contained tests in every file of the module.
"""
import doctest
import importlib
import os
import pkgutil
import sys
import types
import warnings
import tempfile
import unittest

try:
    from unittest import mock
except ImportError:
    import mock

import six

import fs
import fs.opener.parse
from fs.memoryfs import MemoryFS
from fs.subfs import ClosingSubFS

# --- Mocks ------------------------------------------------------------------


def _home_fs():
    """Create a mock filesystem that matches the XDG user-dirs spec."""
    home_fs = MemoryFS()
    home_fs.makedir("Desktop")
    home_fs.makedir("Documents")
    home_fs.makedir("Downloads")
    home_fs.makedir("Music")
    home_fs.makedir("Pictures")
    home_fs.makedir("Public")
    home_fs.makedir("Templates")
    home_fs.makedir("Videos")
    return home_fs


def _open_fs(path):
    """A mock `open_fs` that avoids side effects when running doctests."""
    if "://" not in path:
        path = "osfs://{}".format(path)
    parse_result = fs.opener.parse(path)
    if parse_result.protocol == "osfs" and parse_result.resource == "~":
        home_fs = _home_fs()
        if parse_result.path is not None:
            home_fs = home_fs.opendir(parse_result.path, factory=ClosingSubFS)
        return home_fs
    elif parse_result.protocol in {"ftp", "ftps", "mem"}:
        return MemoryFS()
    else:
        raise RuntimeError("not allowed in doctests: {}".format(path))


def _my_fs(module):
    """Create a mock filesystem to be used in examples."""
    my_fs = MemoryFS()

    if module == "fs.base":
        my_fs.makedir("Desktop")
        my_fs.makedir("Videos")
        my_fs.touch("Videos/starwars.mov")
        my_fs.touch("file.txt")

    elif module == "fs.info":
        my_fs.touch("foo.tar.gz")
        my_fs.settext("foo.py", "print('Hello, world!')")
        my_fs.makedir("bar")

    elif module in {"fs.walk", "fs.glob"}:
        my_fs.makedir("dir1")
        my_fs.makedir("dir2")
        my_fs.settext("foo.py", "print('Hello, world!')")
        my_fs.touch("foo.pyc")
        my_fs.settext("bar.py", "print('ok')\n\n# this is a comment\n")
        my_fs.touch("bar.pyc")

    # # used in `fs.glob`
    # home_fs.touch("foo.pyc")
    # home_fs.touch("bar.pyc")
    return my_fs


def _open(filename, mode="r"):
    """A mock `open` that actually opens a temporary file."""
    return tempfile.NamedTemporaryFile(mode="r+" if mode == "r" else mode)


# --- Loader protocol --------------------------------------------------------


def _load_tests_from_module(tests, module, globs, setUp=None, tearDown=None):
    """Load tests from module, iterating through submodules."""
    for attr in (getattr(module, x) for x in dir(module) if not x.startswith("_")):
        if isinstance(attr, types.ModuleType):
            suite = doctest.DocTestSuite(
                attr,
                globs,
                setUp=setUp,
                tearDown=tearDown,
                optionflags=+doctest.ELLIPSIS,
            )
            tests.addTests(suite)
    return tests


def _load_tests(loader, tests, ignore):
    """`load_test` function used by unittest to find the doctests."""

    # NB (@althonos): we only test docstrings on Python 3 because it's
    # extremely hard to maintain compatibility for both versions without
    # extensively hacking `doctest` and `unittest`.
    if six.PY2:
        return tests

    def setUp(self):
        warnings.simplefilter("ignore")
        self._open_fs_mock = mock.patch.object(fs, "open_fs", new=_open_fs)
        self._open_fs_mock.__enter__()
        self._ftpfs_mock = mock.patch.object(fs.ftpfs, "FTPFS")
        self._ftpfs_mock.__enter__()

    def tearDown(self):
        self._open_fs_mock.__exit__(None, None, None)
        self._ftpfs_mock.__exit__(None, None, None)
        warnings.simplefilter(warnings.defaultaction)

    # doctests are not compatible with `green`, so we may want to bail out
    # early if `green` is running the tests
    if sys.argv[0].endswith("green"):
        return tests

    # recursively traverse all library submodules and load tests from them
    packages = [None, fs]
    for pkg in iter(packages.pop, None):
        for (_, subpkgname, subispkg) in pkgutil.walk_packages(pkg.__path__):
            # import the submodule and add it to the tests
            module = importlib.import_module(".".join([pkg.__name__, subpkgname]))

            # load some useful modules / classes / mocks to the
            # globals so that we don't need to explicitly import
            # them in the doctests
            globs = dict(**module.__dict__)
            globs.update(
                os=os,
                fs=fs,
                my_fs=_my_fs(module.__name__),
                open=_open,
                # NB (@althonos): This allows using OSFS in some examples,
                # while not actually opening the real filesystem
                OSFS=lambda path: MemoryFS(),
                # NB (@althonos): This is for compatibility in `fs.registry`
                print_list=lambda path: None,
            )

            # load the doctests into the unittest test suite
            tests.addTests(
                doctest.DocTestSuite(
                    module,
                    globs=globs,
                    setUp=setUp,
                    tearDown=tearDown,
                    optionflags=+doctest.ELLIPSIS,
                )
            )

            # if the submodule is a package, we need to process its submodules
            # as well, so we add it to the package queue
            if subispkg:
                packages.append(module)

    return tests


# --- Unit test wrapper ------------------------------------------------------
#
# NB (@althonos): Since pytest doesn't support the `load_tests` protocol
# above, we manually build a `unittest.TestCase` using a dedicated test
# method for each doctest. This should be safe to remove when pytest
# supports it, or if we move away from pytest to run tests.


class TestDoctest(unittest.TestCase):
    pass


def make_wrapper(x):
    def _test_wrapper(self):
        x.setUp()
        try:
            x.runTest()
        finally:
            x.tearDown()

    return _test_wrapper


for x in _load_tests(None, unittest.TestSuite(), False):
    setattr(TestDoctest, "test_{}".format(x.id().replace(".", "_")), make_wrapper(x))

@@ -1572,17 +1572,16 @@
 oryFS()%0A
-%0A
     if m
@@ -1740,17 +1740,16 @@
 e.txt%22)%0A
-%0A
     elif
@@ -1891,17 +1891,16 @@
 (%22bar%22)%0A
-%0A
     elif
@@ -2155,16 +2155,16 @@
 ent%5Cn%22)%0A
+
         
@@ -2189,97 +2189,8 @@
 yc%22)
-%0A%0A    # # used in %60fs.glob%60%0A    # home_fs.touch(%22foo.pyc%22)%0A    # home_fs.touch(%22bar.pyc%22)
 %0A   
@@ -3806,187 +3806,8 @@
 n)%0A%0A
-    # doctests are not compatible with %60green%60, so we may want to bail out%0A    # early if %60green%60 is running the tests%0A    if sys.argv%5B0%5D.endswith(%22green%22):%0A        return tests%0A%0A
     

Remove compatibility code from `tests.test_doctest`