From 3b2a775aa75496361bae9f88b7d22c1e0cac1b93 Mon Sep 17 00:00:00 2001 From: Alex Carney Date: Wed, 3 Jan 2024 20:47:17 +0000 Subject: [PATCH] fix: pyodide test suite Instead of trying to run the entire pytest testsuite in the pyodide runtime, in selenium we now only run the end-to-end tests "outside" of pyodide. By passing the ``lsp-runtime pyodide`` argument to pytest, rather than launching the server under test via CPython, the test suite will use NodeJS and a small wrapper script to execute the given server in the Pyodide runtime. --- .github/workflows/ci.yml | 41 +++-- poetry.lock | 218 +----------------------- pyproject.toml | 7 +- tests/conftest.py | 76 ++++++++- tests/pyodide/.gitignore | 2 + tests/pyodide/package-lock.json | 49 ++++++ tests/pyodide/package.json | 10 ++ tests/pyodide/run_server.js | 98 +++++++++++ tests/pyodide_testrunner/.gitignore | 1 - tests/pyodide_testrunner/index.html | 56 ------ tests/pyodide_testrunner/run.py | 131 -------------- tests/pyodide_testrunner/test-runner.js | 38 ----- 12 files changed, 260 insertions(+), 467 deletions(-) create mode 100644 tests/pyodide/.gitignore create mode 100644 tests/pyodide/package-lock.json create mode 100644 tests/pyodide/package.json create mode 100644 tests/pyodide/run_server.js delete mode 100644 tests/pyodide_testrunner/.gitignore delete mode 100644 tests/pyodide_testrunner/index.html delete mode 100644 tests/pyodide_testrunner/run.py delete mode 100644 tests/pyodide_testrunner/test-runner.js diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 58449e81..ac1003e5 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -44,7 +44,6 @@ jobs: - name: Install Poetry uses: snok/install-poetry@v1 with: - version: '1.5.1' virtualenvs-in-project: true - name: Load cached venv id: cached-poetry-dependencies @@ -54,7 +53,7 @@ jobs: key: venv-${{ runner.os }}-${{ steps.setup-python.outputs.python-version }}-${{ hashFiles('**/poetry.lock') }} - name: Install dependencies if: steps.cached-poetry-dependencies.outputs.cache-hit != 'true' - run: poetry install --all-extras + run: poetry install --with test - name: Run tests run: | source $VENV # Only needed because of Github Action caching @@ -65,27 +64,37 @@ jobs: if: needs.pre_job.outputs.should_skip != 'true' runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 - - name: Use Python "3.10" - uses: actions/setup-python@v4 + - uses: actions/checkout@v4 + + - uses: 'actions/setup-node@v4' with: - python-version: "3.10" + node-version: 18.x + cache: 'npm' + cache-dependency-path: 'tests/pyodide/package-lock.json' + + - name: Use Python "3.12" + id: setup-python + uses: actions/setup-python@v5 + with: + python-version: "3.12" + - name: Install Poetry uses: snok/install-poetry@v1 with: virtualenvs-in-project: true + - name: Install Dependencies - if: steps.cached-poetry-dependencies.outputs.cache-hit != 'true' run: | - poetry install --with pyodide - - name: Run Testsuite - uses: nick-fields/retry@v2 - with: - timeout_minutes: 10 - max_attempts: 6 - command: | - source $VENV - poe test-pyodide || true + poetry install --with test + poetry build + + cd tests/pyodide + npm ci + + - name: Run tests + run: | + source $VENV + poe test-pyodide lint: needs: pre_job diff --git a/poetry.lock b/poetry.lock index 842ef1e2..86f9bf5a 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,4 +1,4 @@ -# This file is automatically @generated by Poetry 1.7.1 and should not be changed by hand. +# This file is automatically @generated by Poetry 1.7.0 and should not be changed by hand. [[package]] name = "alabaster" @@ -125,70 +125,6 @@ files = [ {file = "certifi-2023.11.17.tar.gz", hash = "sha256:9b469f3a900bf28dc19b8cfbf8019bf47f7fdd1a65a1d4ffb98fc14166beb4d1"}, ] -[[package]] -name = "cffi" -version = "1.16.0" -description = "Foreign Function Interface for Python calling C code." -optional = false -python-versions = ">=3.8" -files = [ - {file = "cffi-1.16.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:6b3d6606d369fc1da4fd8c357d026317fbb9c9b75d36dc16e90e84c26854b088"}, - {file = "cffi-1.16.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ac0f5edd2360eea2f1daa9e26a41db02dd4b0451b48f7c318e217ee092a213e9"}, - {file = "cffi-1.16.0-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7e61e3e4fa664a8588aa25c883eab612a188c725755afff6289454d6362b9673"}, - {file = "cffi-1.16.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a72e8961a86d19bdb45851d8f1f08b041ea37d2bd8d4fd19903bc3083d80c896"}, - {file = "cffi-1.16.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5b50bf3f55561dac5438f8e70bfcdfd74543fd60df5fa5f62d94e5867deca684"}, - {file = "cffi-1.16.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7651c50c8c5ef7bdb41108b7b8c5a83013bfaa8a935590c5d74627c047a583c7"}, - {file = "cffi-1.16.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e4108df7fe9b707191e55f33efbcb2d81928e10cea45527879a4749cbe472614"}, - {file = "cffi-1.16.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:32c68ef735dbe5857c810328cb2481e24722a59a2003018885514d4c09af9743"}, - {file = "cffi-1.16.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:673739cb539f8cdaa07d92d02efa93c9ccf87e345b9a0b556e3ecc666718468d"}, - {file = "cffi-1.16.0-cp310-cp310-win32.whl", hash = "sha256:9f90389693731ff1f659e55c7d1640e2ec43ff725cc61b04b2f9c6d8d017df6a"}, - {file = "cffi-1.16.0-cp310-cp310-win_amd64.whl", hash = "sha256:e6024675e67af929088fda399b2094574609396b1decb609c55fa58b028a32a1"}, - {file = "cffi-1.16.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b84834d0cf97e7d27dd5b7f3aca7b6e9263c56308ab9dc8aae9784abb774d404"}, - {file = "cffi-1.16.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1b8ebc27c014c59692bb2664c7d13ce7a6e9a629be20e54e7271fa696ff2b417"}, - {file = "cffi-1.16.0-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ee07e47c12890ef248766a6e55bd38ebfb2bb8edd4142d56db91b21ea68b7627"}, - {file = "cffi-1.16.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d8a9d3ebe49f084ad71f9269834ceccbf398253c9fac910c4fd7053ff1386936"}, - {file = "cffi-1.16.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e70f54f1796669ef691ca07d046cd81a29cb4deb1e5f942003f401c0c4a2695d"}, - {file = "cffi-1.16.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5bf44d66cdf9e893637896c7faa22298baebcd18d1ddb6d2626a6e39793a1d56"}, - {file = "cffi-1.16.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7b78010e7b97fef4bee1e896df8a4bbb6712b7f05b7ef630f9d1da00f6444d2e"}, - {file = "cffi-1.16.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:c6a164aa47843fb1b01e941d385aab7215563bb8816d80ff3a363a9f8448a8dc"}, - {file = "cffi-1.16.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:e09f3ff613345df5e8c3667da1d918f9149bd623cd9070c983c013792a9a62eb"}, - {file = "cffi-1.16.0-cp311-cp311-win32.whl", hash = "sha256:2c56b361916f390cd758a57f2e16233eb4f64bcbeee88a4881ea90fca14dc6ab"}, - {file = "cffi-1.16.0-cp311-cp311-win_amd64.whl", hash = "sha256:db8e577c19c0fda0beb7e0d4e09e0ba74b1e4c092e0e40bfa12fe05b6f6d75ba"}, - {file = "cffi-1.16.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:fa3a0128b152627161ce47201262d3140edb5a5c3da88d73a1b790a959126956"}, - {file = "cffi-1.16.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:68e7c44931cc171c54ccb702482e9fc723192e88d25a0e133edd7aff8fcd1f6e"}, - {file = "cffi-1.16.0-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:abd808f9c129ba2beda4cfc53bde801e5bcf9d6e0f22f095e45327c038bfe68e"}, - {file = "cffi-1.16.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:88e2b3c14bdb32e440be531ade29d3c50a1a59cd4e51b1dd8b0865c54ea5d2e2"}, - {file = "cffi-1.16.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fcc8eb6d5902bb1cf6dc4f187ee3ea80a1eba0a89aba40a5cb20a5087d961357"}, - {file = "cffi-1.16.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b7be2d771cdba2942e13215c4e340bfd76398e9227ad10402a8767ab1865d2e6"}, - {file = "cffi-1.16.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e715596e683d2ce000574bae5d07bd522c781a822866c20495e52520564f0969"}, - {file = "cffi-1.16.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:2d92b25dbf6cae33f65005baf472d2c245c050b1ce709cc4588cdcdd5495b520"}, - {file = "cffi-1.16.0-cp312-cp312-win32.whl", hash = "sha256:b2ca4e77f9f47c55c194982e10f058db063937845bb2b7a86c84a6cfe0aefa8b"}, - {file = "cffi-1.16.0-cp312-cp312-win_amd64.whl", hash = "sha256:68678abf380b42ce21a5f2abde8efee05c114c2fdb2e9eef2efdb0257fba1235"}, - {file = "cffi-1.16.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:0c9ef6ff37e974b73c25eecc13952c55bceed9112be2d9d938ded8e856138bcc"}, - {file = "cffi-1.16.0-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a09582f178759ee8128d9270cd1344154fd473bb77d94ce0aeb2a93ebf0feaf0"}, - {file = "cffi-1.16.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e760191dd42581e023a68b758769e2da259b5d52e3103c6060ddc02c9edb8d7b"}, - {file = "cffi-1.16.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:80876338e19c951fdfed6198e70bc88f1c9758b94578d5a7c4c91a87af3cf31c"}, - {file = "cffi-1.16.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a6a14b17d7e17fa0d207ac08642c8820f84f25ce17a442fd15e27ea18d67c59b"}, - {file = "cffi-1.16.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6602bc8dc6f3a9e02b6c22c4fc1e47aa50f8f8e6d3f78a5e16ac33ef5fefa324"}, - {file = "cffi-1.16.0-cp38-cp38-win32.whl", hash = "sha256:131fd094d1065b19540c3d72594260f118b231090295d8c34e19a7bbcf2e860a"}, - {file = "cffi-1.16.0-cp38-cp38-win_amd64.whl", hash = "sha256:31d13b0f99e0836b7ff893d37af07366ebc90b678b6664c955b54561fc36ef36"}, - {file = "cffi-1.16.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:582215a0e9adbe0e379761260553ba11c58943e4bbe9c36430c4ca6ac74b15ed"}, - {file = "cffi-1.16.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:b29ebffcf550f9da55bec9e02ad430c992a87e5f512cd63388abb76f1036d8d2"}, - {file = "cffi-1.16.0-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:dc9b18bf40cc75f66f40a7379f6a9513244fe33c0e8aa72e2d56b0196a7ef872"}, - {file = "cffi-1.16.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9cb4a35b3642fc5c005a6755a5d17c6c8b6bcb6981baf81cea8bfbc8903e8ba8"}, - {file = "cffi-1.16.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b86851a328eedc692acf81fb05444bdf1891747c25af7529e39ddafaf68a4f3f"}, - {file = "cffi-1.16.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c0f31130ebc2d37cdd8e44605fb5fa7ad59049298b3f745c74fa74c62fbfcfc4"}, - {file = "cffi-1.16.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8f8e709127c6c77446a8c0a8c8bf3c8ee706a06cd44b1e827c3e6a2ee6b8c098"}, - {file = "cffi-1.16.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:748dcd1e3d3d7cd5443ef03ce8685043294ad6bd7c02a38d1bd367cfd968e000"}, - {file = "cffi-1.16.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:8895613bcc094d4a1b2dbe179d88d7fb4a15cee43c052e8885783fac397d91fe"}, - {file = "cffi-1.16.0-cp39-cp39-win32.whl", hash = "sha256:ed86a35631f7bfbb28e108dd96773b9d5a6ce4811cf6ea468bb6a359b256b1e4"}, - {file = "cffi-1.16.0-cp39-cp39-win_amd64.whl", hash = "sha256:3686dffb02459559c74dd3d81748269ffb0eb027c39a6fc99502de37d501faa8"}, - {file = "cffi-1.16.0.tar.gz", hash = "sha256:bcb3ef43e58665bbda2fb198698fcae6776483e0c4a631aa5647806c25e02cc0"}, -] - -[package.dependencies] -pycparser = "*" - [[package]] name = "charset-normalizer" version = "3.3.2" @@ -405,17 +341,6 @@ files = [ [package.extras] test = ["pytest (>=6)"] -[[package]] -name = "h11" -version = "0.14.0" -description = "A pure-Python, bring-your-own-I/O implementation of HTTP/1.1" -optional = false -python-versions = ">=3.7" -files = [ - {file = "h11-0.14.0-py3-none-any.whl", hash = "sha256:e3fe4ac4b851c468cc8363d500db52c2ead036020723024a109d37346efaa761"}, - {file = "h11-0.14.0.tar.gz", hash = "sha256:8f19fbbe99e72420ff35c00b27a34cb9937e902a8b810e2c88300c6f0a3b699d"}, -] - [[package]] name = "idna" version = "3.6" @@ -527,16 +452,6 @@ files = [ {file = "MarkupSafe-2.1.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:5bbe06f8eeafd38e5d0a4894ffec89378b6c6a625ff57e3028921f8ff59318ac"}, {file = "MarkupSafe-2.1.3-cp311-cp311-win32.whl", hash = "sha256:dd15ff04ffd7e05ffcb7fe79f1b98041b8ea30ae9234aed2a9168b5797c3effb"}, {file = "MarkupSafe-2.1.3-cp311-cp311-win_amd64.whl", hash = "sha256:134da1eca9ec0ae528110ccc9e48041e0828d79f24121a1a146161103c76e686"}, - {file = "MarkupSafe-2.1.3-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:f698de3fd0c4e6972b92290a45bd9b1536bffe8c6759c62471efaa8acb4c37bc"}, - {file = "MarkupSafe-2.1.3-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:aa57bd9cf8ae831a362185ee444e15a93ecb2e344c8e52e4d721ea3ab6ef1823"}, - {file = "MarkupSafe-2.1.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ffcc3f7c66b5f5b7931a5aa68fc9cecc51e685ef90282f4a82f0f5e9b704ad11"}, - {file = "MarkupSafe-2.1.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:47d4f1c5f80fc62fdd7777d0d40a2e9dda0a05883ab11374334f6c4de38adffd"}, - {file = "MarkupSafe-2.1.3-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1f67c7038d560d92149c060157d623c542173016c4babc0c1913cca0564b9939"}, - {file = "MarkupSafe-2.1.3-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:9aad3c1755095ce347e26488214ef77e0485a3c34a50c5a5e2471dff60b9dd9c"}, - {file = "MarkupSafe-2.1.3-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:14ff806850827afd6b07a5f32bd917fb7f45b046ba40c57abdb636674a8b559c"}, - {file = "MarkupSafe-2.1.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8f9293864fe09b8149f0cc42ce56e3f0e54de883a9de90cd427f191c346eb2e1"}, - {file = "MarkupSafe-2.1.3-cp312-cp312-win32.whl", hash = "sha256:715d3562f79d540f251b99ebd6d8baa547118974341db04f5ad06d5ea3eb8007"}, - {file = "MarkupSafe-2.1.3-cp312-cp312-win_amd64.whl", hash = "sha256:1b8dd8c3fd14349433c79fa8abeb573a55fc0fdd769133baac1f5e07abf54aeb"}, {file = "MarkupSafe-2.1.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:8e254ae696c88d98da6555f5ace2279cf7cd5b3f52be2b5cf97feafe883b58d2"}, {file = "MarkupSafe-2.1.3-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cb0932dc158471523c9637e807d9bfb93e06a95cbf010f1a38b98623b929ef2b"}, {file = "MarkupSafe-2.1.3-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9402b03f1a1b4dc4c19845e5c749e3ab82d5078d16a2a4c2cd2df62d57bb0707"}, @@ -627,20 +542,6 @@ files = [ {file = "mypy_extensions-1.0.0.tar.gz", hash = "sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782"}, ] -[[package]] -name = "outcome" -version = "1.3.0.post0" -description = "Capture the outcome of Python function calls." -optional = false -python-versions = ">=3.7" -files = [ - {file = "outcome-1.3.0.post0-py2.py3-none-any.whl", hash = "sha256:e771c5ce06d1415e356078d3bdd68523f284b4ce5419828922b6871e65eda82b"}, - {file = "outcome-1.3.0.post0.tar.gz", hash = "sha256:9dcf02e65f2971b80047b377468e72a268e15c0af3cf1238e6ff14f7f91143b8"}, -] - -[package.dependencies] -attrs = ">=19.2.0" - [[package]] name = "packaging" version = "23.2" @@ -722,17 +623,6 @@ tomli = ">=1.2.2" [package.extras] poetry-plugin = ["poetry (>=1.0,<2.0)"] -[[package]] -name = "pycparser" -version = "2.21" -description = "C parser in Python" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" -files = [ - {file = "pycparser-2.21-py2.py3-none-any.whl", hash = "sha256:8ee45429555515e1f6b185e78100aea234072576aa43ab53aefcae078162fca9"}, - {file = "pycparser-2.21.tar.gz", hash = "sha256:e644fdec12f7872f86c58ff790da456218b10f863970249516d60a5eaca77206"}, -] - [[package]] name = "pygments" version = "2.17.2" @@ -748,18 +638,6 @@ files = [ plugins = ["importlib-metadata"] windows-terminal = ["colorama (>=0.4.6)"] -[[package]] -name = "pysocks" -version = "1.7.1" -description = "A Python SOCKS client module. See https://github.com/Anorov/PySocks for more information." -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" -files = [ - {file = "PySocks-1.7.1-py27-none-any.whl", hash = "sha256:08e69f092cc6dbe92a0fdd16eeb9b9ffbc13cadfe5ca4c7bd92ffb078b293299"}, - {file = "PySocks-1.7.1-py3-none-any.whl", hash = "sha256:2725bd0a9925919b9b51739eea5f9e2bae91e83288108a9ad338b2e3a4435ee5"}, - {file = "PySocks-1.7.1.tar.gz", hash = "sha256:3f8804571ebe159c380ac6de37643bb4685970655d3bba243530d6558b799aa0"}, -] - [[package]] name = "pytest" version = "7.4.3" @@ -858,23 +736,6 @@ files = [ {file = "ruff-0.1.6.tar.gz", hash = "sha256:1b09f29b16c6ead5ea6b097ef2764b42372aebe363722f1605ecbcd2b9207184"}, ] -[[package]] -name = "selenium" -version = "4.15.2" -description = "" -optional = false -python-versions = ">=3.8" -files = [ - {file = "selenium-4.15.2-py3-none-any.whl", hash = "sha256:9e82cd1ac647fb73cf0d4a6e280284102aaa3c9d94f0fa6e6cc4b5db6a30afbf"}, - {file = "selenium-4.15.2.tar.gz", hash = "sha256:22eab5a1724c73d51b240a69ca702997b717eee4ba1f6065bf5d6b44dba01d48"}, -] - -[package.dependencies] -certifi = ">=2021.10.8" -trio = ">=0.17,<1.0" -trio-websocket = ">=0.9,<1.0" -urllib3 = {version = ">=1.26,<3", extras = ["socks"]} - [[package]] name = "setuptools" version = "69.0.2" @@ -891,17 +752,6 @@ docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "pygments testing = ["build[virtualenv]", "filelock (>=3.4.0)", "flake8-2020", "ini2toml[lite] (>=0.9)", "jaraco.develop (>=7.21)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "pip (>=19.1)", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy (>=0.9.1)", "pytest-perf", "pytest-ruff", "pytest-timeout", "pytest-xdist", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"] testing-integration = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "packaging (>=23.1)", "pytest", "pytest-enabler", "pytest-xdist", "tomli", "virtualenv (>=13.0.0)", "wheel"] -[[package]] -name = "sniffio" -version = "1.3.0" -description = "Sniff out which async library your code is running under" -optional = false -python-versions = ">=3.7" -files = [ - {file = "sniffio-1.3.0-py3-none-any.whl", hash = "sha256:eecefdce1e5bbfb7ad2eeaabf7c1eeb404d7757c379bd1f7e5cce9d8bf425384"}, - {file = "sniffio-1.3.0.tar.gz", hash = "sha256:e60305c5e5d314f5389259b7f22aaa33d8f7dee49763119234af3755c55b9101"}, -] - [[package]] name = "snowballstemmer" version = "2.2.0" @@ -913,17 +763,6 @@ files = [ {file = "snowballstemmer-2.2.0.tar.gz", hash = "sha256:09b16deb8547d3412ad7b590689584cd0fe25ec8db3be37788be3810cbf19cb1"}, ] -[[package]] -name = "sortedcontainers" -version = "2.4.0" -description = "Sorted Containers -- Sorted List, Sorted Dict, Sorted Set" -optional = false -python-versions = "*" -files = [ - {file = "sortedcontainers-2.4.0-py2.py3-none-any.whl", hash = "sha256:a163dcaede0f1c021485e957a39245190e74249897e2ae4b2aa38595db237ee0"}, - {file = "sortedcontainers-2.4.0.tar.gz", hash = "sha256:25caa5a06cc30b6b83d11423433f65d1f9d76c4c6a0c90e3379eaa43b9bfdb88"}, -] - [[package]] name = "sphinx" version = "7.1.2" @@ -1092,42 +931,6 @@ files = [ {file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"}, ] -[[package]] -name = "trio" -version = "0.23.1" -description = "A friendly Python library for async concurrency and I/O" -optional = false -python-versions = ">=3.8" -files = [ - {file = "trio-0.23.1-py3-none-any.whl", hash = "sha256:bb4abb3f4af23f96679e7c8cdabb8b234520f2498550d2cf63ebfd95f2ce27fe"}, - {file = "trio-0.23.1.tar.gz", hash = "sha256:16f89f7dcc8f7b9dcdec1fcd863e0c039af6d0f9a22f8dfd56f75d75ec73fd48"}, -] - -[package.dependencies] -attrs = ">=20.1.0" -cffi = {version = ">=1.14", markers = "os_name == \"nt\" and implementation_name != \"pypy\""} -exceptiongroup = {version = ">=1.0.0rc9", markers = "python_version < \"3.11\""} -idna = "*" -outcome = "*" -sniffio = ">=1.3.0" -sortedcontainers = "*" - -[[package]] -name = "trio-websocket" -version = "0.11.1" -description = "WebSocket library for Trio" -optional = false -python-versions = ">=3.7" -files = [ - {file = "trio-websocket-0.11.1.tar.gz", hash = "sha256:18c11793647703c158b1f6e62de638acada927344d534e3c7628eedcb746839f"}, - {file = "trio_websocket-0.11.1-py3-none-any.whl", hash = "sha256:520d046b0d030cf970b8b2b2e00c4c2245b3807853ecd44214acd33d74581638"}, -] - -[package.dependencies] -exceptiongroup = {version = "*", markers = "python_version < \"3.11\""} -trio = ">=0.11" -wsproto = ">=0.14" - [[package]] name = "typing-extensions" version = "4.8.0" @@ -1150,9 +953,6 @@ files = [ {file = "urllib3-2.1.0.tar.gz", hash = "sha256:df7aa8afb0148fa78488e7899b2c59b5f4ffcfa82e6c54ccb9dd37c1d7b52d54"}, ] -[package.dependencies] -pysocks = {version = ">=1.5.6,<1.5.7 || >1.5.7,<2.0", optional = true, markers = "extra == \"socks\""} - [package.extras] brotli = ["brotli (>=1.0.9)", "brotlicffi (>=0.8.0)"] socks = ["pysocks (>=1.5.6,!=1.5.7,<2.0)"] @@ -1239,20 +1039,6 @@ files = [ {file = "websockets-12.0.tar.gz", hash = "sha256:81df9cbcbb6c260de1e007e58c011bfebe2dafc8435107b0537f393dd38c8b1b"}, ] -[[package]] -name = "wsproto" -version = "1.2.0" -description = "WebSockets state-machine based protocol implementation" -optional = false -python-versions = ">=3.7.0" -files = [ - {file = "wsproto-1.2.0-py3-none-any.whl", hash = "sha256:b9acddd652b585d75b20477888c56642fdade28bdfd3579aa24a4d2c037dd736"}, - {file = "wsproto-1.2.0.tar.gz", hash = "sha256:ad565f26ecb92588a3e43bc3d96164de84cd9902482b130d0ddbaa9664a85065"}, -] - -[package.dependencies] -h11 = ">=0.9.0,<1" - [[package]] name = "zipp" version = "3.17.0" @@ -1274,4 +1060,4 @@ ws = ["websockets"] [metadata] lock-version = "2.0" python-versions = ">=3.8" -content-hash = "ab1b8b368f59bc08732b90a11a3082fc4659bf16b737b0406ef2647e6a88bc52" +content-hash = "b6a548a47694627a5328e68641475a23dc42737a17053cc67d38d1791b6c9105" diff --git a/pyproject.toml b/pyproject.toml index ca1d9465..5e6d6956 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -29,10 +29,8 @@ ws = ["websockets"] [tool.poetry.group.dev.dependencies] # Replaces (amongst many other things) flake8 and bandit ruff = ">=0.1.6" -# TODO `poethepoet>=0.20` needs python 3.8 poethepoet = ">=0.24.4" mypy = ">=1.7.1" -# TODO `black>=23` needs python 3.8 black = ">=23.11.0" [tool.poetry.group.test.dependencies] @@ -46,14 +44,11 @@ pytest-asyncio = ">=0.21.0" sphinx = ">=7.1.2" sphinx-rtd-theme = ">=1.3.0" -[tool.poetry.group.pyodide.dependencies] -selenium = "^4.15.2" - [tool.pytest.ini_options] asyncio_mode = "auto" [tool.poe.tasks] -test-pyodide = "python tests/pyodide_testrunner/run.py" +test-pyodide = "pytest tests/e2e --lsp-runtime pyodide" ruff = "ruff check ." mypy = "mypy -p pygls" check_generated_client = "python scripts/check_client_is_uptodate.py" diff --git a/tests/conftest.py b/tests/conftest.py index 2662744e..1581226b 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -73,8 +73,18 @@ def fn(*args): fpath = pathlib.Path(base_dir, *args) assert fpath.exists() - if runtime != "cpython": - raise NotImplementedError(f"uri_for: {runtime=}") + if runtime == "pyodide": + # Pyodide cannot see the whole file system, so this needs to be made relative to + # the workspace's parent folder + path = str(fpath).replace(str(WORKSPACE_DIR.parent), "") + return uris.from_fs_path(path) + + elif runtime == "wasi": + # WASI cannot see the whole filesystem, so this needs to be made relative to + # the repo root + path = str(fpath).replace(str(REPO_DIR), "") + return uris.from_fs_path(path) + return uris.from_fs_path(str(fpath)) return fn @@ -106,6 +116,60 @@ async def fn(server_name: str): return fn +def get_client_for_pyodide_server(uri_fixture): + """Return a client configured to communicate with a server running under Pyodide. + + This assumes that the pyodide environment has already been bootstrapped. + """ + + async def fn(server_name: str): + client = LanguageClient("pygls-test-suite", "v1") + + PYODIDE_DIR = REPO_DIR / "tests" / "pyodide" + server_py = str(SERVER_DIR / server_name) + + await client.start_io("node", str(PYODIDE_DIR / "run_server.js"), server_py) + + response = await client.initialize( + types.InitializeParams( + capabilities=types.ClientCapabilities(), + root_uri=uri_fixture(""), + ) + ) + assert response is not None + return client, response + + return fn + + +def get_client_for_wasi_server(uri_fixture): + """Return a client configured to communicate with a server running under WASI. + + This assumes the ``wasmtime`` executable is available to be used as the WASI host. + """ + + async def fn(server_name: str): + client = LanguageClient("pygls-test-suite", "v1") + + # WASI cannot see the whole filesystem, so this needs to be made relative to the + # repo root + server_py = str(SERVER_DIR / server_name).replace(str(REPO_DIR), "") + + # TODO: Un-nixfiy this + await client.start_io("python-wasi", server_py) + + response = await client.initialize( + types.InitializeParams( + capabilities=types.ClientCapabilities(), + root_uri=uri_fixture(""), + ) + ) + assert response is not None + return client, response + + return fn + + @pytest.fixture(scope="session") def get_client_for(runtime, uri_for): """Return a client configured to communicate with the specified server. @@ -115,9 +179,15 @@ def get_client_for(runtime, uri_for): It's the consuming fixture's responsibility to stop the client. """ # TODO: Add TCP/WS support. - if runtime != "cpython": + if runtime not in {"cpython", "pyodide", "wasi"}: raise NotImplementedError(f"get_client_for: {runtime=}") + elif runtime == "pyodide": + return get_client_for_pyodide_server(uri_for) + + elif runtime == "wasi": + return get_client_for_wasi_server(uri_for) + return get_client_for_cpython_server(uri_for) diff --git a/tests/pyodide/.gitignore b/tests/pyodide/.gitignore new file mode 100644 index 00000000..76438e01 --- /dev/null +++ b/tests/pyodide/.gitignore @@ -0,0 +1,2 @@ +*.log +node_modules/ \ No newline at end of file diff --git a/tests/pyodide/package-lock.json b/tests/pyodide/package-lock.json new file mode 100644 index 00000000..62125152 --- /dev/null +++ b/tests/pyodide/package-lock.json @@ -0,0 +1,49 @@ +{ + "name": "pyodide_tests", + "version": "0.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "pyodide_tests", + "version": "0.0.0", + "dependencies": { + "pyodide": "^0.24.1" + } + }, + "node_modules/base-64": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/base-64/-/base-64-1.0.0.tgz", + "integrity": "sha512-kwDPIFCGx0NZHog36dj+tHiwP4QMzsZ3AgMViUBKI0+V5n4U0ufTCUMhnQ04diaRI8EX/QcPfql7zlhZ7j4zgg==" + }, + "node_modules/pyodide": { + "version": "0.24.1", + "resolved": "https://registry.npmjs.org/pyodide/-/pyodide-0.24.1.tgz", + "integrity": "sha512-fkNolNwiv41E2KKCP2bgW+dwr95B+0KSC/WG9WCmlWM9MNFbRVX0rF9i4OikRM78bGeVUvLdVJw8jY17wxKoRQ==", + "dependencies": { + "base-64": "^1.0.0", + "ws": "^8.5.0" + } + }, + "node_modules/ws": { + "version": "8.16.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.16.0.tgz", + "integrity": "sha512-HS0c//TP7Ina87TfiPUz1rQzMhHrl/SG2guqRcTOIUYD2q8uhUdNHZYJUaQ8aTGPzCh+c6oawMKW35nFl1dxyQ==", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + } + } +} diff --git a/tests/pyodide/package.json b/tests/pyodide/package.json new file mode 100644 index 00000000..7116640d --- /dev/null +++ b/tests/pyodide/package.json @@ -0,0 +1,10 @@ +{ + "name": "pyodide_tests", + "version": "0.0.0", + "description": "Simple wrapper that executes pygls servers in Pyodide", + "main": "run_server.js", + "author": "openlawlibrary", + "dependencies": { + "pyodide": "^0.24.1" + } +} diff --git a/tests/pyodide/run_server.js b/tests/pyodide/run_server.js new file mode 100644 index 00000000..0475ca3c --- /dev/null +++ b/tests/pyodide/run_server.js @@ -0,0 +1,98 @@ +const fs = require('fs'); +const path = require('path') +const { loadPyodide } = require('pyodide'); + +const consoleLog = console.log + +const WORKSPACE = path.join(__dirname, "..", "..", "examples", "servers", "workspace") +const DIST = path.join(__dirname, "..", "..", "dist") + +// Create a file to log pyodide output to. +const logFile = fs.createWriteStream("pyodide.log") + +function writeToFile(...args) { + logFile.write(args[0] + `\n`); +} + +// Load the workspace into the pyodide runtime. +// +// Unlike WASI, there is no "just works" solution for exposing the workspace/ folder +// to the runtime - it's up to us to manually copy it into pyodide's in-memory filesystem. +function loadWorkspace(pyodide) { + const FS = pyodide.FS + + // Create a folder for the workspace to be copied into. + FS.mkdir('/workspace') + + const workspace = fs.readdirSync(WORKSPACE) + workspace.forEach((file) => { + try { + const filename = "/" + path.join("workspace", file) + // consoleLog(`${file} -> ${filename}`) + + const stream = FS.open(filename, 'w+') + const data = fs.readFileSync(path.join(WORKSPACE, file)) + + FS.write(stream, data, 0, data.length, 0) + FS.close(stream) + } catch (err) { + consoleLog(err) + } + }) +} + +// Find the *.whl file containing the build of pygls to test. +function findWhl() { + const files = fs.readdirSync(DIST); + const whlFile = files.find(file => /pygls-.*\.whl/.test(file)); + + if (whlFile) { + return path.join(DIST, whlFile); + } else { + throw new Error("Unable to find whl file."); + } +} + +async function runServer(serverCode) { + // Annoyingly, while we can redirect stderr/stdout to a file during this setup stage + // it doesn't prevent `micropip.install` from indirectly writing to console.log. + // + // Internally, `micropip.install` calls `pyodide.loadPackage` and doesn't expose loadPacakge's + // options for redirecting output i.e. messageCallback. + // + // So instead, we override console.log globally. + console.log = writeToFile + const pyodide = await loadPyodide({ + // stdin: + stderr: writeToFile, + }) + + loadWorkspace(pyodide) + + await pyodide.loadPackage("micropip") + const micropip = pyodide.pyimport("micropip") + await micropip.install(`file://${findWhl()}`) + + // Restore the original console.log + console.log = consoleLog + await pyodide.runPythonAsync(serverCode) +} + +if (process.argv.length < 3) { + console.error("Missing server.py file") + process.exit(1) +} + + +const serverCode = fs.readFileSync(process.argv[2], 'utf8') + +logFile.once('open', (fd) => { + runServer(serverCode).then(() => { + logFile.end(); + process.exit(0) + }).catch(err => { + logFile.write(`Error in server process\n${err}`) + logFile.end(); + process.exit(1); + }) +}) diff --git a/tests/pyodide_testrunner/.gitignore b/tests/pyodide_testrunner/.gitignore deleted file mode 100644 index 704d3075..00000000 --- a/tests/pyodide_testrunner/.gitignore +++ /dev/null @@ -1 +0,0 @@ -*.whl diff --git a/tests/pyodide_testrunner/index.html b/tests/pyodide_testrunner/index.html deleted file mode 100644 index b3e1b8b4..00000000 --- a/tests/pyodide_testrunner/index.html +++ /dev/null @@ -1,56 +0,0 @@ - - - - - - - - - Pygls Testsuite - - - - - -
-

-    
- - - - - diff --git a/tests/pyodide_testrunner/run.py b/tests/pyodide_testrunner/run.py deleted file mode 100644 index 7b563745..00000000 --- a/tests/pyodide_testrunner/run.py +++ /dev/null @@ -1,131 +0,0 @@ -import os -import pathlib -import shutil -import subprocess -import sys -import tempfile - -from functools import partial -from http.server import SimpleHTTPRequestHandler, ThreadingHTTPServer -from multiprocessing import Process, Queue - -from selenium import webdriver -from selenium.webdriver.common.by import By -from selenium.webdriver.support.ui import WebDriverWait -from selenium.common.exceptions import WebDriverException -from selenium.webdriver.support import expected_conditions as EC - - -# Path to the root of the repo. -REPO = pathlib.Path(__file__).parent.parent.parent -BROWSERS = { - "chrome": (webdriver.Chrome, webdriver.ChromeOptions), - "firefox": (webdriver.Firefox, webdriver.FirefoxOptions), -} - - -def build_wheel() -> str: - """Build a wheel package of ``pygls`` and its testsuite. - - In order to test pygls under pyodide, we need to load the code for both pygls and its - testsuite. This is done by building a wheel. - - To avoid messing with the repo this is all done under a temp directory. - """ - - with tempfile.TemporaryDirectory() as tmpdir: - # Copy all required files. - dest = pathlib.Path(tmpdir) - - # So that we don't have to fuss with packaging, copy the test suite into `pygls` - # as a sub module. - directories = [("pygls", "pygls"), ("tests", "pygls/tests")] - - for src, target in directories: - shutil.copytree(REPO / src, dest / target) - - files = ["pyproject.toml", "poetry.lock", "README.md", "ThirdPartyNotices.txt"] - - for src in files: - shutil.copy(REPO / src, dest) - - # Convert the lock file to requirements.txt. - # Ensures reproducible behavour for testing. - subprocess.run( - [ - "poetry", - "export", - "-f", - "requirements.txt", - "--output", - "requirements.txt", - ], - cwd=dest, - ) - subprocess.run( - ["poetry", "run", "pip", "install", "-r", "requirements.txt"], cwd=dest - ) - # Build the wheel - subprocess.run(["poetry", "build", "--format", "wheel"], cwd=dest) - whl = list((dest / "dist").glob("*.whl"))[0] - shutil.copy(whl, REPO / "tests/pyodide_testrunner") - - return whl.name - - -def spawn_http_server(q: Queue, directory: str): - """A http server is needed to serve the files to the browser.""" - - handler_class = partial(SimpleHTTPRequestHandler, directory=directory) - server = ThreadingHTTPServer(("localhost", 0), handler_class) - q.put(server.server_port) - - server.serve_forever() - - -def main(): - exit_code = 1 - whl = build_wheel() - - q = Queue() - server_process = Process( - target=spawn_http_server, - args=(q, REPO / "tests/pyodide_testrunner"), - daemon=True, - ) - server_process.start() - port = q.get() - - print("Running tests...") - try: - driver_cls, options_cls = BROWSERS[os.environ.get("BROWSER", "chrome")] - - options = options_cls() - if "CI" in os.environ: - options.binary_location = "/usr/bin/google-chrome" - options.add_argument("--headless") - - driver = driver_cls(options=options) - driver.get(f"http://localhost:{port}?whl={whl}") - - wait = WebDriverWait(driver, 120) - try: - button = wait.until(EC.element_to_be_clickable((By.ID, "exit-code"))) - exit_code = int(button.text) - except WebDriverException as e: - print(f"Error while running test: {e!r}") - exit_code = 1 - - console = driver.find_element(By.ID, "console") - print(console.text) - finally: - if hasattr(server_process, "kill"): - server_process.kill() - else: - server_process.terminate() - - return exit_code - - -if __name__ == "__main__": - sys.exit(main()) diff --git a/tests/pyodide_testrunner/test-runner.js b/tests/pyodide_testrunner/test-runner.js deleted file mode 100644 index dbbc01fd..00000000 --- a/tests/pyodide_testrunner/test-runner.js +++ /dev/null @@ -1,38 +0,0 @@ -importScripts("https://cdn.jsdelivr.net/pyodide/v0.21.3/full/pyodide.js") - -// Used to redirect pyodide's stdout to the webpage. -function patchedStdout(...args) { - postMessage(args[0]) -} - -async function runTests(whl) { - console.log("Loading pyodide") - let pyodide = await loadPyodide({ - indexURL: "https://cdn.jsdelivr.net/pyodide/v0.21.3/full/" - }) - - console.log("Installing dependencies") - await pyodide.loadPackage("micropip") - await pyodide.runPythonAsync(` - import sys - import micropip - - await micropip.install('pytest') - await micropip.install('pytest-asyncio') - await micropip.install('${whl}') - `) - - console.log('Running testsuite') - - // Patch stdout to redirect the output. - pyodide.globals.get('sys').stdout.write = patchedStdout - await pyodide.runPythonAsync(` - import pytest - exit_code = pytest.main(['--color', 'no', '--pyargs', 'pygls.tests']) - `) - - postMessage({ exitCode: pyodide.globals.get('exit_code') }) -} - -let queryParams = new URLSearchParams(self.location.search) -runTests(queryParams.get('whl'))