From 0610b04a90008a722d540aa923fb333dd4d7577a Mon Sep 17 00:00:00 2001 From: Andrew Kent Date: Thu, 6 May 2021 17:33:26 -0700 Subject: [PATCH] feat(rpc): make python client compat with python 3.7 --- .github/workflows/ci.yml | 13 ++- cryptol-remote-api/python/README.md | 4 +- .../python/cryptol/bitvector.py | 10 ++- .../python/cryptol/connection.py | 37 ++++++--- cryptol-remote-api/python/poetry.lock | 83 +++++++++---------- cryptol-remote-api/python/pyproject.toml | 4 +- cryptol-remote-api/python/requirements.txt | 81 ------------------ cryptol-remote-api/python/setup.py | 44 ---------- .../python/tests/cryptol/test_EvenMansour.py | 8 +- .../python/tests/cryptol/test_cryptol_api.py | 46 +++++----- .../tests/cryptol/test_low_level_ops.py | 25 ++++-- .../python/tests/cryptol_eval/test_basics.py | 15 ++-- cryptol-remote-api/run_rpc_tests.sh | 24 ++---- cryptol-remote-api/test-cryptol-remote-api.py | 2 +- cryptol-remote-api/test.Dockerfile | 11 +-- cryptol-remote-api/test_docker.sh | 6 +- 16 files changed, 163 insertions(+), 250 deletions(-) delete mode 100644 cryptol-remote-api/python/requirements.txt delete mode 100644 cryptol-remote-api/python/setup.py diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 8ce62a4db..2e5ba3f74 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -86,7 +86,11 @@ jobs: - uses: actions/setup-python@v2 with: - python-version: '3.x' + python-version: '3.7' + + - uses: abatilo/actions-poetry@v2.0.0 + with: + poetry-version: 1.1.6 - uses: actions/setup-haskell@v1 id: setup-haskell @@ -300,7 +304,12 @@ jobs: - if: matrix.image == 'ghcr.io/galoisinc/cryptol-remote-api' uses: actions/setup-python@v2 with: - python-version: '3.x' + python-version: '3.7' + + - if: matrix.image == 'ghcr.io/galoisinc/cryptol-remote-api' + uses: abatilo/actions-poetry@v2.0.0 + with: + poetry-version: 1.1.6 - if: matrix.image == 'ghcr.io/galoisinc/cryptol-remote-api' name: Test cryptol-remote-api diff --git a/cryptol-remote-api/python/README.md b/cryptol-remote-api/python/README.md index 8af5045fd..5ab0c0b49 100644 --- a/cryptol-remote-api/python/README.md +++ b/cryptol-remote-api/python/README.md @@ -38,7 +38,7 @@ $ docker run --name=cryptol-remote-api -d \ ghcr.io/galoisinc/cryptol-remote-api:nightly-portable $ export CRYPTOL_SERVER_URL="http://localhost:8080/" ``` -6. Install the Python client (requires Python v3.8 or newer -- we recommend using [`poetry`](https://python-poetry.org/docs/#installation) to install the package): +6. Install the Python client (requires Python v3.7 or newer -- we recommend using [`poetry`](https://python-poetry.org/docs/#installation) to install the package): ``` $ poetry install ``` @@ -151,7 +151,7 @@ configured properly) should then appear as `cryptol-remote-api` in a user's `PAT # Running Python Cryptol scripts Once the server is setup and any path variables are setup as desired, the -Python (>= v3.8) client can be installed using +Python (>= v3.7) client can be installed using [`poetry`](https://python-poetry.org/docs/#installation) as follows: ``` diff --git a/cryptol-remote-api/python/cryptol/bitvector.py b/cryptol-remote-api/python/cryptol/bitvector.py index 3807bca1d..424645ab6 100644 --- a/cryptol-remote-api/python/cryptol/bitvector.py +++ b/cryptol-remote-api/python/cryptol/bitvector.py @@ -124,7 +124,7 @@ def concat(self, *others : 'BV') -> 'BV': ``others`` in order on the right. """ return reduce(lambda acc, b: acc.__concat_single(b), others, self) - + @staticmethod def join(*bs : 'BV') -> 'BV': """Concatenate the given ``BV``s in order. @@ -178,6 +178,10 @@ def __index__(self) -> int: """Equivalent to ``self.value()``.""" return self.__value + def __int__(self) -> int: + """Equivalent to ``self.value()``.""" + return self.__value + def __len__(self) -> int: """Equivalent to ``self.size()``.""" return self.__size @@ -190,7 +194,7 @@ def __bytes__(self) -> bytes: def split(self, size : int) -> List['BV']: """Split ``self`` into a list of ``BV``s of length ``size``. - + :param size: Size of segments to partition ``self`` into (must evently divide ``self.size()``). """ if not isinstance(size, int) or size <= 0: @@ -209,7 +213,7 @@ def popcount(self) -> int: @staticmethod def from_bytes(bs : bytes, *, size : Optional[int] =None, byteorder : str ='big') -> 'BV': """Convert the given bytes ``bs`` into a ``BV``. - + :param bs: Bytes to convert to a ``BV``. :param size, optional: Desired ``BV``'s size (must be large enough to represent ``bs``). The default (i.e., ``None``) will result in a ``BV`` of size ``len(bs) * 8``. diff --git a/cryptol-remote-api/python/cryptol/connection.py b/cryptol-remote-api/python/cryptol/connection.py index 2ca6563bd..29c174000 100644 --- a/cryptol-remote-api/python/cryptol/connection.py +++ b/cryptol-remote-api/python/cryptol/connection.py @@ -51,14 +51,31 @@ def connect(command : Optional[str]=None, if url is not None: raise ValueError("A Cryptol server URL cannot be specified with a command currently.") c = CryptolConnection(command, cryptol_path) - elif url is not None: + # User-passed url? + if c is None and url is not None: c = CryptolConnection(ServerConnection(HttpProcess(url)), cryptol_path) - elif (command := os.getenv('CRYPTOL_SERVER')) is not None and (command := find_executable(command)) is not None: - c = CryptolConnection(command+" socket", cryptol_path=cryptol_path) - elif (url := os.getenv('CRYPTOL_SERVER_URL')) is not None: - c = CryptolConnection(ServerConnection(HttpProcess(url)), cryptol_path) - elif (command := find_executable('cryptol-remote-api')) is not None: - c = CryptolConnection(command+" socket", cryptol_path=cryptol_path) + # Check `CRYPTOL_SERVER` env var if no connection identified yet + if c is None: + command = os.getenv('CRYPTOL_SERVER') + if command is not None: + command = find_executable(command) + if command is not None: + c = CryptolConnection(command+" socket", cryptol_path=cryptol_path) + # Check `CRYPTOL_SERVER_URL` env var if no connection identified yet + if c is None: + url = os.getenv('CRYPTOL_SERVER_URL') + if url is not None: + c = CryptolConnection(ServerConnection(HttpProcess(url)), cryptol_path) + # Check if `cryptol-remote-api` is in the PATH if no connection identified yet + if c is None: + command = find_executable('cryptol-remote-api') + if command is not None: + c = CryptolConnection(command+" socket", cryptol_path=cryptol_path) + # Raise an error if still no connection identified yet + if c is not None: + if reset_server: + CryptolResetServer(c) + return c else: raise ValueError( """cryptol.connect requires one of the following:", @@ -66,9 +83,7 @@ def connect(command : Optional[str]=None, 2) a URL to connect to a running cryptol server is provided via the `url` keyword argument, 3) the environment variable `CRYPTOL_SERVER` must refer to a valid server executable, or 4) the environment variable `CRYPTOL_SERVER_URL` must refer to the URL of a running cryptol server.""") - if reset_server: - CryptolResetServer(c) - return c + def connect_stdio(command : str, cryptol_path : Optional[str] = None) -> CryptolConnection: @@ -102,7 +117,7 @@ class CryptolConnection: proc: ServerProcess def __init__(self, - command_or_connection : Union[str, ServerConnection, ServerProcess], + command_or_connection : Union[str, ServerConnection, ServerProcess], cryptol_path : Optional[str] = None) -> None: self.most_recent_result = None if isinstance(command_or_connection, ServerProcess): diff --git a/cryptol-remote-api/python/poetry.lock b/cryptol-remote-api/python/poetry.lock index ef8c7e20a..e9f73fca7 100644 --- a/cryptol-remote-api/python/poetry.lock +++ b/cryptol-remote-api/python/poetry.lock @@ -86,7 +86,7 @@ socks = ["PySocks (>=1.5.6,!=1.5.7)", "win-inet-pton"] [[package]] name = "typed-ast" -version = "1.4.2" +version = "1.4.3" description = "a fork of Python 2 and 3 ast modules with type comment support" category = "main" optional = false @@ -94,7 +94,7 @@ python-versions = "*" [[package]] name = "typing-extensions" -version = "3.7.4.3" +version = "3.10.0.0" description = "Backported and Experimental Type Hints for Python 3.5+" category = "main" optional = false @@ -102,21 +102,20 @@ python-versions = "*" [[package]] name = "urllib3" -version = "1.26.4" +version = "1.22" description = "HTTP library with thread-safe connection pooling, file post, and more." category = "main" optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, <4" +python-versions = "*" [package.extras] secure = ["pyOpenSSL (>=0.14)", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "certifi", "ipaddress"] socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"] -brotli = ["brotlipy (>=0.6.0)"] [metadata] lock-version = "1.1" -python-versions = "^3.8" -content-hash = "3a5d27a6af34508eb18ba18ece3a04240e86bba677b152d4a679010a33173935" +python-versions = ">=3.7.0" +content-hash = "41e923e3f418b73af25a9f5557564c8efe79b24ec779a875d706963648aa762d" [metadata.files] argo-client = [ @@ -171,43 +170,43 @@ requests = [ {file = "requests-2.25.1.tar.gz", hash = "sha256:27973dd4a904a4f13b263a19c866c13b92a39ed1c964655f025f3f8d3d75b804"}, ] typed-ast = [ - {file = "typed_ast-1.4.2-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:7703620125e4fb79b64aa52427ec192822e9f45d37d4b6625ab37ef403e1df70"}, - {file = "typed_ast-1.4.2-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:c9aadc4924d4b5799112837b226160428524a9a45f830e0d0f184b19e4090487"}, - {file = "typed_ast-1.4.2-cp35-cp35m-manylinux2014_aarch64.whl", hash = "sha256:9ec45db0c766f196ae629e509f059ff05fc3148f9ffd28f3cfe75d4afb485412"}, - {file = "typed_ast-1.4.2-cp35-cp35m-win32.whl", hash = "sha256:85f95aa97a35bdb2f2f7d10ec5bbdac0aeb9dafdaf88e17492da0504de2e6400"}, - {file = "typed_ast-1.4.2-cp35-cp35m-win_amd64.whl", hash = "sha256:9044ef2df88d7f33692ae3f18d3be63dec69c4fb1b5a4a9ac950f9b4ba571606"}, - {file = "typed_ast-1.4.2-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:c1c876fd795b36126f773db9cbb393f19808edd2637e00fd6caba0e25f2c7b64"}, - {file = "typed_ast-1.4.2-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:5dcfc2e264bd8a1db8b11a892bd1647154ce03eeba94b461effe68790d8b8e07"}, - {file = "typed_ast-1.4.2-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:8db0e856712f79c45956da0c9a40ca4246abc3485ae0d7ecc86a20f5e4c09abc"}, - {file = "typed_ast-1.4.2-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:d003156bb6a59cda9050e983441b7fa2487f7800d76bdc065566b7d728b4581a"}, - {file = "typed_ast-1.4.2-cp36-cp36m-win32.whl", hash = "sha256:4c790331247081ea7c632a76d5b2a265e6d325ecd3179d06e9cf8d46d90dd151"}, - {file = "typed_ast-1.4.2-cp36-cp36m-win_amd64.whl", hash = "sha256:d175297e9533d8d37437abc14e8a83cbc68af93cc9c1c59c2c292ec59a0697a3"}, - {file = "typed_ast-1.4.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:cf54cfa843f297991b7388c281cb3855d911137223c6b6d2dd82a47ae5125a41"}, - {file = "typed_ast-1.4.2-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:b4fcdcfa302538f70929eb7b392f536a237cbe2ed9cba88e3bf5027b39f5f77f"}, - {file = "typed_ast-1.4.2-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:987f15737aba2ab5f3928c617ccf1ce412e2e321c77ab16ca5a293e7bbffd581"}, - {file = "typed_ast-1.4.2-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:37f48d46d733d57cc70fd5f30572d11ab8ed92da6e6b28e024e4a3edfb456e37"}, - {file = "typed_ast-1.4.2-cp37-cp37m-win32.whl", hash = "sha256:36d829b31ab67d6fcb30e185ec996e1f72b892255a745d3a82138c97d21ed1cd"}, - {file = "typed_ast-1.4.2-cp37-cp37m-win_amd64.whl", hash = "sha256:8368f83e93c7156ccd40e49a783a6a6850ca25b556c0fa0240ed0f659d2fe496"}, - {file = "typed_ast-1.4.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:963c80b583b0661918718b095e02303d8078950b26cc00b5e5ea9ababe0de1fc"}, - {file = "typed_ast-1.4.2-cp38-cp38-manylinux1_i686.whl", hash = "sha256:e683e409e5c45d5c9082dc1daf13f6374300806240719f95dc783d1fc942af10"}, - {file = "typed_ast-1.4.2-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:84aa6223d71012c68d577c83f4e7db50d11d6b1399a9c779046d75e24bed74ea"}, - {file = "typed_ast-1.4.2-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:a38878a223bdd37c9709d07cd357bb79f4c760b29210e14ad0fb395294583787"}, - {file = "typed_ast-1.4.2-cp38-cp38-win32.whl", hash = "sha256:a2c927c49f2029291fbabd673d51a2180038f8cd5a5b2f290f78c4516be48be2"}, - {file = "typed_ast-1.4.2-cp38-cp38-win_amd64.whl", hash = "sha256:c0c74e5579af4b977c8b932f40a5464764b2f86681327410aa028a22d2f54937"}, - {file = "typed_ast-1.4.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:07d49388d5bf7e863f7fa2f124b1b1d89d8aa0e2f7812faff0a5658c01c59aa1"}, - {file = "typed_ast-1.4.2-cp39-cp39-manylinux1_i686.whl", hash = "sha256:240296b27397e4e37874abb1df2a608a92df85cf3e2a04d0d4d61055c8305ba6"}, - {file = "typed_ast-1.4.2-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:d746a437cdbca200622385305aedd9aef68e8a645e385cc483bdc5e488f07166"}, - {file = "typed_ast-1.4.2-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:14bf1522cdee369e8f5581238edac09150c765ec1cb33615855889cf33dcb92d"}, - {file = "typed_ast-1.4.2-cp39-cp39-win32.whl", hash = "sha256:cc7b98bf58167b7f2db91a4327da24fb93368838eb84a44c472283778fc2446b"}, - {file = "typed_ast-1.4.2-cp39-cp39-win_amd64.whl", hash = "sha256:7147e2a76c75f0f64c4319886e7639e490fee87c9d25cb1d4faef1d8cf83a440"}, - {file = "typed_ast-1.4.2.tar.gz", hash = "sha256:9fc0b3cb5d1720e7141d103cf4819aea239f7d136acf9ee4a69b047b7986175a"}, + {file = "typed_ast-1.4.3-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:2068531575a125b87a41802130fa7e29f26c09a2833fea68d9a40cf33902eba6"}, + {file = "typed_ast-1.4.3-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:c907f561b1e83e93fad565bac5ba9c22d96a54e7ea0267c708bffe863cbe4075"}, + {file = "typed_ast-1.4.3-cp35-cp35m-manylinux2014_aarch64.whl", hash = "sha256:1b3ead4a96c9101bef08f9f7d1217c096f31667617b58de957f690c92378b528"}, + {file = "typed_ast-1.4.3-cp35-cp35m-win32.whl", hash = "sha256:dde816ca9dac1d9c01dd504ea5967821606f02e510438120091b84e852367428"}, + {file = "typed_ast-1.4.3-cp35-cp35m-win_amd64.whl", hash = "sha256:777a26c84bea6cd934422ac2e3b78863a37017618b6e5c08f92ef69853e765d3"}, + {file = "typed_ast-1.4.3-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:f8afcf15cc511ada719a88e013cec87c11aff7b91f019295eb4530f96fe5ef2f"}, + {file = "typed_ast-1.4.3-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:52b1eb8c83f178ab787f3a4283f68258525f8d70f778a2f6dd54d3b5e5fb4341"}, + {file = "typed_ast-1.4.3-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:01ae5f73431d21eead5015997ab41afa53aa1fbe252f9da060be5dad2c730ace"}, + {file = "typed_ast-1.4.3-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:c190f0899e9f9f8b6b7863debfb739abcb21a5c054f911ca3596d12b8a4c4c7f"}, + {file = "typed_ast-1.4.3-cp36-cp36m-win32.whl", hash = "sha256:398e44cd480f4d2b7ee8d98385ca104e35c81525dd98c519acff1b79bdaac363"}, + {file = "typed_ast-1.4.3-cp36-cp36m-win_amd64.whl", hash = "sha256:bff6ad71c81b3bba8fa35f0f1921fb24ff4476235a6e94a26ada2e54370e6da7"}, + {file = "typed_ast-1.4.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:0fb71b8c643187d7492c1f8352f2c15b4c4af3f6338f21681d3681b3dc31a266"}, + {file = "typed_ast-1.4.3-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:760ad187b1041a154f0e4d0f6aae3e40fdb51d6de16e5c99aedadd9246450e9e"}, + {file = "typed_ast-1.4.3-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:5feca99c17af94057417d744607b82dd0a664fd5e4ca98061480fd8b14b18d04"}, + {file = "typed_ast-1.4.3-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:95431a26309a21874005845c21118c83991c63ea800dd44843e42a916aec5899"}, + {file = "typed_ast-1.4.3-cp37-cp37m-win32.whl", hash = "sha256:aee0c1256be6c07bd3e1263ff920c325b59849dc95392a05f258bb9b259cf39c"}, + {file = "typed_ast-1.4.3-cp37-cp37m-win_amd64.whl", hash = "sha256:9ad2c92ec681e02baf81fdfa056fe0d818645efa9af1f1cd5fd6f1bd2bdfd805"}, + {file = "typed_ast-1.4.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:b36b4f3920103a25e1d5d024d155c504080959582b928e91cb608a65c3a49e1a"}, + {file = "typed_ast-1.4.3-cp38-cp38-manylinux1_i686.whl", hash = "sha256:067a74454df670dcaa4e59349a2e5c81e567d8d65458d480a5b3dfecec08c5ff"}, + {file = "typed_ast-1.4.3-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:7538e495704e2ccda9b234b82423a4038f324f3a10c43bc088a1636180f11a41"}, + {file = "typed_ast-1.4.3-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:af3d4a73793725138d6b334d9d247ce7e5f084d96284ed23f22ee626a7b88e39"}, + {file = "typed_ast-1.4.3-cp38-cp38-win32.whl", hash = "sha256:f2362f3cb0f3172c42938946dbc5b7843c2a28aec307c49100c8b38764eb6927"}, + {file = "typed_ast-1.4.3-cp38-cp38-win_amd64.whl", hash = "sha256:dd4a21253f42b8d2b48410cb31fe501d32f8b9fbeb1f55063ad102fe9c425e40"}, + {file = "typed_ast-1.4.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:f328adcfebed9f11301eaedfa48e15bdece9b519fb27e6a8c01aa52a17ec31b3"}, + {file = "typed_ast-1.4.3-cp39-cp39-manylinux1_i686.whl", hash = "sha256:2c726c276d09fc5c414693a2de063f521052d9ea7c240ce553316f70656c84d4"}, + {file = "typed_ast-1.4.3-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:cae53c389825d3b46fb37538441f75d6aecc4174f615d048321b716df2757fb0"}, + {file = "typed_ast-1.4.3-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:b9574c6f03f685070d859e75c7f9eeca02d6933273b5e69572e5ff9d5e3931c3"}, + {file = "typed_ast-1.4.3-cp39-cp39-win32.whl", hash = "sha256:209596a4ec71d990d71d5e0d312ac935d86930e6eecff6ccc7007fe54d703808"}, + {file = "typed_ast-1.4.3-cp39-cp39-win_amd64.whl", hash = "sha256:9c6d1a54552b5330bc657b7ef0eae25d00ba7ffe85d9ea8ae6540d2197a3788c"}, + {file = "typed_ast-1.4.3.tar.gz", hash = "sha256:fb1bbeac803adea29cedd70781399c99138358c26d05fcbd23c13016b7f5ec65"}, ] typing-extensions = [ - {file = "typing_extensions-3.7.4.3-py2-none-any.whl", hash = "sha256:dafc7639cde7f1b6e1acc0f457842a83e722ccca8eef5270af2d74792619a89f"}, - {file = "typing_extensions-3.7.4.3-py3-none-any.whl", hash = "sha256:7cb407020f00f7bfc3cb3e7881628838e69d8f3fcab2f64742a5e76b2f841918"}, - {file = "typing_extensions-3.7.4.3.tar.gz", hash = "sha256:99d4073b617d30288f569d3f13d2bd7548c3a7e4c8de87db09a9d29bb3a4a60c"}, + {file = "typing_extensions-3.10.0.0-py2-none-any.whl", hash = "sha256:0ac0f89795dd19de6b97debb0c6af1c70987fd80a2d62d1958f7e56fcc31b497"}, + {file = "typing_extensions-3.10.0.0-py3-none-any.whl", hash = "sha256:779383f6086d90c99ae41cf0ff39aac8a7937a9283ce0a414e5dd782f4c94a84"}, + {file = "typing_extensions-3.10.0.0.tar.gz", hash = "sha256:50b6f157849174217d0656f99dc82fe932884fb250826c18350e159ec6cdf342"}, ] urllib3 = [ - {file = "urllib3-1.26.4-py2.py3-none-any.whl", hash = "sha256:2f4da4594db7e1e110a944bb1b551fdf4e6c136ad42e4234131391e21eb5b0df"}, - {file = "urllib3-1.26.4.tar.gz", hash = "sha256:e7b021f7241115872f92f43c6508082facffbd1c048e3c6e2bb9c2a157e28937"}, + {file = "urllib3-1.22-py2.py3-none-any.whl", hash = "sha256:06330f386d6e4b195fbfc736b297f58c5a892e4440e54d294d7004e3a9bbea1b"}, + {file = "urllib3-1.22.tar.gz", hash = "sha256:cc44da8e1145637334317feebd728bd869a35285b93cbb4cca2577da7e62db4f"}, ] diff --git a/cryptol-remote-api/python/pyproject.toml b/cryptol-remote-api/python/pyproject.toml index 75e3cc6f5..8d5bea056 100644 --- a/cryptol-remote-api/python/pyproject.toml +++ b/cryptol-remote-api/python/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "cryptol" -version = "2.11.0" +version = "2.11.1" readme = "README.md" keywords = ["cryptography", "verification"] description = "Cryptol client for the Cryptol 2.11 RPC server" @@ -12,7 +12,7 @@ include = [ ] [tool.poetry.dependencies] -python = "^3.8" +python = ">=3.7.0" requests = "^2.25.1" BitVector = "^3.4.9" argo-client = "0.0.4" diff --git a/cryptol-remote-api/python/requirements.txt b/cryptol-remote-api/python/requirements.txt deleted file mode 100644 index 788438301..000000000 --- a/cryptol-remote-api/python/requirements.txt +++ /dev/null @@ -1,81 +0,0 @@ -argo-client==0.0.4; python_version >= "3.7" \ - --hash=sha256:1ce6af1cbc738d08348dcb62d573968da58e2382cb4ea753cc061aa16d45ff6a \ - --hash=sha256:74c13e9f3bf5a48eeda847af343bdaf54a950c100496ed3c342a51f5406cf568 -bitvector==3.4.9 \ - --hash=sha256:a5e94cbb4804213b1f0c32d84517cd8f0bb8c689b5eb8055d351632e220a5edd -certifi==2020.12.5; python_version >= "3.7" and python_full_version < "3.0.0" or python_full_version >= "3.5.0" and python_version >= "3.7" \ - --hash=sha256:719a74fb9e33b9bd44cc7f3a8d94bc35e4049deebe19ba7d8e108280cfd59830 \ - --hash=sha256:1a4995114262bffbc2413b159f2a1a480c969de6e6eb13ee966d470af86af59c -chardet==4.0.0; python_version >= "3.7" and python_full_version < "3.0.0" or python_full_version >= "3.5.0" and python_version >= "3.7" \ - --hash=sha256:f864054d66fd9118f2e67044ac8981a54775ec5b67aed0441892edb553d21da5 \ - --hash=sha256:0d6f53a15db4120f2b08c94f11e7d93d2c911ee118b6b30a04ec3ee8310179fa -idna==2.10; python_version >= "3.7" and python_full_version < "3.0.0" or python_full_version >= "3.5.0" and python_version >= "3.7" \ - --hash=sha256:b97d804b1e9b523befed77c48dacec60e6dcb0b5391d57af6a65a312a90648c0 \ - --hash=sha256:b307872f855b18632ce0c21c5e45be78c0ea7ae4c15c828c20788b26921eb3f6 -mypy-extensions==0.4.3; python_version >= "3.7" \ - --hash=sha256:090fedd75945a69ae91ce1303b5824f428daf5a028d2f6ab8a299250a846f15d \ - --hash=sha256:2d82818f5bb3e369420cb3c4060a7970edba416647068eb4c5343488a6c604a8 -mypy==0.812; python_version >= "3.5" \ - --hash=sha256:a26f8ec704e5a7423c8824d425086705e381b4f1dfdef6e3a1edab7ba174ec49 \ - --hash=sha256:28fb5479c494b1bab244620685e2eb3c3f988d71fd5d64cc753195e8ed53df7c \ - --hash=sha256:9743c91088d396c1a5a3c9978354b61b0382b4e3c440ce83cf77994a43e8c521 \ - --hash=sha256:d7da2e1d5f558c37d6e8c1246f1aec1e7349e4913d8fb3cb289a35de573fe2eb \ - --hash=sha256:4eec37370483331d13514c3f55f446fc5248d6373e7029a29ecb7b7494851e7a \ - --hash=sha256:d65cc1df038ef55a99e617431f0553cd77763869eebdf9042403e16089fe746c \ - --hash=sha256:61a3d5b97955422964be6b3baf05ff2ce7f26f52c85dd88db11d5e03e146a3a6 \ - --hash=sha256:25adde9b862f8f9aac9d2d11971f226bd4c8fbaa89fb76bdadb267ef22d10064 \ - --hash=sha256:552a815579aa1e995f39fd05dde6cd378e191b063f031f2acfe73ce9fb7f9e56 \ - --hash=sha256:499c798053cdebcaa916eef8cd733e5584b5909f789de856b482cd7d069bdad8 \ - --hash=sha256:5873888fff1c7cf5b71efbe80e0e73153fe9212fafdf8e44adfe4c20ec9f82d7 \ - --hash=sha256:9f94aac67a2045ec719ffe6111df543bac7874cee01f41928f6969756e030564 \ - --hash=sha256:d23e0ea196702d918b60c8288561e722bf437d82cb7ef2edcd98cfa38905d506 \ - --hash=sha256:674e822aa665b9fd75130c6c5f5ed9564a38c6cea6a6432ce47eafb68ee578c5 \ - --hash=sha256:abf7e0c3cf117c44d9285cc6128856106183938c68fd4944763003decdcfeb66 \ - --hash=sha256:0d0a87c0e7e3a9becdfbe936c981d32e5ee0ccda3e0f07e1ef2c3d1a817cf73e \ - --hash=sha256:7ce3175801d0ae5fdfa79b4f0cfed08807af4d075b402b7e294e6aa72af9aa2a \ - --hash=sha256:b09669bcda124e83708f34a94606e01b614fa71931d356c1f1a5297ba11f110a \ - --hash=sha256:33f159443db0829d16f0a8d83d94df3109bb6dd801975fe86bacb9bf71628e97 \ - --hash=sha256:3f2aca7f68580dc2508289c729bd49ee929a436208d2b2b6aab15745a70a57df \ - --hash=sha256:2f9b3407c58347a452fc0736861593e105139b905cca7d097e413453a1d650b4 \ - --hash=sha256:cd07039aa5df222037005b08fbbfd69b3ab0b0bd7a07d7906de75ae52c4e3119 -requests==2.25.1; (python_version >= "2.7" and python_full_version < "3.0.0") or (python_full_version >= "3.5.0") \ - --hash=sha256:c210084e36a42ae6b9219e00e48287def368a26d03a048ddad7bfee44f75871e \ - --hash=sha256:27973dd4a904a4f13b263a19c866c13b92a39ed1c964655f025f3f8d3d75b804 -typed-ast==1.4.2; python_version >= "3.7" \ - --hash=sha256:7703620125e4fb79b64aa52427ec192822e9f45d37d4b6625ab37ef403e1df70 \ - --hash=sha256:c9aadc4924d4b5799112837b226160428524a9a45f830e0d0f184b19e4090487 \ - --hash=sha256:9ec45db0c766f196ae629e509f059ff05fc3148f9ffd28f3cfe75d4afb485412 \ - --hash=sha256:85f95aa97a35bdb2f2f7d10ec5bbdac0aeb9dafdaf88e17492da0504de2e6400 \ - --hash=sha256:9044ef2df88d7f33692ae3f18d3be63dec69c4fb1b5a4a9ac950f9b4ba571606 \ - --hash=sha256:c1c876fd795b36126f773db9cbb393f19808edd2637e00fd6caba0e25f2c7b64 \ - --hash=sha256:5dcfc2e264bd8a1db8b11a892bd1647154ce03eeba94b461effe68790d8b8e07 \ - --hash=sha256:8db0e856712f79c45956da0c9a40ca4246abc3485ae0d7ecc86a20f5e4c09abc \ - --hash=sha256:d003156bb6a59cda9050e983441b7fa2487f7800d76bdc065566b7d728b4581a \ - --hash=sha256:4c790331247081ea7c632a76d5b2a265e6d325ecd3179d06e9cf8d46d90dd151 \ - --hash=sha256:d175297e9533d8d37437abc14e8a83cbc68af93cc9c1c59c2c292ec59a0697a3 \ - --hash=sha256:cf54cfa843f297991b7388c281cb3855d911137223c6b6d2dd82a47ae5125a41 \ - --hash=sha256:b4fcdcfa302538f70929eb7b392f536a237cbe2ed9cba88e3bf5027b39f5f77f \ - --hash=sha256:987f15737aba2ab5f3928c617ccf1ce412e2e321c77ab16ca5a293e7bbffd581 \ - --hash=sha256:37f48d46d733d57cc70fd5f30572d11ab8ed92da6e6b28e024e4a3edfb456e37 \ - --hash=sha256:36d829b31ab67d6fcb30e185ec996e1f72b892255a745d3a82138c97d21ed1cd \ - --hash=sha256:8368f83e93c7156ccd40e49a783a6a6850ca25b556c0fa0240ed0f659d2fe496 \ - --hash=sha256:963c80b583b0661918718b095e02303d8078950b26cc00b5e5ea9ababe0de1fc \ - --hash=sha256:e683e409e5c45d5c9082dc1daf13f6374300806240719f95dc783d1fc942af10 \ - --hash=sha256:84aa6223d71012c68d577c83f4e7db50d11d6b1399a9c779046d75e24bed74ea \ - --hash=sha256:a38878a223bdd37c9709d07cd357bb79f4c760b29210e14ad0fb395294583787 \ - --hash=sha256:a2c927c49f2029291fbabd673d51a2180038f8cd5a5b2f290f78c4516be48be2 \ - --hash=sha256:c0c74e5579af4b977c8b932f40a5464764b2f86681327410aa028a22d2f54937 \ - --hash=sha256:07d49388d5bf7e863f7fa2f124b1b1d89d8aa0e2f7812faff0a5658c01c59aa1 \ - --hash=sha256:240296b27397e4e37874abb1df2a608a92df85cf3e2a04d0d4d61055c8305ba6 \ - --hash=sha256:d746a437cdbca200622385305aedd9aef68e8a645e385cc483bdc5e488f07166 \ - --hash=sha256:14bf1522cdee369e8f5581238edac09150c765ec1cb33615855889cf33dcb92d \ - --hash=sha256:cc7b98bf58167b7f2db91a4327da24fb93368838eb84a44c472283778fc2446b \ - --hash=sha256:7147e2a76c75f0f64c4319886e7639e490fee87c9d25cb1d4faef1d8cf83a440 \ - --hash=sha256:9fc0b3cb5d1720e7141d103cf4819aea239f7d136acf9ee4a69b047b7986175a -typing-extensions==3.7.4.3; python_version >= "3.7" \ - --hash=sha256:dafc7639cde7f1b6e1acc0f457842a83e722ccca8eef5270af2d74792619a89f \ - --hash=sha256:7cb407020f00f7bfc3cb3e7881628838e69d8f3fcab2f64742a5e76b2f841918 \ - --hash=sha256:99d4073b617d30288f569d3f13d2bd7548c3a7e4c8de87db09a9d29bb3a4a60c -urllib3==1.26.4; python_version >= "3.7" and python_full_version < "3.0.0" or python_full_version >= "3.5.0" and python_version < "4" and python_version >= "3.7" \ - --hash=sha256:2f4da4594db7e1e110a944bb1b551fdf4e6c136ad42e4234131391e21eb5b0df \ - --hash=sha256:e7b021f7241115872f92f43c6508082facffbd1c048e3c6e2bb9c2a157e28937 diff --git a/cryptol-remote-api/python/setup.py b/cryptol-remote-api/python/setup.py deleted file mode 100644 index b7a9757cf..000000000 --- a/cryptol-remote-api/python/setup.py +++ /dev/null @@ -1,44 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- - -from setuptools import setup - -def get_README(): - content = "" - with open("README.md") as f: - content += f.read() - return content - -setup( - name="cryptol", - python_requires=">=3.7", - version="0.0.2", - url="https://github.com/GaloisInc/cryptol", - project_urls={ - "Source": "https://github.com/GaloisInc/cryptol/tree/master/cryptol-remote-api/pthon", - "Bug Tracker": "https://github.com/GaloisInc/cryptol/issues" - }, - license="BSD", - description="A scripting library for interacting with the Cryptol RPC server.", - long_description=get_README(), - long_description_content_type="text/markdown", - author="Galois, Inc.", - author_email="andrew@galois.com", - packages=["cryptol"], - package_data={"cryptol": ["py.typed"]}, - zip_safe=False, - install_requires=[ - "BitVector==3.4.9", - "mypy==0.812", - "mypy-extensions==0.4.3", - "argo-client==0.0.4" - ], - classifiers=[ - "Development Status :: 3 - Alpha", - "License :: OSI Approved :: BSD License", - "Operating System :: OS Independent", - "Programming Language :: Python :: 3.7", - "Programming Language :: Python :: 3.8", - "Programming Language :: Python :: 3.9" - ], -) diff --git a/cryptol-remote-api/python/tests/cryptol/test_EvenMansour.py b/cryptol-remote-api/python/tests/cryptol/test_EvenMansour.py index e894a5f7e..a87dc9db4 100644 --- a/cryptol-remote-api/python/tests/cryptol/test_EvenMansour.py +++ b/cryptol-remote-api/python/tests/cryptol/test_EvenMansour.py @@ -10,17 +10,17 @@ class TestEvenMansour(unittest.TestCase): def test_EvenMansour(self): c = cryptol.connect() c.load_file(str(Path('tests','cryptol','test-files','examples','contrib','EvenMansour.cry'))) - + F_10_4 = c.eval('F:[10][4]').result() self.assertTrue(c.call('is_a_permutation', F_10_4).result()) - + Finv_10_4 = c.eval("F':[10][4]").result() digits = [ BV(size=4, value=i) for i in range(0,10) ] # ^ the same as: c.eval('[0..9]:[_][4]').result() self.assertTrue(c.call('is_inverse_permutation', digits, F_10_4, Finv_10_4).result()) - + self.assertTrue(c.check('E_and_D_are_inverses').result().success) - + if __name__ == "__main__": unittest.main() diff --git a/cryptol-remote-api/python/tests/cryptol/test_cryptol_api.py b/cryptol-remote-api/python/tests/cryptol/test_cryptol_api.py index 208fb7cde..d154878a1 100644 --- a/cryptol-remote-api/python/tests/cryptol/test_cryptol_api.py +++ b/cryptol-remote-api/python/tests/cryptol/test_cryptol_api.py @@ -166,27 +166,33 @@ class HttpMultiConnectionTests(unittest.TestCase): @classmethod def setUpClass(self): - if (url := os.getenv('CRYPTOL_SERVER_URL')) is not None: - self.url = url - elif ((command := os.getenv('CRYPTOL_SERVER')) is not None and (command := find_executable(command)) is not None)\ - or (command := find_executable('cryptol-remote-api')) is not None: - self.p = subprocess.Popen( - [command, "http", "/", "--port", "8080"], - stdout=subprocess.PIPE, - stdin=subprocess.DEVNULL, - stderr=subprocess.PIPE, - start_new_session=True) - time.sleep(5) - assert(self.p is not None) - poll_result = self.p.poll() - if poll_result is not None: - print(poll_result) - print(self.p.stdout.read()) - print(self.p.stderr.read()) - assert(poll_result is None) - self.url = "http://localhost:8080/" + server = os.getenv('CRYPTOL_SERVER_URL') + if server is not None: + self.url = server else: - raise RuntimeError("NO CRYPTOL SERVER FOUND") + server = os.getenv('CRYPTOL_SERVER') + if server is not None: + server = find_executable(server) + if server is None: + server = find_executable('cryptol-remote-api') + if server is not None: + self.p = subprocess.Popen( + [server, "http", "/", "--port", "8080"], + stdout=subprocess.PIPE, + stdin=subprocess.DEVNULL, + stderr=subprocess.PIPE, + start_new_session=True) + time.sleep(5) + assert(self.p is not None) + poll_result = self.p.poll() + if poll_result is not None: + print(poll_result) + print(self.p.stdout.read()) + print(self.p.stderr.read()) + assert(poll_result is None) + self.url = "http://localhost:8080/" + else: + raise RuntimeError("NO CRYPTOL SERVER FOUND") @classmethod def tearDownClass(self): diff --git a/cryptol-remote-api/python/tests/cryptol/test_low_level_ops.py b/cryptol-remote-api/python/tests/cryptol/test_low_level_ops.py index ead9a93a0..82d6f7e67 100644 --- a/cryptol-remote-api/python/tests/cryptol/test_low_level_ops.py +++ b/cryptol-remote-api/python/tests/cryptol/test_low_level_ops.py @@ -36,14 +36,23 @@ class LowLevelCryptolApiTests(unittest.TestCase): @classmethod def setUpClass(self): - if (command := os.getenv('CRYPTOL_SERVER')) is not None and (command := find_executable(command)) is not None: - self.c = argo.ServerConnection(argo.DynamicSocketProcess(command + " socket")) - elif (url := os.getenv('CRYPTOL_SERVER_URL')) is not None: - self.c = argo.ServerConnection(argo.HttpProcess(url)) - elif (command := find_executable('cryptol-remote-api')) is not None: - self.c = argo.ServerConnection(argo.StdIOProcess(command + " stdio")) + server = os.getenv('CRYPTOL_SERVER') + if server: + server = find_executable(server) + if server: + self.c = argo.ServerConnection(argo.DynamicSocketProcess(server + " socket")) + else: + raise RuntimeError(f'CRYPTOL_SERVER executable {server} could not be found') else: - raise RuntimeError("NO CRYPTOL SERVER FOUND") + server = os.getenv('CRYPTOL_SERVER_URL') + if server: + self.c = argo.ServerConnection(argo.HttpProcess(server)) + else: + server = find_executable('cryptol-remote-api') + if server: + self.c = argo.ServerConnection(argo.StdIOProcess(server + " stdio")) + else: + raise RuntimeError("NO CRYPTOL SERVER FOUND") def test_low_level_api(self): c = self.c @@ -54,7 +63,7 @@ def test_low_level_api(self): self.assertIn('state', reply['result']) self.assertIn('answer', reply['result']) state = reply['result']['state'] - + uid = c.send_command("evaluate expression", {"expression": {"expression":"call","function":"f","arguments":[{"expression":"bits","encoding":"hex","data":"ff","width":8}]}, "state": state}) reply = c.wait_for_reply_to(uid) self.assertIn('result', reply) diff --git a/cryptol-remote-api/python/tests/cryptol_eval/test_basics.py b/cryptol-remote-api/python/tests/cryptol_eval/test_basics.py index a32932443..731a6f56c 100644 --- a/cryptol-remote-api/python/tests/cryptol_eval/test_basics.py +++ b/cryptol-remote-api/python/tests/cryptol_eval/test_basics.py @@ -16,8 +16,9 @@ class CryptolEvalServerTests(unittest.TestCase): @classmethod def setUpClass(self): dir_path = Path(os.path.dirname(os.path.realpath(__file__)), "test-files") - if command := os.getenv('CRYPTOL_SERVER'): - self.c = cryptol.connect(f'{command} socket --module M', cryptol_path=dir_path) + server = os.getenv('CRYPTOL_SERVER') + if server: + self.c = cryptol.connect(f'{server} socket --module M', cryptol_path=dir_path) else: raise ValueError('CRYPTOL_SERVER environment variable not set (eval server tests currently only work with a local executable)') @@ -58,12 +59,16 @@ class HttpMultiConnectionTests(unittest.TestCase): @classmethod def setUpClass(self): dir_path = Path(os.path.dirname(os.path.realpath(__file__)), "test-files") - if ((command := os.getenv('CRYPTOL_SERVER')) is not None and (command := find_executable(command)) is not None)\ - or (command := find_executable('cryptol-eval-server')) is not None: + server = os.getenv('CRYPTOL_SERVER') + if server is not None: + server = find_executable(server) + if server is None: + server = find_executable('cryptol-eval-server') + if server is not None: new_env = os.environ.copy() new_env["CRYPTOLPATH"] = str(dir_path) self.p = subprocess.Popen( - [command, "http", "/", "--port", "8081", "--module", "M"], + [server, "http", "/", "--port", "8081", "--module", "M"], stdout=subprocess.PIPE, stdin=subprocess.DEVNULL, stderr=subprocess.PIPE, diff --git a/cryptol-remote-api/run_rpc_tests.sh b/cryptol-remote-api/run_rpc_tests.sh index d6e8bc0a2..539be453a 100755 --- a/cryptol-remote-api/run_rpc_tests.sh +++ b/cryptol-remote-api/run_rpc_tests.sh @@ -10,27 +10,22 @@ cabal v2-build exe:cryptol-eval-server pushd $DIR/python NUM_FAILS=0 +function run_test { + "$@" + if (( $? != 0 )); then NUM_FAILS=$(($NUM_FAILS+1)); fi +} echo "Setting up python environment for remote server clients..." -python3 -m venv virtenv -. virtenv/bin/activate -pip install -r requirements.txt +poetry install echo "Typechecking code with mypy..." -mypy cryptol/ tests/ -if [ $? -ne 0 ]; then - echo "Code failed to typecheck with mypy" - NUM_FAILS=$(($NUM_FAILS+1)) -fi +run_test poetry run mypy cryptol/ tests/ export CRYPTOL_SERVER=$(cabal v2-exec which cryptol-remote-api) if [[ -x "$CRYPTOL_SERVER" ]]; then echo "Running cryptol-remote-api tests..." echo "Using server $CRYPTOL_SERVER" - python3 -m unittest discover tests/cryptol - if [ $? -ne 0 ]; then - NUM_FAILS=$(($NUM_FAILS+1)) - fi + run_test poetry run python -m unittest discover tests/cryptol else echo "could not find the cryptol-remote-api via `cabal v2-exec which`" NUM_FAILS=$(($NUM_FAILS+1)) @@ -40,10 +35,7 @@ export CRYPTOL_SERVER=$(cabal v2-exec which cryptol-eval-server) if [[ -x "$CRYPTOL_SERVER" ]]; then echo "Running cryptol-eval-server tests..." echo "Using server $CRYPTOL_SERVER" - python3 -m unittest discover tests/cryptol_eval - if [ $? -ne 0 ]; then - NUM_FAILS=$(($NUM_FAILS+1)) - fi + run_test poetry run python -m unittest discover tests/cryptol_eval else echo "could not find the cryptol-eval-server via `cabal v2-exec which`" NUM_FAILS=$(($NUM_FAILS+1)) diff --git a/cryptol-remote-api/test-cryptol-remote-api.py b/cryptol-remote-api/test-cryptol-remote-api.py index fa086048c..a0fbb4322 100644 --- a/cryptol-remote-api/test-cryptol-remote-api.py +++ b/cryptol-remote-api/test-cryptol-remote-api.py @@ -9,7 +9,7 @@ if connType == 'socket': c = cryptol.connect(argo.RemoteSocketProcess(host, port=port, ipv6=False)) elif connType == 'http': - c = cryptol.CryptolConnection(argo.ServerConnection(argo.HttpProcess(url="http://%s:%d/" % (host,port)))) + c = cryptol.connect(url="http://%s:%d/" % (host,port)) else: raise Exception('specify socket or http for connection type') diff --git a/cryptol-remote-api/test.Dockerfile b/cryptol-remote-api/test.Dockerfile index 78fc12549..1ec2cdff5 100644 --- a/cryptol-remote-api/test.Dockerfile +++ b/cryptol-remote-api/test.Dockerfile @@ -1,8 +1,9 @@ -FROM python:3.9 +FROM python:3.7 # Intended to be built from the root of the cryptol git repository COPY cryptol-remote-api/python python -RUN pip3 install -r python/requirements.txt -RUN pip3 install -e python -COPY cryptol-remote-api/test-cryptol-remote-api.py /entrypoint.py -ENTRYPOINT ["python3", "/entrypoint.py"] +RUN pip3 install 'poetry==1.1.5' +RUN cd python && poetry install +COPY cryptol-remote-api/test-cryptol-remote-api.py /python/entrypoint.py +WORKDIR /python +ENTRYPOINT ["poetry", "run", "python","entrypoint.py"] diff --git a/cryptol-remote-api/test_docker.sh b/cryptol-remote-api/test_docker.sh index 5106bd628..2f70a265a 100755 --- a/cryptol-remote-api/test_docker.sh +++ b/cryptol-remote-api/test_docker.sh @@ -23,12 +23,10 @@ pushd $DIR/python NUM_FAILS=0 echo "Setting up python environment for remote server clients..." -python3 -m venv virtenv -. virtenv/bin/activate -pip install -r requirements.txt +poetry install export CRYPTOL_SERVER_URL="http://localhost:8080/" -python3 -m unittest discover tests/cryptol +poetry run python -m unittest discover tests/cryptol if [ $? -ne 0 ]; then NUM_FAILS=$(($NUM_FAILS+1)) fi