@@ -28,8 +28,7 @@ fn python_install() {
28
28
"### ) ;
29
29
30
30
let bin_python = context
31
- . temp_dir
32
- . child ( "bin" )
31
+ . bin_dir
33
32
. child ( format ! ( "python3.13{}" , std:: env:: consts:: EXE_SUFFIX ) ) ;
34
33
35
34
// The executable should not be installed in the bin directory (requires preview)
@@ -92,6 +91,117 @@ fn python_install() {
92
91
"### ) ;
93
92
}
94
93
94
+ #[ test]
95
+ fn python_install_automatic ( ) {
96
+ let context: TestContext = TestContext :: new_with_versions ( & [ ] )
97
+ . with_filtered_python_keys ( )
98
+ . with_filtered_exe_suffix ( )
99
+ . with_filtered_python_sources ( )
100
+ . with_managed_python_dirs ( ) ;
101
+
102
+ // With downloads disabled, the automatic install should fail
103
+ uv_snapshot ! ( context. filters( ) , context. run( )
104
+ . env_remove( "VIRTUAL_ENV" )
105
+ . arg( "--no-python-downloads" )
106
+ . arg( "python" ) . arg( "-c" ) . arg( "import sys; print(sys.version_info[:2])" ) , @r###"
107
+ success: false
108
+ exit_code: 2
109
+ ----- stdout -----
110
+
111
+ ----- stderr -----
112
+ error: No interpreter found in [PYTHON SOURCES]
113
+ "### ) ;
114
+
115
+ // Otherwise, we should fetch the latest Python version
116
+ uv_snapshot ! ( context. filters( ) , context. run( )
117
+ . env_remove( "VIRTUAL_ENV" )
118
+ . arg( "python" ) . arg( "-c" ) . arg( "import sys; print(sys.version_info[:2])" ) , @r###"
119
+ success: true
120
+ exit_code: 0
121
+ ----- stdout -----
122
+ (3, 13)
123
+
124
+ ----- stderr -----
125
+ "### ) ;
126
+
127
+ // Subsequently, we can use the interpreter even with downloads disabled
128
+ uv_snapshot ! ( context. filters( ) , context. run( )
129
+ . env_remove( "VIRTUAL_ENV" )
130
+ . arg( "--no-python-downloads" )
131
+ . arg( "python" ) . arg( "-c" ) . arg( "import sys; print(sys.version_info[:2])" ) , @r###"
132
+ success: true
133
+ exit_code: 0
134
+ ----- stdout -----
135
+ (3, 13)
136
+
137
+ ----- stderr -----
138
+ "### ) ;
139
+
140
+ // We should respect the Python request
141
+ uv_snapshot ! ( context. filters( ) , context. run( )
142
+ . env_remove( "VIRTUAL_ENV" )
143
+ . arg( "-p" ) . arg( "3.12" )
144
+ . arg( "python" ) . arg( "-c" ) . arg( "import sys; print(sys.version_info[:2])" ) , @r###"
145
+ success: true
146
+ exit_code: 0
147
+ ----- stdout -----
148
+ (3, 12)
149
+
150
+ ----- stderr -----
151
+ "### ) ;
152
+
153
+ // But some requests cannot be mapped to a download
154
+ uv_snapshot ! ( context. filters( ) , context. run( )
155
+ . env_remove( "VIRTUAL_ENV" )
156
+ . arg( "-p" ) . arg( "foobar" )
157
+ . arg( "python" ) . arg( "-c" ) . arg( "import sys; print(sys.version_info[:2])" ) , @r###"
158
+ success: false
159
+ exit_code: 2
160
+ ----- stdout -----
161
+
162
+ ----- stderr -----
163
+ error: No interpreter found for executable name `foobar` in [PYTHON SOURCES]
164
+ "### ) ;
165
+
166
+ // Create a "broken" Python executable in the test context `bin`
167
+ // (the snapshot is different on Windows so we just test on Unix)
168
+ #[ cfg( unix) ]
169
+ {
170
+ use std:: os:: unix:: fs:: PermissionsExt ;
171
+
172
+ let contents = r"#!/bin/sh
173
+ echo 'error: intentionally broken python executable' >&2
174
+ exit 1" ;
175
+ let python = context
176
+ . bin_dir
177
+ . join ( format ! ( "python3{}" , std:: env:: consts:: EXE_SUFFIX ) ) ;
178
+ fs_err:: write ( & python, contents) . unwrap ( ) ;
179
+
180
+ let mut perms = fs_err:: metadata ( & python) . unwrap ( ) . permissions ( ) ;
181
+ perms. set_mode ( 0o755 ) ;
182
+ fs_err:: set_permissions ( & python, perms) . unwrap ( ) ;
183
+
184
+ // We should ignore the broken executable and download a version still
185
+ uv_snapshot ! ( context. filters( ) , context. run( )
186
+ . env_remove( "VIRTUAL_ENV" )
187
+ // In tests, we ignore `PATH` during Python discovery so we need to add the context `bin`
188
+ . env( "UV_TEST_PYTHON_PATH" , context. bin_dir. as_os_str( ) )
189
+ . arg( "-p" ) . arg( "3.11" )
190
+ . arg( "python" ) . arg( "-c" ) . arg( "import sys; print(sys.version_info[:2])" ) , @r###"
191
+ success: false
192
+ exit_code: 2
193
+ ----- stdout -----
194
+
195
+ ----- stderr -----
196
+ error: Failed to inspect Python interpreter from search path at `[BIN]/python3`
197
+ Caused by: Querying Python at `[BIN]/python3` failed with exit status exit status: 1
198
+
199
+ [stderr]
200
+ error: intentionally broken python executable
201
+ "### ) ;
202
+ }
203
+ }
204
+
95
205
#[ test]
96
206
fn python_install_preview ( ) {
97
207
let context: TestContext = TestContext :: new_with_versions ( & [ ] )
@@ -111,8 +221,7 @@ fn python_install_preview() {
111
221
"### ) ;
112
222
113
223
let bin_python = context
114
- . temp_dir
115
- . child ( "bin" )
224
+ . bin_dir
116
225
. child ( format ! ( "python3.13{}" , std:: env:: consts:: EXE_SUFFIX ) ) ;
117
226
118
227
// The executable should be installed in the bin directory
@@ -182,7 +291,7 @@ fn python_install_preview() {
182
291
183
292
----- stderr -----
184
293
error: Failed to install cpython-3.13.1-[PLATFORM]
185
- Caused by: Executable already exists at `[TEMP_DIR]/bin /python3.13` but is not managed by uv; use `--force` to replace it
294
+ Caused by: Executable already exists at `[BIN] /python3.13` but is not managed by uv; use `--force` to replace it
186
295
"### ) ;
187
296
188
297
uv_snapshot ! ( context. filters( ) , context. python_install( ) . arg( "--preview" ) . arg( "--force" ) . arg( "3.13" ) , @r###"
@@ -243,8 +352,7 @@ fn python_install_preview() {
243
352
"### ) ;
244
353
245
354
let bin_python = context
246
- . temp_dir
247
- . child ( "bin" )
355
+ . bin_dir
248
356
. child ( format ! ( "python3.12{}" , std:: env:: consts:: EXE_SUFFIX ) ) ;
249
357
250
358
// The link should be for the newer patch version
@@ -275,8 +383,7 @@ fn python_install_preview_upgrade() {
275
383
. with_managed_python_dirs ( ) ;
276
384
277
385
let bin_python = context
278
- . temp_dir
279
- . child ( "bin" )
386
+ . bin_dir
280
387
. child ( format ! ( "python3.12{}" , std:: env:: consts:: EXE_SUFFIX ) ) ;
281
388
282
389
// Install 3.12.5
@@ -426,8 +533,7 @@ fn python_install_freethreaded() {
426
533
"### ) ;
427
534
428
535
let bin_python = context
429
- . temp_dir
430
- . child ( "bin" )
536
+ . bin_dir
431
537
. child ( format ! ( "python3.13t{}" , std:: env:: consts:: EXE_SUFFIX ) ) ;
432
538
433
539
// The executable should be installed in the bin directory
@@ -528,18 +634,15 @@ fn python_install_default() {
528
634
. with_managed_python_dirs ( ) ;
529
635
530
636
let bin_python_minor_13 = context
531
- . temp_dir
532
- . child ( "bin" )
637
+ . bin_dir
533
638
. child ( format ! ( "python3.13{}" , std:: env:: consts:: EXE_SUFFIX ) ) ;
534
639
535
640
let bin_python_major = context
536
- . temp_dir
537
- . child ( "bin" )
641
+ . bin_dir
538
642
. child ( format ! ( "python3{}" , std:: env:: consts:: EXE_SUFFIX ) ) ;
539
643
540
644
let bin_python_default = context
541
- . temp_dir
542
- . child ( "bin" )
645
+ . bin_dir
543
646
. child ( format ! ( "python{}" , std:: env:: consts:: EXE_SUFFIX ) ) ;
544
647
545
648
// `--preview` is required for `--default`
@@ -656,8 +759,7 @@ fn python_install_default() {
656
759
"### ) ;
657
760
658
761
let bin_python_minor_12 = context
659
- . temp_dir
660
- . child ( "bin" )
762
+ . bin_dir
661
763
. child ( format ! ( "python3.12{}" , std:: env:: consts:: EXE_SUFFIX ) ) ;
662
764
663
765
// All the executables should exist
@@ -857,10 +959,10 @@ fn python_install_preview_broken_link() {
857
959
. with_filtered_exe_suffix ( )
858
960
. with_managed_python_dirs ( ) ;
859
961
860
- let bin_python = context. temp_dir . child ( "bin" ) . child ( "python3.13" ) ;
962
+ let bin_python = context. bin_dir . child ( "python3.13" ) ;
861
963
862
964
// Create a broken symlink
863
- context. temp_dir . child ( "bin" ) . create_dir_all ( ) . unwrap ( ) ;
965
+ context. bin_dir . create_dir_all ( ) . unwrap ( ) ;
864
966
symlink ( context. temp_dir . join ( "does-not-exist" ) , & bin_python) . unwrap ( ) ;
865
967
866
968
// Install
0 commit comments