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_14_0, v3_3_7, v3_5_5, v3_6_6,
21 v3_7_0, v3_8_0, 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.get_symbol("Py_Version") {
303 let version: u32 = process
304 .copy_struct(addr as usize)
305 .context("Failed to copy Py_Version symbol")?;
306
307 let major: u64 = ((version >> 24) & 0xff).into();
309 let minor: u64 = ((version >> 16) & 0xff).into();
310 let patch: u64 = ((version >> 8) & 0xff).into();
311 let release_level = (version >> 4) & 0xf;
312 let release_serial = (version) & 0xf;
313 let release_flags = match release_level {
314 0xA => format!("a{}", release_serial),
315 0xB => format!("b{}", release_serial),
316 0xC => format!("rc{}", release_serial),
317 _ => "".to_owned(),
318 };
319
320 let version = Version {
321 major,
322 minor,
323 patch,
324 release_flags,
325 build_metadata: None,
326 };
327 info!("Got version {} from Py_Version symbol", version);
328 return Ok(version);
329 }
330
331 if let Some(&addr) = python_info
333 .get_symbol("Py_GetVersion.version")
334 .or_else(|| python_info.get_symbol("version"))
335 {
336 info!("Getting version from symbol address");
337 if let Ok(bytes) = process.copy(addr as usize, 128) {
338 if let Ok(version) = Version::scan_bytes(&bytes) {
339 return Ok(version);
340 }
341 }
342 }
343
344 if let Some(ref pb) = python_info.python_binary {
346 info!("Getting version from python binary BSS");
347 let bss = process.copy(pb.bss_addr as usize, pb.bss_size as usize)?;
348 match Version::scan_bytes(&bss) {
349 Ok(version) => return Ok(version),
350 Err(err) => info!("Failed to get version from BSS section: {}", err),
351 }
352 }
353
354 if let Some(ref libpython) = python_info.libpython_binary {
356 info!("Getting version from libpython BSS");
357 let bss = process.copy(libpython.bss_addr as usize, libpython.bss_size as usize)?;
358 match Version::scan_bytes(&bss) {
359 Ok(version) => return Ok(version),
360 Err(err) => info!("Failed to get version from libpython BSS section: {}", err),
361 }
362 }
363
364 info!(
367 "Trying to get version from path: {}",
368 python_info.python_filename.display()
369 );
370 let path = Path::new(&python_info.python_filename);
371 if let Some(python) = path.file_name() {
372 if let Some(python) = python.to_str() {
373 if let Some(stripped_python) = python.strip_prefix("python") {
374 let tokens: Vec<&str> = stripped_python.split('.').collect();
375 if tokens.len() >= 2 {
376 if let (Ok(major), Ok(minor)) =
377 (tokens[0].parse::<u64>(), tokens[1].parse::<u64>())
378 {
379 return Ok(Version {
380 major,
381 minor,
382 patch: 0,
383 release_flags: "".to_owned(),
384 build_metadata: None,
385 });
386 }
387 }
388 }
389 }
390 }
391 Err(format_err!(
392 "Failed to find python version from target process"
393 ))
394}
395
396pub fn get_interpreter_address<P>(
397 python_info: &PythonProcessInfo,
398 process: &P,
399 version: &Version,
400) -> Result<usize, Error>
401where
402 P: ProcessMemory,
403{
404 match get_interpreter_address_from_symbols(python_info, process, version) {
407 Ok(addr) => {
408 match check_interpreter_addresses(&[addr], &*python_info.maps, process, version) {
410 Ok(addr) => return Ok(addr),
411 Err(_) => {
412 warn!("Interpreter address from symbol is invalid {:016x}", addr);
413 }
414 };
415 }
416 Err(err) => {
417 info!("Failed to get interpreter address from symbols {:?}, scanning BSS section from main binary", err)
418 }
419 }
420
421 let err = if let Some(ref pb) = python_info.python_binary {
423 match get_interpreter_address_from_binary(pb, &*python_info.maps, process, version) {
424 Ok(addr) => return Ok(addr),
425 err => Some(err),
426 }
427 } else {
428 None
429 };
430
431 if let Some(ref lpb) = python_info.libpython_binary {
433 info!("Failed to get interpreter from binary BSS, scanning libpython BSS");
434 match get_interpreter_address_from_binary(lpb, &*python_info.maps, process, version) {
435 Ok(addr) => Ok(addr),
436 lib_err => err.unwrap_or(lib_err),
437 }
438 } else {
439 err.expect("Both python and libpython are invalid.")
440 }
441}
442
443fn get_interpreter_address_from_symbols<P>(
445 python_info: &PythonProcessInfo,
446 process: &P,
447 version: &Version,
448) -> Result<usize, Error>
449where
450 P: ProcessMemory,
451{
452 match version {
453 Version {
454 major: 3,
455 minor: 13..=14,
456 ..
457 } => {
458 if let Some(&pyruntime_addr) = python_info.get_symbol("_PyRuntime") {
459 match version {
461 Version {
462 major: 3,
463 minor: 14,
464 ..
465 } => {
466 let debug_offsets: v3_14_0::_Py_DebugOffsets =
467 process.copy_struct(pyruntime_addr as usize)?;
468 return process
469 .copy_struct(
470 pyruntime_addr as usize
471 + debug_offsets.runtime_state.interpreters_head as usize,
472 )
473 .context(
474 "Failed to copy py_debug_offsets.runtime_state.interpreters_head",
475 );
476 }
477 _ => {
478 let debug_offsets: v3_13_0::_Py_DebugOffsets =
479 process.copy_struct(pyruntime_addr as usize)?;
480 return process
481 .copy_struct(
482 pyruntime_addr as usize
483 + debug_offsets.runtime_state.interpreters_head as usize,
484 )
485 .context(
486 "Failed to copy py_debug_offsets.runtime_state.interpreters_head",
487 );
488 }
489 };
490 }
491 }
492 Version {
493 major: 3,
494 minor: 7..=12,
495 ..
496 } => {
497 if let Some(&addr) = python_info.get_symbol("_PyRuntime") {
498 return process
499 .copy_struct(addr as usize + pyruntime::get_interp_head_offset(version))
500 .context("Failed to copy interpreters_head");
501 }
502 }
503 _ => {
504 if let Some(&addr) = python_info.get_symbol("interp_head") {
505 return process
506 .copy_struct(addr as usize)
507 .context("Failed to copy interp_head");
508 }
509 }
510 };
511 return Err(format_err!(
512 "Failed to find _PyRuntime address from symbols"
513 ));
514}
515
516fn get_interpreter_address_from_binary<P>(
517 binary: &BinaryInfo,
518 maps: &dyn ContainsAddr,
519 process: &P,
520 version: &Version,
521) -> Result<usize, Error>
522where
523 P: ProcessMemory,
524{
525 if binary.pyruntime_addr != 0 {
527 let bss = process.copy(
528 binary.pyruntime_addr as usize,
529 binary.pyruntime_size as usize,
530 )?;
531 #[allow(clippy::cast_ptr_alignment)]
532 let addrs = unsafe {
533 slice::from_raw_parts(bss.as_ptr() as *const usize, bss.len() / size_of::<usize>())
534 };
535 if let Ok(addr) = check_interpreter_addresses(addrs, maps, process, version) {
536 return Ok(addr);
537 }
538 }
539
540 let bss = process.copy(binary.bss_addr as usize, binary.bss_size as usize)?;
543
544 #[allow(clippy::cast_ptr_alignment)]
545 let addrs = unsafe {
546 slice::from_raw_parts(bss.as_ptr() as *const usize, bss.len() / size_of::<usize>())
547 };
548 check_interpreter_addresses(addrs, maps, process, version)
549}
550
551fn check_interpreter_addresses<P>(
554 addrs: &[usize],
555 maps: &dyn ContainsAddr,
556 process: &P,
557 version: &Version,
558) -> Result<usize, Error>
559where
560 P: ProcessMemory,
561{
562 fn check<I, P>(addrs: &[usize], maps: &dyn ContainsAddr, process: &P) -> Result<usize, Error>
564 where
565 I: InterpreterState,
566 P: ProcessMemory,
567 {
568 for &addr in addrs {
569 if maps.contains_addr(addr) {
570 let threadstate_ptr_ptr = I::threadstate_ptr_ptr(addr);
573 let maybe_threads = process
574 .copy_struct(threadstate_ptr_ptr as usize)
575 .context("Failed to copy PyThreadState head pointer");
576
577 let threads: *const I::ThreadState = match maybe_threads {
578 Ok(threads) => threads,
579 Err(_) => continue,
580 };
581
582 if maps.contains_addr(threads as usize) {
583 let thread = match process.copy_pointer(threads) {
586 Ok(thread) => thread,
587 Err(_) => continue,
588 };
589
590 if thread.interp() as usize == addr
592 && get_stack_traces::<I, P>(addr, process, 0, None).is_ok()
593 {
594 return Ok(addr);
595 }
596 }
597 }
598 }
599 Err(format_err!(
600 "Failed to find a python interpreter in the .data section"
601 ))
602 }
603
604 match version {
606 Version {
607 major: 2,
608 minor: 3..=7,
609 ..
610 } => check::<v2_7_15::_is, P>(addrs, maps, process),
611 Version {
612 major: 3, minor: 3, ..
613 } => check::<v3_3_7::_is, P>(addrs, maps, process),
614 Version {
615 major: 3,
616 minor: 4..=5,
617 ..
618 } => check::<v3_5_5::_is, P>(addrs, maps, process),
619 Version {
620 major: 3, minor: 6, ..
621 } => check::<v3_6_6::_is, P>(addrs, maps, process),
622 Version {
623 major: 3, minor: 7, ..
624 } => check::<v3_7_0::_is, P>(addrs, maps, process),
625 Version {
626 major: 3, minor: 8, ..
627 } => check::<v3_8_0::_is, P>(addrs, maps, process),
628 Version {
629 major: 3, minor: 9, ..
630 } => check::<v3_9_5::_is, P>(addrs, maps, process),
631 Version {
632 major: 3,
633 minor: 10,
634 ..
635 } => check::<v3_10_0::_is, P>(addrs, maps, process),
636 Version {
637 major: 3,
638 minor: 11,
639 ..
640 } => check::<v3_11_0::_is, P>(addrs, maps, process),
641 Version {
642 major: 3,
643 minor: 12,
644 ..
645 } => check::<v3_12_0::_is, P>(addrs, maps, process),
646 Version {
647 major: 3,
648 minor: 13,
649 ..
650 } => check::<v3_13_0::_is, P>(addrs, maps, process),
651 Version {
652 major: 3,
653 minor: 14,
654 ..
655 } => check::<v3_14_0::_is, P>(addrs, maps, process),
656 _ => Err(format_err!("Unsupported version of Python: {}", version)),
657 }
658}
659
660pub fn get_threadstate_address<P>(
661 interpreter_address: usize,
662 python_info: &PythonProcessInfo,
663 process: &P,
664 version: &Version,
665 config: &Config,
666) -> Result<usize, Error>
667where
668 P: ProcessMemory,
669{
670 let threadstate_address = match version {
671 Version {
672 major: 3,
673 minor: 13..=14,
674 ..
675 } => {
676 let gil_ptr = interpreter_address + std::mem::offset_of!(v3_13_0::_is, ceval.gil);
677 process.copy_struct::<usize>(gil_ptr)?
678 }
679 Version {
680 major: 3,
681 minor: 12,
682 ..
683 } => {
684 let gil_ptr = interpreter_address + std::mem::offset_of!(v3_12_0::_is, ceval.gil);
685 let gil: usize = process.copy_struct(gil_ptr)?;
686 gil
687 }
688 Version {
689 major: 3,
690 minor: 7..=11,
691 ..
692 } => match python_info.get_symbol("_PyRuntime") {
693 Some(&addr) => {
694 if let Some(offset) = pyruntime::get_tstate_current_offset(version) {
695 info!("Found _PyRuntime @ 0x{:016x}, getting gilstate.tstate_current from offset 0x{:x}",
696 addr, offset);
697 addr as usize + offset
698 } else {
699 error_if_gil(
700 config,
701 version,
702 "unknown pyruntime.gilstate.tstate_current offset",
703 )?;
704 0
705 }
706 }
707 None => {
708 error_if_gil(config, version, "failed to find _PyRuntime symbol")?;
709 0
710 }
711 },
712 _ => match python_info.get_symbol("_PyThreadState_Current") {
713 Some(&addr) => {
714 info!("Found _PyThreadState_Current @ 0x{:016x}", addr);
715 addr as usize
716 }
717 None => {
718 error_if_gil(
719 config,
720 version,
721 "failed to find _PyThreadState_Current symbol",
722 )?;
723 0
724 }
725 },
726 };
727
728 Ok(threadstate_address)
729}
730
731fn error_if_gil(config: &Config, version: &Version, msg: &str) -> Result<(), Error> {
732 lazy_static! {
733 static ref WARNED: std::sync::atomic::AtomicBool =
734 std::sync::atomic::AtomicBool::new(false);
735 }
736
737 if config.gil_only {
738 if !WARNED.load(std::sync::atomic::Ordering::Relaxed) {
739 eprintln!(
741 "Cannot detect GIL holding in version '{version}' on the current platform (reason: {msg})"
742 );
743 eprintln!("Please open an issue in https://github.com/benfred/py-spy with the Python version and your platform.");
744 WARNED.store(true, std::sync::atomic::Ordering::Relaxed);
745 }
746 Err(format_err!(
747 "Cannot detect GIL holding in version '{}' on the current platform (reason: {})",
748 version,
749 msg
750 ))
751 } else {
752 warn!("Unable to detect GIL usage: {}", msg);
753 Ok(())
754 }
755}
756
757pub trait ContainsAddr {
758 fn contains_addr(&self, addr: usize) -> bool;
759}
760
761impl ContainsAddr for Vec<MapRange> {
762 #[cfg(windows)]
763 fn contains_addr(&self, _addr: usize) -> bool {
764 true
767 }
768
769 #[cfg(not(windows))]
770 fn contains_addr(&self, addr: usize) -> bool {
771 proc_maps::maps_contain_addr(addr, self)
772 }
773}
774
775#[cfg(target_os = "linux")]
776fn is_dockerized(pid: Pid) -> Result<bool, Error> {
777 let self_mnt = std::fs::read_link("/proc/self/ns/mnt")?;
778 let target_mnt = std::fs::read_link(format!("/proc/{}/ns/mnt", pid))?;
779 Ok(self_mnt != target_mnt)
780}
781
782#[cfg(windows)]
786pub fn get_windows_python_symbols(
787 pid: Pid,
788 filename: &Path,
789 offset: u64,
790) -> std::io::Result<HashMap<String, u64>> {
791 use proc_maps::win_maps::SymbolLoader;
792
793 let handler = SymbolLoader::new(pid)?;
794 let _module = handler.load_module(filename)?; let mut ret = HashMap::new();
797
798 for symbol in ["_PyThreadState_Current", "interp_head", "_PyRuntime"].iter() {
802 if let Ok((base, addr)) = handler.address_from_name(symbol) {
803 let addr = if base == 0 {
806 addr
807 } else {
808 offset + addr - base
809 };
810 ret.insert(String::from(*symbol), addr);
811 }
812 }
813
814 Ok(ret)
815}
816
817#[cfg(any(target_os = "linux", target_os = "freebsd"))]
818pub fn is_python_lib(pathname: &str) -> bool {
819 lazy_static! {
820 static ref RE: Regex = Regex::new(r"/libpython\d.\d\d?(m|d|u)?.so").unwrap();
821 }
822 RE.is_match(pathname)
823}
824
825#[cfg(target_os = "macos")]
826pub fn is_python_lib(pathname: &str) -> bool {
827 lazy_static! {
828 static ref RE: Regex = Regex::new(r"/libpython\d.\d\d?(m|d|u)?.(dylib|so)$").unwrap();
829 }
830 RE.is_match(pathname) || is_python_framework(pathname)
831}
832
833#[cfg(windows)]
834pub fn is_python_lib(pathname: &str) -> bool {
835 lazy_static! {
836 static ref RE: Regex = RegexBuilder::new(r"\\python\d\d\d?(m|d|u)?.dll$")
837 .case_insensitive(true)
838 .build()
839 .unwrap();
840 }
841 RE.is_match(pathname)
842}
843
844#[cfg(target_os = "macos")]
845pub fn is_python_framework(pathname: &str) -> bool {
846 pathname.ends_with("/Python") && !pathname.contains("Python.app")
847}
848
849#[cfg(test)]
850mod tests {
851 use super::*;
852
853 #[cfg(target_os = "macos")]
854 #[test]
855 fn test_is_python_lib() {
856 assert!(is_python_lib("~/Anaconda2/lib/libpython2.7.dylib"));
857
858 assert!(is_python_lib("/lib/libpython3.4d.dylib"));
860
861 assert!(is_python_lib("/usr/local/lib/libpython3.8m.dylib"));
863
864 assert!(is_python_lib("./libpython2.7u.dylib"));
866
867 assert!(!is_python_lib("/libboost_python.dylib"));
868 assert!(!is_python_lib("/lib/heapq.cpython-36m-darwin.dylib"));
869 }
870
871 #[cfg(any(target_os = "linux", target_os = "freebsd"))]
872 #[test]
873 fn test_is_python_lib() {
874 assert!(is_python_lib("/tmp/_MEIOqzg01/libpython2.7.so.1.0"));
876
877 assert!(is_python_lib("./libpython2.7.so"));
879 assert!(is_python_lib("/usr/lib/libpython3.4d.so"));
880 assert!(is_python_lib("/usr/local/lib/libpython3.8m.so"));
881 assert!(is_python_lib("/usr/lib/libpython2.7u.so"));
882
883 assert!(!is_python_lib("/usr/lib/libboost_python.so"));
885 assert!(!is_python_lib(
886 "/usr/lib/x86_64-linux-gnu/libboost_python-py27.so.1.58.0"
887 ));
888 assert!(!is_python_lib("/usr/lib/libboost_python-py35.so"));
889 }
890
891 #[cfg(windows)]
892 #[test]
893 fn test_is_python_lib() {
894 assert!(is_python_lib(
895 "C:\\Users\\test\\AppData\\Local\\Programs\\Python\\Python37\\python37.dll"
896 ));
897 assert!(is_python_lib(
899 "C:\\Users\\test\\AppData\\Local\\Programs\\Python\\Python37\\python37.DLL"
900 ));
901 }
902
903 #[cfg(target_os = "macos")]
904 #[test]
905 fn test_python_frameworks() {
906 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"));
908 assert!(is_python_framework(
909 "/usr/local/Cellar/python@2/2.7.15_1/Frameworks/Python.framework/Versions/2.7/Python"
910 ));
911
912 assert!(!is_python_framework("/System/Library/Frameworks/Python.framework/Versions/2.7/Resources/Python.app/Contents/MacOS/Python"));
914 assert!(is_python_framework(
915 "/System/Library/Frameworks/Python.framework/Versions/2.7/Python"
916 ));
917
918 assert!(is_python_framework(
921 "/Users/ben/.pyenv/versions/3.6.6/Python.framework/Versions/3.6/Python"
922 ));
923 assert!(!is_python_framework("/Users/ben/.pyenv/versions/3.6.6/Python.framework/Versions/3.6/Resources/Python.app/Contents/MacOS/Python"));
924
925 assert!(is_python_framework(
927 "/private/var/folders/3x/qy479lpd1fb2q88lc9g4d3kr0000gn/T/_MEI2Akvi8/Python"
928 ));
929 }
930}