Make GHA tests parallel by using xdist (#32361)
* Add two no-op jobs named "all-prechecks" and "all" These are a suggestion from @tgamblin, they are stable named markers we can use from gitlab and possibly for required checks to make CI more resilient to refactors changing the names of specific checks. * Enable parallel testing using xdist for unit testing in CI * Normalize tmp paths to deal with macos * add -u flag compatibility to spack python As of now, it is accepted and ignored. The usage with xdist, where it is invoked specifically by `python -u spack python` which is then passed `-u` by xdist is the entire reason for doing this. It should never be used without explicitly passing -u to the executing python interpreter. * use spack python in xdist to support python 2 When running on python2, spack has many import cycles unless started through main. To allow that, this uses `spack python` as the interpreter, leveraging the `-u` support so xdist doesn't error out when it unconditionally requests unbuffered binary IO. * Use shutil.move to account for tmpdir being in a separate filesystem sometimes
This commit is contained in:
parent
8e5ccddc13
commit
762ba27036
25 changed files with 221 additions and 123 deletions
1
.flake8
1
.flake8
|
@ -29,6 +29,7 @@ max-line-length = 99
|
||||||
#
|
#
|
||||||
per-file-ignores =
|
per-file-ignores =
|
||||||
var/spack/repos/*/package.py:F403,F405,F821
|
var/spack/repos/*/package.py:F403,F405,F821
|
||||||
|
*-ci-package.py:F403,F405,F821
|
||||||
|
|
||||||
# exclude things we usually do not want linting for.
|
# exclude things we usually do not want linting for.
|
||||||
# These still get linted when passed explicitly, as when spack flake8 passes
|
# These still get linted when passed explicitly, as when spack flake8 passes
|
||||||
|
|
44
.github/workflows/audit.yaml
vendored
Normal file
44
.github/workflows/audit.yaml
vendored
Normal file
|
@ -0,0 +1,44 @@
|
||||||
|
name: audit
|
||||||
|
|
||||||
|
on:
|
||||||
|
workflow_call:
|
||||||
|
inputs:
|
||||||
|
with_coverage:
|
||||||
|
required: true
|
||||||
|
type: string
|
||||||
|
python_version:
|
||||||
|
required: true
|
||||||
|
type: string
|
||||||
|
|
||||||
|
concurrency:
|
||||||
|
group: audit-${{inputs.python_version}}-${{github.ref}}-${{github.event.pull_request.number || github.run_number}}
|
||||||
|
cancel-in-progress: true
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
# Run audits on all the packages in the built-in repository
|
||||||
|
package-audits:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@2541b1294d2704b0964813337f33b291d3f8596b # @v2
|
||||||
|
- uses: actions/setup-python@b55428b1882923874294fa556849718a1d7f2ca5 # @v2
|
||||||
|
with:
|
||||||
|
python-version: ${{inputs.python_version}}
|
||||||
|
- name: Install Python packages
|
||||||
|
run: |
|
||||||
|
pip install --upgrade pip six setuptools pytest codecov 'coverage[toml]<=6.2'
|
||||||
|
- name: Package audits (with coverage)
|
||||||
|
if: ${{ inputs.with_coverage == 'true' }}
|
||||||
|
run: |
|
||||||
|
. share/spack/setup-env.sh
|
||||||
|
coverage run $(which spack) audit packages
|
||||||
|
coverage combine
|
||||||
|
coverage xml
|
||||||
|
- name: Package audits (without coverage)
|
||||||
|
if: ${{ inputs.with_coverage == 'false' }}
|
||||||
|
run: |
|
||||||
|
. share/spack/setup-env.sh
|
||||||
|
$(which spack) audit packages
|
||||||
|
- uses: codecov/codecov-action@81cd2dc8148241f03f5839d295e000b8f761e378 # @v2.1.0
|
||||||
|
if: ${{ inputs.with_coverage == 'true' }}
|
||||||
|
with:
|
||||||
|
flags: unittests,linux,audits
|
2
.github/workflows/bootstrap.yml
vendored
2
.github/workflows/bootstrap.yml
vendored
|
@ -9,7 +9,7 @@ on:
|
||||||
- cron: '16 2 * * *'
|
- cron: '16 2 * * *'
|
||||||
|
|
||||||
concurrency:
|
concurrency:
|
||||||
group: bootstrap-${{ github.workflow }}-${{ github.event.pull_request.number || github.run_number }}
|
group: bootstrap-${{github.ref}}-${{github.event.pull_request.number || github.run_number}}
|
||||||
cancel-in-progress: true
|
cancel-in-progress: true
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
|
|
2
.github/workflows/build-containers.yml
vendored
2
.github/workflows/build-containers.yml
vendored
|
@ -20,7 +20,7 @@ on:
|
||||||
types: [published]
|
types: [published]
|
||||||
|
|
||||||
concurrency:
|
concurrency:
|
||||||
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.run_number }}
|
group: build_containers-${{github.ref}}-${{github.event.pull_request.number || github.run_number}}
|
||||||
cancel-in-progress: true
|
cancel-in-progress: true
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
|
|
25
.github/workflows/ci.yaml
vendored
25
.github/workflows/ci.yaml
vendored
|
@ -11,7 +11,7 @@ on:
|
||||||
- releases/**
|
- releases/**
|
||||||
|
|
||||||
concurrency:
|
concurrency:
|
||||||
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.run_number }}
|
group: ci-${{github.ref}}-${{github.event.pull_request.number || github.run_number}}
|
||||||
cancel-in-progress: true
|
cancel-in-progress: true
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
|
@ -20,6 +20,18 @@ jobs:
|
||||||
uses: ./.github/workflows/valid-style.yml
|
uses: ./.github/workflows/valid-style.yml
|
||||||
with:
|
with:
|
||||||
with_coverage: ${{ needs.changes.outputs.with_coverage }}
|
with_coverage: ${{ needs.changes.outputs.with_coverage }}
|
||||||
|
audit-ancient-python:
|
||||||
|
uses: ./.github/workflows/audit.yaml
|
||||||
|
needs: [ changes ]
|
||||||
|
with:
|
||||||
|
with_coverage: ${{ needs.changes.outputs.with_coverage }}
|
||||||
|
python_version: 2.7
|
||||||
|
all-prechecks:
|
||||||
|
needs: [ prechecks ]
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Success
|
||||||
|
run: "true"
|
||||||
# Check which files have been updated by the PR
|
# Check which files have been updated by the PR
|
||||||
changes:
|
changes:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
@ -53,6 +65,7 @@ jobs:
|
||||||
- 'share/spack/**'
|
- 'share/spack/**'
|
||||||
- '.github/workflows/bootstrap.yml'
|
- '.github/workflows/bootstrap.yml'
|
||||||
core:
|
core:
|
||||||
|
- '!lib/spack/docs/**'
|
||||||
- './!(var/**)/**'
|
- './!(var/**)/**'
|
||||||
packages:
|
packages:
|
||||||
- 'var/**'
|
- 'var/**'
|
||||||
|
@ -79,7 +92,7 @@ jobs:
|
||||||
needs: [ prechecks, changes ]
|
needs: [ prechecks, changes ]
|
||||||
uses: ./.github/workflows/bootstrap.yml
|
uses: ./.github/workflows/bootstrap.yml
|
||||||
unit-tests:
|
unit-tests:
|
||||||
if: ${{ github.repository == 'spack/spack' }}
|
if: ${{ github.repository == 'spack/spack' && needs.changes.outputs.core == 'true' }}
|
||||||
needs: [ prechecks, changes ]
|
needs: [ prechecks, changes ]
|
||||||
uses: ./.github/workflows/unit_tests.yaml
|
uses: ./.github/workflows/unit_tests.yaml
|
||||||
with:
|
with:
|
||||||
|
@ -87,7 +100,13 @@ jobs:
|
||||||
packages: ${{ needs.changes.outputs.packages }}
|
packages: ${{ needs.changes.outputs.packages }}
|
||||||
with_coverage: ${{ needs.changes.outputs.with_coverage }}
|
with_coverage: ${{ needs.changes.outputs.with_coverage }}
|
||||||
windows:
|
windows:
|
||||||
if: ${{ github.repository == 'spack/spack' }}
|
if: ${{ github.repository == 'spack/spack' && needs.changes.outputs.core == 'true' }}
|
||||||
needs: [ prechecks ]
|
needs: [ prechecks ]
|
||||||
uses: ./.github/workflows/windows_python.yml
|
uses: ./.github/workflows/windows_python.yml
|
||||||
|
all:
|
||||||
|
needs: [ windows, unit-tests, bootstrap, audit-ancient-python ]
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Success
|
||||||
|
run: "true"
|
||||||
|
|
||||||
|
|
33
.github/workflows/unit_tests.yaml
vendored
33
.github/workflows/unit_tests.yaml
vendored
|
@ -1,6 +1,7 @@
|
||||||
name: unit tests
|
name: unit tests
|
||||||
|
|
||||||
on:
|
on:
|
||||||
|
workflow_dispatch:
|
||||||
workflow_call:
|
workflow_call:
|
||||||
inputs:
|
inputs:
|
||||||
core:
|
core:
|
||||||
|
@ -14,7 +15,7 @@ on:
|
||||||
type: string
|
type: string
|
||||||
|
|
||||||
concurrency:
|
concurrency:
|
||||||
group: unit_tests-${{ github.workflow }}-${{ github.event.pull_request.number || github.run_number }}
|
group: unit_tests-${{github.ref}}-${{github.event.pull_request.number || github.run_number}}
|
||||||
cancel-in-progress: true
|
cancel-in-progress: true
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
|
@ -25,11 +26,26 @@ jobs:
|
||||||
matrix:
|
matrix:
|
||||||
python-version: ['2.7', '3.6', '3.7', '3.8', '3.9', '3.10']
|
python-version: ['2.7', '3.6', '3.7', '3.8', '3.9', '3.10']
|
||||||
concretizer: ['clingo']
|
concretizer: ['clingo']
|
||||||
|
on_develop:
|
||||||
|
- ${{ github.ref == 'refs/heads/develop' }}
|
||||||
include:
|
include:
|
||||||
- python-version: 2.7
|
- python-version: 2.7
|
||||||
concretizer: original
|
concretizer: original
|
||||||
- python-version: 3.9
|
on_develop: false
|
||||||
|
- python-version: '3.10'
|
||||||
concretizer: original
|
concretizer: original
|
||||||
|
on_develop: false
|
||||||
|
exclude:
|
||||||
|
- python-version: '3.7'
|
||||||
|
concretizer: 'clingo'
|
||||||
|
on_develop: false
|
||||||
|
- python-version: '3.8'
|
||||||
|
concretizer: 'clingo'
|
||||||
|
on_develop: false
|
||||||
|
- python-version: '3.9'
|
||||||
|
concretizer: 'clingo'
|
||||||
|
on_develop: false
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@2541b1294d2704b0964813337f33b291d3f8596b # @v2
|
- uses: actions/checkout@2541b1294d2704b0964813337f33b291d3f8596b # @v2
|
||||||
with:
|
with:
|
||||||
|
@ -46,7 +62,7 @@ jobs:
|
||||||
patchelf cmake bison libbison-dev kcov
|
patchelf cmake bison libbison-dev kcov
|
||||||
- name: Install Python packages
|
- name: Install Python packages
|
||||||
run: |
|
run: |
|
||||||
pip install --upgrade pip six setuptools pytest codecov "coverage[toml]<=6.2"
|
pip install --upgrade pip six setuptools pytest codecov "coverage[toml]<=6.2" pytest-xdist
|
||||||
# ensure style checks are not skipped in unit tests for python >= 3.6
|
# ensure style checks are not skipped in unit tests for python >= 3.6
|
||||||
# note that true/false (i.e., 1/0) are opposite in conditions in python and bash
|
# note that true/false (i.e., 1/0) are opposite in conditions in python and bash
|
||||||
if python -c 'import sys; sys.exit(not sys.version_info >= (3, 6))'; then
|
if python -c 'import sys; sys.exit(not sys.version_info >= (3, 6))'; then
|
||||||
|
@ -108,7 +124,7 @@ jobs:
|
||||||
sudo apt-get install -y coreutils kcov csh zsh tcsh fish dash bash
|
sudo apt-get install -y coreutils kcov csh zsh tcsh fish dash bash
|
||||||
- name: Install Python packages
|
- name: Install Python packages
|
||||||
run: |
|
run: |
|
||||||
pip install --upgrade pip six setuptools pytest codecov coverage[toml]==6.2
|
pip install --upgrade pip six setuptools pytest codecov coverage[toml]==6.2 pytest-xdist
|
||||||
- name: Setup git configuration
|
- name: Setup git configuration
|
||||||
run: |
|
run: |
|
||||||
# Need this for the git tests to succeed.
|
# Need this for the git tests to succeed.
|
||||||
|
@ -174,7 +190,7 @@ jobs:
|
||||||
patchelf kcov
|
patchelf kcov
|
||||||
- name: Install Python packages
|
- name: Install Python packages
|
||||||
run: |
|
run: |
|
||||||
pip install --upgrade pip six setuptools pytest codecov coverage[toml]==6.2 clingo
|
pip install --upgrade pip six setuptools pytest codecov coverage[toml]==6.2 clingo pytest-xdist
|
||||||
- name: Setup git configuration
|
- name: Setup git configuration
|
||||||
run: |
|
run: |
|
||||||
# Need this for the git tests to succeed.
|
# Need this for the git tests to succeed.
|
||||||
|
@ -216,7 +232,7 @@ jobs:
|
||||||
- name: Install Python packages
|
- name: Install Python packages
|
||||||
run: |
|
run: |
|
||||||
pip install --upgrade pip six setuptools
|
pip install --upgrade pip six setuptools
|
||||||
pip install --upgrade pytest codecov coverage[toml]==6.2
|
pip install --upgrade pytest codecov coverage[toml]==6.2 pytest-xdist
|
||||||
- name: Setup Homebrew packages
|
- name: Setup Homebrew packages
|
||||||
run: |
|
run: |
|
||||||
brew install dash fish gcc gnupg2 kcov
|
brew install dash fish gcc gnupg2 kcov
|
||||||
|
@ -229,9 +245,10 @@ jobs:
|
||||||
. share/spack/setup-env.sh
|
. share/spack/setup-env.sh
|
||||||
$(which spack) bootstrap untrust spack-install
|
$(which spack) bootstrap untrust spack-install
|
||||||
$(which spack) solve zlib
|
$(which spack) solve zlib
|
||||||
|
common_args=(--dist loadfile --tx '4*popen//python=./bin/spack-tmpconfig python -u ./bin/spack python' -x)
|
||||||
if [ "${{ inputs.with_coverage }}" == "true" ]
|
if [ "${{ inputs.with_coverage }}" == "true" ]
|
||||||
then
|
then
|
||||||
coverage run $(which spack) unit-test -x
|
coverage run $(which spack) unit-test "${common_args[@]}"
|
||||||
coverage combine
|
coverage combine
|
||||||
coverage xml
|
coverage xml
|
||||||
# Delete the symlink going from ./lib/spack/docs/_spack_root back to
|
# Delete the symlink going from ./lib/spack/docs/_spack_root back to
|
||||||
|
@ -239,7 +256,7 @@ jobs:
|
||||||
rm lib/spack/docs/_spack_root
|
rm lib/spack/docs/_spack_root
|
||||||
else
|
else
|
||||||
echo "ONLY PACKAGE RECIPES CHANGED [skipping coverage]"
|
echo "ONLY PACKAGE RECIPES CHANGED [skipping coverage]"
|
||||||
$(which spack) unit-test -x -m "not maybeslow" -k "test_all_virtual_packages_have_default_providers"
|
$(which spack) unit-test "${common_args[@]}" -m "not maybeslow" -k "test_all_virtual_packages_have_default_providers"
|
||||||
fi
|
fi
|
||||||
- uses: codecov/codecov-action@81cd2dc8148241f03f5839d295e000b8f761e378 # @v2.1.0
|
- uses: codecov/codecov-action@81cd2dc8148241f03f5839d295e000b8f761e378 # @v2.1.0
|
||||||
if: ${{ inputs.with_coverage == 'true' }}
|
if: ${{ inputs.with_coverage == 'true' }}
|
||||||
|
|
34
.github/workflows/valid-style.yml
vendored
34
.github/workflows/valid-style.yml
vendored
|
@ -8,7 +8,7 @@ on:
|
||||||
type: string
|
type: string
|
||||||
|
|
||||||
concurrency:
|
concurrency:
|
||||||
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.run_number }}
|
group: style-${{github.ref}}-${{github.event.pull_request.number || github.run_number}}
|
||||||
cancel-in-progress: true
|
cancel-in-progress: true
|
||||||
|
|
||||||
|
|
||||||
|
@ -53,30 +53,8 @@ jobs:
|
||||||
- name: Run style tests
|
- name: Run style tests
|
||||||
run: |
|
run: |
|
||||||
share/spack/qa/run-style-tests
|
share/spack/qa/run-style-tests
|
||||||
# Run audits on all the packages in the built-in repository
|
audit:
|
||||||
package-audits:
|
uses: ./.github/workflows/audit.yaml
|
||||||
runs-on: ubuntu-latest
|
with:
|
||||||
steps:
|
with_coverage: ${{ inputs.with_coverage }}
|
||||||
- uses: actions/checkout@2541b1294d2704b0964813337f33b291d3f8596b # @v2
|
python_version: '3.10'
|
||||||
- uses: actions/setup-python@b55428b1882923874294fa556849718a1d7f2ca5 # @v2
|
|
||||||
with:
|
|
||||||
python-version: '3.10'
|
|
||||||
- name: Install Python packages
|
|
||||||
run: |
|
|
||||||
pip install --upgrade pip six setuptools pytest codecov coverage[toml]==6.2
|
|
||||||
- name: Package audits (with coverage)
|
|
||||||
if: ${{ inputs.with_coverage == 'true' }}
|
|
||||||
run: |
|
|
||||||
. share/spack/setup-env.sh
|
|
||||||
coverage run $(which spack) audit packages
|
|
||||||
coverage combine
|
|
||||||
coverage xml
|
|
||||||
- name: Package audits (without coverage)
|
|
||||||
if: ${{ inputs.with_coverage == 'false' }}
|
|
||||||
run: |
|
|
||||||
. share/spack/setup-env.sh
|
|
||||||
$(which spack) audit packages
|
|
||||||
- uses: codecov/codecov-action@81cd2dc8148241f03f5839d295e000b8f761e378 # @v2.1.0
|
|
||||||
if: ${{ inputs.with_coverage == 'true' }}
|
|
||||||
with:
|
|
||||||
flags: unittests,linux,audits
|
|
||||||
|
|
2
.github/workflows/windows_python.yml
vendored
2
.github/workflows/windows_python.yml
vendored
|
@ -4,7 +4,7 @@ on:
|
||||||
workflow_call:
|
workflow_call:
|
||||||
|
|
||||||
concurrency:
|
concurrency:
|
||||||
group: windows-${{ github.workflow }}-${{ github.event.pull_request.number || github.run_number }}
|
group: windows-${{github.ref}}-${{github.event.pull_request.number || github.run_number}}
|
||||||
cancel-in-progress: true
|
cancel-in-progress: true
|
||||||
|
|
||||||
defaults:
|
defaults:
|
||||||
|
|
|
@ -7,7 +7,6 @@ export TMPDIR="${XDG_RUNTIME_DIR}"
|
||||||
export TMP_DIR="$(mktemp -d -t spack-test-XXXXX)"
|
export TMP_DIR="$(mktemp -d -t spack-test-XXXXX)"
|
||||||
clean_up() {
|
clean_up() {
|
||||||
[[ -n "$TMPCONFIG_DEBUG" ]] && printf "cleaning up: $TMP_DIR\n"
|
[[ -n "$TMPCONFIG_DEBUG" ]] && printf "cleaning up: $TMP_DIR\n"
|
||||||
[[ -n "$TMPCONFIG_DEBUG" ]] && tree "$TMP_DIR"
|
|
||||||
rm -rf "$TMP_DIR"
|
rm -rf "$TMP_DIR"
|
||||||
}
|
}
|
||||||
trap clean_up EXIT
|
trap clean_up EXIT
|
||||||
|
|
|
@ -228,8 +228,8 @@ def __init__(self, controller_function, minion_function):
|
||||||
self.minion_function = minion_function
|
self.minion_function = minion_function
|
||||||
|
|
||||||
# these can be optionally set to change defaults
|
# these can be optionally set to change defaults
|
||||||
self.controller_timeout = 1
|
self.controller_timeout = 3
|
||||||
self.sleep_time = 0
|
self.sleep_time = 0.1
|
||||||
|
|
||||||
def start(self, **kwargs):
|
def start(self, **kwargs):
|
||||||
"""Start the controller and minion processes.
|
"""Start the controller and minion processes.
|
||||||
|
|
|
@ -30,6 +30,12 @@ def setup_parser(subparser):
|
||||||
help="print the Python version number and exit",
|
help="print the Python version number and exit",
|
||||||
)
|
)
|
||||||
subparser.add_argument("-c", dest="python_command", help="command to execute")
|
subparser.add_argument("-c", dest="python_command", help="command to execute")
|
||||||
|
subparser.add_argument(
|
||||||
|
"-u",
|
||||||
|
dest="unbuffered",
|
||||||
|
action="store_true",
|
||||||
|
help="for compatibility with xdist, do not use without adding -u to the interpreter",
|
||||||
|
)
|
||||||
subparser.add_argument(
|
subparser.add_argument(
|
||||||
"-i",
|
"-i",
|
||||||
dest="python_interpreter",
|
dest="python_interpreter",
|
||||||
|
|
|
@ -18,6 +18,7 @@
|
||||||
import pstats
|
import pstats
|
||||||
import re
|
import re
|
||||||
import signal
|
import signal
|
||||||
|
import subprocess as sp
|
||||||
import sys
|
import sys
|
||||||
import traceback
|
import traceback
|
||||||
import warnings
|
import warnings
|
||||||
|
@ -623,15 +624,19 @@ class SpackCommand(object):
|
||||||
their output.
|
their output.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, command_name):
|
def __init__(self, command_name, subprocess=False):
|
||||||
"""Create a new SpackCommand that invokes ``command_name`` when called.
|
"""Create a new SpackCommand that invokes ``command_name`` when called.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
command_name (str): name of the command to invoke
|
command_name (str): name of the command to invoke
|
||||||
|
subprocess (bool): whether to fork a subprocess or not. Currently not supported on
|
||||||
|
Windows, where it is always False.
|
||||||
"""
|
"""
|
||||||
self.parser = make_argument_parser()
|
self.parser = make_argument_parser()
|
||||||
self.command = self.parser.add_command(command_name)
|
self.command = self.parser.add_command(command_name)
|
||||||
self.command_name = command_name
|
self.command_name = command_name
|
||||||
|
# TODO: figure out how to support this on windows
|
||||||
|
self.subprocess = subprocess if sys.platform != "win32" else False
|
||||||
|
|
||||||
def __call__(self, *argv, **kwargs):
|
def __call__(self, *argv, **kwargs):
|
||||||
"""Invoke this SpackCommand.
|
"""Invoke this SpackCommand.
|
||||||
|
@ -656,25 +661,36 @@ def __call__(self, *argv, **kwargs):
|
||||||
self.error = None
|
self.error = None
|
||||||
|
|
||||||
prepend = kwargs["global_args"] if "global_args" in kwargs else []
|
prepend = kwargs["global_args"] if "global_args" in kwargs else []
|
||||||
|
|
||||||
args, unknown = self.parser.parse_known_args(prepend + [self.command_name] + list(argv))
|
|
||||||
|
|
||||||
fail_on_error = kwargs.get("fail_on_error", True)
|
fail_on_error = kwargs.get("fail_on_error", True)
|
||||||
|
|
||||||
out = StringIO()
|
if self.subprocess:
|
||||||
try:
|
p = sp.Popen(
|
||||||
with log_output(out):
|
[spack.paths.spack_script, self.command_name] + prepend + list(argv),
|
||||||
self.returncode = _invoke_command(self.command, self.parser, args, unknown)
|
stdout=sp.PIPE,
|
||||||
|
stderr=sp.STDOUT,
|
||||||
|
)
|
||||||
|
out, self.returncode = p.communicate()
|
||||||
|
out = out.decode()
|
||||||
|
else:
|
||||||
|
args, unknown = self.parser.parse_known_args(
|
||||||
|
prepend + [self.command_name] + list(argv)
|
||||||
|
)
|
||||||
|
|
||||||
except SystemExit as e:
|
out = StringIO()
|
||||||
self.returncode = e.code
|
try:
|
||||||
|
with log_output(out):
|
||||||
|
self.returncode = _invoke_command(self.command, self.parser, args, unknown)
|
||||||
|
|
||||||
except BaseException as e:
|
except SystemExit as e:
|
||||||
tty.debug(e)
|
self.returncode = e.code
|
||||||
self.error = e
|
|
||||||
if fail_on_error:
|
except BaseException as e:
|
||||||
self._log_command_output(out)
|
tty.debug(e)
|
||||||
raise
|
self.error = e
|
||||||
|
if fail_on_error:
|
||||||
|
self._log_command_output(out)
|
||||||
|
raise
|
||||||
|
out = out.getvalue()
|
||||||
|
|
||||||
if fail_on_error and self.returncode not in (None, 0):
|
if fail_on_error and self.returncode not in (None, 0):
|
||||||
self._log_command_output(out)
|
self._log_command_output(out)
|
||||||
|
@ -683,7 +699,7 @@ def __call__(self, *argv, **kwargs):
|
||||||
% (self.returncode, self.command_name, ", ".join("'%s'" % a for a in argv))
|
% (self.returncode, self.command_name, ", ".join("'%s'" % a for a in argv))
|
||||||
)
|
)
|
||||||
|
|
||||||
return out.getvalue()
|
return out
|
||||||
|
|
||||||
def _log_command_output(self, out):
|
def _log_command_output(self, out):
|
||||||
if tty.is_verbose():
|
if tty.is_verbose():
|
||||||
|
|
|
@ -484,6 +484,7 @@ def test_get_spec_filter_list(mutable_mock_env_path, config, mutable_mock_repo):
|
||||||
assert affected_pkg_names == expected_affected_pkg_names
|
assert affected_pkg_names == expected_affected_pkg_names
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.maybeslow
|
||||||
@pytest.mark.regression("29947")
|
@pytest.mark.regression("29947")
|
||||||
def test_affected_specs_on_first_concretization(mutable_mock_env_path, config):
|
def test_affected_specs_on_first_concretization(mutable_mock_env_path, config):
|
||||||
e = ev.create("first_concretization")
|
e = ev.create("first_concretization")
|
||||||
|
|
|
@ -50,12 +50,14 @@ def test_checksum(arguments, expected, mock_packages, mock_stage):
|
||||||
|
|
||||||
@pytest.mark.skipif(sys.platform == "win32", reason="Not supported on Windows (yet)")
|
@pytest.mark.skipif(sys.platform == "win32", reason="Not supported on Windows (yet)")
|
||||||
def test_checksum_interactive(mock_packages, mock_fetch, mock_stage, monkeypatch):
|
def test_checksum_interactive(mock_packages, mock_fetch, mock_stage, monkeypatch):
|
||||||
|
# TODO: mock_fetch doesn't actually work with stage, working around with ignoring
|
||||||
|
# fail_on_error for now
|
||||||
def _get_number(*args, **kwargs):
|
def _get_number(*args, **kwargs):
|
||||||
return 1
|
return 1
|
||||||
|
|
||||||
monkeypatch.setattr(tty, "get_number", _get_number)
|
monkeypatch.setattr(tty, "get_number", _get_number)
|
||||||
|
|
||||||
output = spack_checksum("preferred-test")
|
output = spack_checksum("preferred-test", fail_on_error=False)
|
||||||
assert "version of preferred-test" in output
|
assert "version of preferred-test" in output
|
||||||
assert "version(" in output
|
assert "version(" in output
|
||||||
|
|
||||||
|
|
|
@ -2192,10 +2192,8 @@ def fake_download_and_extract_artifacts(url, work_dir):
|
||||||
)
|
)
|
||||||
def test_ci_help(subcmd, capsys):
|
def test_ci_help(subcmd, capsys):
|
||||||
"""Make sure `spack ci` --help describes the (sub)command help."""
|
"""Make sure `spack ci` --help describes the (sub)command help."""
|
||||||
with pytest.raises(SystemExit):
|
out = spack.main.SpackCommand("ci", subprocess=True)(subcmd, "--help")
|
||||||
ci_cmd(subcmd, "--help")
|
|
||||||
|
|
||||||
out = str(capsys.readouterr())
|
|
||||||
usage = "usage: spack ci {0}{1}[".format(subcmd, " " if subcmd else "")
|
usage = "usage: spack ci {0}{1}[".format(subcmd, " " if subcmd else "")
|
||||||
assert usage in out
|
assert usage in out
|
||||||
|
|
||||||
|
|
|
@ -16,7 +16,7 @@
|
||||||
import spack.paths
|
import spack.paths
|
||||||
from spack.cmd.commands import _positional_to_subroutine
|
from spack.cmd.commands import _positional_to_subroutine
|
||||||
|
|
||||||
commands = spack.main.SpackCommand("commands")
|
commands = spack.main.SpackCommand("commands", subprocess=True)
|
||||||
|
|
||||||
parser = spack.main.make_argument_parser()
|
parser = spack.main.make_argument_parser()
|
||||||
spack.main.add_all_commands(parser)
|
spack.main.add_all_commands(parser)
|
||||||
|
@ -104,17 +104,18 @@ def test_rst_with_input_files(tmpdir):
|
||||||
|
|
||||||
|
|
||||||
def test_rst_with_header(tmpdir):
|
def test_rst_with_header(tmpdir):
|
||||||
|
local_commands = spack.main.SpackCommand("commands")
|
||||||
fake_header = "this is a header!\n\n"
|
fake_header = "this is a header!\n\n"
|
||||||
|
|
||||||
filename = tmpdir.join("header.txt")
|
filename = tmpdir.join("header.txt")
|
||||||
with filename.open("w") as f:
|
with filename.open("w") as f:
|
||||||
f.write(fake_header)
|
f.write(fake_header)
|
||||||
|
|
||||||
out = commands("--format=rst", "--header", str(filename))
|
out = local_commands("--format=rst", "--header", str(filename))
|
||||||
assert out.startswith(fake_header)
|
assert out.startswith(fake_header)
|
||||||
|
|
||||||
with pytest.raises(spack.main.SpackCommandError):
|
with pytest.raises(spack.main.SpackCommandError):
|
||||||
commands("--format=rst", "--header", "asdfjhkf")
|
local_commands("--format=rst", "--header", "asdfjhkf")
|
||||||
|
|
||||||
|
|
||||||
def test_rst_update(tmpdir):
|
def test_rst_update(tmpdir):
|
||||||
|
@ -207,13 +208,14 @@ def test_update_completion_arg(tmpdir, monkeypatch):
|
||||||
|
|
||||||
monkeypatch.setattr(spack.cmd.commands, "update_completion_args", mock_args)
|
monkeypatch.setattr(spack.cmd.commands, "update_completion_args", mock_args)
|
||||||
|
|
||||||
|
local_commands = spack.main.SpackCommand("commands")
|
||||||
# ensure things fail if --update-completion isn't specified alone
|
# ensure things fail if --update-completion isn't specified alone
|
||||||
with pytest.raises(spack.main.SpackCommandError):
|
with pytest.raises(spack.main.SpackCommandError):
|
||||||
commands("--update-completion", "-a")
|
local_commands("--update-completion", "-a")
|
||||||
|
|
||||||
# ensure arg is restored
|
# ensure arg is restored
|
||||||
assert "--update-completion" not in mock_bashfile.read()
|
assert "--update-completion" not in mock_bashfile.read()
|
||||||
commands("--update-completion")
|
local_commands("--update-completion")
|
||||||
assert "--update-completion" in mock_bashfile.read()
|
assert "--update-completion" in mock_bashfile.read()
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -869,7 +869,7 @@ def test_env_with_included_config_var_path(packages_file):
|
||||||
|
|
||||||
config_real_path = substitute_path_variables(config_var_path)
|
config_real_path = substitute_path_variables(config_var_path)
|
||||||
fs.mkdirp(os.path.dirname(config_real_path))
|
fs.mkdirp(os.path.dirname(config_real_path))
|
||||||
fs.rename(packages_file.strpath, config_real_path)
|
shutil.move(packages_file.strpath, config_real_path)
|
||||||
assert os.path.exists(config_real_path)
|
assert os.path.exists(config_real_path)
|
||||||
|
|
||||||
with e:
|
with e:
|
||||||
|
|
|
@ -11,7 +11,7 @@
|
||||||
@pytest.mark.xfail
|
@pytest.mark.xfail
|
||||||
def test_reuse_after_help():
|
def test_reuse_after_help():
|
||||||
"""Test `spack help` can be called twice with the same SpackCommand."""
|
"""Test `spack help` can be called twice with the same SpackCommand."""
|
||||||
help_cmd = SpackCommand("help")
|
help_cmd = SpackCommand("help", subprocess=True)
|
||||||
help_cmd()
|
help_cmd()
|
||||||
|
|
||||||
# This second invocation will somehow fail because the parser no
|
# This second invocation will somehow fail because the parser no
|
||||||
|
@ -30,14 +30,14 @@ def test_reuse_after_help():
|
||||||
|
|
||||||
def test_help():
|
def test_help():
|
||||||
"""Sanity check the help command to make sure it works."""
|
"""Sanity check the help command to make sure it works."""
|
||||||
help_cmd = SpackCommand("help")
|
help_cmd = SpackCommand("help", subprocess=True)
|
||||||
out = help_cmd()
|
out = help_cmd()
|
||||||
assert "These are common spack commands:" in out
|
assert "These are common spack commands:" in out
|
||||||
|
|
||||||
|
|
||||||
def test_help_all():
|
def test_help_all():
|
||||||
"""Test the spack help --all flag"""
|
"""Test the spack help --all flag"""
|
||||||
help_cmd = SpackCommand("help")
|
help_cmd = SpackCommand("help", subprocess=True)
|
||||||
out = help_cmd("--all")
|
out = help_cmd("--all")
|
||||||
assert "Complete list of spack commands:" in out
|
assert "Complete list of spack commands:" in out
|
||||||
|
|
||||||
|
|
|
@ -10,7 +10,7 @@
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from llnl.util.filesystem import mkdirp
|
from llnl.util.filesystem import mkdirp, working_dir
|
||||||
|
|
||||||
import spack
|
import spack
|
||||||
from spack.util.executable import which
|
from spack.util.executable import which
|
||||||
|
@ -40,28 +40,29 @@ def check_git_version():
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture(scope="function")
|
@pytest.fixture(scope="function")
|
||||||
def git_tmp_worktree(tmpdir):
|
def git_tmp_worktree(tmpdir, mock_git_version_info):
|
||||||
"""Create new worktree in a temporary folder and monkeypatch
|
"""Create new worktree in a temporary folder and monkeypatch
|
||||||
spack.paths.prefix to point to it.
|
spack.paths.prefix to point to it.
|
||||||
"""
|
"""
|
||||||
# TODO: This is fragile and should be high priority for
|
with working_dir(mock_git_version_info[0]):
|
||||||
# follow up fixes. 27021
|
# TODO: This is fragile and should be high priority for
|
||||||
# Path length is occasionally too long on Windows
|
# follow up fixes. 27021
|
||||||
# the following reduces the path length to acceptable levels
|
# Path length is occasionally too long on Windows
|
||||||
if sys.platform == "win32":
|
# the following reduces the path length to acceptable levels
|
||||||
long_pth = str(tmpdir).split(os.path.sep)
|
if sys.platform == "win32":
|
||||||
tmp_worktree = os.path.sep.join(long_pth[:-1])
|
long_pth = str(tmpdir).split(os.path.sep)
|
||||||
else:
|
tmp_worktree = os.path.sep.join(long_pth[:-1])
|
||||||
tmp_worktree = str(tmpdir)
|
else:
|
||||||
worktree_root = os.path.sep.join([tmp_worktree, "wrktree"])
|
tmp_worktree = str(tmpdir)
|
||||||
|
worktree_root = os.path.sep.join([tmp_worktree, "wrktree"])
|
||||||
|
|
||||||
mkdirp(worktree_root)
|
mkdirp(worktree_root)
|
||||||
|
|
||||||
git("worktree", "add", "--detach", worktree_root, "HEAD")
|
git("worktree", "add", "--detach", worktree_root, "HEAD")
|
||||||
|
|
||||||
yield worktree_root
|
yield worktree_root
|
||||||
|
|
||||||
git("worktree", "remove", "--force", worktree_root)
|
git("worktree", "remove", "--force", worktree_root)
|
||||||
|
|
||||||
|
|
||||||
def test_is_git_repo_in_worktree(git_tmp_worktree):
|
def test_is_git_repo_in_worktree(git_tmp_worktree):
|
||||||
|
|
|
@ -210,7 +210,7 @@ def test_setdefault_command(mutable_database, mutable_config):
|
||||||
for k in preferred, other_spec:
|
for k in preferred, other_spec:
|
||||||
assert os.path.exists(writers[k].layout.filename)
|
assert os.path.exists(writers[k].layout.filename)
|
||||||
assert os.path.exists(link_name) and os.path.islink(link_name)
|
assert os.path.exists(link_name) and os.path.islink(link_name)
|
||||||
assert os.path.realpath(link_name) == writers[other_spec].layout.filename
|
assert os.path.realpath(link_name) == os.path.realpath(writers[other_spec].layout.filename)
|
||||||
|
|
||||||
# Reset the default to be the preferred spec
|
# Reset the default to be the preferred spec
|
||||||
module("lmod", "setdefault", preferred)
|
module("lmod", "setdefault", preferred)
|
||||||
|
@ -219,4 +219,4 @@ def test_setdefault_command(mutable_database, mutable_config):
|
||||||
for k in preferred, other_spec:
|
for k in preferred, other_spec:
|
||||||
assert os.path.exists(writers[k].layout.filename)
|
assert os.path.exists(writers[k].layout.filename)
|
||||||
assert os.path.exists(link_name) and os.path.islink(link_name)
|
assert os.path.exists(link_name) and os.path.islink(link_name)
|
||||||
assert os.path.realpath(link_name) == writers[preferred].layout.filename
|
assert os.path.realpath(link_name) == os.path.realpath(writers[preferred].layout.filename)
|
||||||
|
|
|
@ -46,22 +46,22 @@ def has_develop_branch():
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture(scope="function")
|
@pytest.fixture(scope="function")
|
||||||
def flake8_package():
|
def flake8_package(tmpdir):
|
||||||
"""Style only checks files that have been modified. This fixture makes a small
|
"""Style only checks files that have been modified. This fixture makes a small
|
||||||
change to the ``flake8`` mock package, yields the filename, then undoes the
|
change to the ``flake8`` mock package, yields the filename, then undoes the
|
||||||
change on cleanup.
|
change on cleanup.
|
||||||
"""
|
"""
|
||||||
repo = spack.repo.Repo(spack.paths.mock_packages_path)
|
repo = spack.repo.Repo(spack.paths.mock_packages_path)
|
||||||
filename = repo.filename_for_package_name("flake8")
|
filename = repo.filename_for_package_name("flake8")
|
||||||
tmp = filename + ".tmp"
|
rel_path = os.path.dirname(os.path.relpath(filename, spack.paths.prefix))
|
||||||
|
tmp = tmpdir / rel_path / "flake8-ci-package.py"
|
||||||
|
tmp.ensure()
|
||||||
|
tmp = str(tmp)
|
||||||
|
|
||||||
try:
|
shutil.copy(filename, tmp)
|
||||||
shutil.copy(filename, tmp)
|
package = FileFilter(tmp)
|
||||||
package = FileFilter(filename)
|
package.filter("state = 'unmodified'", "state = 'modified'", string=True)
|
||||||
package.filter("state = 'unmodified'", "state = 'modified'", string=True)
|
yield tmp
|
||||||
yield filename
|
|
||||||
finally:
|
|
||||||
shutil.move(tmp, filename)
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
|
@ -71,20 +71,17 @@ def flake8_package_with_errors(scope="function"):
|
||||||
filename = repo.filename_for_package_name("flake8")
|
filename = repo.filename_for_package_name("flake8")
|
||||||
tmp = filename + ".tmp"
|
tmp = filename + ".tmp"
|
||||||
|
|
||||||
try:
|
shutil.copy(filename, tmp)
|
||||||
shutil.copy(filename, tmp)
|
package = FileFilter(tmp)
|
||||||
package = FileFilter(filename)
|
|
||||||
|
|
||||||
# this is a black error (quote style and spacing before/after operator)
|
# this is a black error (quote style and spacing before/after operator)
|
||||||
package.filter('state = "unmodified"', "state = 'modified'", string=True)
|
package.filter('state = "unmodified"', "state = 'modified'", string=True)
|
||||||
|
|
||||||
# this is an isort error (orderign) and a flake8 error (unused import)
|
# this is an isort error (orderign) and a flake8 error (unused import)
|
||||||
package.filter(
|
package.filter(
|
||||||
"from spack.package import *", "from spack.package import *\nimport os", string=True
|
"from spack.package import *", "from spack.package import *\nimport os", string=True
|
||||||
)
|
)
|
||||||
yield filename
|
yield tmp
|
||||||
finally:
|
|
||||||
shutil.move(tmp, filename)
|
|
||||||
|
|
||||||
|
|
||||||
def test_changed_files_from_git_rev_base(tmpdir, capfd):
|
def test_changed_files_from_git_rev_base(tmpdir, capfd):
|
||||||
|
@ -125,7 +122,7 @@ def test_changed_no_base(tmpdir, capfd):
|
||||||
assert "This repository does not have a 'foobar'" in err
|
assert "This repository does not have a 'foobar'" in err
|
||||||
|
|
||||||
|
|
||||||
def test_changed_files_all_files(flake8_package):
|
def test_changed_files_all_files():
|
||||||
# it's hard to guarantee "all files", so do some sanity checks.
|
# it's hard to guarantee "all files", so do some sanity checks.
|
||||||
files = set(
|
files = set(
|
||||||
[
|
[
|
||||||
|
@ -139,13 +136,18 @@ def test_changed_files_all_files(flake8_package):
|
||||||
|
|
||||||
# a builtin package
|
# a builtin package
|
||||||
zlib = spack.repo.path.get_pkg_class("zlib")
|
zlib = spack.repo.path.get_pkg_class("zlib")
|
||||||
assert zlib.module.__file__ in files
|
zlib_file = zlib.module.__file__
|
||||||
|
if zlib_file.endswith("pyc"):
|
||||||
|
zlib_file = zlib_file[:-1]
|
||||||
|
assert zlib_file in files
|
||||||
|
|
||||||
# a core spack file
|
# a core spack file
|
||||||
assert os.path.join(spack.paths.module_path, "spec.py") in files
|
assert os.path.join(spack.paths.module_path, "spec.py") in files
|
||||||
|
|
||||||
# a mock package
|
# a mock package
|
||||||
assert flake8_package in files
|
repo = spack.repo.Repo(spack.paths.mock_packages_path)
|
||||||
|
filename = repo.filename_for_package_name("flake8")
|
||||||
|
assert filename in files
|
||||||
|
|
||||||
# this test
|
# this test
|
||||||
assert __file__ in files
|
assert __file__ in files
|
||||||
|
|
|
@ -228,14 +228,17 @@ def test_missing_command():
|
||||||
],
|
],
|
||||||
ids=["no_stem", "vacuous", "leading_hyphen", "basic_good", "trailing_slash", "hyphenated"],
|
ids=["no_stem", "vacuous", "leading_hyphen", "basic_good", "trailing_slash", "hyphenated"],
|
||||||
)
|
)
|
||||||
def test_extension_naming(extension_path, expected_exception, config):
|
def test_extension_naming(tmpdir, extension_path, expected_exception, config):
|
||||||
"""Ensure that we are correctly validating configured extension paths
|
"""Ensure that we are correctly validating configured extension paths
|
||||||
for conformity with the rules: the basename should match
|
for conformity with the rules: the basename should match
|
||||||
``spack-<name>``; <name> may have embedded hyphens but not begin with one.
|
``spack-<name>``; <name> may have embedded hyphens but not begin with one.
|
||||||
"""
|
"""
|
||||||
with spack.config.override("config:extensions", [extension_path]):
|
# NOTE: if the directory is a valid extension directory name the "vacuous" test will
|
||||||
with pytest.raises(expected_exception):
|
# fail because it resolves to current working directory
|
||||||
spack.cmd.get_module("no-such-command")
|
with tmpdir.as_cwd():
|
||||||
|
with spack.config.override("config:extensions", [extension_path]):
|
||||||
|
with pytest.raises(expected_exception):
|
||||||
|
spack.cmd.get_module("no-such-command")
|
||||||
|
|
||||||
|
|
||||||
def test_missing_command_function(extension_creator, capsys):
|
def test_missing_command_function(extension_creator, capsys):
|
||||||
|
|
|
@ -10,6 +10,7 @@
|
||||||
|
|
||||||
"""
|
"""
|
||||||
import code
|
import code
|
||||||
|
import io
|
||||||
import os
|
import os
|
||||||
import pdb
|
import pdb
|
||||||
import signal
|
import signal
|
||||||
|
@ -53,7 +54,10 @@ class and use as a drop in for Pdb, although the syntax here is slightly differe
|
||||||
the run of Spack.install, or any where else Spack spawns a child process.
|
the run of Spack.install, or any where else Spack spawns a child process.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
_original_stdin_fd = sys.stdin.fileno()
|
try:
|
||||||
|
_original_stdin_fd = sys.stdin.fileno()
|
||||||
|
except io.UnsupportedOperation:
|
||||||
|
_original_stdin_fd = None
|
||||||
_original_stdin = None
|
_original_stdin = None
|
||||||
|
|
||||||
def __init__(self, stdout_fd=None, stderr_fd=None):
|
def __init__(self, stdout_fd=None, stderr_fd=None):
|
||||||
|
|
|
@ -54,6 +54,11 @@ elif [[ "$SPACK_TEST_SOLVER" == "original" ]]; then
|
||||||
export PYTEST_ADDOPTS='-m "not maybeslow"'
|
export PYTEST_ADDOPTS='-m "not maybeslow"'
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# Check if xdist is available
|
||||||
|
if python -m pytest --trace-config 2>&1 | grep xdist; then
|
||||||
|
export PYTEST_ADDOPTS="$PYTEST_ADDOPTS --dist loadfile --tx '${SPACK_TEST_PARALLEL:=3}*popen//python=./bin/spack-tmpconfig python -u ./bin/spack python'"
|
||||||
|
fi
|
||||||
|
|
||||||
$coverage_run $(which spack) unit-test -x --verbose
|
$coverage_run $(which spack) unit-test -x --verbose
|
||||||
|
|
||||||
bash "$QA_DIR/test-env-cfg.sh"
|
bash "$QA_DIR/test-env-cfg.sh"
|
||||||
|
|
|
@ -1568,7 +1568,7 @@ _spack_pydoc() {
|
||||||
_spack_python() {
|
_spack_python() {
|
||||||
if $list_options
|
if $list_options
|
||||||
then
|
then
|
||||||
SPACK_COMPREPLY="-h --help -V --version -c -i -m --path"
|
SPACK_COMPREPLY="-h --help -V --version -c -u -i -m --path"
|
||||||
else
|
else
|
||||||
SPACK_COMPREPLY=""
|
SPACK_COMPREPLY=""
|
||||||
fi
|
fi
|
||||||
|
|
Loading…
Reference in a new issue