<commit_before>import sys
import os
from io import StringIO
import textwrap
import site
import contextlib
import platform
import tempfile

from distutils.core import Distribution
from distutils.command.build_ext import build_ext
from distutils import sysconfig
from distutils.tests.support import (
    TempdirManager,
    LoggingSilencer,
    copy_xxmodule_c,
    fixup_build_ext,
)
from distutils.extension import Extension
from distutils.errors import (
    CompileError,
    DistutilsPlatformError,
    DistutilsSetupError,
    UnknownFileError,
)

from test import support
from . import py38compat as os_helper
from . import py38compat as import_helper
import pytest
import re


@pytest.fixture()
def user_site_dir(request):
    self = request.instance
    self.tmp_dir = self.mkdtemp()
    from distutils.command import build_ext

    orig_user_base = site.USER_BASE

    site.USER_BASE = self.mkdtemp()
    build_ext.USER_BASE = site.USER_BASE

    # bpo-30132: On Windows, a .pdb file may be created in the current
    # working directory. Create a temporary working directory to cleanup
    # everything at the end of the test.
    with os_helper.change_cwd(self.tmp_dir):
        yield

    site.USER_BASE = orig_user_base
    build_ext.USER_BASE = orig_user_base


@contextlib.contextmanager
def cleanup(mod):
    """
    Tests will fail to tear down an extension module if it's been imported.

    Move the file to a temporary directory that won't be cleaned up.
    """
    try:
        yield
    finally:
        filename = sys.modules[mod].__file__
        if platform.system() != "Windows":
            return
        dest = os.path.join(
            tempfile.mkdtemp(prefix='deleteme'), os.path.basename(filename)
        )
        os.rename(filename, dest)
        # TODO: can the file be scheduled for deletion?


@pytest.mark.usefixtures('user_site_dir')
class TestBuildExt(TempdirManager, LoggingSilencer):
    def build_ext(self, *args, **kwargs):
        return build_ext(*args, **kwargs)

    def test_build_ext(self):
        cmd = support.missing_compiler_executable()
        copy_xxmodule_c(self.tmp_dir)
        xx_c = os.path.join(self.tmp_dir, 'xxmodule.c')
        xx_ext = Extension('xx', [xx_c])
        dist = Distribution({'name': 'xx', 'ext_modules': [xx_ext]})
        dist.package_dir = self.tmp_dir
        cmd = self.build_ext(dist)
        fixup_build_ext(cmd)
        cmd.build_lib = self.tmp_dir
        cmd.build_temp = self.tmp_dir

        old_stdout = sys.stdout
        if not support.verbose:
            # silence compiler output
            sys.stdout = StringIO()
        try:
            cmd.ensure_finalized()
            cmd.run()
        finally:
            sys.stdout = old_stdout

        with import_helper.CleanImport('xx'):
            with import_helper.DirsOnSysPath(self.tmp_dir):
                self._test_xx()

    @staticmethod
    @cleanup('xx')
    def _test_xx():
        import xx

        for attr in ('error', 'foo', 'new', 'roj'):
            assert hasattr(xx, attr)

        assert xx.foo(2, 5) == 7
        assert xx.foo(13, 15) == 28
        assert xx.new().demo() is None
        if support.HAVE_DOCSTRINGS:
            doc = 'This is a template module just for instruction.'
            assert xx.__doc__ == doc
        assert isinstance(xx.Null(), xx.Null)
        assert isinstance(xx.Str(), xx.Str)

    def test_solaris_enable_shared(self):
        dist = Distribution({'name': 'xx'})
        cmd = self.build_ext(dist)
        old = sys.platform

        sys.platform = 'sunos'  # fooling finalize_options
        from distutils.sysconfig import _config_vars<commit_msg>Redirect extension module to a directory that's not deleted on Windows.<commit_after>import sys
import os
from io import StringIO
import textwrap
import site
import contextlib
import platform
import tempfile
import importlib
import shutil

from distutils.core import Distribution
from distutils.command.build_ext import build_ext
from distutils import sysconfig
from distutils.tests.support import (
    TempdirManager,
    LoggingSilencer,
    copy_xxmodule_c,
    fixup_build_ext,
)
from distutils.extension import Extension
from distutils.errors import (
    CompileError,
    DistutilsPlatformError,
    DistutilsSetupError,
    UnknownFileError,
)

from test import support
from . import py38compat as os_helper
from . import py38compat as import_helper
import pytest
import re


@pytest.fixture()
def user_site_dir(request):
    self = request.instance
    self.tmp_dir = self.mkdtemp()
    from distutils.command import build_ext

    orig_user_base = site.USER_BASE

    site.USER_BASE = self.mkdtemp()
    build_ext.USER_BASE = site.USER_BASE

    # bpo-30132: On Windows, a .pdb file may be created in the current
    # working directory. Create a temporary working directory to cleanup
    # everything at the end of the test.
    with os_helper.change_cwd(self.tmp_dir):
        yield

    site.USER_BASE = orig_user_base
    build_ext.USER_BASE = orig_user_base


@contextlib.contextmanager
def safe_extension_import(name, path):
    with import_helper.CleanImport(name):
        with extension_redirect(name, path) as new_path:
            with import_helper.DirsOnSysPath(new_path):
                yield


@contextlib.contextmanager
def extension_redirect(mod, path):
    """
    Tests will fail to tear down an extension module if it's been imported.

    Before importing, copy the file to a temporary directory that won't
    be cleaned up. Yield the new path.
    """
    if platform.system() != "Windows":
        yield path
        return
    with import_helper.DirsOnSysPath(path):
        spec = importlib.util.find_spec(mod)
    filename = os.path.basename(spec.origin)
    trash_dir = tempfile.mkdtemp(prefix='deleteme')
    dest = os.path.join(trash_dir, os.path.basename(filename))
    shutil.copy(spec.origin, dest)
    yield trash_dir
    # TODO: can the file be scheduled for deletion?


@pytest.mark.usefixtures('user_site_dir')
class TestBuildExt(TempdirManager, LoggingSilencer):
    def build_ext(self, *args, **kwargs):
        return build_ext(*args, **kwargs)

    def test_build_ext(self):
        cmd = support.missing_compiler_executable()
        copy_xxmodule_c(self.tmp_dir)
        xx_c = os.path.join(self.tmp_dir, 'xxmodule.c')
        xx_ext = Extension('xx', [xx_c])
        dist = Distribution({'name': 'xx', 'ext_modules': [xx_ext]})
        dist.package_dir = self.tmp_dir
        cmd = self.build_ext(dist)
        fixup_build_ext(cmd)
        cmd.build_lib = self.tmp_dir
        cmd.build_temp = self.tmp_dir

        old_stdout = sys.stdout
        if not support.verbose:
            # silence compiler output
            sys.stdout = StringIO()
        try:
            cmd.ensure_finalized()
            cmd.run()
        finally:
            sys.stdout = old_stdout

        with safe_extension_import('xx', self.tmp_dir):
            self._test_xx()

    @staticmethod
    def _test_xx():
        import xx

        for attr in ('error', 'foo', 'new', 'roj'):
            assert hasattr(xx, attr)

        assert xx.foo(2, 5) == 7
        assert xx.foo(13, 15) == 28
        assert xx.new().demo() is None
        if support.HAVE_DOCSTRINGS:
            doc = 'This is a template module just for instruction.'
            assert xx.__doc__ == doc
        assert isinstance(xx.Null(), xx.Null)
        assert isinstance(xx.Str(), xx.Str)

    def test_solaris_enable_shared(self):
        dist = Distribution({'name': 'xx'})
        cmd = self.build_ext(dist)
        old = sys.platform

        sys.platform = 'sunos'  # fooling finalize_options
        from distutils.sysconfig import _config_vars
{'fraction_non_alphanumeric': 0.08837631203835687, 'fraction_numerical': 0.004665025268886873, 'mean_word_length': 2.7520661157024793, 'pattern_counts': {'":': 2, '<': 3, '<?xml version=': 0, '>': 3, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '4419466', 'n_tokens_mistral': 2377, 'n_tokens_neox': 2296, 'n_words': 620}