Source code for pywincffi.dev.testutil

"""
Test Utility
------------

This module is used by the unittests.
"""

import os
import socket
import subprocess
import sys
from random import choice
from string import ascii_lowercase, ascii_uppercase

from cffi import FFI, CDefError
from six import PY2, PY3

try:
    # The setup.py file installs unittest2 for Python 2
    # which backports newer test framework features.
    from unittest2 import TestCase as _TestCase, skipUnless
except ImportError:  # pragma: no cover
    # pylint: disable=wrong-import-order
    from unittest import TestCase as _TestCase, skipUnless

from pywincffi.core.config import config
from pywincffi.core.logger import get_logger

logger = get_logger("core.testutil")

# To keep lint on non-windows platforms happy.
try:
    WindowsError
except NameError:  # pragma: no cover
    WindowsError = OSError  # pylint: disable=redefined-builtin

# Load in our own kernel32 with the function(s) we need
# so we don't have to rely on pywincffi.core
libtest = None  # pylint: disable=invalid-name
ffi = FFI()

# pylint: disable=invalid-name
skip_unless_python2 = skipUnless(PY2, "Not Python 2")
skip_unless_python3 = skipUnless(PY3, "Not Python 3")

try:
    ffi.cdef("void SetLastError(DWORD);")
    libtest = ffi.dlopen("kernel32")  # pylint: disable=invalid-name
except (AttributeError, OSError, CDefError):  # pragma: no cover
    if os.name == "nt":
        logger.warning("Failed to build SetLastError()")


[docs]class TestCase(_TestCase): """ A base class for all test cases. By default the core test case just provides some extra functionality. """ REQUIRES_INTERNET = False _HAS_INTERNET = None
[docs] def setUp(self): # pragma: no cover if self.REQUIRES_INTERNET and not self.internet_connected(): if os.environ.get("CI"): self.fail( "Test requires internet but we do not seem to be " "connected.") self.skipTest("Internet unavailable") if os.name == "nt": # pragma: no cover # Always reset the last error to 0 between tests. This # ensures that any error we intentionally throw in one # test does not causes an error to be raised in another. self.SetLastError(0) config.load()
@classmethod
[docs] def internet_connected(cls): """ Returns ``True`` if there appears to be internet access by attempting to connect to a few different domains. The first answer will be cached. """ if TestCase._HAS_INTERNET is not None: return TestCase._HAS_INTERNET original_timeout = socket.getdefaulttimeout() socket.setdefaulttimeout(1) for hostname in ("github.com", "readthedocs.org", "example.com"): sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) try: sock.connect((hostname, 80)) TestCase._HAS_INTERNET = True break # pragma: no cover except Exception: # pylint: disable=broad-except pass finally: sock.close() else: # pragma: no cover TestCase._HAS_INTERNET = False socket.setdefaulttimeout(original_timeout) return TestCase._HAS_INTERNET
# pylint: disable=invalid-name
[docs] def SetLastError(self, value=0, lib=None): # pragma: no cover """Calls the Windows API function SetLastError()""" if os.name != "nt": self.fail("Only an NT system should call this method") if lib is None: lib = libtest if lib is None: self.fail("`lib` was not defined") if not isinstance(value, int): self.fail("Expected int for `value`") return lib.SetLastError(ffi.cast("DWORD", value))
def _terminate_process(self, process): # pylint: disable=no-self-use """ Calls terminnate() on ``process`` and ignores any errors produced. """ try: process.terminate() except Exception: # pylint: disable=broad-except pass
[docs] def create_python_process(self, command): """Creates a Python process that run ``command``""" process = subprocess.Popen( [sys.executable, "-c", command], stdout=subprocess.PIPE, stderr=subprocess.PIPE) self.addCleanup(self._terminate_process, process) return process
[docs] def random_string(self, length): """ Returns a random string as long as ``length``. The first character will always be a letter. All other characters will be A-F, A-F or 0-9. """ if length < 1: # pragma: no cover self.fail("Length must be at least 1.") # First character should always be a letter so the string # can be used in object names. output = choice(ascii_lowercase) length -= 1 while length: length -= 1 output += choice(ascii_lowercase + ascii_uppercase + "0123456789") return output