Fedyashov's Blog
Just another WordPress.com site
Monthly Archives: October 2011
Dealing with several Python versions in Sublime Text 2
Sublime Text 2 contains the following build system definition for Python (refer to ~/.config/sublime-text-2/Packages/Python/Python.sublime-build):
{
"cmd": ["python", "-u", "$file"],
"file_regex": "^[ ]*File \"(...*?)\", line ([0-9]*)",
"selector": "source.python"
}
Thus default Python interpreter is used (which is typically a symlink to concrete version). In case if you have several versions of Python installed – you’d want to control which version of Python to invoke on Sublime Text’s “Build” command.
It can be easily done by adding custom build system in Tools > New Build System… menu with a slightly changed definition, e.g:
{
"cmd": ["python3.2", "-u", "$file"],
"file_regex": "^[ ]*File \"(...*?)\", line ([0-9]*)",
"selector": "source.python"
}
After saving this file, e.g. as Python3.sublime-build, and restarting Sublime Text editor it would be possible to specify the version of interpreter you need in Tools > Build System menu.
If you’re interested in Russian version of this entry – take a look here on my impulse9 blog
Python ctypes as a handy tool for C/C++ developers
I have recently learned how useful Python ctypes library might be when it comes to cross-platform testing of dynamic libraries. ctypes handles library loading and allows easy function invocation in case if library exposes C-like interfaces. In addition, library provides a way of using non-trivial data types as function call arguments (such as struct, union, C-style arrays and their combinations).
Solution is cross-platform, it significantly simplifies development and testing of new functions addition to existing dynamic libraries at early stages, so that there are no other clients for those functions ready yet. Also, it can be used as a good sanity measure for automated build regression checks.
As an example, here is a small use case: suppose I have to check the following function from a dynamic library (.dll for Windows, .so on Linux) written in C/C++:
int get_ips(const unsigned short af, struct ipaddress_info ** ppaddrinfo);
Where ipaddress_info is defined like this:
typedef struct ipaddress_info {
unsigned short family;
unsigned char addr[16];
unsigned int scope_id;
} IPADDRESS_INFO;
So given the definition above – you’d have a DLL on Windows (or a shared library on Linux) that exports get_ips function. And here is the sample Python script that is capable to load that library, perform a call to get_ips and display the contents of a returned array of structures:
import os
import sys
from ctypes import *
from socket import AF_INET, AF_INET6, AF_UNSPEC
# Python representation of a C struct defined
# in a shared library being tested
class ipaddress_info(Structure):
_fields_ = [
("family", c_ushort),
("addr", c_ubyte * 16),
("scope_id", c_uint),
]
def bytes2str(v):
s = ""
for x in v:
s += "%.02X " % (x)
return s
class dynlib_bridge:
def __init__(self, dll_path, working_dir="."):
tmp = os.getcwd()
os.chdir(working_dir)
self.invoke = cdll.LoadLibrary(dll_path)
os.chdir(tmp)
class test_case_base:
def __init__(self, real_values):
self.__inout__ = dict()
self.__result__ = None
for arg_name, arg_value in real_values.items():
self.__inout__[arg_name] = arg_value
def actual(self, name):
return self.__inout__[name]
def result(self):
return self.__result__
def setResult(self, v):
self.__result__ = v
# performs a positive test case
def test_pos(testobj):
result = testobj.run()
if result:
print ("[tc+] OK :", testobj.describe())
else:
print ("[tc+] FAIL:", testobj.describe())
# performs a negative test case
def test_neg(testobj):
result = testobj.run()
if not result:
print ("[tc-] OK :", testobj.describe())
else:
print ("[tc-] FAIL:", testobj.describe())
# wraps invokation of a shared library function
class get_ips(test_case_base):
def __init__(self, dll, af):
super(get_ips, self).__init__({
'af': c_ushort(af),
'ppaddrinfo': POINTER(ipaddress_info)(),
})
self.dll = dll
def run(self):
invoke_result = self.dll.invoke.get_ips(
self.actual('af'),
byref(self.actual('ppaddrinfo')))
self.setResult(invoke_result)
return (invoke_result > 0)
def describe(self):
p = self.actual('ppaddrinfo')
ips = []
if self.result() > 0:
for i in range(self.result()):
ips.append("#%d: family = %d, scope = %d, %s" % (
i,
p[i].family,
p[i].scope_id,
bytes2str(p[i].addr)))
s = "result: %.02d\n%s" % (self.result(), "\n".join(ips))
return s
if __name__ == "__main__":
# you'd probably have to use LD_LIBRARY_PATH to make this work on Linux
path_to_lib = sys.argv[1]
path_to_env = sys.argv[2]
print ("lib path :", path_to_lib)
print ("env path :", path_to_env)
dll = dynlib_bridge(path_to_lib, working_dir=path_to_env)
test_pos(get_ips(dll, AF_UNSPEC))
test_pos(get_ips(dll, AF_INET))
test_pos(get_ips(dll, AF_INET6))
test_neg(get_ips(dll, 99))
The first argument is a library file name; second – is the location from which library load should be performed (it is sufficient on Windows, on Linux you might need to deal with LD_LIBRARY_PATH if your library has its own dependencies).
For simplicity, I removed most of error handling code and some not very interesting things like calling another library function to perform memory cleanup. Also, code assumes cdecl calling convention (which actually was my case for both tested platforms)
If you’re interested in Russian version of this entry – take a look here on my impulse9 blog