1use regex::Regex;
2#[cfg(windows)]
3use regex::RegexBuilder;
4#[cfg(windows)]
5use std::collections::HashMap;
6use std::mem::size_of;
7use std::path::Path;
8use std::slice;
9
10use anyhow::{Context, Error, Result};
11use lazy_static::lazy_static;
12use proc_maps::{get_process_maps, MapRange};
13#[cfg(not(target_os = "macos"))]
14use remoteprocess::Pid;
15use remoteprocess::ProcessMemory;
16
17use crate::binary_parser::{parse_binary, BinaryInfo};
18use crate::config::Config;
19use crate::python_bindings::{
20 pyruntime, v2_7_15, v3_10_0, v3_11_0, v3_12_0, v3_13_0, v3_3_7, v3_5_5, v3_6_6, v3_7_0, v3_8_0,
21 v3_9_5,
22};
23use crate::python_interpreters::{InterpreterState, ThreadState};
24use crate::stack_trace::get_stack_traces;
25use crate::version::Version;
26
27pub struct PythonProcessInfo {
30 pub python_binary: Option<BinaryInfo>,
31 pub libpython_binary: Option<BinaryInfo>,
34 pub maps: Box<dyn ContainsAddr>,
35 pub python_filename: std::path::PathBuf,
36 #[cfg(target_os = "linux")]
37 pub dockerized: bool,
38}
39
40impl PythonProcessInfo {
41 pub fn new(process: &remoteprocess::Process) -> Result<PythonProcessInfo, Error> {
42 let filename = process
43 .exe()
44 .context("Failed to get process executable name. Check that the process is running.")?;
45
46 #[cfg(windows)]
47 let filename = filename.to_lowercase();
48
49 #[cfg(windows)]
50 let is_python_bin = |pathname: &str| pathname.to_lowercase() == filename;
51
52 #[cfg(not(windows))]
53 let is_python_bin = |pathname: &str| pathname == filename;
54
55 let maps = get_process_maps(process.pid)?;
57 info!("Got virtual memory maps from pid {}:", process.pid);
58 for map in &maps {
59 debug!(
60 "map: {:016x}-{:016x} {}{}{} {}",
61 map.start(),
62 map.start() + map.size(),
63 if map.is_read() { 'r' } else { '-' },
64 if map.is_write() { 'w' } else { '-' },
65 if map.is_exec() { 'x' } else { '-' },
66 map.filename()
67 .unwrap_or(&std::path::PathBuf::from(""))
68 .display()
69 );
70 }
71
72 let (python_binary, python_filename) = {
74 let map = maps.iter().find(|m| {
76 if let Some(pathname) = m.filename() {
77 if let Some(pathname) = pathname.to_str() {
78 #[cfg(not(windows))]
79 {
80 return is_python_bin(pathname) && m.is_exec();
81 }
82 #[cfg(windows)]
83 {
84 return is_python_bin(pathname);
85 }
86 }
87 }
88 false
89 });
90
91 let map = match map {
92 Some(map) => map,
93 None => {
94 warn!("Failed to find '{}' in virtual memory maps, falling back to first map region", filename);
95 maps.first().ok_or_else(|| {
99 format_err!("Failed to get virtual memory maps from process")
100 })?
101 }
102 };
103
104 #[cfg(not(target_os = "linux"))]
105 let filename = std::path::PathBuf::from(filename);
106
107 #[cfg(target_os = "linux")]
110 let filename = std::path::PathBuf::from(format!("/proc/{}/exe", process.pid));
111
112 let python_binary = parse_binary(&filename, map.start() as u64, map.size() as u64);
114
115 #[cfg(windows)]
117 let python_binary = python_binary.and_then(|mut pb| {
118 get_windows_python_symbols(process.pid, &filename, map.start() as u64)
119 .map(|symbols| {
120 pb.symbols.extend(symbols);
121 pb
122 })
123 .map_err(|err| err.into())
124 });
125
126 #[cfg(target_os = "macos")]
129 let python_binary = python_binary.map(|mut pb| {
130 let offset = pb.symbols["_mh_execute_header"] - map.start() as u64;
131 for address in pb.symbols.values_mut() {
132 *address -= offset;
133 }
134
135 if pb.bss_addr != 0 {
136 pb.bss_addr -= offset;
137 }
138 pb
139 });
140
141 (python_binary, filename)
142 };
143
144 let libpython_binary = {
146 let libmaps: Vec<_> = maps
147 .iter()
148 .filter(|m| {
149 if let Some(pathname) = m.filename() {
150 if let Some(pathname) = pathname.to_str() {
151 #[cfg(not(windows))]
152 {
153 return is_python_lib(pathname) && m.is_exec();
154 }
155 #[cfg(windows)]
156 {
157 return is_python_lib(pathname);
158 }
159 }
160 }
161 false
162 })
163 .collect();
164
165 let mut libpython_binary: Option<BinaryInfo> = None;
166
167 #[cfg(not(target_os = "linux"))]
168 let libpython_option = if !libmaps.is_empty() {
169 Some(&libmaps[0])
170 } else {
171 None
172 };
173 #[cfg(target_os = "linux")]
174 let libpython_option = libmaps.iter().min_by_key(|m| m.offset);
175
176 if let Some(libpython) = libpython_option {
177 if let Some(filename) = &libpython.filename() {
178 info!("Found libpython binary @ {}", filename.display());
179
180 #[cfg(target_os = "linux")]
182 let filename = &std::path::PathBuf::from(format!(
183 "/proc/{}/root{}",
184 process.pid,
185 filename.display()
186 ));
187
188 #[allow(unused_mut)]
189 let mut parsed =
190 parse_binary(filename, libpython.start() as u64, libpython.size() as u64)?;
191 #[cfg(windows)]
192 parsed.symbols.extend(get_windows_python_symbols(
193 process.pid,
194 filename,
195 libpython.start() as u64,
196 )?);
197 libpython_binary = Some(parsed);
198 }
199 }
200
201 #[cfg(target_os = "macos")]
205 {
206 if libpython_binary.is_none() {
207 use proc_maps::mac_maps::get_dyld_info;
208 let dyld_infos = get_dyld_info(process.pid)?;
209
210 for dyld in &dyld_infos {
211 let segname =
212 unsafe { std::ffi::CStr::from_ptr(dyld.segment.segname.as_ptr()) };
213 debug!(
214 "dyld: {:016x}-{:016x} {:10} {}",
215 dyld.segment.vmaddr,
216 dyld.segment.vmaddr + dyld.segment.vmsize,
217 segname.to_string_lossy(),
218 dyld.filename.display()
219 );
220 }
221
222 let python_dyld_data = dyld_infos.iter().find(|m| {
223 if let Some(filename) = m.filename.to_str() {
224 return is_python_framework(filename)
225 && m.segment.segname[0..7] == [95, 95, 68, 65, 84, 65, 0];
226 }
227 false
228 });
229
230 if let Some(libpython) = python_dyld_data {
231 info!(
232 "Found libpython binary from dyld @ {}",
233 libpython.filename.display()
234 );
235
236 let mut binary = parse_binary(
237 &libpython.filename,
238 libpython.segment.vmaddr,
239 libpython.segment.vmsize,
240 )?;
241
242 binary.bss_addr = libpython.segment.vmaddr;
247 binary.bss_size = libpython.segment.vmsize;
248 libpython_binary = Some(binary);
249 }
250 }
251 }
252
253 libpython_binary
254 };
255
256 let python_binary = match libpython_binary {
258 None => Some(python_binary.context("Failed to parse python binary")?),
259 _ => python_binary.ok(),
260 };
261
262 #[cfg(target_os = "linux")]
263 let dockerized = is_dockerized(process.pid).unwrap_or(false);
264
265 Ok(PythonProcessInfo {
266 python_binary,
267 libpython_binary,
268 maps: Box::new(maps),
269 python_filename,
270 #[cfg(target_os = "linux")]
271 dockerized,
272 })
273 }
274
275 pub fn get_symbol(&self, symbol: &str) -> Option<&u64> {
276 if let Some(ref pb) = self.python_binary {
277 if let Some(addr) = pb.symbols.get(symbol) {
278 info!("got symbol {} (0x{:016x}) from python binary", symbol, addr);
279 return Some(addr);
280 }
281 }
282
283 if let Some(ref binary) = self.libpython_binary {
284 if let Some(addr) = binary.symbols.get(symbol) {
285 info!(
286 "got symbol {} (0x{:016x}) from libpython binary",
287 symbol, addr
288 );
289 return Some(addr);
290 }
291 }
292 None
293 }
294}
295
296pub fn get_python_version<P>(python_info: &PythonProcessInfo, process: &P) -> Result<Version, Error>
298where
299 P: ProcessMemory,
300{
301 if let Some(&addr) = python_info
303 .get_symbol("Py_GetVersion.version")
304 .or_else(|| python_info.get_symbol("version"))
305 {
306 info!("Getting version from symbol address");
307 if let Ok(bytes) = process.copy(addr as usize, 128) {
308 if let Ok(version) = Version::scan_bytes(&bytes) {
309 return Ok(version);
310 }
311 }
312 }
313
314 if let Some(ref pb) = python_info.python_binary {
316 info!("Getting version from python binary BSS");
317 let bss = process.copy(pb.bss_addr as usize, pb.bss_size as usize)?;
318 match Version::scan_bytes(&bss) {
319 Ok(version) => return Ok(version),
320 Err(err) => info!("Failed to get version from BSS section: {}", err),
321 }
322 }
323
324 if let Some(ref libpython) = python_info.libpython_binary {
326 info!("Getting version from libpython BSS");
327 let bss = process.copy(libpython.bss_addr as usize, libpython.bss_size as usize)?;
328 match Version::scan_bytes(&bss) {
329 Ok(version) => return Ok(version),
330 Err(err) => info!("Failed to get version from libpython BSS section: {}", err),
331 }
332 }
333
334 info!(
337 "Trying to get version from path: {}",
338 python_info.python_filename.display()
339 );
340 let path = Path::new(&python_info.python_filename);
341 if let Some(python) = path.file_name() {
342 if let Some(python) = python.to_str() {
343 if let Some(stripped_python) = python.strip_prefix("python") {
344 let tokens: Vec<&str> = stripped_python.split('.').collect();
345 if tokens.len() >= 2 {
346 if let (Ok(major), Ok(minor)) =
347 (tokens[0].parse::<u64>(), tokens[1].parse::<u64>())
348 {
349 return Ok(Version {
350 major,
351 minor,
352 patch: 0,
353 release_flags: "".to_owned(),
354 build_metadata: None,
355 });
356 }
357 }
358 }
359 }
360 }
361 Err(format_err!(
362 "Failed to find python version from target process"
363 ))
364}
365
366pub fn get_interpreter_address<P>(
367 python_info: &PythonProcessInfo,
368 process: &P,
369 version: &Version,
370) -> Result<usize, Error>
371where
372 P: ProcessMemory,
373{
374 match version {
377 Version {
378 major: 3,
379 minor: 13,
380 ..
381 } => {
382 if let Some(&addr) = python_info.get_symbol("_PyRuntime") {
383 let debug_offsets: v3_13_0::_Py_DebugOffsets =
385 process.copy_struct(addr as usize)?;
386 let addr = process.copy_struct(
387 addr as usize + debug_offsets.runtime_state.interpreters_head as usize,
388 )?;
389
390 match check_interpreter_addresses(&[addr], &*python_info.maps, process, version) {
392 Ok(addr) => return Ok(addr),
393 Err(_) => {
394 warn!(
395 "Interpreter address from _PyRuntime symbol is invalid {:016x}",
396 addr
397 );
398 }
399 };
400 }
401 }
402 Version {
403 major: 3,
404 minor: 7..=12,
405 ..
406 } => {
407 if let Some(&addr) = python_info.get_symbol("_PyRuntime") {
408 let addr = process
409 .copy_struct(addr as usize + pyruntime::get_interp_head_offset(version))?;
410
411 match check_interpreter_addresses(&[addr], &*python_info.maps, process, version) {
413 Ok(addr) => return Ok(addr),
414 Err(_) => {
415 warn!(
416 "Interpreter address from _PyRuntime symbol is invalid {:016x}",
417 addr
418 );
419 }
420 };
421 }
422 }
423 _ => {
424 if let Some(&addr) = python_info.get_symbol("interp_head") {
425 let addr = process.copy_struct(addr as usize)?;
426 match check_interpreter_addresses(&[addr], &*python_info.maps, process, version) {
427 Ok(addr) => return Ok(addr),
428 Err(_) => {
429 warn!(
430 "Interpreter address from interp_head symbol is invalid {:016x}",
431 addr
432 );
433 }
434 };
435 }
436 }
437 };
438 info!("Failed to find runtime address from symbols, scanning BSS section from main binary");
439
440 let err = if let Some(ref pb) = python_info.python_binary {
442 match get_interpreter_address_from_binary(pb, &*python_info.maps, process, version) {
443 Ok(addr) => return Ok(addr),
444 err => Some(err),
445 }
446 } else {
447 None
448 };
449 if let Some(ref lpb) = python_info.libpython_binary {
451 info!("Failed to get interpreter from binary BSS, scanning libpython BSS");
452 match get_interpreter_address_from_binary(lpb, &*python_info.maps, process, version) {
453 Ok(addr) => Ok(addr),
454 lib_err => err.unwrap_or(lib_err),
455 }
456 } else {
457 err.expect("Both python and libpython are invalid.")
458 }
459}
460
461fn get_interpreter_address_from_binary<P>(
462 binary: &BinaryInfo,
463 maps: &dyn ContainsAddr,
464 process: &P,
465 version: &Version,
466) -> Result<usize, Error>
467where
468 P: ProcessMemory,
469{
470 if binary.pyruntime_addr != 0 {
472 let bss = process.copy(
473 binary.pyruntime_addr as usize,
474 binary.pyruntime_size as usize,
475 )?;
476 #[allow(clippy::cast_ptr_alignment)]
477 let addrs = unsafe {
478 slice::from_raw_parts(bss.as_ptr() as *const usize, bss.len() / size_of::<usize>())
479 };
480 if let Ok(addr) = check_interpreter_addresses(addrs, maps, process, version) {
481 return Ok(addr);
482 }
483 }
484
485 let bss = process.copy(binary.bss_addr as usize, binary.bss_size as usize)?;
488
489 #[allow(clippy::cast_ptr_alignment)]
490 let addrs = unsafe {
491 slice::from_raw_parts(bss.as_ptr() as *const usize, bss.len() / size_of::<usize>())
492 };
493 check_interpreter_addresses(addrs, maps, process, version)
494}
495
496fn check_interpreter_addresses<P>(
499 addrs: &[usize],
500 maps: &dyn ContainsAddr,
501 process: &P,
502 version: &Version,
503) -> Result<usize, Error>
504where
505 P: ProcessMemory,
506{
507 fn check<I, P>(addrs: &[usize], maps: &dyn ContainsAddr, process: &P) -> Result<usize, Error>
509 where
510 I: InterpreterState,
511 P: ProcessMemory,
512 {
513 for &addr in addrs {
514 if maps.contains_addr(addr) {
515 let threadstate_ptr_ptr = I::threadstate_ptr_ptr(addr);
518 let maybe_threads = process
519 .copy_struct(threadstate_ptr_ptr as usize)
520 .context("Failed to copy PyThreadState head pointer");
521
522 let threads: *const I::ThreadState = match maybe_threads {
523 Ok(threads) => threads,
524 Err(_) => continue,
525 };
526
527 if maps.contains_addr(threads as usize) {
528 let thread = match process.copy_pointer(threads) {
531 Ok(thread) => thread,
532 Err(_) => continue,
533 };
534
535 if thread.interp() as usize == addr
537 && get_stack_traces::<I, P>(addr, process, 0, None).is_ok()
538 {
539 return Ok(addr);
540 }
541 }
542 }
543 }
544 Err(format_err!(
545 "Failed to find a python interpreter in the .data section"
546 ))
547 }
548
549 match version {
551 Version {
552 major: 2,
553 minor: 3..=7,
554 ..
555 } => check::<v2_7_15::_is, P>(addrs, maps, process),
556 Version {
557 major: 3, minor: 3, ..
558 } => check::<v3_3_7::_is, P>(addrs, maps, process),
559 Version {
560 major: 3,
561 minor: 4..=5,
562 ..
563 } => check::<v3_5_5::_is, P>(addrs, maps, process),
564 Version {
565 major: 3, minor: 6, ..
566 } => check::<v3_6_6::_is, P>(addrs, maps, process),
567 Version {
568 major: 3, minor: 7, ..
569 } => check::<v3_7_0::_is, P>(addrs, maps, process),
570 Version {
571 major: 3,
572 minor: 8,
573 patch: 0,
574 ..
575 } => match version.release_flags.as_ref() {
576 "a1" | "a2" | "a3" => check::<v3_7_0::_is, P>(addrs, maps, process),
577 _ => check::<v3_8_0::_is, P>(addrs, maps, process),
578 },
579 Version {
580 major: 3, minor: 8, ..
581 } => check::<v3_8_0::_is, P>(addrs, maps, process),
582 Version {
583 major: 3, minor: 9, ..
584 } => check::<v3_9_5::_is, P>(addrs, maps, process),
585 Version {
586 major: 3,
587 minor: 10,
588 ..
589 } => check::<v3_10_0::_is, P>(addrs, maps, process),
590 Version {
591 major: 3,
592 minor: 11,
593 ..
594 } => check::<v3_11_0::_is, P>(addrs, maps, process),
595 Version {
596 major: 3,
597 minor: 12,
598 ..
599 } => check::<v3_12_0::_is, P>(addrs, maps, process),
600 Version {
601 major: 3,
602 minor: 13,
603 ..
604 } => check::<v3_13_0::_is, P>(addrs, maps, process),
605 _ => Err(format_err!("Unsupported version of Python: {}", version)),
606 }
607}
608
609pub fn get_threadstate_address<P>(
610 interpreter_address: usize,
611 python_info: &PythonProcessInfo,
612 process: &P,
613 version: &Version,
614 config: &Config,
615) -> Result<usize, Error>
616where
617 P: ProcessMemory,
618{
619 let threadstate_address = match version {
620 Version {
621 major: 3,
622 minor: 13,
623 ..
624 } => {
625 let gil_ptr = interpreter_address + std::mem::offset_of!(v3_13_0::_is, ceval.gil);
626 let gil = process.copy_struct::<usize>(gil_ptr)?;
627 gil
628 }
629 Version {
630 major: 3,
631 minor: 12,
632 ..
633 } => {
634 let gil_ptr = interpreter_address + std::mem::offset_of!(v3_12_0::_is, ceval.gil);
635 let gil: usize = process.copy_struct(gil_ptr)?;
636 gil
637 }
638 Version {
639 major: 3,
640 minor: 7..=11,
641 ..
642 } => match python_info.get_symbol("_PyRuntime") {
643 Some(&addr) => {
644 if let Some(offset) = pyruntime::get_tstate_current_offset(version) {
645 info!("Found _PyRuntime @ 0x{:016x}, getting gilstate.tstate_current from offset 0x{:x}",
646 addr, offset);
647 addr as usize + offset
648 } else {
649 error_if_gil(
650 config,
651 version,
652 "unknown pyruntime.gilstate.tstate_current offset",
653 )?;
654 0
655 }
656 }
657 None => {
658 error_if_gil(config, version, "failed to find _PyRuntime symbol")?;
659 0
660 }
661 },
662 _ => match python_info.get_symbol("_PyThreadState_Current") {
663 Some(&addr) => {
664 info!("Found _PyThreadState_Current @ 0x{:016x}", addr);
665 addr as usize
666 }
667 None => {
668 error_if_gil(
669 config,
670 version,
671 "failed to find _PyThreadState_Current symbol",
672 )?;
673 0
674 }
675 },
676 };
677
678 Ok(threadstate_address)
679}
680
681fn error_if_gil(config: &Config, version: &Version, msg: &str) -> Result<(), Error> {
682 lazy_static! {
683 static ref WARNED: std::sync::atomic::AtomicBool =
684 std::sync::atomic::AtomicBool::new(false);
685 }
686
687 if config.gil_only {
688 if !WARNED.load(std::sync::atomic::Ordering::Relaxed) {
689 eprintln!(
691 "Cannot detect GIL holding in version '{}' on the current platform (reason: {})",
692 version, msg
693 );
694 eprintln!("Please open an issue in https://github.com/benfred/py-spy with the Python version and your platform.");
695 WARNED.store(true, std::sync::atomic::Ordering::Relaxed);
696 }
697 Err(format_err!(
698 "Cannot detect GIL holding in version '{}' on the current platform (reason: {})",
699 version,
700 msg
701 ))
702 } else {
703 warn!("Unable to detect GIL usage: {}", msg);
704 Ok(())
705 }
706}
707
708pub trait ContainsAddr {
709 fn contains_addr(&self, addr: usize) -> bool;
710}
711
712impl ContainsAddr for Vec<MapRange> {
713 #[cfg(windows)]
714 fn contains_addr(&self, _addr: usize) -> bool {
715 true
718 }
719
720 #[cfg(not(windows))]
721 fn contains_addr(&self, addr: usize) -> bool {
722 proc_maps::maps_contain_addr(addr, self)
723 }
724}
725
726#[cfg(target_os = "linux")]
727fn is_dockerized(pid: Pid) -> Result<bool, Error> {
728 let self_mnt = std::fs::read_link("/proc/self/ns/mnt")?;
729 let target_mnt = std::fs::read_link(format!("/proc/{}/ns/mnt", pid))?;
730 Ok(self_mnt != target_mnt)
731}
732
733#[cfg(windows)]
737pub fn get_windows_python_symbols(
738 pid: Pid,
739 filename: &Path,
740 offset: u64,
741) -> std::io::Result<HashMap<String, u64>> {
742 use proc_maps::win_maps::SymbolLoader;
743
744 let handler = SymbolLoader::new(pid)?;
745 let _module = handler.load_module(filename)?; let mut ret = HashMap::new();
748
749 for symbol in ["_PyThreadState_Current", "interp_head", "_PyRuntime"].iter() {
753 if let Ok((base, addr)) = handler.address_from_name(symbol) {
754 let addr = if base == 0 {
757 addr
758 } else {
759 offset + addr - base
760 };
761 ret.insert(String::from(*symbol), addr);
762 }
763 }
764
765 Ok(ret)
766}
767
768#[cfg(any(target_os = "linux", target_os = "freebsd"))]
769pub fn is_python_lib(pathname: &str) -> bool {
770 lazy_static! {
771 static ref RE: Regex = Regex::new(r"/libpython\d.\d\d?(m|d|u)?.so").unwrap();
772 }
773 RE.is_match(pathname)
774}
775
776#[cfg(target_os = "macos")]
777pub fn is_python_lib(pathname: &str) -> bool {
778 lazy_static! {
779 static ref RE: Regex = Regex::new(r"/libpython\d.\d\d?(m|d|u)?.(dylib|so)$").unwrap();
780 }
781 RE.is_match(pathname) || is_python_framework(pathname)
782}
783
784#[cfg(windows)]
785pub fn is_python_lib(pathname: &str) -> bool {
786 lazy_static! {
787 static ref RE: Regex = RegexBuilder::new(r"\\python\d\d\d?(m|d|u)?.dll$")
788 .case_insensitive(true)
789 .build()
790 .unwrap();
791 }
792 RE.is_match(pathname)
793}
794
795#[cfg(target_os = "macos")]
796pub fn is_python_framework(pathname: &str) -> bool {
797 pathname.ends_with("/Python") && !pathname.contains("Python.app")
798}
799
800#[cfg(test)]
801mod tests {
802 use super::*;
803
804 #[cfg(target_os = "macos")]
805 #[test]
806 fn test_is_python_lib() {
807 assert!(is_python_lib("~/Anaconda2/lib/libpython2.7.dylib"));
808
809 assert!(is_python_lib("/lib/libpython3.4d.dylib"));
811
812 assert!(is_python_lib("/usr/local/lib/libpython3.8m.dylib"));
814
815 assert!(is_python_lib("./libpython2.7u.dylib"));
817
818 assert!(!is_python_lib("/libboost_python.dylib"));
819 assert!(!is_python_lib("/lib/heapq.cpython-36m-darwin.dylib"));
820 }
821
822 #[cfg(any(target_os = "linux", target_os = "freebsd"))]
823 #[test]
824 fn test_is_python_lib() {
825 assert!(is_python_lib("/tmp/_MEIOqzg01/libpython2.7.so.1.0"));
827
828 assert!(is_python_lib("./libpython2.7.so"));
830 assert!(is_python_lib("/usr/lib/libpython3.4d.so"));
831 assert!(is_python_lib("/usr/local/lib/libpython3.8m.so"));
832 assert!(is_python_lib("/usr/lib/libpython2.7u.so"));
833
834 assert!(!is_python_lib("/usr/lib/libboost_python.so"));
836 assert!(!is_python_lib(
837 "/usr/lib/x86_64-linux-gnu/libboost_python-py27.so.1.58.0"
838 ));
839 assert!(!is_python_lib("/usr/lib/libboost_python-py35.so"));
840 }
841
842 #[cfg(windows)]
843 #[test]
844 fn test_is_python_lib() {
845 assert!(is_python_lib(
846 "C:\\Users\\test\\AppData\\Local\\Programs\\Python\\Python37\\python37.dll"
847 ));
848 assert!(is_python_lib(
850 "C:\\Users\\test\\AppData\\Local\\Programs\\Python\\Python37\\python37.DLL"
851 ));
852 }
853
854 #[cfg(target_os = "macos")]
855 #[test]
856 fn test_python_frameworks() {
857 assert!(!is_python_framework("/usr/local/Cellar/python@2/2.7.15_1/Frameworks/Python.framework/Versions/2.7/Resources/Python.app/Contents/MacOS/Python"));
859 assert!(is_python_framework(
860 "/usr/local/Cellar/python@2/2.7.15_1/Frameworks/Python.framework/Versions/2.7/Python"
861 ));
862
863 assert!(!is_python_framework("/System/Library/Frameworks/Python.framework/Versions/2.7/Resources/Python.app/Contents/MacOS/Python"));
865 assert!(is_python_framework(
866 "/System/Library/Frameworks/Python.framework/Versions/2.7/Python"
867 ));
868
869 assert!(is_python_framework(
872 "/Users/ben/.pyenv/versions/3.6.6/Python.framework/Versions/3.6/Python"
873 ));
874 assert!(!is_python_framework("/Users/ben/.pyenv/versions/3.6.6/Python.framework/Versions/3.6/Resources/Python.app/Contents/MacOS/Python"));
875
876 assert!(is_python_framework(
878 "/private/var/folders/3x/qy479lpd1fb2q88lc9g4d3kr0000gn/T/_MEI2Akvi8/Python"
879 ));
880 }
881}