[Python-checkins] distutils2: Refactor support code for compiling xxmodule.c.

eric.araujo python-checkins at python.org
Mon Sep 19 15:12:39 CEST 2011


http://hg.python.org/distutils2/rev/14261f7dbdd4
changeset:   1157:14261f7dbdd4
user:        Éric Araujo <merwok at netwok.org>
date:        Mon Sep 19 02:08:16 2011 +0200
summary:
  Refactor support code for compiling xxmodule.c.

This file is needed in other tests, so it’s better to have the support
code in tests.support.  It’s also simpler to just have a skip instead of
custom print/return/test suite fiddling.  Unfortunately, the xxmodule.c
file (resurrected from the repo, and also identical to the version in
Python 2.7) cannot be compiled by Python 2.4 and 2.5 on my computer, so
the test is skipped.

The code to fix up build_ext for Unix shared builds and Windows debug
builds was also moved to support for future reuse.

Finally, I fixed code using sysconfig._CONFIG_VARS directly so that it
calls get_config_var first, so that _CONFIG_VARS is a dict instead of
None.

files:
  distutils2/tests/support.py                |   70 +-
  distutils2/tests/test_command_build_ext.py |   72 +-
  distutils2/tests/xxmodule.c                |  379 ++++++++++
  setup.cfg                                  |    3 +
  4 files changed, 458 insertions(+), 66 deletions(-)


diff --git a/distutils2/tests/support.py b/distutils2/tests/support.py
--- a/distutils2/tests/support.py
+++ b/distutils2/tests/support.py
@@ -21,12 +21,12 @@
             ...  # other setup code
 
 Also provided is a DummyCommand class, useful to mock commands in the
-tests of another command that needs them, a create_distribution function
-and a skip_unless_symlink decorator.
+tests of another command that needs them, for example to fake
+compilation in build_ext (this requires that the mock build_ext command
+be injected into the distribution object's command_obj dictionary).
 
-Also provided is a DummyCommand class, useful to mock commands in the
-tests of another command that needs them, a create_distribution function
-and a skip_unless_symlink decorator.
+For tests that need to compile an extension module, use the
+copy_xxmodule_c and fixup_build_ext functions.
 
 Each class or function has a docstring to explain its purpose and usage.
 Existing tests should also be used as examples.
@@ -50,6 +50,7 @@
 
 from distutils2.dist import Distribution
 from distutils2.tests import unittest
+from distutils2._backport import sysconfig
 
 # define __all__ to make pydoc more useful
 __all__ = [
@@ -58,7 +59,7 @@
     # mocks
     'DummyCommand', 'TestDistribution',
     # misc. functions and decorators
-    'fake_dec', 'create_distribution',
+    'fake_dec', 'create_distribution', 'copy_xxmodule_c', 'fixup_build_ext',
     # imported from this module for backport purposes
     'unittest', 'requires_zlib', 'skip_unless_symlink',
 ]
@@ -298,12 +299,69 @@
     return _wrap
 
 
+def copy_xxmodule_c(directory):
+    """Helper for tests that need the xxmodule.c source file.
+
+    Example use:
+
+        def test_compile(self):
+            copy_xxmodule_c(self.tmpdir)
+            self.assertIn('xxmodule.c', os.listdir(self.tmpdir))
+
+    If the source file can be found, it will be copied to *directory*.  If not,
+    the test will be skipped.  Errors during copy are not caught.
+    """
+    filename = _get_xxmodule_path()
+    if filename is None:
+        raise unittest.SkipTest('cannot find xxmodule.c (test must run in '
+                                'the python build dir)')
+    shutil.copy(filename, directory)
+
+
+def _get_xxmodule_path():
+    path = os.path.join(os.path.dirname(__file__), 'xxmodule.c')
+    if os.path.exists(path):
+        return path
+
+
+def fixup_build_ext(cmd):
+    """Function needed to make build_ext tests pass.
+
+    When Python was built with --enable-shared on Unix, -L. is not enough to
+    find libpython<blah>.so, because regrtest runs in a tempdir, not in the
+    source directory where the .so lives.
+
+    When Python was built with in debug mode on Windows, build_ext commands
+    need their debug attribute set, and it is not done automatically for
+    some reason.
+
+    This function handles both of these things.  Example use:
+
+        cmd = build_ext(dist)
+        support.fixup_build_ext(cmd)
+        cmd.ensure_finalized()
+    """
+    if os.name == 'nt':
+        cmd.debug = sys.executable.endswith('_d.exe')
+    elif sysconfig.get_config_var('Py_ENABLE_SHARED'):
+        # To further add to the shared builds fun on Unix, we can't just add
+        # library_dirs to the Extension() instance because that doesn't get
+        # plumbed through to the final compiler command.
+        runshared = sysconfig.get_config_var('RUNSHARED')
+        if runshared is None:
+            cmd.library_dirs = ['.']
+        else:
+            name, equals, value = runshared.partition('=')
+            cmd.library_dirs = value.split(os.pathsep)
+
+
 try:
     from test.test_support import skip_unless_symlink
 except ImportError:
     skip_unless_symlink = unittest.skip(
         'requires test.test_support.skip_unless_symlink')
 
+
 requires_zlib = unittest.skipUnless(zlib, 'requires zlib')
 
 
diff --git a/distutils2/tests/test_command_build_ext.py b/distutils2/tests/test_command_build_ext.py
--- a/distutils2/tests/test_command_build_ext.py
+++ b/distutils2/tests/test_command_build_ext.py
@@ -4,26 +4,15 @@
 import shutil
 import textwrap
 from StringIO import StringIO
-from distutils2._backport import sysconfig
-from distutils2._backport.sysconfig import _CONFIG_VARS
 from distutils2.dist import Distribution
 from distutils2.errors import (UnknownFileError, CompileError,
                                PackagingPlatformError)
 from distutils2.command.build_ext import build_ext
 from distutils2.compiler.extension import Extension
-from distutils2.tests.support import assert_python_ok
+from distutils2._backport import sysconfig
 
 from distutils2.tests import support, unittest, verbose
-
-
-def _get_source_filename():
-    # use installed copy if available
-    tests_f = os.path.join(os.path.dirname(__file__), 'xxmodule.c')
-    if os.path.exists(tests_f):
-        return tests_f
-    # otherwise try using copy from build directory
-    srcdir = sysconfig.get_config_var('srcdir')
-    return os.path.join(srcdir, 'Modules', 'xxmodule.c')
+from distutils2.tests.support import assert_python_ok
 
 
 class BuildExtTestCase(support.TempdirManager,
@@ -32,9 +21,6 @@
     def setUp(self):
         super(BuildExtTestCase, self).setUp()
         self.tmp_dir = self.mkdtemp()
-        filename = _get_source_filename()
-        if os.path.exists(filename):
-            shutil.copy(filename, self.tmp_dir)
         if sys.version > "2.6":
             self.old_user_base = site.USER_BASE
             site.USER_BASE = self.mkdtemp()
@@ -45,40 +31,16 @@
 
         super(BuildExtTestCase, self).tearDown()
 
-    def _fixup_command(self, cmd):
-        # When Python was build with --enable-shared, -L. is not good enough
-        # to find the libpython<blah>.so.  This is because regrtest runs it
-        # under a tempdir, not in the top level where the .so lives.  By the
-        # time we've gotten here, Python's already been chdir'd to the
-        # tempdir.
-        #
-        # To further add to the fun, we can't just add library_dirs to the
-        # Extension() instance because that doesn't get plumbed through to the
-        # final compiler command.
-        if (sysconfig.get_config_var('Py_ENABLE_SHARED') and
-            not sys.platform.startswith('win')):
-            runshared = sysconfig.get_config_var('RUNSHARED')
-            if runshared is None:
-                cmd.library_dirs = ['.']
-            else:
-                name, equals, value = runshared.partition('=')
-                cmd.library_dirs = value.split(os.pathsep)
-
+    @unittest.skipIf(sys.version_info[:2] < (2, 6),
+                     "can't compile xxmodule successfully")
     def test_build_ext(self):
+        support.copy_xxmodule_c(self.tmp_dir)
         xx_c = os.path.join(self.tmp_dir, 'xxmodule.c')
-        if not os.path.exists(xx_c):
-            # skipping if we cannot find it
-            return
         xx_ext = Extension('xx', [xx_c])
         dist = Distribution({'name': 'xx', 'ext_modules': [xx_ext]})
         dist.package_dir = self.tmp_dir
         cmd = build_ext(dist)
-        self._fixup_command(cmd)
-
-        if os.name == "nt":
-            # On Windows, we must build a debug version iff running
-            # a debug build of Python
-            cmd.debug = sys.executable.endswith("_d.exe")
+        support.fixup_build_ext(cmd)
         cmd.build_lib = self.tmp_dir
         cmd.build_temp = self.tmp_dir
 
@@ -118,16 +80,16 @@
 
         sys.platform = 'sunos'  # fooling finalize_options
 
-        old_var = _CONFIG_VARS.get('Py_ENABLE_SHARED')
-        _CONFIG_VARS['Py_ENABLE_SHARED'] = 1
+        old_var = sysconfig.get_config_var('Py_ENABLE_SHARED')
+        sysconfig._CONFIG_VARS['Py_ENABLE_SHARED'] = 1
         try:
             cmd.ensure_finalized()
         finally:
             sys.platform = old
             if old_var is None:
-                del _CONFIG_VARS['Py_ENABLE_SHARED']
+                del sysconfig._CONFIG_VARS['Py_ENABLE_SHARED']
             else:
-                _CONFIG_VARS['Py_ENABLE_SHARED'] = old_var
+                sysconfig._CONFIG_VARS['Py_ENABLE_SHARED'] = old_var
 
         # make sure we get some library dirs under solaris
         self.assertGreater(len(cmd.library_dirs), 0)
@@ -265,13 +227,10 @@
         dist = Distribution({'name': 'xx',
                              'ext_modules': [ext]})
         cmd = build_ext(dist)
-        self._fixup_command(cmd)
+        support.fixup_build_ext(cmd)
         cmd.ensure_finalized()
         self.assertEqual(len(cmd.get_outputs()), 1)
 
-        if os.name == "nt":
-            cmd.debug = sys.executable.endswith("_d.exe")
-
         cmd.build_lib = os.path.join(self.tmp_dir, 'build')
         cmd.build_temp = os.path.join(self.tmp_dir, 'tempt')
 
@@ -454,14 +413,7 @@
 
 
 def test_suite():
-    src = _get_source_filename()
-    if not os.path.exists(src):
-        if verbose:
-            print ('test_command_build_ext: Cannot find source code (test'
-                   ' must run in python build dir)')
-        return unittest.TestSuite()
-    else:
-        return unittest.makeSuite(BuildExtTestCase)
+    return unittest.makeSuite(BuildExtTestCase)
 
 if __name__ == '__main__':
     unittest.main(defaultTest='test_suite')
diff --git a/distutils2/tests/xxmodule.c b/distutils2/tests/xxmodule.c
new file mode 100644
--- /dev/null
+++ b/distutils2/tests/xxmodule.c
@@ -0,0 +1,379 @@
+
+/* Use this file as a template to start implementing a module that
+   also declares object types. All occurrences of 'Xxo' should be changed
+   to something reasonable for your objects. After that, all other
+   occurrences of 'xx' should be changed to something reasonable for your
+   module. If your module is named foo your sourcefile should be named
+   foomodule.c.
+
+   You will probably want to delete all references to 'x_attr' and add
+   your own types of attributes instead.  Maybe you want to name your
+   local variables other than 'self'.  If your object type is needed in
+   other files, you'll have to create a file "foobarobject.h"; see
+   intobject.h for an example. */
+
+/* Xxo objects */
+
+#include "Python.h"
+
+static PyObject *ErrorObject;
+
+typedef struct {
+	PyObject_HEAD
+	PyObject	*x_attr;	/* Attributes dictionary */
+} XxoObject;
+
+static PyTypeObject Xxo_Type;
+
+#define XxoObject_Check(v)	(Py_TYPE(v) == &Xxo_Type)
+
+static XxoObject *
+newXxoObject(PyObject *arg)
+{
+	XxoObject *self;
+	self = PyObject_New(XxoObject, &Xxo_Type);
+	if (self == NULL)
+		return NULL;
+	self->x_attr = NULL;
+	return self;
+}
+
+/* Xxo methods */
+
+static void
+Xxo_dealloc(XxoObject *self)
+{
+	Py_XDECREF(self->x_attr);
+	PyObject_Del(self);
+}
+
+static PyObject *
+Xxo_demo(XxoObject *self, PyObject *args)
+{
+	if (!PyArg_ParseTuple(args, ":demo"))
+		return NULL;
+	Py_INCREF(Py_None);
+	return Py_None;
+}
+
+static PyMethodDef Xxo_methods[] = {
+	{"demo",	(PyCFunction)Xxo_demo,	METH_VARARGS,
+		PyDoc_STR("demo() -> None")},
+	{NULL,		NULL}		/* sentinel */
+};
+
+static PyObject *
+Xxo_getattr(XxoObject *self, char *name)
+{
+	if (self->x_attr != NULL) {
+		PyObject *v = PyDict_GetItemString(self->x_attr, name);
+		if (v != NULL) {
+			Py_INCREF(v);
+			return v;
+		}
+	}
+	return Py_FindMethod(Xxo_methods, (PyObject *)self, name);
+}
+
+static int
+Xxo_setattr(XxoObject *self, char *name, PyObject *v)
+{
+	if (self->x_attr == NULL) {
+		self->x_attr = PyDict_New();
+		if (self->x_attr == NULL)
+			return -1;
+	}
+	if (v == NULL) {
+		int rv = PyDict_DelItemString(self->x_attr, name);
+		if (rv < 0)
+			PyErr_SetString(PyExc_AttributeError,
+			        "delete non-existing Xxo attribute");
+		return rv;
+	}
+	else
+		return PyDict_SetItemString(self->x_attr, name, v);
+}
+
+static PyTypeObject Xxo_Type = {
+	/* The ob_type field must be initialized in the module init function
+	 * to be portable to Windows without using C++. */
+	PyVarObject_HEAD_INIT(NULL, 0)
+	"xxmodule.Xxo",		/*tp_name*/
+	sizeof(XxoObject),	/*tp_basicsize*/
+	0,			/*tp_itemsize*/
+	/* methods */
+	(destructor)Xxo_dealloc, /*tp_dealloc*/
+	0,			/*tp_print*/
+	(getattrfunc)Xxo_getattr, /*tp_getattr*/
+	(setattrfunc)Xxo_setattr, /*tp_setattr*/
+	0,			/*tp_compare*/
+	0,			/*tp_repr*/
+	0,			/*tp_as_number*/
+	0,			/*tp_as_sequence*/
+	0,			/*tp_as_mapping*/
+	0,			/*tp_hash*/
+        0,                      /*tp_call*/
+        0,                      /*tp_str*/
+        0,                      /*tp_getattro*/
+        0,                      /*tp_setattro*/
+        0,                      /*tp_as_buffer*/
+        Py_TPFLAGS_DEFAULT,     /*tp_flags*/
+        0,                      /*tp_doc*/
+        0,                      /*tp_traverse*/
+        0,                      /*tp_clear*/
+        0,                      /*tp_richcompare*/
+        0,                      /*tp_weaklistoffset*/
+        0,                      /*tp_iter*/
+        0,                      /*tp_iternext*/
+        0,                      /*tp_methods*/
+        0,                      /*tp_members*/
+        0,                      /*tp_getset*/
+        0,                      /*tp_base*/
+        0,                      /*tp_dict*/
+        0,                      /*tp_descr_get*/
+        0,                      /*tp_descr_set*/
+        0,                      /*tp_dictoffset*/
+        0,                      /*tp_init*/
+        0,                      /*tp_alloc*/
+        0,                      /*tp_new*/
+        0,                      /*tp_free*/
+        0,                      /*tp_is_gc*/
+};
+/* --------------------------------------------------------------------- */
+
+/* Function of two integers returning integer */
+
+PyDoc_STRVAR(xx_foo_doc,
+"foo(i,j)\n\
+\n\
+Return the sum of i and j.");
+
+static PyObject *
+xx_foo(PyObject *self, PyObject *args)
+{
+	long i, j;
+	long res;
+	if (!PyArg_ParseTuple(args, "ll:foo", &i, &j))
+		return NULL;
+	res = i+j; /* XXX Do something here */
+	return PyInt_FromLong(res);
+}
+
+
+/* Function of no arguments returning new Xxo object */
+
+static PyObject *
+xx_new(PyObject *self, PyObject *args)
+{
+	XxoObject *rv;
+
+	if (!PyArg_ParseTuple(args, ":new"))
+		return NULL;
+	rv = newXxoObject(args);
+	if (rv == NULL)
+		return NULL;
+	return (PyObject *)rv;
+}
+
+/* Example with subtle bug from extensions manual ("Thin Ice"). */
+
+static PyObject *
+xx_bug(PyObject *self, PyObject *args)
+{
+	PyObject *list, *item;
+
+	if (!PyArg_ParseTuple(args, "O:bug", &list))
+		return NULL;
+
+	item = PyList_GetItem(list, 0);
+	/* Py_INCREF(item); */
+	PyList_SetItem(list, 1, PyInt_FromLong(0L));
+	PyObject_Print(item, stdout, 0);
+	printf("\n");
+	/* Py_DECREF(item); */
+
+	Py_INCREF(Py_None);
+	return Py_None;
+}
+
+/* Test bad format character */
+
+static PyObject *
+xx_roj(PyObject *self, PyObject *args)
+{
+	PyObject *a;
+	long b;
+	if (!PyArg_ParseTuple(args, "O#:roj", &a, &b))
+		return NULL;
+	Py_INCREF(Py_None);
+	return Py_None;
+}
+
+
+/* ---------- */
+
+static PyTypeObject Str_Type = {
+	/* The ob_type field must be initialized in the module init function
+	 * to be portable to Windows without using C++. */
+	PyVarObject_HEAD_INIT(NULL, 0)
+	"xxmodule.Str",		/*tp_name*/
+	0,			/*tp_basicsize*/
+	0,			/*tp_itemsize*/
+	/* methods */
+	0,			/*tp_dealloc*/
+	0,			/*tp_print*/
+	0,			/*tp_getattr*/
+	0,			/*tp_setattr*/
+	0,			/*tp_compare*/
+	0,			/*tp_repr*/
+	0,			/*tp_as_number*/
+	0,			/*tp_as_sequence*/
+	0,			/*tp_as_mapping*/
+	0,			/*tp_hash*/
+	0,			/*tp_call*/
+	0,			/*tp_str*/
+	0,			/*tp_getattro*/
+	0,			/*tp_setattro*/
+	0,			/*tp_as_buffer*/
+	Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /*tp_flags*/
+	0,			/*tp_doc*/
+	0,			/*tp_traverse*/
+	0,			/*tp_clear*/
+	0,			/*tp_richcompare*/
+	0,			/*tp_weaklistoffset*/
+	0,			/*tp_iter*/
+	0,			/*tp_iternext*/
+	0,			/*tp_methods*/
+	0,			/*tp_members*/
+	0,			/*tp_getset*/
+	0, /* see initxx */	/*tp_base*/
+	0,			/*tp_dict*/
+	0,			/*tp_descr_get*/
+	0,			/*tp_descr_set*/
+	0,			/*tp_dictoffset*/
+	0,			/*tp_init*/
+	0,			/*tp_alloc*/
+	0,			/*tp_new*/
+	0,			/*tp_free*/
+	0,			/*tp_is_gc*/
+};
+
+/* ---------- */
+
+static PyObject *
+null_richcompare(PyObject *self, PyObject *other, int op)
+{
+	Py_INCREF(Py_NotImplemented);
+	return Py_NotImplemented;
+}
+
+static PyTypeObject Null_Type = {
+	/* The ob_type field must be initialized in the module init function
+	 * to be portable to Windows without using C++. */
+	PyVarObject_HEAD_INIT(NULL, 0)
+	"xxmodule.Null",	/*tp_name*/
+	0,			/*tp_basicsize*/
+	0,			/*tp_itemsize*/
+	/* methods */
+	0,			/*tp_dealloc*/
+	0,			/*tp_print*/
+	0,			/*tp_getattr*/
+	0,			/*tp_setattr*/
+	0,			/*tp_compare*/
+	0,			/*tp_repr*/
+	0,			/*tp_as_number*/
+	0,			/*tp_as_sequence*/
+	0,			/*tp_as_mapping*/
+	0,			/*tp_hash*/
+	0,			/*tp_call*/
+	0,			/*tp_str*/
+	0,			/*tp_getattro*/
+	0,			/*tp_setattro*/
+	0,			/*tp_as_buffer*/
+	Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /*tp_flags*/
+	0,			/*tp_doc*/
+	0,			/*tp_traverse*/
+	0,			/*tp_clear*/
+	null_richcompare,	/*tp_richcompare*/
+	0,			/*tp_weaklistoffset*/
+	0,			/*tp_iter*/
+	0,			/*tp_iternext*/
+	0,			/*tp_methods*/
+	0,			/*tp_members*/
+	0,			/*tp_getset*/
+	0, /* see initxx */	/*tp_base*/
+	0,			/*tp_dict*/
+	0,			/*tp_descr_get*/
+	0,			/*tp_descr_set*/
+	0,			/*tp_dictoffset*/
+	0,			/*tp_init*/
+	0,			/*tp_alloc*/
+	0, /* see initxx */	/*tp_new*/
+	0,			/*tp_free*/
+	0,			/*tp_is_gc*/
+};
+
+
+/* ---------- */
+
+
+/* List of functions defined in the module */
+
+static PyMethodDef xx_methods[] = {
+	{"roj",		xx_roj,		METH_VARARGS,
+		PyDoc_STR("roj(a,b) -> None")},
+	{"foo",		xx_foo,		METH_VARARGS,
+	 	xx_foo_doc},
+	{"new",		xx_new,		METH_VARARGS,
+		PyDoc_STR("new() -> new Xx object")},
+	{"bug",		xx_bug,		METH_VARARGS,
+		PyDoc_STR("bug(o) -> None")},
+	{NULL,		NULL}		/* sentinel */
+};
+
+PyDoc_STRVAR(module_doc,
+"This is a template module just for instruction.");
+
+/* Initialization function for the module (*must* be called initxx) */
+
+PyMODINIT_FUNC
+initxx(void)
+{
+	PyObject *m;
+
+	/* Due to cross platform compiler issues the slots must be filled
+	 * here. It's required for portability to Windows without requiring
+	 * C++. */
+	Null_Type.tp_base = &PyBaseObject_Type;
+	Null_Type.tp_new = PyType_GenericNew;
+	Str_Type.tp_base = &PyUnicode_Type;
+
+	/* Finalize the type object including setting type of the new type
+	 * object; doing it here is required for portability, too. */
+	if (PyType_Ready(&Xxo_Type) < 0)
+		return;
+
+	/* Create the module and add the functions */
+	m = Py_InitModule3("xx", xx_methods, module_doc);
+	if (m == NULL)
+		return;
+
+	/* Add some symbolic constants to the module */
+	if (ErrorObject == NULL) {
+		ErrorObject = PyErr_NewException("xx.error", NULL, NULL);
+		if (ErrorObject == NULL)
+			return;
+	}
+	Py_INCREF(ErrorObject);
+	PyModule_AddObject(m, "error", ErrorObject);
+
+	/* Add Str */
+	if (PyType_Ready(&Str_Type) < 0)
+		return;
+	PyModule_AddObject(m, "Str", (PyObject *)&Str_Type);
+
+	/* Add Null */
+	if (PyType_Ready(&Null_Type) < 0)
+		return;
+	PyModule_AddObject(m, "Null", (PyObject *)&Null_Type);
+}
diff --git a/setup.cfg b/setup.cfg
--- a/setup.cfg
+++ b/setup.cfg
@@ -34,10 +34,13 @@
 package_data =
     distutils2._backport = sysconfig.cfg
     distutils2.command = wininst*.exe
+    distutils2.tests = xxmodule.c
 scripts =
     pysetup
 
 # TODO build hashlib for Python < 2.4
+# TODO add all test data files
+# FIXME cfg_to_args should support comments in multi-line fields
 
 [build_ext]
 # needed so that tests work without mucking with sys.path

-- 
Repository URL: http://hg.python.org/distutils2


More information about the Python-checkins mailing list