diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index c1fab71c7d..fe41436285 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -73,34 +73,16 @@ jobs: with: python-version: ${{ inputs.python_version }} - - name: "Create virtual environment in the current workspace" - shell: pwsh - run: python -m venv .venv --system-site-packages - - - name: "Update PATH for virtual environment" - run: | - echo "${{ github.workspace }}\.venv\Scripts" >> $env:GITHUB_PATH - - - name: "Update pip to the latest version" + - name: "Update pip to the latest version and install tox" shell: pwsh run: | python -m pip install -U pip - - - name: "Install requirements" - shell: pwsh - run: | - python -m pip install -r requirements/requirements_build.txt + python -m pip install tox tox-uv - name: "Build the wheel" shell: pwsh run: | - $os = "${{ matrix.os }}" - if ($os -eq "ubuntu-latest") { - $platform = "manylinux_2_17" - } else { - $platform = "win" - } - python .ci/build_wheel.py -p $platform -w + tox -e build-wheel - name: "Expose the wheel" shell: bash @@ -111,11 +93,6 @@ jobs: echo ${name} echo "wheel_name=${name[0]}" >> $GITHUB_OUTPUT - - name: "Install package wheel" - shell: pwsh - run: | - python -m pip install dist/${{ steps.wheel.outputs.wheel_name }}[plotting] - - name: "Install DPF" id: set-server-path uses: ansys/pydpf-actions/install-dpf-server@v2.3 @@ -128,9 +105,9 @@ jobs: uses: ansys/pydpf-actions/check-licenses@v2.3 - name: "Test import" - shell: pwsh - working-directory: tests + shell: bash run: | + python -m pip install dist/${{ steps.wheel.outputs.wheel_name }} python -c "from ansys.dpf import core" - name: "Setup headless display" @@ -143,53 +120,10 @@ jobs: run: | choco install pandoc - - name: "Install documentation packages for Python" - shell: pwsh - run: | - python -m pip install -r requirements/requirements_docs.txt - - - name: "Kill all servers" - uses: ansys/pydpf-actions/kill-dpf-servers@v2.3 - - - name: "Ensure VTK compatibility" - shell: pwsh - run: | - python -m pip uninstall --yes vtk - python -m pip install --extra-index-url https://wheels.vtk.org vtk-osmesa==${{ env.VTK_OSMESA_VERSION }} - - - name: "List installed packages" - shell: pwsh - run: | - python -m pip list - - name: "Build HTML Documentation" - shell: cmd /D /E:ON /V:OFF /C "CALL "{0}"" - working-directory: .ci - run: | - build_doc.bat > ..\doc\log.txt && type ..\doc\log.txt 2>&1 - timeout-minutes: 60 - env: - MEILISEARCH_PUBLIC_API_KEY: ${{ secrets.MEILISEARCH_PUBLIC_API_KEY }} - - - name: "Check for success" shell: bash - working-directory: doc run: | - case `grep -F "build succeeded" log.txt >/dev/null; echo $?` in - 0) - echo "Build succeeded!" - exit 0;; - 1) - echo "Documentation generation failed, please check previous step!" - exit 1;; - *) - echo "An error occurred while checking success of the previous step!" - exit 1;; - esac - - - name: "Kill all servers" - uses: ansys/pydpf-actions/kill-dpf-servers@v2.3 - if: always() + tox -e doc-html --installpkg dist/${{ steps.wheel.outputs.wheel_name }} -x testenv:doc-html.setenv+='VIRTUALENV_SYSTEM_SITE_PACKAGES=true' - name: "Retrieve package version" shell: bash diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 1a4b7ce5b9..774918d704 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -15,15 +15,15 @@ on: wheel: required: false type: string - default: false + default: 'false' wheelhouse: required: false type: string - default: false + default: 'false' DOCSTRING: required: false type: string - default: true + default: 'true' standalone_suffix: description: "Suffix of the branch on standalone" required: false @@ -33,7 +33,7 @@ on: description: "Test the any version of the wheel" required: false type: string - default: false + default: 'false' # Can be called manually workflow_dispatch: inputs: @@ -91,7 +91,7 @@ jobs: tests: name: "Tests" needs: setup - timeout-minutes: 35 + timeout-minutes: 120 runs-on: ${{ matrix.os }} strategy: fail-fast: false @@ -114,8 +114,20 @@ jobs: with: python-version: ${{ matrix.python-version }} - - name: "Install requirements" - run: pip install -r requirements/requirements_build.txt + - name: "Update pip to the latest version and install tox" + shell: pwsh + run: | + python -m pip install -U pip + python -m pip install -U tox tox-uv + + - name: Cache tox environments + uses: actions/cache@v4 + with: + path: | + .tox + key: ${{ matrix.os }}-${{ matrix.python-version }}-${{ inputs.ANSYS_VERSION }}-${{ hashFiles('requirements/*', 'pyproject.toml') }} + restore-keys: | + ${{ matrix.os }}-${{ matrix.python-version }}-${{ inputs.ANSYS_VERSION }}- - name: "Build the wheel" shell: bash @@ -127,7 +139,7 @@ jobs: else export platform="win" fi - python .ci/build_wheel.py -p $platform -w + tox -e build-wheel -- $platform - name: "Expose the wheel" shell: bash @@ -173,11 +185,6 @@ jobs: path: ${{ steps.wheelhouse.outputs.name }} retention-days: 7 - - name: "Install package wheel" - shell: bash - run: | - pip install dist/${{ steps.wheel.outputs.wheel_name }}[plotting] - - name: "Install DPF" id: set-server-path uses: ansys/pydpf-actions/install-dpf-server@v2.3 @@ -191,18 +198,15 @@ jobs: - name: "Test import" shell: bash - working-directory: tests - run: python -c "from ansys.dpf import core" + run: | + python -m pip install dist/${{ steps.wheel.outputs.wheel_name }} + python -c "from ansys.dpf import core" - name: "Prepare Testing Environment" uses: ansys/pydpf-actions/prepare_tests@v2.3 with: DEBUG: true - - name: "List installed packages" - shell: bash - run: pip list - - name: "Test Docstrings" if: (inputs.DOCSTRING == 'true') && !((inputs.test_any == 'true') && (matrix.os == 'ubuntu-latest')) uses: ansys/pydpf-actions/test_docstrings@v2.3 @@ -211,154 +215,129 @@ jobs: PACKAGE_NAME: ${{env.PACKAGE_NAME}} working-directory: src - - name: "Separate long Core tests" - shell: pwsh + - name: "Set tox extra CLI arguments" + id: tox-cli-arguments + shell: bash run: | - .github\workflows\scripts\separate_long_core_tests.ps1 + if [ $MODE == 'PIP' ]; then + if [ ${{ matrix.os }} == 'ubuntu-latest' ]; then + echo 'TOX_EXTRA_ARG=--installpkg dist/${{ steps.wheel.outputs.wheel_name }} -x testenv.deps+="-e dpf-standalone/v${{inputs.ANSYS_VERSION}}" -x testenv.commands_pre+="uv pip install --extra-index-url https://wheels.vtk.org vtk-osmesa==9.2.20230527.dev0"' >> "$GITHUB_OUTPUT" + else + echo 'TOX_EXTRA_ARG=--installpkg dist/${{ steps.wheel.outputs.wheel_name }} -x testenv.deps+="-e dpf-standalone/v${{inputs.ANSYS_VERSION}}"' >> "$GITHUB_OUTPUT" + fi + else + if [ ${{ matrix.os }} == 'ubuntu-latest' ]; then + echo 'TOX_EXTRA_ARG=--installpkg dist/${{ steps.wheel.outputs.wheel_name }} -x testenv.commands_pre+="uv pip install --extra-index-url https://wheels.vtk.org vtk-osmesa==9.2.20230527.dev0"' >> "$GITHUB_OUTPUT" + else + echo 'TOX_EXTRA_ARG=--installpkg dist/${{ steps.wheel.outputs.wheel_name }}' >> "$GITHUB_OUTPUT" + fi + fi - - name: "Set pytest arguments" + - name: "Organize test files" shell: bash run: | - echo "COVERAGE=--cov=ansys.dpf.${{env.MODULE}} --cov-report=xml --cov-report=html --log-level=ERROR --cov-append" >> $GITHUB_ENV - echo "RERUNS=--reruns 2 --reruns-delay 1" >> $GITHUB_ENV + tox -e pretest - name: "Test API" shell: bash - working-directory: tests run: | - pytest $DEBUG $COVERAGE $RERUNS --junitxml=junit/test-results.xml . - - - name: "Kill all servers" - uses: ansys/pydpf-actions/kill-dpf-servers@v2.3 - + tox -e test-api,kill-servers ${{ steps.tox-cli-arguments.outputs.TOX_EXTRA_ARG }} + - name: "Test API test_launcher" uses: nick-fields/retry@v3 with: - timeout_minutes: 2 + timeout_minutes: 10 max_attempts: 2 shell: bash command: | - pytest $DEBUG $COVERAGE $RERUNS --junitxml=../tests/junit/test-results2.xml test_launcher/. - - - name: "Kill all servers" - uses: ansys/pydpf-actions/kill-dpf-servers@v2.3 + tox -e test-launcher,kill-servers ${{ steps.tox-cli-arguments.outputs.TOX_EXTRA_ARG }} - name: "Test API test_server" uses: nick-fields/retry@v3 with: - timeout_minutes: 5 + timeout_minutes: 10 max_attempts: 2 shell: bash command: | - pytest $DEBUG $COVERAGE $RERUNS --junitxml=../tests/junit/test-results3.xml test_server/. - - - name: "Kill all servers" - uses: ansys/pydpf-actions/kill-dpf-servers@v2.3 + tox -e test-server,kill-servers ${{ steps.tox-cli-arguments.outputs.TOX_EXTRA_ARG }} - name: "Test API test_local_server" uses: nick-fields/retry@v3 with: - timeout_minutes: 2 + timeout_minutes: 10 max_attempts: 2 shell: bash command: | - pytest $DEBUG $COVERAGE $RERUNS --junitxml=../tests/junit/test-results4.xml test_local_server/. - - - name: "Kill all servers" - uses: ansys/pydpf-actions/kill-dpf-servers@v2.3 + tox -e test-local_server,kill-servers ${{ steps.tox-cli-arguments.outputs.TOX_EXTRA_ARG }} - name: "Test API test_multi_server" uses: nick-fields/retry@v3 with: - timeout_minutes: 5 + timeout_minutes: 10 max_attempts: 2 shell: bash command: | - pytest $DEBUG $COVERAGE $RERUNS --junitxml=../tests/junit/test-results5.xml test_multi_server/. - - - name: "Kill all servers" - uses: ansys/pydpf-actions/kill-dpf-servers@v2.3 + tox -e test-multi_server,kill-servers ${{ steps.tox-cli-arguments.outputs.TOX_EXTRA_ARG }} - name: "Test API test_remote_workflow" uses: nick-fields/retry@v3 with: - timeout_minutes: 4 + timeout_minutes: 10 max_attempts: 3 shell: bash command: | - pytest $DEBUG $COVERAGE $RERUNS --junitxml=../tests/junit/test-results6.xml test_remote_workflow/. - - - name: "Kill all servers" - uses: ansys/pydpf-actions/kill-dpf-servers@v2.3 + tox -e test-remote_workflow,kill-servers ${{ steps.tox-cli-arguments.outputs.TOX_EXTRA_ARG }} - name: "Test API test_remote_operator" shell: bash - working-directory: test_remote_operator run: | - pytest $DEBUG $COVERAGE $RERUNS --junitxml=../tests/junit/test-results7.xml . - - - name: "Kill all servers" - uses: ansys/pydpf-actions/kill-dpf-servers@v2.3 + tox -e test-remote_operator,kill-servers ${{ steps.tox-cli-arguments.outputs.TOX_EXTRA_ARG }} - name: "Test API test_workflow" uses: nick-fields/retry@v3 with: - timeout_minutes: 3 + timeout_minutes: 10 max_attempts: 4 - retry_wait_seconds: 15 shell: bash command: | - pytest $DEBUG $COVERAGE $RERUNS --junitxml=../tests/junit/test-results8.xml test_workflow/. - - - name: "Kill all servers" - uses: ansys/pydpf-actions/kill-dpf-servers@v2.3 + tox -e test-workflow,kill-servers ${{ steps.tox-cli-arguments.outputs.TOX_EXTRA_ARG }} - name: "Test API test_service" uses: nick-fields/retry@v3 with: - timeout_minutes: 3 + timeout_minutes: 10 max_attempts: 2 shell: bash command: | - pytest $DEBUG $COVERAGE $RERUNS --junitxml=tests/junit/test-results9.xml test_service/. - - - name: "Kill all servers" - uses: ansys/pydpf-actions/kill-dpf-servers@v2.3 + tox -e test-service,kill-servers ${{ steps.tox-cli-arguments.outputs.TOX_EXTRA_ARG }} - name: "Test API Entry" shell: bash - working-directory: tests run: | - cd entry - pytest $DEBUG $COVERAGE $RERUNS --junitxml=../junit/test-results10.xml . - timeout-minutes: 30 - - - name: "Kill all servers" - uses: ansys/pydpf-actions/kill-dpf-servers@v2.3 + tox -e test-api_entry,kill-servers ${{ steps.tox-cli-arguments.outputs.TOX_EXTRA_ARG }} - name: "Test API test_custom_type_field" uses: nick-fields/retry@v3 with: - timeout_minutes: 2 + timeout_minutes: 10 max_attempts: 2 shell: bash command: | - pytest $DEBUG $COVERAGE $RERUNS --junitxml=../tests/junit/test-results11.xml test_custom_type_field/. - - - name: "Kill all servers" - uses: ansys/pydpf-actions/kill-dpf-servers@v2.3 + tox -e test-custom_type_field,kill-servers ${{ steps.tox-cli-arguments.outputs.TOX_EXTRA_ARG }} - name: "Test Operators" uses: nick-fields/retry@v3 with: - timeout_minutes: 2 + timeout_minutes: 10 max_attempts: 2 shell: bash command: | - pytest $DEBUG $COVERAGE $RERUNS --junitxml=../tests/junit/test-results12.xml tests/operators/. + tox -e test-operators,kill-servers ${{ steps.tox-cli-arguments.outputs.TOX_EXTRA_ARG }} - - name: "Kill all servers" - uses: ansys/pydpf-actions/kill-dpf-servers@v2.3 + - name: "Combine coverage results" + shell: bash + run: | + tox -e covreport - name: "Upload Test Results" uses: actions/upload-artifact@v4 @@ -370,5 +349,6 @@ jobs: uses: codecov/codecov-action@v4 with: token: ${{ secrets.CODECOV_TOKEN }} # required + file: ./.tox/.cov/coverage.xml name: ${{ env.PACKAGE_NAME }}_${{ matrix.python-version }}_${{ matrix.os }}_pytest_${{ inputs.ANSYS_VERSION }}${{ inputs.test_any == 'true' && '_any' || '' }}.xml flags: ${{ inputs.ANSYS_VERSION }},${{ matrix.os }},${{ matrix.python-version }}${{ inputs.test_any == 'true' && ',any' || '' }} diff --git a/tox.ini b/tox.ini index efaa78ea11..d89e3b7d75 100644 --- a/tox.ini +++ b/tox.ini @@ -2,45 +2,93 @@ # Usage instructions: -# `tox` will run all tests sequentially, `tox --parallel` will run all tests in parallel (much faster). -# Run specific selection of tests with `tox -e pretest,,posttest` e.g., `tox -e pretest,test-api,test-launcher,posttest` -# `--parallel` flag can be passed when running specific selections. +# `tox` will run all "envlist" tests sequentially, `tox --parallel` same tests in parallel (much faster). +# "othertests" are advisable to run sequentially as parallel running can lead to segmentation faults and weird errors. Hence, +# the reason for their separation. `tox -m othertests` will run these tests sequentially. + +# Run specific selection of tests with `tox -e pretest,,posttest,kill-servers` e.g., `tox -e pretest,test-api,test-launcher,posttest,kill-servers` +# `--parallel` flag can also be passed when running specific selections. # For packaging, build wheels for specific platform with `tox -e build-wheel -- `. # If `tox -e build-wheel` is run without passing a platform, tox will automatically build the ffl wheels based on the operating system # on which it is executing: windows -> "win_amd64", linux -> "manylinux_2_17_x86_64", mac -> "any" +# For html documentation generation, run `tox -e doc-html`. To clean previously existing documentation before generating +# a new one, run `tox -e doc-clean,doc-html` + +# Current tox configuration can automatically detect DPF server installation in these cases: +# - Unified install +# - if ANSYS_DPF_PATH is set and points to a valid DPF server installation +# Which means invoking tox with previous commands necessitate having a server available via above methods + +# Understandably for development purposes, more flexibility may be desired and there are various ways of invoking tox to achieve desired effects. +# For example, to use a standalone dpf server (present in ansys_dpf_server_win_v2025.1.pre0/ directory) in editable mode in each tox environment, +# you can do something like `tox -m othertests -x testenv.deps+="-e ansys_dpf_server_win_v2025.1.pre0"`. +# The tox documentation should be consulted for a quick overview of different CLI flags that can be used to customize invocation. + [tox] description = Default tox environment list and core configurations -envlist = pretest,test-{api,launcher,server,local_server,multi_server,remote_workflow,remote_operator,workflow,service,operators},posttest +envlist = pretest,test-{api,launcher,server,local_server,multi_server,api_entry,custom_type_field,operators},posttest,kill-servers +labels = + othertests = pretest,test-{workflow,remote_workflow,remote_operator,service},posttest,kill-servers + isolated_build_env = build [testenv] description = Default configuration for test environments, unless overridden +uv_seed = true + pass_env = PACKAGE_NAME MODULE ANSYS_DPF_ACCEPT_LA ANSYSLMD_LICENSE_FILE - AWP_ROOT242 + AWP_ROOT* + ANSYS_DPF_PATH -[testenv:pretest] -description = Environment to kill servers and organize test files prior to testing +deps = + -r requirements/requirements_test.txt + +[testenv:build-wheel] +description = Environment for custom build of package wheels + +skip_install = True + +deps = + -r requirements/requirements_build.txt + +commands = + python .ci/build_wheel.py -p {posargs:{on_platform}} -w + +[testenv:kill-servers] +description = Environment for clearing running servers + +depends = test-{api,launcher,server,local_server,multi_server,remote_workflow,remote_operator,workflow,service,api_entry,custom_type_field,operators} deps = psutil +commands_pre = + skip_install = True commands = - # Clear any running servers that may be locking resources python -c "import psutil; proc_name = 'Ans.Dpf.Grpc'; nb_procs = len([proc.kill() for proc in psutil.process_iter() if proc_name in proc.name()]); \ print(f'Killed \{nb_procs} \{proc_name} processes.')" - # Organize test files +[testenv:pretest] +description = Environment to organize test files prior to testing + +skip_install = True + +deps = + +commands_pre = + +commands = python -c "\ import os, shutil; \ test_data=['test_launcher','test_server','test_local_server','test_multi_server','test_workflow','test_remote_workflow','test_remote_operator','test_service','test_custom_type_field']; \ @@ -48,37 +96,34 @@ commands = [os.remove(f'tests/\{d}.py') for d in test_data if os.path.exists(f'tests/\{d}.py')]" [testenv:posttest] -description = Environment to kill servers and revert test files to original state after testing +description = Environment to revert test files to original state after testing + +depends = pretest, test-{api,launcher,server,local_server,multi_server,remote_workflow,remote_operator,workflow,service,api_entry,custom_type_field,operators} -depends = pretest, test-{api,launcher,server,local_server,multi_server,remote_workflow,remote_operator,workflow,service,operators} +skip_install = True deps = - psutil -skip_install = True +commands_pre = commands = - # Revert project layout to previous state python -c "\ import os, shutil; \ test_data=['test_launcher','test_server','test_local_server','test_multi_server','test_workflow','test_remote_workflow','test_remote_operator','test_service', 'test_custom_type_field']; \ [shutil.move(f'\{d}/\{d}.py', f'tests/\{d}.py') for d in test_data if os.path.exists(f'\{d}/\{d}.py')]; \ [shutil.rmtree(d) for d in test_data if os.path.exists(d)]" - # Clear any running servers that may be locking resources - python -c "import psutil; proc_name = 'Ans.Dpf.Grpc'; nb_procs = len([proc.kill() for proc in psutil.process_iter() if proc_name in proc.name()]); \ - print(f'Killed \{nb_procs} \{proc_name} processes.')" - -[testenv:test-{api,launcher,server,local_server,multi_server,remote_workflow,remote_operator,workflow,service,operators}] +[testenv:test-{api,launcher,server,local_server,multi_server,remote_workflow,remote_operator,workflow,service,api_entry,custom_type_field,operators}] description = Environment where project testing configuration is defined depends = pretest setenv = # Pytest extra arguments - COVERAGE = --cov=ansys.dpf.core --cov-report=xml --cov-report=html --log-level=ERROR --cov-append + COVERAGE = --cov=ansys.dpf.core --log-level=ERROR --cov-report= RERUNS = --reruns=2 --reruns-delay=1 DEBUG = -v -s --durations=10 --durations-min=1.0 + COVERAGE_FILE = {work_dir}/.cov/.coverage.{env_name} api: JUNITXML = --junitxml=tests/junit/test-results.xml launcher: JUNITXML = --junitxml=tests/junit/test-results2.xml @@ -89,7 +134,9 @@ setenv = remote_operator: JUNITXML = --junitxml=tests/junit/test-results7.xml workflow: JUNITXML = --junitxml=tests/junit/test-results8.xml service: JUNITXML = --junitxml=tests/junit/test-results9.xml - operators: JUNITXML = --junitxml=../tests/junit/test-results12.xml + api_entry: JUNITXML = --junitxml=tests/junit/test-results10.xml + custom_type_field: JUNITXML = --junitxml=tests/junit/test-results11.xml + operators: JUNITXML = --junitxml=tests/junit/test-results12.xml # Tests sets api: PYTEST_PYTHON_FILES = tests @@ -101,13 +148,27 @@ setenv = remote_operator: PYTEST_PYTHON_FILES = test_remote_operator workflow: PYTEST_PYTHON_FILES = test_workflow service: PYTEST_PYTHON_FILES = test_service + api_entry: PYTEST_PYTHON_FILES = tests/entry + custom_type_field: PYTEST_PYTHON_FILES = test_custom_type_field operators: PYTEST_PYTHON_FILES = tests/operators -deps = - -r requirements/requirements_test.txt + TEMP = {env_tmp_dir} + TMP = {env_tmp_dir} commands = - pytest {env:PYTEST_PYTHON_FILES} {env:DEBUG} {env:COVERAGE} {env:RERUNS} {env:JUNITXML} + python -m pytest {env:PYTEST_PYTHON_FILES} {env:DEBUG} {env:RERUNS} {env:JUNITXML} {env:COVERAGE} {posargs} + +[testenv:covreport] +skip_install = true + +deps = coverage + +change_dir = {work_dir}/.cov + +commands = + coverage combine + coverage xml + coverage erase # deletes only .coverage data file, otherwise codecov action will generate coverage.xml report again [testenv:doc-{clean,links,html}] description = Environment for documentation generation @@ -122,7 +183,11 @@ setenv = skip_install = clean: True +extras = + html: plotting + deps = + clean: links,html: -r requirements/requirements_docs.txt commands_pre = @@ -154,7 +219,7 @@ commands = html: [shutil.rmtree(p) for p in ['{env:SOURCE_DIR}/_temp'] if exists(p)]" # Build documentation - html,links: sphinx-build -b {env:BUILDER} {env:SOURCE_DIR} {env:BUILD_DIR}/{env:BUILDER} {env:BUILDER_OPTS} + html,links: {env_bin_dir}/sphinx-build -b {env:BUILDER} {env:SOURCE_DIR} {env:BUILD_DIR}/{env:BUILDER} {env:BUILDER_OPTS} # Patch pyVista issue with elemental plots by copying necessary images html: python -c "\ @@ -162,16 +227,7 @@ commands = html: [(shutil.copy(src, 'build/html/_images') if os.path.exists(src) else print(f'Source not found: {src}')) for src in \ html: glob.glob('{env:SOURCE_DIR}/examples/04-advanced/02-volume_averaged_stress/*') + glob.glob('{env:SOURCE_DIR}/examples/12-fluids/02-fluids_results/*')]" - commands_post = # Clear any running servers that may be locking resources html,links: python -c "import psutil; proc_name = 'Ans.Dpf.Grpc'; nb_procs = len([proc.kill() for proc in psutil.process_iter() if proc_name in proc.name()]); \ html,links: print(f'Killed \{nb_procs} \{proc_name} processes.')" - -[testenv:build-wheel] -description = Environment for custom build of package wheels - -skip_install = True - -commands = - python .ci/build_wheel.py -p {posargs:{on_platform}} -w