-
Notifications
You must be signed in to change notification settings - Fork 0
Feat/ffi provider mike #1
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: feat/ffi-provider
Are you sure you want to change the base?
Changes from 6 commits
903371b
0089937
da49cd7
34f67a7
afd0931
6c95248
de93a1f
be6c971
4b239e4
e7d686a
050ee0e
7dc8864
e370786
7d18a2f
214c47d
a84f856
2d30706
c6ded0a
e9c1bb0
358748f
070f905
cf0bfee
f2230b6
43fedd1
3471c12
11228ed
69233a6
3486281
283dcef
411ad66
bd809ff
08f0dc0
216229e
d773d85
a2ce9f4
65d99d3
ccb2641
273e1f3
55f4e55
8701015
087a655
f3601fd
741cf99
5703160
b18128a
5ebf947
53819eb
3882837
3c82f1f
778ef4c
f91e9b1
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -2,3 +2,4 @@ | |
| ignore = E226,E302,E41,W503 | ||
| max-line-length = 160 | ||
| max-complexity = 10 | ||
| exclude = venv, .tox | ||
This file was deleted.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,58 @@ | ||
| """Wrapper to pact reference dynamic libraries using FFI.""" | ||
|
|
||
| import platform | ||
|
|
||
| from cffi import FFI | ||
|
|
||
|
|
||
| class PactFFI(object): | ||
| """This interfaces with the Rust crate `pact_ffi`_, which exposes the Pact | ||
| API via a C Foreign Function Interface. In the case of python, the library | ||
| is then accessed using `CFFI`_. | ||
| This class will implement the shared library loading, along with a wrapper | ||
| for the functions provided by the base crate. For each of the Rust modules | ||
| exposed, a corresponding python class will extend this base class, and | ||
| provide the wrapper for the functions the module provides. | ||
| .. _pact_ffi: | ||
| https://docs.rs/pact_ffi/0.0.1/pact_ffi/index.html | ||
| .. _CFFI: | ||
| https://cffi.readthedocs.io/en/latest/ | ||
| """ | ||
|
|
||
| def version(self) -> str: | ||
| """Get the current library version. | ||
| :return: pact_ffi library version, for example "0.0.1" | ||
| """ | ||
| ffi = FFI() | ||
| ffi.cdef( | ||
| """ | ||
| char *pactffi_version(void); | ||
| """ | ||
| ) | ||
| lib = self._load_ffi_library(ffi) | ||
| result = lib.pactffi_version() | ||
|
|
||
| return ffi.string(result).decode('utf-8') | ||
|
|
||
| def _load_ffi_library(self, ffi): | ||
| """Load the appropriate library for the current platform.""" | ||
| target_platform = platform.platform().lower() | ||
|
|
||
| if target_platform in ['darwin', 'macos']: | ||
| libname = "pact/bin/libpact_ffi-osx-x86_64.dylib" | ||
| elif 'linux' in target_platform: | ||
| libname = "pact/bin/libpact_ffi-linux-x86_64.so" | ||
| elif 'windows' in target_platform: | ||
| libname = "pact/bin/pact_ffi-windows-x86_64.dll" | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This had the osx lib, think this is correct but no Windows to check it actually works |
||
| else: | ||
| msg = ( | ||
| f'Unfortunately, {platform.platform()} is not a supported ' | ||
| f'platform. Only Linux, Windows, and OSX are currently ' | ||
| f'supported.' | ||
| ) | ||
| raise Exception(msg) | ||
|
|
||
| return ffi.dlopen(libname) | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,79 @@ | ||
| """Wrapper to pact reference dynamic libraries using FFI.""" | ||
| import os | ||
| import tempfile | ||
| from enum import Enum, unique | ||
| from typing import NamedTuple, List | ||
|
|
||
| from cffi import FFI | ||
|
|
||
| from pact.ffi.pact_ffi import PactFFI | ||
|
|
||
|
|
||
| @unique | ||
| class VerifyStatus(Enum): | ||
| SUCCESS = 0 # Operation succeeded | ||
| VERIFIER_FAILED = 1 # The verification process failed, see output for errors | ||
| NULL_POINTER = 2 # A null pointer was received | ||
| PANIC = 3 # The method panicked | ||
| INVALID_ARGS = 4 # Invalid arguments were provided to the verification process | ||
|
|
||
|
|
||
| @unique | ||
| class LogToBufferStatus(Enum): | ||
| SUCCESS = 0 # Operation succeeded | ||
| CANT_SET_LOGGER = -1 # Can't set the logger | ||
| NO_LOGGER = -2 # No logger has been initialized | ||
| SPECIFIER_NOT_UTF8 = -3 # The sink specifier was not UTF-8 encoded | ||
| UNKNOWN_SINK_TYPE = -4 # The sink type specified is not a known type | ||
| MISSING_FILE_PATH = -5 # No file path was specified in the sink specification | ||
| CANT_OPEN_SINK_TO_FILE = -6 # Opening a sink to the given file failed | ||
| CANT_CONSTRUCT_SINK = -7 # Can't construct sink | ||
|
|
||
|
|
||
| class VerifyResult(NamedTuple): | ||
| return_code: VerifyStatus | ||
| logs: List[str] | ||
|
|
||
|
|
||
| class Verifier(PactFFI): | ||
| """A Pact Verifier Wrapper. | ||
|
|
||
| This interfaces with the Rust FFI crate pact_ffi, specifically the | ||
| `verifier`_ module. | ||
|
|
||
| .. _verifier: | ||
| https://docs.rs/pact_ffi/0.0.1/pact_ffi/verifier/index.html | ||
| """ | ||
|
|
||
| def verify(self, args=None) -> VerifyResult: | ||
| """Call verify method.""" | ||
| ffi = FFI() | ||
| ffi.cdef( | ||
| """ | ||
| int pactffi_verify(char *); | ||
| int pactffi_log_to_file(char *); | ||
| """ | ||
| ) | ||
| lib = self._load_ffi_library(ffi) | ||
|
|
||
| if args: | ||
| c_args = ffi.new('char[]', bytes(args, 'utf-8')) | ||
| else: | ||
| c_args = ffi.NULL | ||
|
|
||
| # This fails if called a second time after the library has been loaded | ||
|
||
| # ..but still seems to work | ||
| # result = lib.pactffi_log_to_buffer() | ||
| # assert LogToBufferStatus(result) == LogToBufferStatus.SUCCESS | ||
| # Additionally, when reading from the buffer it seems to come back empty | ||
| # when running normally. Storing to a temporary file, at least for now. | ||
| with tempfile.TemporaryDirectory() as td: | ||
| output = os.path.join(td, 'output') | ||
| output_c = ffi.new('char[]', bytes(output, 'utf-8')) | ||
| lib.pactffi_log_to_file(output_c) | ||
|
|
||
| result = lib.pactffi_verify(c_args) | ||
|
|
||
| lines = open(output).readlines() | ||
|
|
||
| return VerifyResult(result, lines) | ||
This file was deleted.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,17 @@ | ||
| from unittest import TestCase | ||
|
|
||
| from pact.ffi.verifier import Verifier, VerifyStatus | ||
|
|
||
|
|
||
| class VerifierTestCase(TestCase): | ||
| def test_version(self): | ||
| assert Verifier().version() == "0.0.1" | ||
|
|
||
| def test_verify_no_args(self): | ||
| result = Verifier().verify(args=None) | ||
| self.assertEqual(VerifyStatus(result.return_code), VerifyStatus.NULL_POINTER) | ||
|
|
||
| def test_verify_invalid_args(self): | ||
| result = Verifier().verify(args="Your argument is invalid") | ||
| assert VerifyStatus(result.return_code) == VerifyStatus.INVALID_ARGS | ||
| self.assertIn('UnknownArgument', '\n'.join(result.logs)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Matching up with the list in tox.ini