From 8bc5ebf6d24bdc4d061804100ecc4f898ff551aa Mon Sep 17 00:00:00 2001 From: Oliver Palmer Date: Sun, 26 Jul 2015 12:13:49 -0400 Subject: [PATCH 1/6] move new_ffi into Loader --- pywincffi/core/ffi.py | 17 ++++++------ pywincffi/kernel32/io.py | 4 +-- pywincffi/kernel32/process.py | 4 +-- tests/test_core/test_ffi.py | 50 +++++++++++++++++------------------ 4 files changed, 38 insertions(+), 37 deletions(-) diff --git a/pywincffi/core/ffi.py b/pywincffi/core/ffi.py index 707b26d..2d6a552 100644 --- a/pywincffi/core/ffi.py +++ b/pywincffi/core/ffi.py @@ -18,7 +18,7 @@ logger = get_logger("core.ffi") -class Library(object): +class Loader(object): """ A wrapper around :meth:`FFI.cdef` and :meth:`FFI.dlopen` that also performs caching. Without caching a library could be @@ -27,6 +27,13 @@ class Library(object): """ CACHE = {} + @staticmethod + def ffi(): + """Returns an instance of :class:`FFI`""" + instance = FFI() + instance.set_unicode(True) + return instance + @staticmethod def _load_header(header_name): """ @@ -109,10 +116,4 @@ def load(cls, library_name, ffi_instance=None, header=None): return library -def new_ffi(): - """Returns an instance of :class:`FFI`""" - ffi_instance = FFI() - ffi_instance.set_unicode(True) - return ffi_instance - -ffi = new_ffi() +ffi = Loader.ffi() diff --git a/pywincffi/kernel32/io.py b/pywincffi/kernel32/io.py index 71be820..543560f 100644 --- a/pywincffi/kernel32/io.py +++ b/pywincffi/kernel32/io.py @@ -7,10 +7,10 @@ from six import integer_types -from pywincffi.core.ffi import Library, ffi +from pywincffi.core.ffi import Loader, ffi from pywincffi.core.checks import Enums, input_check, error_check, NoneType -kernel32 = Library.load("kernel32") +kernel32 = Loader.load("kernel32") def CreatePipe(nSize=0, lpPipeAttributes=None): diff --git a/pywincffi/kernel32/process.py b/pywincffi/kernel32/process.py index 1b4b823..0bd6a9b 100644 --- a/pywincffi/kernel32/process.py +++ b/pywincffi/kernel32/process.py @@ -17,10 +17,10 @@ import six -from pywincffi.core.ffi import Library, ffi +from pywincffi.core.ffi import Loader, ffi from pywincffi.core.checks import input_check, error_check -kernel32 = Library.load("kernel32") +kernel32 = Loader.load("kernel32") PROCESS_CREATE_PROCESS = 0x0080 PROCESS_CREATE_THREAD = 0x0002 diff --git a/tests/test_core/test_ffi.py b/tests/test_core/test_ffi.py index bf13beb..5f39941 100644 --- a/tests/test_core/test_ffi.py +++ b/tests/test_core/test_ffi.py @@ -10,7 +10,7 @@ from six.moves import builtins import pywincffi -from pywincffi.core.ffi import Library, new_ffi, ffi +from pywincffi.core.ffi import Loader, ffi from pywincffi.core.testutil import TestCase from pywincffi.exceptions import HeaderNotFoundError @@ -35,10 +35,10 @@ def test_loads_header_from_correct_path(self): with open(path, "rb") as stream: header = stream.read().decode() - self.assertEqual(Library._load_header("kernel32.h"), header) + self.assertEqual(Loader._load_header("kernel32.h"), header) def test_returns_none_when_header_not_found(self): - self.assertIsNone(Library._load_header("foobar")) + self.assertIsNone(Loader._load_header("foobar")) def test_raises_non_not_found_errors(self): def side_effect(*args, **kwargs): @@ -46,7 +46,7 @@ def side_effect(*args, **kwargs): with patch.object(builtins, "open", side_effect=side_effect): with self.assertRaises(OSError) as raised_error: - Library._load_header("kernel32") + Loader._load_header("kernel32") self.assertEqual(raised_error.exception.errno, 42) @@ -55,36 +55,36 @@ class TestLibraryLoad(TestCase): Tests for ``pywincffi.core.ffi.Library.load`` """ def setUp(self): - self._cache = Library.CACHE.copy() - Library.CACHE.clear() + self._cache = Loader.CACHE.copy() + Loader.CACHE.clear() def tearDown(self): - Library.CACHE.clear() - Library.CACHE.update(self._cache) + Loader.CACHE.clear() + Loader.CACHE.update(self._cache) def test_returns_cached(self): - ffi_instance = new_ffi() - Library.CACHE[ffi_instance] = {"foo_library1": True} + ffi_instance = Loader.ffi() + Loader.CACHE[ffi_instance] = {"foo_library1": True} self.assertIs( - Library.load("foo_library1", ffi_instance=ffi_instance), + Loader.load("foo_library1", ffi_instance=ffi_instance), True ) def test_returns_cached_default_ffi_instance(self): - Library.CACHE[ffi] = {"foo_library2": False} - self.assertIs(Library.load("foo_library2"), False) + Loader.CACHE[ffi] = {"foo_library2": False} + self.assertIs(Loader.load("foo_library2"), False) def test_header_not_found(self): - with patch.object(Library, "_load_header", return_value=None): + with patch.object(Loader, "_load_header", return_value=None): with self.assertRaises(HeaderNotFoundError): - Library.load("kernel32") + Loader.load("kernel32") def test_loads_library(self): fake_header = dedent(""" #define HELLO_WORLD 42 """) - with patch.object(Library, "_load_header", return_value=fake_header): - library = Library.load("kernel32") + with patch.object(Loader, "_load_header", return_value=fake_header): + library = Loader.load("kernel32") self.assertEqual(library.HELLO_WORLD, 42) @@ -92,11 +92,11 @@ def test_caches_library(self): fake_header = dedent(""" #define HELLO_WORLD 42 """) - self.assertNotIn(ffi, Library.CACHE) - with patch.object(Library, "_load_header", return_value=fake_header): - library1 = Library.load("kernel32") - self.assertIn(ffi, Library.CACHE) - library2 = Library.load("kernel32") - - self.assertIs(Library.CACHE[ffi]["kernel32"], library1) - self.assertIs(Library.CACHE[ffi]["kernel32"], library2) + self.assertNotIn(ffi, Loader.CACHE) + with patch.object(Loader, "_load_header", return_value=fake_header): + library1 = Loader.load("kernel32") + self.assertIn(ffi, Loader.CACHE) + library2 = Loader.load("kernel32") + + self.assertIs(Loader.CACHE[ffi]["kernel32"], library1) + self.assertIs(Loader.CACHE[ffi]["kernel32"], library2) From a3df5add620dfa1fa2070632c89e286ff1b06cd3 Mon Sep 17 00:00:00 2001 From: Oliver Palmer Date: Sun, 26 Jul 2015 13:47:09 -0400 Subject: [PATCH 2/6] updating to move away from dlopen which does not always work for some functions --- pywincffi/core/cdefs/headers/constants.h | 2 + pywincffi/core/cdefs/headers/functions.h | 8 + .../cdefs/headers/structs.h} | 8 - pywincffi/core/cdefs/sources/main.c | 1 + pywincffi/core/checks.py | 5 +- pywincffi/core/ffi.py | 139 ++++++++---------- pywincffi/exceptions.py | 4 +- pywincffi/kernel32/io.py | 24 +-- pywincffi/kernel32/process.py | 7 +- tests/test_core/test_checks.py | 15 +- tests/test_core/test_ffi.py | 87 +++++------ tests/test_kernel32/test_process.py | 4 +- 12 files changed, 147 insertions(+), 157 deletions(-) create mode 100644 pywincffi/core/cdefs/headers/constants.h create mode 100644 pywincffi/core/cdefs/headers/functions.h rename pywincffi/{headers/kernel32.h => core/cdefs/headers/structs.h} (59%) create mode 100644 pywincffi/core/cdefs/sources/main.c diff --git a/pywincffi/core/cdefs/headers/constants.h b/pywincffi/core/cdefs/headers/constants.h new file mode 100644 index 0000000..33a1943 --- /dev/null +++ b/pywincffi/core/cdefs/headers/constants.h @@ -0,0 +1,2 @@ +#define MAX_PATH ... + diff --git a/pywincffi/core/cdefs/headers/functions.h b/pywincffi/core/cdefs/headers/functions.h new file mode 100644 index 0000000..8a49bfb --- /dev/null +++ b/pywincffi/core/cdefs/headers/functions.h @@ -0,0 +1,8 @@ +// Processes +HANDLE OpenProcess(DWORD, BOOL, DWORD); + +// Files +BOOL CreatePipe(PHANDLE, PHANDLE, LPSECURITY_ATTRIBUTES, DWORD); +BOOL CloseHandle(HANDLE); +BOOL WriteFile(HANDLE, LPCVOID, DWORD, LPDWORD, LPOVERLAPPED); +BOOL ReadFile(HANDLE, LPVOID, DWORD, LPDWORD, LPOVERLAPPED); diff --git a/pywincffi/headers/kernel32.h b/pywincffi/core/cdefs/headers/structs.h similarity index 59% rename from pywincffi/headers/kernel32.h rename to pywincffi/core/cdefs/headers/structs.h index b3fe583..e99ef60 100644 --- a/pywincffi/headers/kernel32.h +++ b/pywincffi/core/cdefs/headers/structs.h @@ -17,11 +17,3 @@ typedef struct _OVERLAPPED { HANDLE hEvent; } OVERLAPPED, *LPOVERLAPPED; -// Processes -BOOL OpenProcess(DWORD, BOOL, DWORD); - -// Files -BOOL CreatePipe(PHANDLE, PHANDLE, LPSECURITY_ATTRIBUTES, DWORD); -BOOL CloseHandle(HANDLE); -BOOL WriteFile(HANDLE, LPCVOID, DWORD, LPDWORD, LPOVERLAPPED); -BOOL ReadFile(HANDLE, LPCVOID, DWORD, LPDWORD, LPOVERLAPPED); diff --git a/pywincffi/core/cdefs/sources/main.c b/pywincffi/core/cdefs/sources/main.c new file mode 100644 index 0000000..776a87c --- /dev/null +++ b/pywincffi/core/cdefs/sources/main.c @@ -0,0 +1 @@ +#include diff --git a/pywincffi/core/checks.py b/pywincffi/core/checks.py index 09deda8..fc21bd1 100644 --- a/pywincffi/core/checks.py +++ b/pywincffi/core/checks.py @@ -3,7 +3,7 @@ from six import string_types -from pywincffi.core.ffi import ffi +from pywincffi.core.ffi import Library from pywincffi.core.logger import get_logger from pywincffi.exceptions import WindowsAPIError, InputError @@ -55,6 +55,8 @@ def error_check(api_function, code=None, expected=0): :raises pywincffi.exceptions.WindowsAPIError: Raised if we receive an unexpected result from a Windows API call """ + ffi, _ = Library.load() + if code is None: result, api_error_message = ffi.getwinerror() else: @@ -100,6 +102,7 @@ def input_check(name, value, allowed_types): Raised if ``value`` is not an instance of ``allowed_types`` """ assert isinstance(name, string_types) + ffi, _ = Library.load() logger.debug( "input_check(name=%r, value=%r, allowed_types=%r", diff --git a/pywincffi/core/ffi.py b/pywincffi/core/ffi.py index 2d6a552..e626863 100644 --- a/pywincffi/core/ffi.py +++ b/pywincffi/core/ffi.py @@ -7,113 +7,92 @@ """ from errno import ENOENT +from os.path import join from cffi import FFI from pkg_resources import resource_filename from pywincffi.core.logger import get_logger -from pywincffi.exceptions import HeaderNotFoundError +from pywincffi.exceptions import ResourceNotFoundError logger = get_logger("core.ffi") -class Loader(object): +class Library(object): """ A wrapper around :meth:`FFI.cdef` and :meth:`FFI.dlopen` that also performs caching. Without caching a library could be loaded multiple times causing an exception to be raised when a function is redefined. """ - CACHE = {} + CACHE = None + VERIFY_LIBRARIES = ("kernel32", ) + HEADERS_ROOT = resource_filename( + "pywincffi", join("core", "cdefs", "headers")) + SOURCES_ROOT = resource_filename( + "pywincffi", join("core", "cdefs", "sources")) + HEADERS = ( + join(HEADERS_ROOT, "structs.h"), + join(HEADERS_ROOT, "constants.h"), + join(HEADERS_ROOT, "functions.h") + ) + SOURCES = ( + join(SOURCES_ROOT, "main.c"), + ) @staticmethod - def ffi(): - """Returns an instance of :class:`FFI`""" - instance = FFI() - instance.set_unicode(True) - return instance - - @staticmethod - def _load_header(header_name): + def _load_files(filepaths): """ - For the given ``header_name`` locate the path on disk and - attempt to load it. This function will search the `headers` - directory under the root of the pywincffi project. + Given a tuple of file paths open each file and return a string + containing the contents of all the files loaded. - :param str header_name: - The name of the header to load, kernel32.h for example. This - will be passed along to :func:`resource_filename` to construct - the path. + :param tuple filepaths: + A tuple of file paths to load - :returns: - Returns the loaded header file or ``None`` if no header for - for the given name could be found. - """ - header_name = "headers/" + header_name - logger.debug("Searching for header %r", header_name) - path = resource_filename("pywincffi", header_name) - - logger.debug("Loading header for %s from %s", header_name, path) - - try: - with open(path, "rb") as stream: - header_data = stream.read().decode() - except (OSError, IOError, WindowsError) as error: - if error.errno == ENOENT: - logger.error("No such header %s", path) - return None - raise - return header_data + :raises ResourceNotFoundError: + Raised if one of the paths in ``filepaths`` could not + be loaded. - @classmethod - def load(cls, library_name, ffi_instance=None, header=None): - """ - Loads the given ``library_name`` and returns a bound instance of - :class:`FFI`. The return value may either a new instance or - a cached result we've seen before. - - :param str library_name: - The name of the library to load (ex. kernel32) - - :keyword cffi.api.FFI ffi_instance: - The optional instance of :class:`FFI` to bind to. If no value - is provided we'll use the global ``pywincffi.core.ffi.ffi` - instance instead. - - :keyword str header: - An optional header to provide the definitions for the - given ``library_name``. This keyword is mainly used - when testing and when not provided we use :meth:`_find_header` - to locate the path to the header file for the given - library_name. + :return: + Returns a string containing all the loaded paths. """ - if ffi_instance is None: - ffi_instance = ffi + output = "" - cached = cls.CACHE.get(ffi_instance, None) - if cached is not None and library_name in cached: - logger.debug( - "Returning cached library %s for %s", - library_name, ffi_instance - ) - return cached[library_name] + for path in filepaths: + logger.debug("Reading %s", path) + try: + with open(path, "r") as open_file: + output += open_file.read() - logger.debug("Loading %s onto %s", library_name, ffi_instance) + except (OSError, IOError, WindowsError) as error: + if error.errno == ENOENT: + raise ResourceNotFoundError("Failed to locate %s" % path) - if header is None: - header = cls._load_header(library_name + ".h") + return output + + @classmethod + def load(cls, cached=True): + """ + Main function which loads up an instance of the library. + + :returns: + Returns a tuple of :class:`FFI` and the loaded library. + """ + if cached and cls.CACHE is not None: + return cls.CACHE - if header is None: - raise HeaderNotFoundError( - "Failed to locate header for %s" % library_name - ) + # Read in headers + csource = cls._load_files(cls.HEADERS) + source = cls._load_files(cls.SOURCES) - ffi_instance.cdef(header) - library = ffi_instance.dlopen(library_name) - instance_cache = cls.CACHE.setdefault(ffi_instance, {}) - instance_cache[library_name] = library - return library + # Compile + # TODO: this should do something slightly different if pre-compiled + ffi = FFI() + ffi.set_unicode(True) + ffi.cdef(csource) + library = ffi.verify(source, libraries=cls.VERIFY_LIBRARIES) + cls.CACHE = (ffi, library) -ffi = Loader.ffi() + return cls.CACHE diff --git a/pywincffi/exceptions.py b/pywincffi/exceptions.py index 6b2c901..4a30f0c 100644 --- a/pywincffi/exceptions.py +++ b/pywincffi/exceptions.py @@ -57,5 +57,5 @@ def __init__(self, api_function, api_error_message, code, expected_code): super(WindowsAPIError, self).__init__(self.message) -class HeaderNotFoundError(PyWinCFFIError): - """Raised when we fail to locate a specific header file""" +class ResourceNotFoundError(PyWinCFFIError): + """Raised when we fail to locate a specific resource""" diff --git a/pywincffi/kernel32/io.py b/pywincffi/kernel32/io.py index 543560f..98ae78f 100644 --- a/pywincffi/kernel32/io.py +++ b/pywincffi/kernel32/io.py @@ -7,17 +7,16 @@ from six import integer_types -from pywincffi.core.ffi import Loader, ffi +from pywincffi.core.ffi import Library from pywincffi.core.checks import Enums, input_check, error_check, NoneType -kernel32 = Loader.load("kernel32") - def CreatePipe(nSize=0, lpPipeAttributes=None): """ Creates an anonymous pipe and returns the read and write handles. - >>> from pywincffi.core.ffi import ffi + >>> from pywincffi.core.ffi import Library + >>> ffi, library = Library.load() >>> lpPipeAttributes = ffi.new( ... "SECURITY_ATTRIBUTES[1]", [{ ... "nLength": ffi.sizeof("SECURITY_ATTRIBUTES"), @@ -49,6 +48,7 @@ def CreatePipe(nSize=0, lpPipeAttributes=None): """ input_check("nSize", nSize, int) input_check("lpPipeAttributes", lpPipeAttributes, (NoneType, dict)) + ffi, library = Library.load() hReadPipe = ffi.new("PHANDLE") hWritePipe = ffi.new("PHANDLE") @@ -56,7 +56,7 @@ def CreatePipe(nSize=0, lpPipeAttributes=None): if lpPipeAttributes is None: lpPipeAttributes = ffi.NULL - code = kernel32.CreatePipe(hReadPipe, hWritePipe, lpPipeAttributes, nSize) + code = library.CreatePipe(hReadPipe, hWritePipe, lpPipeAttributes, nSize) error_check("CreatePipe", code=code, expected=Enums.NON_ZERO) return hReadPipe[0], hWritePipe[0] @@ -74,8 +74,9 @@ def CloseHandle(hObject): https://msdn.microsoft.com/en-us/library/windows/desktop/ms724211 """ input_check("hObject", hObject, Enums.HANDLE) + ffi, library = Library.load() - code = kernel32.CloseHandle(hObject) + code = library.CloseHandle(hObject) error_check("CloseHandle", code=code, expected=Enums.NON_ZERO) @@ -112,6 +113,8 @@ def WriteFile(hFile, lpBuffer, lpOverlapped=None): https://msdn.microsoft.com/en-us/library/windows/desktop/aa365747 """ + ffi, library = Library.load() + if lpOverlapped is None: lpOverlapped = ffi.NULL @@ -124,7 +127,7 @@ def WriteFile(hFile, lpBuffer, lpOverlapped=None): lpBuffer = ffi.new("wchar_t[%d]" % nNumberOfBytesToWrite, lpBuffer) bytes_written = ffi.new("LPDWORD") - code = kernel32.WriteFile( + code = library.WriteFile( hFile, lpBuffer, ffi.sizeof(lpBuffer), bytes_written, lpOverlapped) error_check("WriteFile", code=code, expected=Enums.NON_ZERO) @@ -145,7 +148,8 @@ def ReadFile(hFile, nNumberOfBytesToRead, lpOverlapped=None): documentation for intended usage and below for an example of this struct. - >>> from pywincffi.core.ffi import ffi + >>> from pywincffi.core.ffi import Library + >>> ffi, library = Library.load() >>> reader = None # normally, this would be a handle >>> struct = ffi.new( ... "OVERLAPPED[1]", [{ @@ -161,6 +165,8 @@ def ReadFile(hFile, nNumberOfBytesToRead, lpOverlapped=None): https://msdn.microsoft.com/en-us/library/windows/desktop/aa365467 """ + ffi, library = Library.load() + if lpOverlapped is None: lpOverlapped = ffi.NULL @@ -170,7 +176,7 @@ def ReadFile(hFile, nNumberOfBytesToRead, lpOverlapped=None): lpBuffer = ffi.new("wchar_t[%d]" % nNumberOfBytesToRead) bytes_read = ffi.new("LPDWORD") - code = kernel32.ReadFile( + code = library.ReadFile( hFile, lpBuffer, ffi.sizeof(lpBuffer), bytes_read, lpOverlapped ) error_check("ReadFile", code=code, expected=Enums.NON_ZERO) diff --git a/pywincffi/kernel32/process.py b/pywincffi/kernel32/process.py index 0bd6a9b..95d40d9 100644 --- a/pywincffi/kernel32/process.py +++ b/pywincffi/kernel32/process.py @@ -17,11 +17,9 @@ import six -from pywincffi.core.ffi import Loader, ffi +from pywincffi.core.ffi import Library from pywincffi.core.checks import input_check, error_check -kernel32 = Loader.load("kernel32") - PROCESS_CREATE_PROCESS = 0x0080 PROCESS_CREATE_THREAD = 0x0002 PROCESS_DUP_HANDLE = 0x0040 @@ -62,8 +60,9 @@ def OpenProcess(dwDesiredAccess, bInheritHandle, dwProcessId): input_check("dwDesiredAccess", dwDesiredAccess, six.integer_types) input_check("bInheritHandle", bInheritHandle, bool) input_check("dwProcessId", dwProcessId, six.integer_types) + ffi, library = Library.load() - handle_id = kernel32.OpenProcess( + handle_id = library.OpenProcess( ffi.cast("DWORD", dwDesiredAccess), ffi.cast("BOOL", bInheritHandle), ffi.cast("DWORD", dwProcessId) diff --git a/tests/test_core/test_checks.py b/tests/test_core/test_checks.py index c5bbd84..2e9d415 100644 --- a/tests/test_core/test_checks.py +++ b/tests/test_core/test_checks.py @@ -5,7 +5,7 @@ from pywincffi.core.checks import ( INPUT_CHECK_MAPPINGS, CheckMapping, Enums, input_check, error_check) -from pywincffi.core.ffi import ffi +from pywincffi.core.ffi import Library from pywincffi.core.testutil import TestCase from pywincffi.exceptions import WindowsAPIError, InputError @@ -15,19 +15,27 @@ class TestCheckErrorCode(TestCase): Tests for :func:`pywincffi.core.ffi.check_error_code` """ def test_default_code_does_match_expected(self): + ffi, _ = Library.load() + with patch.object(ffi, "getwinerror", return_value=(0, "GTG")): error_check("Foobar") def test_default_code_does_not_match_expected(self): + ffi, _ = Library.load() + with patch.object(ffi, "getwinerror", return_value=(0, "NGTG")): with self.assertRaises(WindowsAPIError): error_check("Foobar", expected=2) def test_non_zero(self): + ffi, _ = Library.load() + with patch.object(ffi, "getwinerror", return_value=(1, "NGTG")): error_check("Foobar", expected=Enums.NON_ZERO) def test_non_zero_success(self): + ffi, _ = Library.load() + with patch.object(ffi, "getwinerror", return_value=(0, "NGTG")): error_check("Foobar", code=1, expected=Enums.NON_ZERO) @@ -45,12 +53,14 @@ def test_handle_type_failure(self): input_check("", None, Enums.HANDLE) def test_not_a_handle(self): + ffi, _ = Library.load() typeof = Mock(kind="", cname="") with patch.object(ffi, "typeof", return_value=typeof): with self.assertRaises(InputError): input_check("", None, Enums.HANDLE) def test_handle_type_success(self): + ffi, _ = Library.load() typeof = Mock(kind="pointer", cname="void *") with patch.object(ffi, "typeof", return_value=typeof): # The value does not matter here since we're @@ -78,11 +88,13 @@ def test_nullable(self): # If something is nullable but kind/cname don't match it # should not fail the input check + ffi, _ = Library.load() typeof = Mock(kind="pointer", cname="void *") with patch.object(ffi, "typeof", return_value=typeof): input_check("", ffi.NULL, "mapping") def test_not_nullable(self): + ffi, _ = Library.load() INPUT_CHECK_MAPPINGS.update( mapping=CheckMapping( kind="foo", @@ -108,6 +120,7 @@ def test_kind_and_cname(self): # If something is nullable but kind/cname don't match it # should not fail the input check + ffi, _ = Library.load() typeof = Mock(kind="foo", cname="bar") with patch.object(ffi, "typeof", return_value=typeof): input_check("", "", "mapping") diff --git a/tests/test_core/test_ffi.py b/tests/test_core/test_ffi.py index 5f39941..86b6eff 100644 --- a/tests/test_core/test_ffi.py +++ b/tests/test_core/test_ffi.py @@ -10,9 +10,9 @@ from six.moves import builtins import pywincffi -from pywincffi.core.ffi import Loader, ffi +from pywincffi.core.ffi import Library from pywincffi.core.testutil import TestCase -from pywincffi.exceptions import HeaderNotFoundError +from pywincffi.exceptions import ResourceNotFoundError class TestFFI(TestCase): @@ -20,34 +20,33 @@ class TestFFI(TestCase): Tests the ``pywinffi.core.ffi.ffi`` global. """ def test_unicode(self): + ffi, _ = Library.load() self.assertTrue(ffi._windows_unicode) def test_instance(self): + ffi, _ = Library.load() self.assertIsInstance(ffi, FFI) -class TestLibraryLoadHeader(TestCase): +class TestSourcePaths(TestCase): """ - Tests for ``pywincffi.core.ffi.Library._load_header`` + Tests for ``pywincffi.core.ffi.Library.[HEADERS|SOURCES]`` """ - def test_loads_header_from_correct_path(self): - path = join(dirname(pywincffi.__file__), "headers", "kernel32.h") - with open(path, "rb") as stream: - header = stream.read().decode() - - self.assertEqual(Loader._load_header("kernel32.h"), header) - - def test_returns_none_when_header_not_found(self): - self.assertIsNone(Loader._load_header("foobar")) - - def test_raises_non_not_found_errors(self): - def side_effect(*args, **kwargs): - raise OSError(42, "fail") - - with patch.object(builtins, "open", side_effect=side_effect): - with self.assertRaises(OSError) as raised_error: - Loader._load_header("kernel32") - self.assertEqual(raised_error.exception.errno, 42) + def test_sources_exist(self): + for path in Library.SOURCES: + try: + with open(path, "r"): + pass + except (OSError, IOError, WindowsError) as error: + self.fail("Failed to load %s: %s" % (path, error)) + + def test_headers_exist(self): + for path in Library.HEADERS: + try: + with open(path, "r"): + pass + except (OSError, IOError, WindowsError) as error: + self.fail("Failed to load %s: %s" % (path, error)) class TestLibraryLoad(TestCase): @@ -55,48 +54,34 @@ class TestLibraryLoad(TestCase): Tests for ``pywincffi.core.ffi.Library.load`` """ def setUp(self): - self._cache = Loader.CACHE.copy() - Loader.CACHE.clear() + self._cache = Library.CACHE + Library.CACHE = None def tearDown(self): - Loader.CACHE.clear() - Loader.CACHE.update(self._cache) - - def test_returns_cached(self): - ffi_instance = Loader.ffi() - Loader.CACHE[ffi_instance] = {"foo_library1": True} - self.assertIs( - Loader.load("foo_library1", ffi_instance=ffi_instance), - True - ) - - def test_returns_cached_default_ffi_instance(self): - Loader.CACHE[ffi] = {"foo_library2": False} - self.assertIs(Loader.load("foo_library2"), False) + Library.CACHE = self._cache def test_header_not_found(self): - with patch.object(Loader, "_load_header", return_value=None): - with self.assertRaises(HeaderNotFoundError): - Loader.load("kernel32") + with patch.object(Library, "HEADERS", ("foobar", )): + with self.assertRaises(ResourceNotFoundError): + Library.load() def test_loads_library(self): fake_header = dedent(""" #define HELLO_WORLD 42 """) - with patch.object(Loader, "_load_header", return_value=fake_header): - library = Loader.load("kernel32") + with patch.object(Library, "_load_files", return_value=fake_header): + ffi, library = Library.load() self.assertEqual(library.HELLO_WORLD, 42) def test_caches_library(self): + self.assertIsNone(Library.CACHE) + fake_header = dedent(""" #define HELLO_WORLD 42 """) - self.assertNotIn(ffi, Loader.CACHE) - with patch.object(Loader, "_load_header", return_value=fake_header): - library1 = Loader.load("kernel32") - self.assertIn(ffi, Loader.CACHE) - library2 = Loader.load("kernel32") - - self.assertIs(Loader.CACHE[ffi]["kernel32"], library1) - self.assertIs(Loader.CACHE[ffi]["kernel32"], library2) + with patch.object(Library, "_load_files", return_value=fake_header): + ffi1, lib1 = Library.load() + ffi2, lib2 = Library.load() + self.assertIs(ffi1, ffi2) + self.assertIs(lib1, lib2) diff --git a/tests/test_kernel32/test_process.py b/tests/test_kernel32/test_process.py index 4a9bdb3..9e09f8a 100644 --- a/tests/test_kernel32/test_process.py +++ b/tests/test_kernel32/test_process.py @@ -1,6 +1,6 @@ import os -from pywincffi.core.ffi import ffi +from pywincffi.core.ffi import Library from pywincffi.core.testutil import TestCase from pywincffi.exceptions import WindowsAPIError from pywincffi.kernel32.process import ( @@ -12,6 +12,8 @@ class TestOpenProcess(TestCase): Tests for :func:`pywincffi.kernel32.OpenProcess` """ def test_returns_handle(self): + ffi, library = Library.load() + handle = OpenProcess( PROCESS_QUERY_LIMITED_INFORMATION, False, From 1b90e9c81ebc1a8571b332f5dcf1a0df16a8eae1 Mon Sep 17 00:00:00 2001 From: Oliver Palmer Date: Sun, 26 Jul 2015 13:49:54 -0400 Subject: [PATCH 3/6] whitespace fix --- pywincffi/exceptions.py | 1 + 1 file changed, 1 insertion(+) diff --git a/pywincffi/exceptions.py b/pywincffi/exceptions.py index 4a30f0c..2fc5e8e 100644 --- a/pywincffi/exceptions.py +++ b/pywincffi/exceptions.py @@ -5,6 +5,7 @@ Custom exceptions that ``pywincffi`` can throw. """ + class PyWinCFFIError(Exception): """ The base class for all custom exceptions that pywincffi can throw. From 38a569bc63a1489728fb65387d9c0782799104e3 Mon Sep 17 00:00:00 2001 From: Oliver Palmer Date: Sun, 26 Jul 2015 13:50:15 -0400 Subject: [PATCH 4/6] fix order of header loads --- pywincffi/core/ffi.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pywincffi/core/ffi.py b/pywincffi/core/ffi.py index e626863..1fdd771 100644 --- a/pywincffi/core/ffi.py +++ b/pywincffi/core/ffi.py @@ -33,8 +33,8 @@ class Library(object): SOURCES_ROOT = resource_filename( "pywincffi", join("core", "cdefs", "sources")) HEADERS = ( - join(HEADERS_ROOT, "structs.h"), join(HEADERS_ROOT, "constants.h"), + join(HEADERS_ROOT, "structs.h"), join(HEADERS_ROOT, "functions.h") ) SOURCES = ( From 70b88c4751eb965e8dbcb5132eba64e6654593e1 Mon Sep 17 00:00:00 2001 From: Oliver Palmer Date: Sun, 26 Jul 2015 13:53:28 -0400 Subject: [PATCH 5/6] move process.py constants into constants.h --- pywincffi/core/cdefs/headers/constants.h | 19 ++++++++++++++++++- pywincffi/kernel32/process.py | 14 +------------- tests/test_kernel32/test_process.py | 6 +++--- 3 files changed, 22 insertions(+), 17 deletions(-) diff --git a/pywincffi/core/cdefs/headers/constants.h b/pywincffi/core/cdefs/headers/constants.h index 33a1943..86f1cc6 100644 --- a/pywincffi/core/cdefs/headers/constants.h +++ b/pywincffi/core/cdefs/headers/constants.h @@ -1,2 +1,19 @@ -#define MAX_PATH ... +// +// This file contains constants which can be used either internally or +// by users of pywincffi. +// +#define MAX_PATH ... +#define PROCESS_CREATE_PROCESS ... +#define PROCESS_CREATE_THREAD ... +#define PROCESS_DUP_HANDLE ... +#define PROCESS_QUERY_INFORMATION ... +#define PROCESS_QUERY_LIMITED_INFORMATION ... +#define PROCESS_SET_INFORMATION ... +#define PROCESS_SET_QUOTA ... +#define PROCESS_SUSPEND_RESUME ... +#define PROCESS_TERMINATE ... +#define PROCESS_VM_OPERATION ... +#define PROCESS_VM_READ ... +#define PROCESS_VM_WRITE ... +#define SYNCHRONIZE ... diff --git a/pywincffi/kernel32/process.py b/pywincffi/kernel32/process.py index 95d40d9..3405675 100644 --- a/pywincffi/kernel32/process.py +++ b/pywincffi/kernel32/process.py @@ -20,19 +20,7 @@ from pywincffi.core.ffi import Library from pywincffi.core.checks import input_check, error_check -PROCESS_CREATE_PROCESS = 0x0080 -PROCESS_CREATE_THREAD = 0x0002 -PROCESS_DUP_HANDLE = 0x0040 -PROCESS_QUERY_INFORMATION = 0x0400 -PROCESS_QUERY_LIMITED_INFORMATION = 0x1000 -PROCESS_SET_INFORMATION = 0x0200 -PROCESS_SET_QUOTA = 0x0100 -PROCESS_SUSPEND_RESUME = 0x0800 -PROCESS_TERMINATE = 0x0001 -PROCESS_VM_OPERATION = 0x0008 -PROCESS_VM_READ = 0x0008 -PROCESS_VM_WRITE = 0x0020 -SYNCHRONIZE = 0x00100000 + def OpenProcess(dwDesiredAccess, bInheritHandle, dwProcessId): diff --git a/tests/test_kernel32/test_process.py b/tests/test_kernel32/test_process.py index 9e09f8a..bf1078f 100644 --- a/tests/test_kernel32/test_process.py +++ b/tests/test_kernel32/test_process.py @@ -3,8 +3,7 @@ from pywincffi.core.ffi import Library from pywincffi.core.testutil import TestCase from pywincffi.exceptions import WindowsAPIError -from pywincffi.kernel32.process import ( - PROCESS_QUERY_LIMITED_INFORMATION, OpenProcess) +from pywincffi.kernel32.process import OpenProcess class TestOpenProcess(TestCase): @@ -15,10 +14,11 @@ def test_returns_handle(self): ffi, library = Library.load() handle = OpenProcess( - PROCESS_QUERY_LIMITED_INFORMATION, + library.PROCESS_QUERY_LIMITED_INFORMATION, False, os.getpid() ) + typeof = ffi.typeof(handle) self.assertEqual(typeof.kind, "pointer") self.assertEqual(typeof.cname, "void *") From f1506c434960d0cd9234af6b2fec032254183bc5 Mon Sep 17 00:00:00 2001 From: Oliver Palmer Date: Sun, 26 Jul 2015 13:56:05 -0400 Subject: [PATCH 6/6] whitespace fix --- pywincffi/kernel32/process.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/pywincffi/kernel32/process.py b/pywincffi/kernel32/process.py index 3405675..bf4c146 100644 --- a/pywincffi/kernel32/process.py +++ b/pywincffi/kernel32/process.py @@ -21,8 +21,6 @@ from pywincffi.core.checks import input_check, error_check - - def OpenProcess(dwDesiredAccess, bInheritHandle, dwProcessId): """ Opens an existing local process object.