diff --git a/.dockerignore b/.dockerignore
index c755c4e711..5e92d3aa73 100644
--- a/.dockerignore
+++ b/.dockerignore
@@ -1,7 +1,16 @@
.git
__pycache__
-cscs-checks
external
Jenkinsfile*
-*.md
*.log
+
+# Virtual environments
+venv*
+.venv*
+ENV/
+uv.lock
+
+# ReFrame files
+stage/
+output/
+perflogs/
\ No newline at end of file
diff --git a/.github/pseudo-cluster/reframe/Dockerfile b/.github/pseudo-cluster/reframe/Dockerfile
index c8d5521ccc..0606603207 100644
--- a/.github/pseudo-cluster/reframe/Dockerfile
+++ b/.github/pseudo-cluster/reframe/Dockerfile
@@ -4,6 +4,7 @@
# SPDX-License-Identifier: BSD-3-Clause
FROM ubuntu:22.04
+COPY --from=ghcr.io/astral-sh/uv:latest /uv /uvx /usr/local/bin/
ARG DEBIAN_FRONTEND=noninteractive
diff --git a/.github/pseudo-cluster/reframe/docker-entrypoint.sh b/.github/pseudo-cluster/reframe/docker-entrypoint.sh
index a01dba35f2..c3ddd86899 100755
--- a/.github/pseudo-cluster/reframe/docker-entrypoint.sh
+++ b/.github/pseudo-cluster/reframe/docker-entrypoint.sh
@@ -7,14 +7,13 @@ sudo service munge start
# Needs to be copied in the shared home directory
cp -r /usr/local/share/reframe .
cd reframe
-./bootstrap.sh
-pip install coverage
+uv sync --group dev
source $HOME/.profile
echo "Running unittests with backend scheduler: ${BACKEND}"
tempdir=$(mktemp -d -p /scratch)
-TMPDIR=$tempdir coverage run --source=reframe ./test_reframe.py \
+TMPDIR=$tempdir uv run coverage run --source=reframe ./test_reframe.py \
--rfm-user-config=ci-scripts/configs/ci-cluster.py \
--rfm-user-system=pseudo-cluster:compute-${BACKEND:-squeue}
-coverage xml -o coverage.xml
+uv run coverage xml -o coverage.xml
diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml
index f9e86a3677..698d7837f4 100644
--- a/.github/workflows/main.yml
+++ b/.github/workflows/main.yml
@@ -7,7 +7,7 @@ jobs:
runs-on: ubuntu-latest
strategy:
matrix:
- python-version: ['3.9', '3.10', '3.11', '3.12', '3.13']
+ python-version: ['3.10', '3.11', '3.12', '3.13', '3.14']
steps:
- uses: actions/checkout@v4
- name: Set up Python ${{ matrix.python-version }}
@@ -16,12 +16,12 @@ jobs:
python-version: ${{ matrix.python-version }}
- name: Install dependencies
run: |
- ./bootstrap.sh
+ curl -LsSf https://astral.sh/uv/install.sh | sh
+ uv sync --group dev
- name: Generic Unittests
run: |
- pip install coverage
- coverage run --source=reframe ./test_reframe.py
- coverage xml -o coverage.xml
+ uv run coverage run --source=reframe ./test_reframe.py
+ uv run coverage xml -o coverage.xml
- name: Upload coverage reports
uses: codecov/codecov-action@v4.2.0
@@ -29,7 +29,7 @@ jobs:
runs-on: macos-latest
strategy:
matrix:
- python-version: ['3.9', '3.10', '3.11', '3.12', '3.13']
+ python-version: ['3.10', '3.11', '3.12', '3.13', '3.14']
steps:
- uses: actions/checkout@v4
- name: Set up Python ${{ matrix.python-version }}
@@ -38,12 +38,12 @@ jobs:
python-version: ${{ matrix.python-version }}
- name: Install dependencies
run: |
- ./bootstrap.sh
+ curl -LsSf https://astral.sh/uv/install.sh | sh
+ uv sync --group dev
- name: Generic Unittests
run: |
- pip install coverage
- coverage run --source=reframe ./test_reframe.py
- coverage xml -o coverage.xml
+ uv run coverage run --source=reframe ./test_reframe.py
+ uv run coverage xml -o coverage.xml
- name: Upload coverage reports
uses: codecov/codecov-action@v4.2.0
@@ -106,7 +106,7 @@ jobs:
runs-on: ubuntu-latest
strategy:
matrix:
- python-version: ['3.9', '3.10', '3.11', '3.12', '3.13']
+ python-version: ['3.10', '3.11', '3.12', '3.13', '3.14']
steps:
- uses: actions/checkout@v4
- name: Setup up Python ${{ matrix.python-version }}
@@ -115,11 +115,11 @@ jobs:
python-version: ${{ matrix.python-version }}
- name: Generate Wheel
run: |
- python -m pip install --upgrade pip setuptools build
- python -m build
+ curl -LsSf https://astral.sh/uv/install.sh | sh
+ uv build
- name: Install Wheel
run: |
- python -m pip install dist/*.whl
+ uv tool install dist/*.whl
- name: Test Installation
run: |
reframe -V
@@ -129,7 +129,7 @@ jobs:
runs-on: ubuntu-latest
strategy:
matrix:
- python-version: ['3.9', '3.10', '3.11', '3.12', '3.13']
+ python-version: ['3.10', '3.11', '3.12', '3.13', '3.14']
steps:
- uses: actions/checkout@v4
- name: Set up Python ${{ matrix.python-version }}
@@ -138,7 +138,8 @@ jobs:
python-version: ${{ matrix.python-version }}
- name: Install Doc Requirements
run: |
- python -m pip install -r docs/requirements.txt
+ curl -LsSf https://astral.sh/uv/install.sh | sh
+ uv sync --group docs
- name: Build documentation
run: |
- make -C docs
+ uv run make -C docs
diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml
index 3ef51dd905..554feb5a2e 100644
--- a/.github/workflows/publish.yml
+++ b/.github/workflows/publish.yml
@@ -9,14 +9,13 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- - name: Setup up Python 3.13
+ - name: Setup up Python 3.14
uses: actions/setup-python@v5
with:
- python-version: 3.13
+ python-version: 3.14
- name: Generate dist packages
run: |
- python -m pip install --upgrade pip setuptools build
- python -m build
+ uv build
- name: Publish ReFrame to PyPI
uses: pypa/gh-action-pypi-publish@release/v1
with:
diff --git a/.github/workflows/test-flux.yaml b/.github/workflows/test-flux.yaml
index fbe6b0dd4d..06864f00b4 100644
--- a/.github/workflows/test-flux.yaml
+++ b/.github/workflows/test-flux.yaml
@@ -26,21 +26,25 @@ jobs:
- name: Checkout
uses: actions/checkout@v4
- - name: Install Reframe
+ # Flux Python bindings are installed in the container; we need to create
+ # and include them in the venv, because the simple `uv run` command runs
+ # in an isolated venv
+ - name: Install ReFrame
run: |
- apt-get update && apt-get install -y python3-pip
- ./bootstrap.sh
- export PATH=$PWD/bin:$PATH
- which reframe
+ apt-get update && apt-get install -y curl
+ curl -LsSf https://astral.sh/uv/install.sh | sh
+ . $HOME/.local/bin/env
+ uv venv --system-site-packages
+ . .venv/bin/activate
+ uv sync --group dev
# Any additional examples added here will be tested
- name: Start Flux and Run Test
run: |
- export PATH=$PWD/bin:$PATH
- which reframe
- flux start reframe -c examples/howto/flux -C examples/howto/flux/settings.py -l
- flux start reframe -c examples/howto/flux -C examples/howto/flux/settings.py -r
- flux start coverage run --source=reframe ./test_reframe.py --rfm-user-config=examples/howto/flux/settings.py
- coverage xml -o coverage.xml
+ # export PATH=$PWD/bin:$PATH
+ flux start uv run reframe -c examples/howto/flux -C examples/howto/flux/settings.py -l
+ flux start uv run reframe -c examples/howto/flux -C examples/howto/flux/settings.py -r
+ flux start uv run coverage run --source=reframe ./test_reframe.py --rfm-user-config=examples/howto/flux/settings.py
+ uv run coverage xml -o coverage.xml
- name: Upload coverage reports
uses: codecov/codecov-action@v4.2.0
diff --git a/.gitignore b/.gitignore
index 4eaed2111c..e91550b05f 100644
--- a/.gitignore
+++ b/.gitignore
@@ -85,7 +85,8 @@ celerybeat-schedule
.env
# virtualenv
-venv/
+venv*
+.venv*
ENV/
# Spyder project settings
diff --git a/README.md b/README.md
index 45f4be1e7d..857ecd81f1 100644
--- a/README.md
+++ b/README.md
@@ -18,7 +18,7 @@
[](https://zenodo.org/badge/latestdoi/89384186)
[](https://twitter.com/ReFrameHPC)
-# ReFrame in a Nutshell
+# Overview
ReFrame is a powerful framework for writing system regression tests and benchmarks, specifically targeted to HPC systems.
The goal of the framework is to abstract away the complexity of the interactions with the system, separating the logic of a test from the low-level details, which pertain to the system configuration and setup.
@@ -36,50 +36,106 @@ Please visit the project's documentation [page](https://reframe-hpc.readthedocs.
## Installation
-ReFrame is fairly easy to install.
-All you need is Python 3.6 or above and to run its bootstrap script:
+ReFrame is fairly easy to install as PyPI package.
```bash
-git clone https://github.com/reframe-hpc/reframe.git
-cd reframe
-./bootstrap.sh
-./bin/reframe -V
+# Fetch uv
+curl -LsSf https://astral.sh/uv/install.sh | sh
+
+# Install ReFrame
+uv tool install reframe-hpc
+
+# Check the installation
+reframe -V
+```
+
+This will place the `reframe` executable under `$HOME/.local/bin` and the package under `$HOME/.local/share/uv/tools/reframe-hpc`.
+To make available the manpage and the shell completions add the following lines in your shell's profile script:
+
+### Bash/Zsh
+
+Add the following lines to your `$HOME/.profile`:
+
+```bash
+export MANPATH=${HOME}/.local/share/uv/tools/reframe-hpc/share/man:${MANPATH}:
+source ${HOME}/.local/share/uv/tools/reframe-hpc/share/bash-completion/completions/reframe
+```
+
+### Fish
+
+Add the following lines to your `$HOME/.config/fish/config.fish`:
+
+```bash
+set -apgx MANPATH ${HOME}/.local/share/uv/tools/reframe-hpc/share/man ""
+source ${HOME}/.local/share/uv/tools/reframe-hpc/share/fish/vendor_completions.d/reframe.fish
+```
+
+> NOTE: Using `uv` is not required to install ReFrame.
+> You can use any modern Python build system that recognizes the `pyproject.toml` file.
+
+### Multi-architecture installations on shared filesystem
+
+If you plan to install ReFrame for multiple platforms in a shared installation, you should make sure each installation resides in a different prefix.
+You can achieve this with `uv` as follows:
+
+```bash
+export UV_TOOL_BIN_DIR=$HOME/.local/$(uname -m)/bin
+export UV_TOOL_DIR=$HOME/.local/$(uname -m)/share/uv/tools
+uv tool install reframe-hpc
+export PATH=$UV_TOOL_BIN_DIR:$PATH
+reframe -V
+```
+
+Alternatively, you can use ephemeral venvs and let `uv` handle them with `uvx`:
+
+```bash
+uvx reframe --version
```
-If you want a specific release, please refer to the documentation [page](https://reframe-hpc.readthedocs.io/en/stable/started.html).
+This will pull ReFrame's dependencies and run it.
+It also caches them, so that the next time you invoke it, it will not download and install them again.
+The only "downside" of this method is that you have to always invoke ReFrame through `uvx`, as opposed to the `uv tool install` method, where the `reframe` execcutable is installed in a standard path.
+
+
+## Running from source
+If you want to run the latest ReFrame directly from the repo, you can simply clone the repo and `uv run` ReFrame:
+
+```bash
+git clone https://github.com/reframe-hpc/reframe.git
+cd reframe
+uv run reframe --version
+```
### Running the unit tests
-You can optionally run the framework's unit tests with the following command:
+To run the the framework's unit tests use the following command:
```bash
-./test_reframe.py -v
+uv sync --group dev
+uv run ./test_reframe.py -v
```
-NOTE: Unit tests require a POSIX-compliant C compiler (available through the `cc` command), as well as the `make` utility.
+> NOTE: Unit tests require a POSIX-compliant C compiler (available through the `cc` command), as well as the `make` utility.
### Building the documentation locally
-You may build the documentation of the master manually as follows:
+You may also build the documentation locally:
-```
-./bootstrap.sh +docs
+```bash
+uv sync --group docs
+uv run make -C docs
```
For viewing it, you may do the following:
-```
+```bash
cd docs/html
python3 -m http.server
```
The documentation is now up on [localhost:8000](http://localhost:8000), where you can navigate with your browser.
-## Test library
-
-The framework comes with an experimental library of tests that users can either run from the command line directly or extend and fine tune them for their systems. See [here](https://reframe-hpc.readthedocs.io/en/stable/hpctestlib.html) for more details.
-
## Public test repositories
The ReFrame HPC [community Github page](https://github.com/reframe-hpc) provides mirror forks of interesting ReFrame test repositories maintained by various sites or projects.
diff --git a/bin/reframe b/bin/reframe
deleted file mode 100755
index 0c11f31899..0000000000
--- a/bin/reframe
+++ /dev/null
@@ -1,23 +0,0 @@
-#!/usr/bin/env python3
-# PYTHON_ARGCOMPLETE_OK
-#
-# Copyright 2016-2024 Swiss National Supercomputing Centre (CSCS/ETH Zurich)
-# ReFrame Project Developers. See the top-level LICENSE file for details.
-#
-# SPDX-License-Identifier: BSD-3-Clause
-
-import os
-import platform
-import sys
-
-prefix = os.path.normpath(
- os.path.join(os.path.abspath(os.path.dirname(__file__)), '..')
-)
-external = os.path.join(prefix, 'external', platform.machine())
-sys.path = [prefix, external] + sys.path
-
-
-import reframe.frontend.cli as cli # noqa: F401, F403
-
-if __name__ == '__main__':
- cli.main()
diff --git a/bootstrap.sh b/bootstrap.sh
deleted file mode 100755
index 29a99060d8..0000000000
--- a/bootstrap.sh
+++ /dev/null
@@ -1,125 +0,0 @@
-#!/bin/bash
-#
-# Copyright 2016-2024 Swiss National Supercomputing Centre (CSCS/ETH Zurich)
-# ReFrame Project Developers. See the top-level LICENSE file for details.
-#
-# SPDX-License-Identifier: BSD-3-Clause
-
-#
-# Bootstrap script for running ReFrame from source
-#
-
-if [ -t 1 ]; then
- BLUE='\033[0;34m'
- GREEN='\033[0;32m'
- YELLOW='\033[0;33m'
- NC='\033[0m'
-fi
-
-INFO()
-{
- echo -e "${BLUE}==>${NC}" ${YELLOW}$*${NC}
-}
-
-
-CMD()
-{
- echo -e "${BLUE}==>${NC}" ${YELLOW}$*${NC} && $*
-}
-
-CMD_M()
-{
- msg=$1
- shift && echo -e "${BLUE}==> [$msg]${NC}" ${YELLOW}$*${NC} && $*
-}
-
-usage()
-{
- echo "Usage: $0 [-h] [+docs] [+pygelf]"
- echo "Bootstrap ReFrame by pulling all its dependencies"
- echo " -P EXEC Use EXEC as Python interpreter"
- echo " -h Print this help message and exit"
- echo " --ignore-errors Ignore installation errors"
- echo " --pip-opts Pass additional options to pip."
- echo " +docs Build also the documentation"
- echo " +pygelf Install also the pygelf Python package"
-}
-
-
-while getopts "hP:-:" opt; do
- case $opt in
- "P") python=$OPTARG ;;
- "h") usage && exit 0 ;;
- "-")
- case "${OPTARG}" in
- "ignore-errors") ignore_errors=1 ;;
- pip-opts)
- PIPOPTS="${!OPTIND}"; OPTIND=$(( $OPTIND + 1 )) ;;
- pip-opts=*)
- PIPOPTS=${OPTARG#*=} ;;
- esac;;
- "?") usage && exit 0 ;;
- esac
-done
-
-if [ -z $ignore_errors ]; then
- set -e
-fi
-
-shift $((OPTIND - 1))
-if [ -z "$python" ]; then
- python=python3
-fi
-
-while [ -n "$1" ]; do
- case "$1" in
- "+docs") MAKEDOCS="true" && shift ;;
- "+pygelf") PYGELF="true" && shift ;;
- *) usage && exit 1 ;;
- esac
-done
-
-if $python -c 'import sys; sys.exit(sys.version_info[:2] >= (3, 6))'; then
- echo -e "ReFrame requires Python >= 3.6 (found $($python -V 2>&1))"
- exit 1
-fi
-
-venvdir=$(mktemp -d)
-CMD python3 -m venv --without-pip $venvdir
-CMD source $venvdir/bin/activate
-
-_destroy_venv() {
- deactivate
- /bin/rm -rf $venvdir
-}
-
-trap _destroy_venv EXIT
-
-# Create an arch-specific installation
-py_pkg_prefix=external/$(uname -m)
-
-# Install a fresh pip in the current environment
-pyver=$(python3 -c 'import sys; print(".".join(str(s) for s in sys.version_info[:2]))')
-if [ "$pyver" == "3.6" ]; then
- get_pip="$PWD/tools/python/3.6/get-pip.py"
-elif [ "$pyver" == "3.7" ]; then
- get_pip="$PWD/tools/python/3.7/get-pip.py"
-else
- get_pip="$PWD/tools/python/get-pip.py"
-fi
-
-$python $get_pip
-export PATH=$PWD/$py_pkg_prefix/usr/bin:$PATH
-export PYTHONPATH=$PWD/$py_pkg_prefix:$PYTHONPATH
-if [ -n "$PYGELF" ]; then
- tmp_requirements=$(mktemp)
- sed -e 's/^#+pygelf%//g' requirements.txt > $tmp_requirements
- CMD_M +pygelf $python -m pip install --no-cache-dir -q -r $tmp_requirements --target=$py_pkg_prefix/ --upgrade $PIPOPTS && rm $tmp_requirements
-else
- CMD $python -m pip install --use-pep517 --no-cache-dir -q -r requirements.txt --target=$py_pkg_prefix/ --upgrade $PIPOPTS
-fi
-
-if [ -n "$MAKEDOCS" ]; then
- CMD_M +docs $python -m pip install --use-pep517 --no-cache-dir -q -r docs/requirements.txt --target=$py_pkg_prefix/ --upgrade $PIPOPTS
- make -C docs PYTHON=$python
-fi
diff --git a/ci-scripts/ci-runner.bash b/ci-scripts/ci-runner.bash
deleted file mode 100644
index 08e0062c9d..0000000000
--- a/ci-scripts/ci-runner.bash
+++ /dev/null
@@ -1,189 +0,0 @@
-#!/bin/bash
-
-scriptname=`basename $0`
-CI_FOLDER=""
-CI_GENERIC=0
-CI_TUTORIAL=0
-CI_EXITCODE=0
-TERM="${TERM:-xterm}"
-PROFILE=""
-MODULEUSE=""
-
-
-#
-# This function prints the script usage form
-#
-usage()
-{
- cat <$(tput sgr0)
-
- $(tput setaf 3)OPTIONS:$(tput sgr0)
-
- $(tput setaf 3)-f | --folder$(tput sgr0) $(tput setaf 1)DIR$(tput sgr0) ci folder, e.g. reframe-ci
- $(tput setaf 3)-i | --invocation$(tput sgr0) $(tput setaf 1)ARGS$(tput sgr0) invocation for modified user checks. Multiple \`-i' options are multiple invocations
- $(tput setaf 3)-l | --load-profile$(tput sgr0) $(tput setaf 1)ARGS$(tput sgr0) sources the given file before any execution of commands
- $(tput setaf 3)-m | --module-use$(tput sgr0) $(tput setaf 1)ARGS$(tput sgr0) executes module use of the give folder before loading the regression
- $(tput setaf 3)-g | --generic-only$(tput sgr0) executes unit tests using the generic configuration
- $(tput setaf 3)-t | --tutorial-only$(tput sgr0) executes only the modified/new tutorial tests
- $(tput setaf 3)-h | --help$(tput sgr0) prints this help and exits
-
-EOF
-} # end of usage
-
-checked_exec()
-{
- echo "[RUN] $@" && "$@"
- if [ $? -ne 0 ]; then
- CI_EXITCODE=1
- fi
-}
-
-run_tutorial_checks()
-{
- export RFM_AUTODETECT_XTHOSTNAME=1
- cmd="./bin/reframe -C tutorials/config/daint_ext.py -J account=jenscscs \
---save-log-files --flex-alloc-nodes=2 -r -x HelloThreadedExtendedTest|BZip2.*Check $@"
- echo "[INFO] Running tutorial checks with \`$cmd'"
- checked_exec $cmd
-}
-
-### Main script ###
-
-shortopts="h,g,t,f:,i:,l:,m:"
-longopts="help,generic-only,tutorial-only,folder:,invocation:,load-profile:,module-use:"
-
-eval set -- $(getopt -o ${shortopts} -l ${longopts} \
- -n ${scriptname} -- "$@" 2> /dev/null)
-
-num_invocations=0
-invocations=()
-while [ $# -ne 0 ]; do
- case $1 in
- -h | --help)
- usage
- exit 0 ;;
- -f | --folder)
- shift
- CI_FOLDER="$1" ;;
- -i | --invocation)
- shift
-
- # We need to set explicitly the array elements, in order to account
- # for whitespace characters. The alternative way of expanding the
- # array `invocations+=($1)' whould not treat whitespace correctly.
- invocations[$num_invocations]=$1
- ((num_invocations++)) ;;
- -l | --load-profile)
- shift
- PROFILE="$1" ;;
- -m | --module-use)
- shift
- MODULEUSE="$1" ;;
- -g | --generic-only)
- shift
- CI_GENERIC=1 ;;
- -t | --tutorial-only)
- shift
- CI_TUTORIAL=1 ;;
- --)
- ;;
- *)
- echo "[ERROR] ${scriptname}: Unrecognized argument \`$1'" >&2
- usage
- exit 1 ;;
- esac
- shift
-done
-
-#
-# Check if package_list_file was defined
-#
-if [ "X${CI_FOLDER}" == "X" ]; then
- usage
- exit 1
-fi
-
-#
-# Sourcing a given profile
-#
-if [ "X${PROFILE}" != "X" ]; then
- source ${PROFILE}
-fi
-
-#
-# module use for a given folder
-#
-if [ "X${MODULEUSE}" != "X" ]; then
- module use ${MODULEUSE}
-fi
-
-parallel_opts=""
-
-# Bootstrap ReFrame
-./bootstrap.sh
-
-echo "[INFO] Loaded Modules"
-module list
-
-cd ${CI_FOLDER}
-echo "[INFO] Running unit tests on $(hostname) in ${CI_FOLDER}"
-
-if [ $CI_GENERIC -eq 1 ]; then
- # Run unit tests for the public release
- echo "[INFO] Running unit tests with generic settings"
- checked_exec ./test_reframe.py ${parallel_opts} \
- -W=error::reframe.core.warnings.ReframeDeprecationWarning -ra
- checked_exec ! ./bin/reframe.py --system=generic -l 2>&1 | \
- grep -- '--- Logging error ---'
-elif [ $CI_TUTORIAL -eq 1 ]; then
- # Run tutorial checks
- # Find modified or added tutorial checks
- tutorialchecks=( $(git diff origin/master...HEAD --name-only --oneline --no-merges | \
- grep -e '^tutorials/.*\.py') )
-
- if [ ${#tutorialchecks[@]} -ne 0 ]; then
- tutorialchecks_path=""
- for check in ${tutorialchecks[@]}; do
- tutorialchecks_path="${tutorialchecks_path} -c ${check}"
- done
-
- if [[ $(hostname) =~ daint ]]; then
- echo "[INFO] Applying tutorial patch for daint"
- patch -s -p0 < ci-scripts/tutorials.patch
- fi
-
- echo "[INFO] Modified tutorial checks"
- echo ${tutorialchecks_path}
- for i in ${!invocations[@]}; do
- run_tutorial_checks ${tutorialchecks_path} ${invocations[i]}
- done
- fi
-else
- # Run unit tests with the scheduler backends
- tempdir=$(mktemp -d -p $SCRATCH)
- echo "[INFO] Using temporary directory: $tempdir"
- export RFM_AUTODETECT_XTHOSTNAME=1
- if [[ $(hostname) =~ dom ]]; then
- PATH_save=$PATH
- export PATH=/apps/dom/UES/karakasv/slurm-wrappers/bin:$PATH
- for backend in slurm pbs torque; do
- echo "[INFO] Running unit tests with ${backend}"
- TMPDIR=$tempdir checked_exec ./test_reframe.py ${parallel_opts} \
- --rfm-user-config=ci-scripts/configs/cscs-ci.py \
- -W=error::reframe.core.warnings.ReframeDeprecationWarning \
- --rfm-user-system=dom:${backend} -ra
- done
- export PATH=$PATH_save
- else
- echo "[INFO] Running unit tests"
- TMPDIR=$tempdir checked_exec ./test_reframe.py ${parallel_opts} \
- --rfm-user-config=ci-scripts/configs/cscs-ci.py \
- -W=error::reframe.core.warnings.ReframeDeprecationWarning -ra
- fi
-
- if [ $CI_EXITCODE -eq 0 ]; then
- /bin/rm -rf $tempdir
- fi
-fi
-exit $CI_EXITCODE
diff --git a/ci-scripts/dockerfiles/Lmod.dockerfile b/ci-scripts/dockerfiles/Lmod.dockerfile
index 4240d9eb5a..411528364e 100644
--- a/ci-scripts/dockerfiles/Lmod.dockerfile
+++ b/ci-scripts/dockerfiles/Lmod.dockerfile
@@ -1,3 +1,8 @@
+# Copyright 2016-2026 Swiss National Supercomputing Centre (CSCS/ETH Zurich)
+# ReFrame Project Developers. See the top-level LICENSE file for details.
+#
+# SPDX-License-Identifier: BSD-3-Clause
+
FROM ubuntu:24.04
ENV TZ=Europe/Zurich
diff --git a/ci-scripts/dockerfiles/Lmod77.dockerfile b/ci-scripts/dockerfiles/Lmod77.dockerfile
index 93612eaf56..7d3fa27dfa 100644
--- a/ci-scripts/dockerfiles/Lmod77.dockerfile
+++ b/ci-scripts/dockerfiles/Lmod77.dockerfile
@@ -1,3 +1,8 @@
+# Copyright 2016-2026 Swiss National Supercomputing Centre (CSCS/ETH Zurich)
+# ReFrame Project Developers. See the top-level LICENSE file for details.
+#
+# SPDX-License-Identifier: BSD-3-Clause
+
#
# LMod versions prior to 8.2 emitted Python commands differently, so we use this
# Dockerfile to test the bindings of older versions
diff --git a/ci-scripts/dockerfiles/Tmod32.dockerfile b/ci-scripts/dockerfiles/Tmod32.dockerfile
index fd9e94ecd8..ac91935ff3 100644
--- a/ci-scripts/dockerfiles/Tmod32.dockerfile
+++ b/ci-scripts/dockerfiles/Tmod32.dockerfile
@@ -1,3 +1,8 @@
+# Copyright 2016-2026 Swiss National Supercomputing Centre (CSCS/ETH Zurich)
+# ReFrame Project Developers. See the top-level LICENSE file for details.
+#
+# SPDX-License-Identifier: BSD-3-Clause
+
FROM centos:7
RUN sed -i s/mirror.centos.org/vault.centos.org/g /etc/yum.repos.d/*.repo && \
diff --git a/ci-scripts/dockerfiles/eb-spack-howto.dockerfile b/ci-scripts/dockerfiles/eb-spack-howto.dockerfile
index c0a92e2962..9058dcc2b4 100644
--- a/ci-scripts/dockerfiles/eb-spack-howto.dockerfile
+++ b/ci-scripts/dockerfiles/eb-spack-howto.dockerfile
@@ -1,9 +1,15 @@
+# Copyright 2016-2026 Swiss National Supercomputing Centre (CSCS/ETH Zurich)
+# ReFrame Project Developers. See the top-level LICENSE file for details.
+#
+# SPDX-License-Identifier: BSD-3-Clause
+
#
# Execute this from the top-level ReFrame source directory
#
FROM ghcr.io/reframe-hpc/lmod:9.0.4
+COPY --from=ghcr.io/astral-sh/uv:latest /uv /uvx /usr/local/bin/
ENV _SPACK_VER=1.1.0
ENV _EB_VER=5.1.2
@@ -20,7 +26,6 @@ USER rfmuser
# Install Spack
RUN git clone --branch v${_SPACK_VER} --depth 1 https://github.com/spack/spack ~/spack
-RUN pip3 install --break-system-packages easybuild==${_EB_VER}
ENV PATH="/home/rfmuser/.local/bin:${PATH}"
@@ -29,10 +34,12 @@ COPY --chown=rfmuser . /home/rfmuser/reframe/
WORKDIR /home/rfmuser/reframe
-RUN ./bootstrap.sh
+RUN uv sync --group dev && \
+ echo '. /usr/local/lmod/lmod/init/profile && . /home/rfmuser/spack/share/spack/setup-env.sh' >> /home/rfmuser/.profile
-RUN echo '. /usr/local/lmod/lmod/init/profile && . /home/rfmuser/spack/share/spack/setup-env.sh' > /home/rfmuser/setup.sh
+# Install EasyBuild
+RUN uv pip install easybuild==${_EB_VER}
-ENV BASH_ENV=/home/rfmuser/setup.sh
+ENV BASH_ENV=/home/rfmuser/.profile
-CMD ["/bin/bash", "-c", "./bin/reframe --system=tutorialsys --exec-policy=serial -r -C examples/tutorial/config/baseline_modules.py -R -c examples/tutorial/easybuild/eb_test.py -c examples/tutorial/spack/spack_test.py"]
+CMD ["/bin/bash", "-c", "uv run reframe --system=tutorialsys --exec-policy=serial -r -C examples/tutorial/config/baseline_modules.py -R -c examples/tutorial/easybuild/eb_test.py -c examples/tutorial/spack/spack_test.py"]
diff --git a/ci-scripts/dockerfiles/envmodules.dockerfile b/ci-scripts/dockerfiles/envmodules.dockerfile
index e9360e38c0..d1513c1475 100644
--- a/ci-scripts/dockerfiles/envmodules.dockerfile
+++ b/ci-scripts/dockerfiles/envmodules.dockerfile
@@ -1,3 +1,8 @@
+# Copyright 2016-2026 Swiss National Supercomputing Centre (CSCS/ETH Zurich)
+# ReFrame Project Developers. See the top-level LICENSE file for details.
+#
+# SPDX-License-Identifier: BSD-3-Clause
+
FROM ubuntu:24.04
ENV TZ=Europe/Zurich
diff --git a/ci-scripts/dockerfiles/reframe-envmodules.dockerfile b/ci-scripts/dockerfiles/reframe-envmodules.dockerfile
index 6ab319c745..b57656b76e 100644
--- a/ci-scripts/dockerfiles/reframe-envmodules.dockerfile
+++ b/ci-scripts/dockerfiles/reframe-envmodules.dockerfile
@@ -1,14 +1,19 @@
+# Copyright 2016-2026 Swiss National Supercomputing Centre (CSCS/ETH Zurich)
+# ReFrame Project Developers. See the top-level LICENSE file for details.
+#
+# SPDX-License-Identifier: BSD-3-Clause
+
#
# Execute this from the top-level ReFrame source directory
#
FROM ghcr.io/reframe-hpc/envmodules:5.6.1
-
+COPY --from=ghcr.io/astral-sh/uv:latest /uv /uvx /usr/local/bin/
# ReFrame requirements
RUN \
apt-get -y update && \
- apt-get -y install gcc make git python3 python3-pip
+ apt-get -y install gcc make git python3
# ReFrame user
RUN useradd -ms /bin/bash rfmuser
@@ -20,8 +25,8 @@ COPY --chown=rfmuser . /home/rfmuser/reframe/
WORKDIR /home/rfmuser/reframe
-RUN ./bootstrap.sh
-RUN pip install --break-system-packages coverage
+RUN uv sync --group dev && \
+ echo ". $BASH_ENV" >> /home/rfmuser/.profile
ENV BASH_ENV=/home/rfmuser/.profile
-CMD ["/bin/bash", "-c", "coverage run --source=reframe ./test_reframe.py --rfm-user-config=ci-scripts/configs/envmod.py; coverage xml -o coverage.xml"]
+CMD ["/bin/bash", "-c", "uv run coverage run --source=reframe ./test_reframe.py --rfm-user-config=ci-scripts/configs/envmod.py; uv run coverage xml -o coverage.xml"]
diff --git a/ci-scripts/dockerfiles/reframe-lmod.dockerfile b/ci-scripts/dockerfiles/reframe-lmod.dockerfile
index 2bd098d4a9..d112a577d2 100644
--- a/ci-scripts/dockerfiles/reframe-lmod.dockerfile
+++ b/ci-scripts/dockerfiles/reframe-lmod.dockerfile
@@ -1,13 +1,18 @@
+# Copyright 2016-2026 Swiss National Supercomputing Centre (CSCS/ETH Zurich)
+# ReFrame Project Developers. See the top-level LICENSE file for details.
+#
+# SPDX-License-Identifier: BSD-3-Clause
+
#
# Execute this from the top-level ReFrame source directory
#
-
FROM ghcr.io/reframe-hpc/lmod:9.0.4
+COPY --from=ghcr.io/astral-sh/uv:latest /uv /uvx /usr/local/bin/
# Install ReFrame unit test requirements
RUN apt-get -y update && \
- apt-get -y install gcc git make python3 python3-pip
+ apt-get -y install gcc git make python3
# ReFrame user
RUN useradd -ms /bin/bash rfmuser
@@ -19,9 +24,8 @@ COPY --chown=rfmuser . /home/rfmuser/reframe/
WORKDIR /home/rfmuser/reframe
-RUN ./bootstrap.sh
-RUN pip install --break-system-packages coverage
-RUN echo '. /usr/local/lmod/lmod/init/profile' >> /home/rfmuser/.profile
+RUN uv sync --group dev && \
+ echo ". $BASH_ENV" >> /home/rfmuser/.profile
ENV BASH_ENV=/home/rfmuser/.profile
-CMD ["/bin/bash", "-c", "coverage run --source=reframe ./test_reframe.py -v --rfm-user-config=ci-scripts/configs/lmod.py; coverage xml -o coverage.xml"]
+CMD ["/bin/bash", "-c", "uv run coverage run --source=reframe ./test_reframe.py -v --rfm-user-config=ci-scripts/configs/lmod.py; uv run coverage xml -o coverage.xml"]
diff --git a/ci-scripts/dockerfiles/reframe-python.dockerfile b/ci-scripts/dockerfiles/reframe-python.dockerfile
deleted file mode 100644
index 13156b8ed8..0000000000
--- a/ci-scripts/dockerfiles/reframe-python.dockerfile
+++ /dev/null
@@ -1,24 +0,0 @@
-# Copyright 2016-2025 Swiss National Supercomputing Centre (CSCS/ETH Zurich)
-# ReFrame Project Developers. See the top-level LICENSE file for details.
-#
-# SPDX-License-Identifier: BSD-3-Clause
-
-ARG PYTHON_VERSION=3.9
-
-FROM docker.io/python:${PYTHON_VERSION}
-
-# ReFrame user
-RUN useradd -ms /bin/bash rfmuser
-
-USER rfmuser
-
-# Install ReFrame from the current directory
-COPY --chown=rfmuser . /home/rfmuser/reframe/
-
-WORKDIR /home/rfmuser/reframe
-
-RUN ./bootstrap.sh
-RUN pip install --break-system-packages coverage
-ENV BASH_ENV=/home/rfmuser/.profile
-
-CMD ["/bin/bash", "-c", "coverage run --source=reframe ./test_reframe.py; coverage xml -o coverage.xml"]
diff --git a/ci-scripts/dockerfiles/reframe-spack.dockerfile b/ci-scripts/dockerfiles/reframe-spack.dockerfile
index c8c2bebd97..a671ac74c8 100644
--- a/ci-scripts/dockerfiles/reframe-spack.dockerfile
+++ b/ci-scripts/dockerfiles/reframe-spack.dockerfile
@@ -1,15 +1,21 @@
+# Copyright 2016-2026 Swiss National Supercomputing Centre (CSCS/ETH Zurich)
+# ReFrame Project Developers. See the top-level LICENSE file for details.
+#
+# SPDX-License-Identifier: BSD-3-Clause
+
#
# Execute this from the top-level ReFrame source directory
#
FROM ubuntu:24.04
+COPY --from=ghcr.io/astral-sh/uv:latest /uv /uvx /usr/local/bin/
ENV _SPACK_VER=1.1.0
# Install ReFrame unit test requirements
RUN apt-get -y update && \
- apt-get -y install gcc git make python3 python3-pip
+ apt-get -y install gcc git make python3
# ReFrame user
RUN useradd -ms /bin/bash rfmuser
@@ -24,10 +30,8 @@ COPY --chown=rfmuser . /home/rfmuser/reframe/
WORKDIR /home/rfmuser/reframe
-RUN ./bootstrap.sh
-RUN pip install --break-system-packages coverage
-
-RUN echo '. /home/rfmuser/spack/share/spack/setup-env.sh' >> /home/rfmuser/.profile
+RUN uv sync --group dev && \
+ echo '. /home/rfmuser/spack/share/spack/setup-env.sh' >> /home/rfmuser/.profile
ENV BASH_ENV=/home/rfmuser/.profile
-CMD ["/bin/bash", "-c", "coverage run --source=reframe ./test_reframe.py -v --rfm-user-config=ci-scripts/configs/spack.py; coverage xml -o coverage.xml"]
+CMD ["/bin/bash", "-c", "uv run coverage run --source=reframe ./test_reframe.py -v --rfm-user-config=ci-scripts/configs/spack.py; uv run coverage xml -o coverage.xml"]
diff --git a/ci-scripts/tutorials.patch b/ci-scripts/tutorials.patch
deleted file mode 100644
index 9a4bb3c04e..0000000000
--- a/ci-scripts/tutorials.patch
+++ /dev/null
@@ -1,68 +0,0 @@
-diff -Nru tutorials.orig/advanced/affinity/affinity.py tutorials/advanced/affinity/affinity.py
---- tutorials.orig/advanced/affinity/affinity.py 2022-11-17 19:13:28.000000000 +0100
-+++ tutorials/advanced/affinity/affinity.py 2022-11-17 19:12:26.000000000 +0100
-@@ -26,3 +26,11 @@
- @sanity_function
- def validate_test(self):
- return sn.assert_found(r'CPU affinity', self.stdout)
-+
-+ @run_before('compile')
-+ def prgenv_nvidia_workaround(self):
-+ ce = self.current_environ.name
-+ if ce == 'nvidia':
-+ self.build_system.cppflags += [
-+ '-D__GCC_ATOMIC_TEST_AND_SET_TRUEVAL'
-+ ]
-diff -Nru tutorials.orig/basics/hello/hello2.py tutorials/basics/hello/hello2.py
---- tutorials.orig/basics/hello/hello2.py 2022-11-17 19:13:28.000000000 +0100
-+++ tutorials/basics/hello/hello2.py 2022-11-17 19:12:26.000000000 +0100
-@@ -13,6 +13,7 @@
-
- valid_systems = ['*']
- valid_prog_environs = ['*']
-+ build_system = 'SingleSource'
-
- @run_before('compile')
- def set_sourcepath(self):
-@@ -21,3 +22,11 @@
- @sanity_function
- def assert_hello(self):
- return sn.assert_found(r'Hello, World\!', self.stdout)
-+
-+ @run_before('compile')
-+ def prgenv_nvidia_workaround(self):
-+ ce = self.current_environ.name
-+ if ce == 'nvidia' and self.lang=='cpp':
-+ self.build_system.cppflags = [
-+ '-D__GCC_ATOMIC_TEST_AND_SET_TRUEVAL'
-+ ]
-diff -Nru tutorials.orig/basics/hellomp/hellomp1.py tutorials/basics/hellomp/hellomp1.py
---- tutorials.orig/basics/hellomp/hellomp1.py 2022-11-17 19:13:28.000000000 +0100
-+++ tutorials/basics/hellomp/hellomp1.py 2022-11-17 19:12:26.000000000 +0100
-@@ -25,3 +25,11 @@
- @sanity_function
- def assert_hello(self):
- return sn.assert_found(r'Hello, World\!', self.stdout)
-+
-+ @run_before('compile')
-+ def prgenv_nvidia_workaround(self):
-+ ce = self.current_environ.name
-+ if ce == 'nvidia':
-+ self.build_system.cppflags += [
-+ '-D__GCC_ATOMIC_TEST_AND_SET_TRUEVAL'
-+ ]
-diff -Nru tutorials.orig/basics/hellomp/hellomp3.py tutorials/basics/hellomp/hellomp3.py
---- tutorials.orig/basics/hellomp/hellomp3.py 2022-11-17 19:13:28.000000000 +0100
-+++ tutorials/basics/hellomp/hellomp3.py 2022-11-17 19:12:26.000000000 +0100
-@@ -28,3 +28,11 @@
- num_messages = sn.len(sn.findall(r'\[\s?\d+\] Hello, World\!',
- self.stdout))
- return sn.assert_eq(num_messages, 16)
-+
-+ @run_before('compile')
-+ def prgenv_nvidia_workaround(self):
-+ ce = self.current_environ.name
-+ if ce == 'nvidia':
-+ self.build_system.cppflags += [
-+ '-D__GCC_ATOMIC_TEST_AND_SET_TRUEVAL'
-+ ]
diff --git a/docs/requirements.txt b/docs/requirements.txt
deleted file mode 100644
index a56e89b77d..0000000000
--- a/docs/requirements.txt
+++ /dev/null
@@ -1,13 +0,0 @@
-archspec==0.2.5
-ClusterShell==1.9.3
-docutils==0.21.2; python_version >= '3.9'
-fasteners==0.19; python_version < '3.10'
-fasteners==0.20; python_version >= '3.10'
-jinja2==3.1.6
-jsonschema==3.2.0
-PyYAML==6.0.3
-semver==3.0.4
-Sphinx==7.4.7; python_version == '3.9'
-Sphinx==8.1.3; python_version == '3.10'
-Sphinx==8.2.3; python_version >= '3.11'
-sphinx-rtd-theme==3.1.0
diff --git a/docs/started.rst b/docs/started.rst
index fa6bd84aa5..018387e943 100644
--- a/docs/started.rst
+++ b/docs/started.rst
@@ -5,8 +5,7 @@ Getting Started
Requirements
------------
-* Python 3.9 or higher.
- Python 2 is not supported.
+* Python 3.10 or higher.
* The required Python packages are the following:
.. literalinclude:: ../requirements.txt
@@ -20,84 +19,77 @@ Requirements
.. versionchanged:: 4.10
- Support for Python < 3.9 is dropped.
+ Support for Python <= 3.9 is dropped.
Getting the Framework
---------------------
-Stable ReFrame releases are available through different channels.
+ReFrame is available as modern Python package using ``pyproject.toml`` and it can be installed using different Python package managers.
+---
+uv
+---
------
-Spack
------
-
-ReFrame is available as a `Spack `__ package:
+ReFrame can be installed using the `uv `__ package manager:
.. code:: bash
- spack install reframe
-
+ # Install standard ReFrame
+ uv tool install reframe-hpc
-There are the following variants available:
+ # Install with Graylog bindings
+ uv tool install reframe-hpc --extra graylog
-- ``+docs``: This will install the man pages of ReFrame.
-- ``+gelf``: This will install the bindings for handling `Graylog `__ log messages.
-
-
----------
-EasyBuild
----------
-
-
-ReFrame is available as an `EasyBuild `__ package:
-
-.. code:: bash
-
- eb ReFrame-VERSION.eb -r
-
-
-This will install the man pages as well as the `Graylog `__ bindings.
+ # Install a dev release from Github
+ uv tool install git+https://github.com/reframe-hpc/reframe.git@VERSION_TAG
----
PyPI
----
-
-ReFrame is available as a `PyPI `__ package:
+ReFrame is available as a `PyPI `__ package:
.. code:: bash
+ # Install standard ReFrame
pip install reframe-hpc
+ # Install with Graylog bindings
+ pip install reframe-hpc[graylog]
+
+ # Install a dev release from Github
+ pip install git+https://github.com/reframe-hpc/reframe.git@VERSION_TAG
+
-This is a bare installation of the framework.
-It will not install the documentation, the tutorial examples or the bindings for handling `Graylog `__ log messages.
+-----------
+From source
+-----------
+You can also install ReFrame from source by cloning the repository and running it as follows:
-------
-Github
-------
+.. code-block:: bash
+
+ git clone https://github.com/reframe-hpc/reframe.git
+ cd reframe
+ uv run reframe --version
-Any ReFrame version can be very easily installed directly from Github:
+If you are contributing to ReFrame, you should also install the deppendencies for the unit tests and the documentation:
.. code-block:: bash
- pushd /path/to/install/prefix
- git clone -q --depth 1 --branch VERSION_TAG https://github.com/reframe-hpc/reframe.git
- pushd reframe && ./bootstrap.sh && popd
- export PATH=$(pwd)/bin:$PATH
- popd
+ uv sync --group dev --group docs
+
+ # Run the unit tests
+ uv run ./test_reframe.py
-The ``VERSION_TAG`` is the version number prefixed by ``v``, e.g., ``v3.5.0``.
-The ``./bootstrap.sh`` script will fetch ReFrame's requirements under its installation prefix.
-It will not set the ``PYTHONPATH``, so it will not affect the user's Python installation.
-The ``./bootstrap.sh`` has two additional variant options:
+ # Build the documentation
+ uv run make -C docs
+
+ # View the documentation locally
+ cd docs/html && python -m http.server
-- ``+docs``: This will also build the documentation.
-- ``+pygelf``: This will install the bindings for handling `Graylog `__ log messages.
.. note::
.. versionadded:: 3.1
@@ -108,19 +100,72 @@ The ``./bootstrap.sh`` has two additional variant options:
ReFrame now supports multiarch builds and it will place all of its dependencies in an arch-specific directory under its prefix.
Also, ``pip`` is no more required, as the bootstrap script will start a virtual environment without ``pip`` and will fetch a fresh ``pip``, which will be used to install the dependencies.
+ .. versionchanged:: 4.10
+ ReFrame has become a ``pyproject.toml``-based package.
+ The ``bootstrap.sh`` is no more available.
+ Users can now run ``uv run reframe`` directly.
+
+
+-----
+Spack
+-----
+
+ReFrame is available as a `Spack `__ package:
+
+.. code:: bash
+
+ spack install reframe
+
+
+There are the following variants available:
+
+- ``+docs``: This will install the man pages of ReFrame.
+- ``+gelf``: This will install the bindings for handling `Graylog `__ log messages.
+
+
+.. note::
+
+ This is maintained by the Spack community and it may not be up to date with the latest ReFrame releases.
+
+
+---------
+EasyBuild
+---------
+
+
+ReFrame is available as an `EasyBuild `__ package:
+
+.. code:: bash
+
+ eb ReFrame-VERSION.eb -r
+
+
+This will install the man pages as well as the `Graylog `__ bindings.
+
+.. note::
+
+ This is maintained by the EasyBuild community and it may not be up to date with the latest ReFrame releases.
+
Enabling auto-completion
------------------------
.. versionadded:: 3.4.1
-You can enable auto-completion for ReFrame by sourcing in your shell the corresponding script in ``/share/completions/reframe.``.
-Auto-completion is supported for Bash, Tcsh and Fish shells.
+You can enable auto-completion for ReFrame by sourcing the completion script for your shell.
+ReFrame stores the completions in the standard locations used by the different shells under its installation prefix.
+For bash completions, this is ``/share/bash-completion/completions/reframe``, whereas of for Fish shell, this is ``/share/fish/vendor_completions.d/reframe.fish``.
+
+The installation prefix varies based on the method you used to install ReFrame.
+If you installed ReFrame using ``uv tool install``, the installation prefix ``$UV_TOOL_DIR/reframe-hpc/share``, where ``$UV_TOOL_DIR`` is by default ``~/.local/share/uv/tools``.
+If you installed ReFrame using ``pip install`` inside a virtual environment, the installation prefix is the ``$VIRTUAL_ENV/share``.
.. note::
.. versionchanged:: 3.4.2
The shell completion scripts have been moved under ``share/completions/``.
+ .. versionchanged:: 4.10
+ The shell completion scripts are now installed by default with ReFrame.
Where to Go from Here
diff --git a/examples/howto/flux/settings.py b/examples/howto/flux/settings.py
index 0e7de6327c..a4ffbb871b 100644
--- a/examples/howto/flux/settings.py
+++ b/examples/howto/flux/settings.py
@@ -16,7 +16,7 @@
'partitions': [
{
'name': 'default',
- 'scheduler': 'flux',
+ 'scheduler': 'flux',
'launcher': 'local',
'environs': ['builtin']
}
@@ -66,4 +66,20 @@
]
}
],
+ 'general': [
+ {
+ # Flux Python bindings are supplied by the container, so reframe's
+ # default uv run method will not work, as it runs in an isolated
+ # environment. The unit tests create a venv pulling in system site
+ # packages, so here we install reframe in that venv.
+ #
+ # FIXME: Ideally, Flux bindings should be added and installed as a
+ # ReFrame extra, but this at the moment does not seem
+ # straightforward.
+ 'remote_install': [
+ 'cp -t . -r ../reframe ../README.md ../pyproject.toml', # noqa: E501
+ 'uv tool install .'
+ ]
+ }
+ ]
}
diff --git a/examples/tutorial/dockerfiles/eb-spack.dockerfile b/examples/tutorial/dockerfiles/eb-spack.dockerfile
index 34a6fe1217..f22937b908 100644
--- a/examples/tutorial/dockerfiles/eb-spack.dockerfile
+++ b/examples/tutorial/dockerfiles/eb-spack.dockerfile
@@ -4,6 +4,7 @@
FROM ghcr.io/reframe-hpc/lmod:9.0.4
+COPY --from=ghcr.io/astral-sh/uv:latest /uv /uvx /usr/local/bin/
ENV _SPACK_VER=1.1.0
ENV _EB_VER=5.1.2
@@ -14,13 +15,6 @@ RUN apt-get -y update && \
apt-get -y install python3-pip && \
apt-get -y install gcc git jq libomp-dev tree vim
-# Install reframe
-ARG REFRAME_TAG=develop
-WORKDIR /usr/local/share
-RUN git clone --depth 1 --branch $REFRAME_TAG https://github.com/reframe-hpc/reframe.git && \
- cd reframe/ && ./bootstrap.sh
-ENV PATH=/usr/local/share/reframe/bin:$PATH
-
# Install EasyBuild
RUN pip3 install --break-system-packages easybuild==${_EB_VER}
@@ -35,5 +29,10 @@ WORKDIR /home/user
RUN mkdir .local && cd .local && \
git clone --branch v${_SPACK_VER} --depth 1 https://github.com/spack/spack
+# Install reframe
+COPY . reframe/
+RUN cd reframe && uv tool install . && \
+ echo 'export PATH=/home/user/.local/bin:$PATH' >> /home/user/.profile
+
RUN echo '. /usr/local/lmod/lmod/init/profile && . /home/user/.local/spack/share/spack/setup-env.sh' >> /home/user/.profile
ENV BASH_ENV=/home/user/.profile
diff --git a/examples/tutorial/dockerfiles/slurm-cluster/reframe/Dockerfile b/examples/tutorial/dockerfiles/slurm-cluster/reframe/Dockerfile
index 5a4a2b76b7..944471b38e 100644
--- a/examples/tutorial/dockerfiles/slurm-cluster/reframe/Dockerfile
+++ b/examples/tutorial/dockerfiles/slurm-cluster/reframe/Dockerfile
@@ -4,6 +4,7 @@
# SPDX-License-Identifier: BSD-3-Clause
FROM ubuntu:22.04
+COPY --from=ghcr.io/astral-sh/uv:latest /uv /uvx /usr/local/bin/
ARG DEBIAN_FRONTEND=noninteractive
@@ -40,8 +41,7 @@ ARG REFRAME_TAG=develop
ARG REFRAME_REPO=reframe-hpc
WORKDIR /usr/local/share
RUN git clone --depth 1 --branch $REFRAME_TAG https://github.com/$REFRAME_REPO/reframe.git && \
- cd reframe/ && ./bootstrap.sh
-ENV PATH=/usr/local/share/reframe/bin:$PATH
+ cd reframe/ && uv tool install .
WORKDIR /home/admin
diff --git a/multiarch.sh b/multiarch.sh
new file mode 100755
index 0000000000..eadbc840fd
--- /dev/null
+++ b/multiarch.sh
@@ -0,0 +1,109 @@
+#!/bin/sh
+#
+# Copyright 2016-2026 Swiss National Supercomputing Centre (CSCS/ETH Zurich)
+# ReFrame Project Developers. See the top-level LICENSE file for details.
+#
+# SPDX-License-Identifier: BSD-3-Clause
+
+#
+# Utility script for a multi-arch installations of reframe in the same file
+# system
+#
+
+# Colors
+bold=$(tput bold)
+red=$(tput setaf 1)
+blue=$(tput setaf 4)
+white=$(tput setaf 7)
+reset=$(tput sgr0)
+
+
+usage() {
+ printf "${bold}Usage: %s [-h] [--prefix DIR] [install|uninstall]\n" "$0${reset}"
+ exit 1
+}
+
+# Default values
+_prefix="$HOME/.local"
+
+action=""
+while [ $# -gt 0 ]; do
+ case "$1" in
+ -h|--help)
+ usage
+ ;;
+ --prefix)
+ if [ -n "$2" ] && [ "${2#-}" = "$2" ]; then
+ _prefix="$2"
+ shift 2
+ else
+ printf "Error: %s requires an argument.\n" "$1" >&2
+ exit 1
+ fi
+ ;;
+ --prefix=*)
+ _prefix="${1#*=}"
+ shift 1
+ ;;
+ --)
+ shift
+ break
+ ;;
+ -*)
+ printf "error: unknown option '%s'\n" "$1" >&2
+ usage
+ ;;
+ "install")
+ action="install"
+ shift
+ ;;
+ "uninstall")
+ action="uninstall"
+ shift
+ ;;
+ *)
+ break
+ ;;
+ esac
+done
+
+if [ -z $action ]; then
+ echo "${bold}${red}error: no action specified${reset}"
+ usage
+ exit 1
+fi
+
+_prefix=${_prefix}/$(uname -m)
+export UV_TOOL_BIN_DIR=${_prefix}/bin
+export UV_TOOL_DIR=${_prefix}/share/uv/tools
+
+rfm_srcdir=$(dirname "$(realpath "${BASH_SOURCE[0]}")")
+case $action in
+ "install")
+ uv tool install $rfm_srcdir ;;
+ "uninstall")
+ uv tool uninstall reframe-hpc
+ exit 0 ;;
+esac
+
+case "${SHELL}" in
+ *bin/fish)
+ cat <=1.29"]
+build-backend = "hatchling.build"
+
+[project]
+name = "reframe-hpc"
+dynamic = ["version"]
+authors = [
+ {name = "Swiss National Supercomputing Center (CSCS/ETH Zurich)"},
+ {name = "ReFrame Project Developers"}
+]
+description = "ReFrame is a powerful framework for writing system regression tests and benchmarks, specifically targeted to HPC systems"
+license = "BSD-3-Clause"
+license-files = ["LICENSE"]
+classifiers = [
+ "Development Status :: 5 - Production/Stable",
+ "Programming Language :: Python :: 3.10",
+ "Programming Language :: Python :: 3.11",
+ "Programming Language :: Python :: 3.12",
+ "Programming Language :: Python :: 3.13",
+ "Programming Language :: Python :: 3.14",
+ "License :: OSI Approved :: BSD License",
+ "Operating System :: MacOS",
+ "Operating System :: POSIX :: Linux",
+ "Environment :: Console",
+]
+requires-python = ">=3.10"
+dependencies = [
+ "archspec >= 0.2.4",
+ "argcomplete ~= 3.6",
+ "ClusterShell ~= 1.9",
+ "fasteners ~= 0.20",
+ "jinja2 ~= 3.1",
+ "jsonschema ~= 4.26",
+ "lxml ~= 6.0",
+ "polars ~= 1.39",
+ "PyYAML ~= 6.0",
+ "requests ~= 2.33",
+ "semver ~= 3.0",
+ "tabulate ~= 0.10",
+]
+keywords = ["benchmarking", "framework", "continuous integration", "hpc",
+ "regresion testing", "performance testing", "validation testing"]
+
+[project.urls]
+documentation = "https://reframe-hpc.readthedocs.io/"
+releasenotes = "https://github.com/reframe-hpc/reframe/releases"
+repository = "https://github.com/reframe-hpc/reframe"
+tracker = "https://github.com/reframe-hpc/reframe/issues"
+
+[project.readme]
+file = "README.md"
+content-type = "text/markdown"
+
+[project.scripts]
+reframe = "reframe.frontend.cli:main"
+
+[project.optional-dependencies]
+graylog = ["pygelf ~= 0.4"]
+
+[dependency-groups]
+dev = [
+ "coverage ~= 7.13",
+ "pytest ~= 9.0",
+ "pytest-forked ~= 1.6",
+ "pytest-rerunfailures ~= 16.1"
+]
+docs = [
+ "Sphinx ~= 8.1; python_version == '3.10'",
+ "Sphinx ~= 8.2; python_version >= '3.11'",
+ "sphinx-rtd-theme ~=3.1"
+]
+
+[tool.hatch.version]
+path = "reframe/__init__.py"
+
+[tool.hatch.build.targets.sdist]
+include = [
+ "/reframe/**/*.py",
+ "/reframe/schemas/**/*",
+ "/docs/man/man1/reframe.1",
+ "/docs/man/man8/reframe.settings.8",
+ "/examples",
+ "/share/completions",
+ "/tools",
+ "/CONTRIBUTING.md",
+ "/LICENSE",
+ "/REAMDE.md",
+]
+
+[tool.hatch.build.targets.wheel]
+packages = ["reframe"]
+
+
+[tool.hatch.build.targets.wheel.shared-data]
+"docs/man/man1/reframe.1" = "share/man/man1/reframe.1"
+"docs/man/man8/reframe.8" = "share/man/man8/reframe.8"
+"examples" = "share/doc/reframe-hpc/examples"
+"share/completions/reframe.bash" = "share/bash-completion/completions/reframe"
+"share/completions/reframe.fish" = "share/fish/vendor_completions.d/reframe.fish"
+"share/completions/reframe.tcsh" = "share/reframe/completions/reframe.tcsh"
+
+[tool.flake8]
+extend-ignore = "E129,E221,E226,E241,E402,E272,E741,E742,E743,F821,W504"
+exclude = ".git,__pycache__,docs/conf.py,external"
diff --git a/reframe/__init__.py b/reframe/__init__.py
index 46a97b7fef..804561048b 100644
--- a/reframe/__init__.py
+++ b/reframe/__init__.py
@@ -1,4 +1,4 @@
-# Copyright 2016-2024 Swiss National Supercomputing Centre (CSCS/ETH Zurich)
+# Copyright 2016-2026 Swiss National Supercomputing Centre (CSCS/ETH Zurich)
# ReFrame Project Developers. See the top-level LICENSE file for details.
#
# SPDX-License-Identifier: BSD-3-Clause
@@ -10,12 +10,12 @@
INSTALL_PREFIX = os.path.normpath(
os.path.abspath(os.path.join(os.path.dirname(__file__), '..'))
)
-MIN_PYTHON_VERSION = (3, 9, 0)
+MIN_PYTHON_VERSION = (3, 10)
# Check python version
if sys.version_info[:3] < MIN_PYTHON_VERSION:
sys.stderr.write('Unsupported Python version: '
- 'Python >= %d.%d.%d is required\n' % MIN_PYTHON_VERSION)
+ 'Python >= %d.%d is required\n' % MIN_PYTHON_VERSION)
sys.exit(1)
os.environ['RFM_INSTALL_PREFIX'] = INSTALL_PREFIX
diff --git a/reframe/frontend/autodetect.py b/reframe/frontend/autodetect.py
index a84dbd72fd..3efb31b5cf 100644
--- a/reframe/frontend/autodetect.py
+++ b/reframe/frontend/autodetect.py
@@ -47,8 +47,7 @@ def __enter__(self):
self._workdir = os.path.abspath(
tempfile.mkdtemp(prefix='rfm.', dir=self._prefix)
)
- paths = ['bin/', 'reframe/', 'tools/',
- 'bootstrap.sh', 'requirements.txt']
+ paths = ['reframe/', 'README.md', 'pyproject.toml']
use_pip = False
custom_command = runtime.runtime().get_option(
'general/0/remote_install'
@@ -141,19 +140,15 @@ def _remote_detect(part, cli_job_options):
def _emit_script_for_source(job, env):
commands = [
- './bootstrap.sh',
- './bin/reframe --detect-host-topology=topo.json'
+ 'curl -LsSf https://astral.sh/uv/install.sh | sh',
+ 'uv run reframe --detect-host-topology=topo.json'
]
job.prepare(commands, env, trap_errors=True, login=use_login_shell)
def _emit_script_for_pip(job, env):
commands = [
- 'python3 -m venv venv.reframe',
- 'source venv.reframe/bin/activate',
- 'pip install --upgrade pip',
- f'pip install reframe-hpc=={rfm.VERSION}',
- 'reframe --detect-host-topology=topo.json',
- 'deactivate'
+ 'curl -LsSf https://astral.sh/uv/install.sh | sh',
+ f'uvx --from=reframe-hpc@{rfm.VERSION} reframe --detect-host-topology=topo.json', # noqa: E501
]
job.prepare(commands, env, trap_errors=True, login=use_login_shell)
diff --git a/requirements.txt b/requirements.txt
deleted file mode 100644
index 5e3d7006dc..0000000000
--- a/requirements.txt
+++ /dev/null
@@ -1,25 +0,0 @@
-archspec==0.2.5
-argcomplete==3.6.3; python_version >= '3.8'
-ClusterShell==1.9.3
-fasteners==0.19; python_version < '3.10'
-fasteners==0.20; python_version >= '3.10'
-jinja2==3.1.6
-jsonschema==3.2.0
-lxml==6.0.2
-polars==1.35.1; python_version == '3.9'
-polars==1.39.3; python_version >= '3.10'
-pytest==8.4.2; python_version == '3.9'
-pytest==9.0.2; python_version >= '3.10'
-pytest-forked==1.6.0
-pytest-parallel==0.1.1
-pytest-rerunfailures==16.0.1; python_version == '3.9'
-pytest-rerunfailures==16.1; python_version >= '3.10'
-PyYAML==6.0.3
-requests==2.32.4; python_version == '3.9'
-requests==2.33.1; python_version >= '3.10'
-semver==3.0.4
-setuptools==82.0.1
-tabulate==0.9.0; python_version == '3.9'
-tabulate==0.10.0; python_version >= '3.10'
-wcwidth==0.6.0
-#+pygelf%pygelf==0.4.3
diff --git a/setup.cfg b/setup.cfg
deleted file mode 100644
index 8acab03341..0000000000
--- a/setup.cfg
+++ /dev/null
@@ -1,49 +0,0 @@
-[metadata]
-name = ReFrame-HPC
-version = attr: reframe.VERSION
-author = Swiss National Supercomputing Center (CSCS/ETH Zurich), ReFrame Project Developers
-description = ReFrame is a powerful framework for writing system regression tests and benchmarks, specifically targeted to HPC systems
-url = https://github.com/reframe-hpc/reframe
-license = BSD 3-Clause
-long_description = file: README_minimal.md
-long_description_content_type = text/markdown
-classifiers =
- Development Status :: 5 - Production/Stable
- Programming Language :: Python :: 3.9
- Programming Language :: Python :: 3.10
- Programming Language :: Python :: 3.11
- Programming Language :: Python :: 3.12
- Programming Language :: Python :: 3.13
- License :: OSI Approved :: BSD License
- Operating System :: MacOS
- Operating System :: POSIX :: Linux
- Environment :: Console
-
-[options]
-packages = find_namespace:
-python_requires = >=3.9
-scripts = bin/reframe
-install_requires =
- archspec >= 0.2.4
- argcomplete
- ClusterShell
- fasteners==0.19; python_version < '3.10'
- fasteners
- jinja2
- jsonschema
- lxml
- polars
- PyYAML
- requests
- semver
- tabulate
-
-[options.packages.find]
-include = reframe,reframe.*,hpctestlib.*
-
-[options.package_data]
-reframe = schemas/*
-
-[flake8]
-extend-ignore = E129,E221,E226,E241,E402,E272,E741,E742,E743,F821,W504
-exclude = .git,__pycache__,docs/conf.py,external
diff --git a/test_reframe.py b/test_reframe.py
index 59d62d6d21..436d3be40c 100755
--- a/test_reframe.py
+++ b/test_reframe.py
@@ -1,24 +1,19 @@
#!/usr/bin/env python3
#
-# Copyright 2016-2024 Swiss National Supercomputing Centre (CSCS/ETH Zurich)
+# Copyright 2016-2026 Swiss National Supercomputing Centre (CSCS/ETH Zurich)
# ReFrame Project Developers. See the top-level LICENSE file for details.
#
# SPDX-License-Identifier: BSD-3-Clause
+import argparse
+import pytest
import os
-import platform
import sys
-prefix = os.path.abspath(os.path.dirname(__file__))
-external = os.path.join(prefix, 'external', platform.machine())
-sys.path = [prefix, external] + sys.path
+import unittests.utility as test_util
-import argparse # noqa: F401, F403
-import pytest # noqa: F401, F403
-import unittests.utility as test_util # noqa: F401, F403
-
-if __name__ == '__main__':
+def main():
# Unset any ReFrame environment variable; unit tests must start in a clean
# environment
for var in list(os.environ.keys()):
@@ -27,7 +22,7 @@
parser = argparse.ArgumentParser(
add_help=False,
- usage='%(prog)s [REFRAME_OPTIONS...] [NOSE_OPTIONS...]')
+ usage='%(prog)s [REFRAME_OPTIONS...] [PYTEST_OPTIONS...]')
parser.add_argument(
'--rfm-offline', action='store_true',
help='Skip unit tests that require Internet access'
@@ -62,3 +57,7 @@
sys.argv = [sys.argv[0], *rem_args]
sys.exit(pytest.main())
+
+
+if __name__ == '__main__':
+ main()
diff --git a/unittests/test_autodetect.py b/unittests/test_autodetect.py
index ebfb8da291..68240fe18d 100644
--- a/unittests/test_autodetect.py
+++ b/unittests/test_autodetect.py
@@ -119,7 +119,7 @@ def test_autotect_with_invalid_files(invalid_topo_exec_ctx):
assert part.devices == []
-def test_remote_autodetect(remote_exec_ctx):
+def test_remote_autodetect(remote_exec_ctx, custom_install):
# All we can do with this test is to trigger the remote auto-detection
# path; since we don't know what the remote user system is, we cannot test
# if the topology is right.
@@ -132,7 +132,7 @@ def test_remote_autodetect(remote_exec_ctx):
autodetect.detect_topology()
- if runtime().get_option('general/0/remote_install'):
+ if custom_install:
remote_workdir = runtime().get_option('general/0/remote_workdir')
assert os.path.exists(os.path.join(remote_workdir, 'foo.txt'))
os.remove(os.path.join(remote_workdir, 'foo.txt'))