44set -eu # (no pipefail in POSIX sh)
55
66usage () {
7- echo " Usage: uv-test [-p|--python PY_VER] [pytest args...]"
8- echo " -p, --python Set Python version (default: \$ UV_PY or 3.14)"
9- echo " -h, --help Show this help"
7+ echo " Usage: uv-test [-p|--python PY_VER] [-a|--all-versions] [pytest args...]"
8+ echo " -p, --python Set Python version (default: \$ UV_PY or 3.14)"
9+ echo " -a, --all-versions Run tests on all minor versions from pyproject.toml"
10+ echo " -h, --help Show this help"
11+ }
12+
13+ # Extract supported Python versions from pyproject.toml
14+ get_supported_versions () {
15+ if [ ! -f pyproject.toml ]; then
16+ echo " error: pyproject.toml not found" >&2
17+ return 1
18+ fi
19+
20+ # Extract requires-python field (e.g., ">=3.10", ">=3.8,<3.13")
21+ req_python=$( grep -E ' requires-python\s*=' pyproject.toml | sed -E ' s/.*"([^"]+)".*/\1/' )
22+
23+ if [ -z " $req_python " ]; then
24+ echo " error: requires-python not found in pyproject.toml" >&2
25+ return 1
26+ fi
27+
28+ # Extract minimum version (e.g., ">=3.10" -> "3.10")
29+ min_ver=$( echo " $req_python " | sed -E ' s/^[^0-9]*([0-9]+\.[0-9]+).*$/\1/' )
30+ min_major=$( echo " $min_ver " | cut -d. -f1)
31+ min_minor=$( echo " $min_ver " | cut -d. -f2)
32+
33+ # uv doesn't support Python <= 3.7, so enforce minimum of 3.8
34+ if [ " $min_major " -eq 3 ] && [ " $min_minor " -lt 8 ]; then
35+ min_minor=8
36+ fi
37+
38+ # Extract maximum version if present (e.g., "<3.13" -> "3.13")
39+ max_ver=$( echo " $req_python " | sed -E ' s/.*<\s*([0-9]+\.[0-9]+).*/\1/' )
40+ if [ " $max_ver " = " $req_python " ]; then
41+ # No max version specified, use current stable (3.14 as of now)
42+ max_ver=" 3.14"
43+ fi
44+ max_major=$( echo " $max_ver " | cut -d. -f1)
45+ max_minor=$( echo " $max_ver " | cut -d. -f2)
46+
47+ # Generate list of minor versions
48+ versions=" "
49+ current_minor=$min_minor
50+ while [ " $min_major " -eq " $max_major " ] && [ " $current_minor " -lt " $max_minor " ]; do
51+ versions=" $versions $min_major .$current_minor "
52+ current_minor=$(( current_minor + 1 ))
53+ done
54+
55+ echo " $versions "
1056}
1157
1258PYVER=" ${UV_PY:- 3.14} "
59+ ALL_VERSIONS=0
1360
1461# Parse only our flags; pass the rest to pytest
1562while [ $# -gt 0 ]; do
@@ -18,6 +65,8 @@ while [ $# -gt 0 ]; do
1865 shift
1966 [ $# -gt 0 ] || { echo " error: -p/--python requires a version" >&2 ; exit 2; }
2067 PYVER=" $1 " ; shift ;;
68+ -a|--all-versions)
69+ ALL_VERSIONS=1; shift ;;
2170 -h|--help) usage; exit 0 ;;
2271 --) shift ; break ;;
2372 * ) break ;;
@@ -30,4 +79,61 @@ if [ ! -f pyproject.toml ] && [ ! -f setup.py ]; then
3079 exit 1
3180fi
3281
33- exec uvx --python " $PYVER " --with pytest --isolated --with-editable . -- pytest " $@ "
82+ # Run tests for all versions if requested
83+ if [ " $ALL_VERSIONS " -eq 1 ]; then
84+ versions=$( get_supported_versions)
85+ if [ -z " $versions " ]; then
86+ echo " error: could not determine supported Python versions" >&2
87+ exit 1
88+ fi
89+
90+ echo " Running tests for Python versions:$versions "
91+ echo " "
92+
93+ # Create a temporary directory for log files
94+ tmpdir=$( mktemp -d)
95+ trap ' rm -rf "$tmpdir"' EXIT
96+
97+ # Start all tests in parallel
98+ pids=" "
99+ for ver in $versions ; do
100+ (
101+ echo " ========================================"
102+ echo " Testing with Python $ver "
103+ echo " ========================================"
104+ uvx --python " $ver " --with pytest --isolated --with-editable . -- pytest " $@ "
105+ echo " $? " > " $tmpdir /$ver .exit"
106+ ) > " $tmpdir /$ver .log" 2>&1 &
107+ pids=" $pids $! "
108+ done
109+
110+ # Wait for all tests to complete
111+ for pid in $pids ; do
112+ wait " $pid " || true
113+ done
114+
115+ # Collect results and display logs
116+ failed_versions=" "
117+ for ver in $versions ; do
118+ echo " "
119+ cat " $tmpdir /$ver .log"
120+ exit_code=$( cat " $tmpdir /$ver .exit" )
121+ if [ " $exit_code " -ne 0 ]; then
122+ failed_versions=" $failed_versions $ver "
123+ fi
124+ done
125+
126+ echo " "
127+ echo " ========================================"
128+ echo " Summary"
129+ echo " ========================================"
130+ if [ -z " $failed_versions " ]; then
131+ echo " All versions passed!"
132+ exit 0
133+ else
134+ echo " Failed versions:$failed_versions "
135+ exit 1
136+ fi
137+ else
138+ exec uvx --python " $PYVER " --with pytest --isolated --with-editable . -- pytest " $@ "
139+ fi
0 commit comments