spytools/process/
python_process_type.rs

1use regex::Regex;
2
3use crate::process::ProcessType;
4
5/// Dummy type for providing a Python implementation of the trait
6pub struct PythonProcessType {}
7
8impl ProcessType for PythonProcessType {
9    #[cfg(windows)]
10    fn windows_symbols() -> Vec<String> {
11        vec![
12            "_PyThreadState_Current".to_string(),
13            "interp_head".to_string(),
14            "_PyRuntime".to_string(),
15        ]
16    }
17
18    fn library_regex() -> Regex {
19        #[cfg(any(target_os = "linux", target_os = "freebsd"))]
20        return Regex::new(r"/libpython\d.\d\d?(m|d|u)?.so").unwrap();
21
22        #[cfg(target_os = "macos")]
23        return Regex::new(r"/libpython\d.\d\d?(m|d|u)?.(dylib|so)$").unwrap();
24
25        #[cfg(windows)]
26        return regex::RegexBuilder::new(r"\\python\d\d\d?(m|d|u)?.dll$")
27            .case_insensitive(true)
28            .build()
29            .unwrap();
30    }
31
32    #[cfg(target_os = "macos")]
33    fn is_framework(path: &std::path::Path) -> bool {
34        path.ends_with("Python") && !path.to_string_lossy().contains("Python.app")
35    }
36}
37
38#[cfg(test)]
39mod tests {
40    use super::*;
41    use std::path::PathBuf;
42
43    use crate::process::process_info::is_lib;
44
45    #[cfg(target_os = "macos")]
46    #[test]
47    fn test_python_is_lib() {
48        assert!(is_lib::<PythonProcessType>(&PathBuf::from(
49            "~/Anaconda2/lib/libpython2.7.dylib"
50        )));
51
52        // python lib configured with --with-pydebug (flag: d)
53        assert!(is_lib::<PythonProcessType>(&PathBuf::from(
54            "/lib/libpython3.4d.dylib"
55        )));
56
57        // configured --with-pymalloc (flag: m)
58        assert!(is_lib::<PythonProcessType>(&PathBuf::from(
59            "/usr/local/lib/libpython3.8m.dylib"
60        )));
61
62        // python2 configured with --with-wide-unicode (flag: u)
63        assert!(is_lib::<PythonProcessType>(&PathBuf::from(
64            "./libpython2.7u.dylib"
65        )));
66
67        assert!(!is_lib::<PythonProcessType>(&PathBuf::from(
68            "/libboost_python.dylib"
69        )));
70        assert!(!is_lib::<PythonProcessType>(&PathBuf::from(
71            "/lib/heapq.cpython-36m-darwin.dylib"
72        )));
73    }
74
75    #[cfg(any(target_os = "linux", target_os = "freebsd"))]
76    #[test]
77    fn test_python_is_lib() {
78        // libpython bundled by pyinstaller https://github.com/benfred/py-spy/issues/42
79        assert!(is_lib::<PythonProcessType>(&PathBuf::from(
80            "/tmp/_MEIOqzg01/libpython2.7.so.1.0"
81        )));
82
83        // test debug/malloc/unicode flags
84        assert!(is_lib::<PythonProcessType>(&PathBuf::from(
85            "./libpython2.7.so"
86        )));
87        assert!(is_lib::<PythonProcessType>(&PathBuf::from(
88            "/usr/lib/libpython3.4d.so"
89        )));
90        assert!(is_lib::<PythonProcessType>(&PathBuf::from(
91            "/usr/local/lib/libpython3.8m.so"
92        )));
93        assert!(is_lib::<PythonProcessType>(&PathBuf::from(
94            "/usr/lib/libpython2.7u.so"
95        )));
96
97        // don't blindly match libraries with python in the name (boost_python etc)
98        assert!(!is_lib::<PythonProcessType>(&PathBuf::from(
99            "/usr/lib/libboost_python.so"
100        )));
101        assert!(!is_lib::<PythonProcessType>(&PathBuf::from(
102            "/usr/lib/x86_64-linux-gnu/libboost_python-py27.so.1.58.0"
103        )));
104        assert!(!is_lib::<PythonProcessType>(&PathBuf::from(
105            "/usr/lib/libboost_python-py35.so"
106        )));
107    }
108
109    #[cfg(windows)]
110    #[test]
111    fn test_python_is_lib() {
112        assert!(is_lib::<PythonProcessType>(&PathBuf::from(
113            "C:\\Users\\test\\AppData\\Local\\Programs\\Python\\Python37\\python37.dll"
114        )));
115        // .NET host via https://github.com/pythonnet/pythonnet
116        assert!(is_lib::<PythonProcessType>(&PathBuf::from(
117            "C:\\Users\\test\\AppData\\Local\\Programs\\Python\\Python37\\python37.DLL"
118        )));
119    }
120
121    #[cfg(target_os = "macos")]
122    #[test]
123    fn test_python_frameworks() {
124        // homebrew v2
125        assert!(!PythonProcessType::is_framework(&PathBuf::from("/usr/local/Cellar/python@2/2.7.15_1/Frameworks/Python.framework/Versions/2.7/Resources/Python.app/Contents/MacOS/Python")));
126        assert!(PythonProcessType::is_framework(&PathBuf::from(
127            "/usr/local/Cellar/python@2/2.7.15_1/Frameworks/Python.framework/Versions/2.7/Python"
128        )));
129
130        // System python from osx 10.13.6 (high sierra)
131        assert!(!PythonProcessType::is_framework(&PathBuf::from("/System/Library/Frameworks/Python.framework/Versions/2.7/Resources/Python.app/Contents/MacOS/Python")));
132        assert!(PythonProcessType::is_framework(&PathBuf::from(
133            "/System/Library/Frameworks/Python.framework/Versions/2.7/Python"
134        )));
135
136        // pyenv 3.6.6 with OSX framework enabled (https://github.com/benfred/py-spy/issues/15)
137        // env PYTHON_CONFIGURE_OPTS="--enable-framework" pyenv install 3.6.6
138        assert!(PythonProcessType::is_framework(&PathBuf::from(
139            "/Users/ben/.pyenv/versions/3.6.6/Python.framework/Versions/3.6/Python"
140        )));
141        assert!(!PythonProcessType::is_framework(&PathBuf::from("/Users/ben/.pyenv/versions/3.6.6/Python.framework/Versions/3.6/Resources/Python.app/Contents/MacOS/Python")));
142
143        // single file pyinstaller
144        assert!(PythonProcessType::is_framework(&PathBuf::from(
145            "/private/var/folders/3x/qy479lpd1fb2q88lc9g4d3kr0000gn/T/_MEI2Akvi8/Python"
146        )));
147    }
148}