- 
                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