@@ -100,3 +100,244 @@ fn get_version(folder_name: &str) -> Option<String> {
100100 }
101101 }
102102}
103+
104+ #[ cfg( test) ]
105+ mod tests {
106+ use super :: * ;
107+ use std:: fs;
108+ use std:: path:: PathBuf ;
109+ use tempfile:: tempdir;
110+
111+ // get_version tests
112+ #[ test]
113+ fn get_version_parses_stable_version ( ) {
114+ assert_eq ! ( get_version( "3.10.10" ) , Some ( "3.10.10" . to_string( ) ) ) ;
115+ assert_eq ! ( get_version( "3.12.0" ) , Some ( "3.12.0" . to_string( ) ) ) ;
116+ assert_eq ! ( get_version( "2.7.18" ) , Some ( "2.7.18" . to_string( ) ) ) ;
117+ }
118+
119+ #[ test]
120+ fn get_version_parses_dev_version ( ) {
121+ assert_eq ! ( get_version( "3.10-dev" ) , Some ( "3.10-dev" . to_string( ) ) ) ;
122+ assert_eq ! ( get_version( "3.13-dev" ) , Some ( "3.13-dev" . to_string( ) ) ) ;
123+ }
124+
125+ #[ test]
126+ fn get_version_parses_alpha_rc_version ( ) {
127+ assert_eq ! ( get_version( "3.10.0a3" ) , Some ( "3.10.0a3" . to_string( ) ) ) ;
128+ assert_eq ! ( get_version( "3.12.0b1" ) , Some ( "3.12.0b1" . to_string( ) ) ) ;
129+ }
130+
131+ #[ test]
132+ fn get_version_returns_none_for_multi_letter_prerelease ( ) {
133+ // Known limitation: BETA_PYTHON_VERSION regex uses \w (single char) so multi-letter
134+ // pre-release tags like "rc" are not captured. Real pyenv installs can have rc versions
135+ // (e.g. 3.13.0rc1), but version detection falls back to header files in that case.
136+ assert_eq ! ( get_version( "3.11.0rc2" ) , None ) ;
137+ }
138+
139+ #[ test]
140+ fn get_version_parses_win32_version ( ) {
141+ assert_eq ! ( get_version( "3.11.0a4-win32" ) , Some ( "3.11.0a4" . to_string( ) ) ) ;
142+ }
143+
144+ #[ test]
145+ fn get_version_returns_none_for_non_version_strings ( ) {
146+ assert_eq ! ( get_version( "mambaforge-4.10.1-4" ) , None ) ;
147+ assert_eq ! ( get_version( "pypy3.9-7.3.15" ) , None ) ;
148+ assert_eq ! ( get_version( "my-virtual-env" ) , None ) ;
149+ assert_eq ! ( get_version( "" ) , None ) ;
150+ }
151+
152+ #[ test]
153+ fn get_version_returns_none_for_partial_version ( ) {
154+ assert_eq ! ( get_version( "3.10" ) , None ) ;
155+ }
156+
157+ // get_generic_python_environment tests
158+ #[ test]
159+ fn get_generic_python_environment_with_stable_version_folder ( ) {
160+ let root = tempdir ( ) . unwrap ( ) ;
161+ let env_path = root. path ( ) . join ( "3.12.0" ) ;
162+ let bin_dir = if cfg ! ( windows) {
163+ env_path. join ( "Scripts" )
164+ } else {
165+ env_path. join ( "bin" )
166+ } ;
167+ fs:: create_dir_all ( & bin_dir) . unwrap ( ) ;
168+ let exe = if cfg ! ( windows) {
169+ bin_dir. join ( "python.exe" )
170+ } else {
171+ bin_dir. join ( "python" )
172+ } ;
173+ fs:: write ( & exe, b"" ) . unwrap ( ) ;
174+
175+ let result = get_generic_python_environment ( & exe, & env_path, & None ) . unwrap ( ) ;
176+
177+ assert_eq ! ( result. kind, Some ( PythonEnvironmentKind :: Pyenv ) ) ;
178+ assert_eq ! (
179+ result. executable. as_ref( ) . unwrap( ) . file_name( ) ,
180+ exe. file_name( )
181+ ) ;
182+ assert_eq ! ( result. version, Some ( "3.12.0" . to_string( ) ) ) ;
183+ assert_eq ! (
184+ result. prefix. as_ref( ) . unwrap( ) . file_name( ) ,
185+ env_path. file_name( )
186+ ) ;
187+ assert ! ( result. manager. is_none( ) ) ;
188+ }
189+
190+ #[ test]
191+ fn get_generic_python_environment_with_win32_folder_sets_x86_arch ( ) {
192+ let root = tempdir ( ) . unwrap ( ) ;
193+ let env_path = root. path ( ) . join ( "3.11.0a4-win32" ) ;
194+ let bin_dir = if cfg ! ( windows) {
195+ env_path. join ( "Scripts" )
196+ } else {
197+ env_path. join ( "bin" )
198+ } ;
199+ fs:: create_dir_all ( & bin_dir) . unwrap ( ) ;
200+ let exe = if cfg ! ( windows) {
201+ bin_dir. join ( "python.exe" )
202+ } else {
203+ bin_dir. join ( "python" )
204+ } ;
205+ fs:: write ( & exe, b"" ) . unwrap ( ) ;
206+
207+ let result = get_generic_python_environment ( & exe, & env_path, & None ) . unwrap ( ) ;
208+
209+ assert_eq ! ( result. arch, Some ( Architecture :: X86 ) ) ;
210+ }
211+
212+ #[ test]
213+ fn get_generic_python_environment_with_non_win32_folder_has_no_arch ( ) {
214+ let root = tempdir ( ) . unwrap ( ) ;
215+ let env_path = root. path ( ) . join ( "3.12.0" ) ;
216+ let bin_dir = if cfg ! ( windows) {
217+ env_path. join ( "Scripts" )
218+ } else {
219+ env_path. join ( "bin" )
220+ } ;
221+ fs:: create_dir_all ( & bin_dir) . unwrap ( ) ;
222+ let exe = if cfg ! ( windows) {
223+ bin_dir. join ( "python.exe" )
224+ } else {
225+ bin_dir. join ( "python" )
226+ } ;
227+ fs:: write ( & exe, b"" ) . unwrap ( ) ;
228+
229+ let result = get_generic_python_environment ( & exe, & env_path, & None ) . unwrap ( ) ;
230+
231+ assert ! ( result. arch. is_none( ) ) ;
232+ }
233+
234+ #[ test]
235+ fn get_generic_python_environment_includes_manager_when_provided ( ) {
236+ let root = tempdir ( ) . unwrap ( ) ;
237+ let env_path = root. path ( ) . join ( "3.12.0" ) ;
238+ let bin_dir = if cfg ! ( windows) {
239+ env_path. join ( "Scripts" )
240+ } else {
241+ env_path. join ( "bin" )
242+ } ;
243+ fs:: create_dir_all ( & bin_dir) . unwrap ( ) ;
244+ let exe = if cfg ! ( windows) {
245+ bin_dir. join ( "python.exe" )
246+ } else {
247+ bin_dir. join ( "python" )
248+ } ;
249+ fs:: write ( & exe, b"" ) . unwrap ( ) ;
250+
251+ let mgr = EnvManager :: new (
252+ PathBuf :: from ( "/usr/bin/pyenv" ) ,
253+ pet_core:: manager:: EnvManagerType :: Pyenv ,
254+ Some ( "2.4.0" . to_string ( ) ) ,
255+ ) ;
256+ let result = get_generic_python_environment ( & exe, & env_path, & Some ( mgr. clone ( ) ) ) . unwrap ( ) ;
257+
258+ assert_eq ! ( result. manager, Some ( mgr) ) ;
259+ }
260+
261+ #[ test]
262+ fn get_generic_python_environment_with_unrecognized_folder_name ( ) {
263+ let root = tempdir ( ) . unwrap ( ) ;
264+ let env_path = root. path ( ) . join ( "mambaforge-4.10.1-4" ) ;
265+ let bin_dir = if cfg ! ( windows) {
266+ env_path. join ( "Scripts" )
267+ } else {
268+ env_path. join ( "bin" )
269+ } ;
270+ fs:: create_dir_all ( & bin_dir) . unwrap ( ) ;
271+ let exe = if cfg ! ( windows) {
272+ bin_dir. join ( "python.exe" )
273+ } else {
274+ bin_dir. join ( "python" )
275+ } ;
276+ fs:: write ( & exe, b"" ) . unwrap ( ) ;
277+
278+ let result = get_generic_python_environment ( & exe, & env_path, & None ) . unwrap ( ) ;
279+
280+ assert_eq ! ( result. kind, Some ( PythonEnvironmentKind :: Pyenv ) ) ;
281+ // No version extractable from folder name and no header files
282+ assert ! ( result. version. is_none( ) ) ;
283+ }
284+
285+ // get_virtual_env_environment tests
286+ #[ test]
287+ fn get_virtual_env_returns_none_without_pyvenv_cfg ( ) {
288+ let root = tempdir ( ) . unwrap ( ) ;
289+ let env_path = root. path ( ) . join ( "my-venv" ) ;
290+ let bin_dir = if cfg ! ( windows) {
291+ env_path. join ( "Scripts" )
292+ } else {
293+ env_path. join ( "bin" )
294+ } ;
295+ fs:: create_dir_all ( & bin_dir) . unwrap ( ) ;
296+ let exe = if cfg ! ( windows) {
297+ bin_dir. join ( "python.exe" )
298+ } else {
299+ bin_dir. join ( "python" )
300+ } ;
301+ fs:: write ( & exe, b"" ) . unwrap ( ) ;
302+
303+ let result = get_virtual_env_environment ( & exe, & env_path, & None ) ;
304+
305+ assert ! ( result. is_none( ) ) ;
306+ }
307+
308+ #[ test]
309+ fn get_virtual_env_returns_env_with_pyvenv_cfg ( ) {
310+ let root = tempdir ( ) . unwrap ( ) ;
311+ let env_path = root. path ( ) . join ( "my-venv" ) ;
312+ let bin_dir = if cfg ! ( windows) {
313+ env_path. join ( "Scripts" )
314+ } else {
315+ env_path. join ( "bin" )
316+ } ;
317+ fs:: create_dir_all ( & bin_dir) . unwrap ( ) ;
318+ let exe = if cfg ! ( windows) {
319+ bin_dir. join ( "python.exe" )
320+ } else {
321+ bin_dir. join ( "python" )
322+ } ;
323+ fs:: write ( & exe, b"" ) . unwrap ( ) ;
324+ fs:: write (
325+ env_path. join ( "pyvenv.cfg" ) ,
326+ "version = 3.12.0\n home = /usr/bin\n " ,
327+ )
328+ . unwrap ( ) ;
329+
330+ let result = get_virtual_env_environment ( & exe, & env_path, & None ) . unwrap ( ) ;
331+
332+ assert_eq ! ( result. kind, Some ( PythonEnvironmentKind :: PyenvVirtualEnv ) ) ;
333+ assert_eq ! ( result. version, Some ( "3.12.0" . to_string( ) ) ) ;
334+ assert_eq ! (
335+ result. executable. as_ref( ) . unwrap( ) . file_name( ) ,
336+ exe. file_name( )
337+ ) ;
338+ assert_eq ! (
339+ result. prefix. as_ref( ) . unwrap( ) . file_name( ) ,
340+ env_path. file_name( )
341+ ) ;
342+ }
343+ }
0 commit comments