add a replacement API for PyCObject, PyCapsule #5630

All stdlib modules with C-APIs now use this.

Patch by Larry Hastings
diff --git a/Modules/_testcapimodule.c b/Modules/_testcapimodule.c
index 3f7190c..f3899f5 100644
--- a/Modules/_testcapimodule.c
+++ b/Modules/_testcapimodule.c
@@ -1102,6 +1102,155 @@
 }
 
 
+/* Coverage testing of capsule objects. */
+
+static const char *capsule_name = "capsule name";
+static       char *capsule_pointer = "capsule pointer";
+static       char *capsule_context = "capsule context";
+static const char *capsule_error = NULL;
+static int
+capsule_destructor_call_count = 0;
+
+static void
+capsule_destructor(PyObject *o) {
+	capsule_destructor_call_count++;
+	if (PyCapsule_GetContext(o) != capsule_context) {
+		capsule_error = "context did not match in destructor!";
+	} else if (PyCapsule_GetDestructor(o) != capsule_destructor) {
+		capsule_error = "destructor did not match in destructor!  (woah!)";
+	} else if (PyCapsule_GetName(o) != capsule_name) {
+		capsule_error = "name did not match in destructor!";
+	} else if (PyCapsule_GetPointer(o, capsule_name) != capsule_pointer) {
+		capsule_error = "pointer did not match in destructor!";
+	}
+}
+
+typedef struct {
+	char *name;
+	char *module;
+	char *attribute;
+} known_capsule;
+
+static PyObject *
+test_capsule(PyObject *self, PyObject *args)
+{
+	PyObject *object;
+	const char *error = NULL;
+	void *pointer;
+	void *pointer2;
+	known_capsule known_capsules[] = {
+		#define KNOWN_CAPSULE(module, name)	{ module "." name, module, name }
+		KNOWN_CAPSULE("_socket", "CAPI"),
+		KNOWN_CAPSULE("_curses", "_C_API"),
+		KNOWN_CAPSULE("datetime", "datetime_CAPI"),
+		{ NULL, NULL },
+	};
+	known_capsule *known = &known_capsules[0];
+
+#define FAIL(x) { error = (x); goto exit; }
+
+#define CHECK_DESTRUCTOR \
+	if (capsule_error) { \
+		FAIL(capsule_error); \
+	} \
+	else if (!capsule_destructor_call_count) {	\
+		FAIL("destructor not called!"); \
+	} \
+	capsule_destructor_call_count = 0; \
+
+	object = PyCapsule_New(capsule_pointer, capsule_name, capsule_destructor);
+	PyCapsule_SetContext(object, capsule_context);
+	capsule_destructor(object);
+	CHECK_DESTRUCTOR;
+	Py_DECREF(object);
+	CHECK_DESTRUCTOR;
+
+	object = PyCapsule_New(known, "ignored", NULL);
+	PyCapsule_SetPointer(object, capsule_pointer);
+	PyCapsule_SetName(object, capsule_name);
+	PyCapsule_SetDestructor(object, capsule_destructor);
+	PyCapsule_SetContext(object, capsule_context);
+	capsule_destructor(object);
+	CHECK_DESTRUCTOR;
+	/* intentionally access using the wrong name */
+	pointer2 = PyCapsule_GetPointer(object, "the wrong name");
+	if (!PyErr_Occurred()) {
+		FAIL("PyCapsule_GetPointer should have failed but did not!");
+	}
+	PyErr_Clear();
+	if (pointer2) {
+		if (pointer2 == capsule_pointer) {
+			FAIL("PyCapsule_GetPointer should not have"
+				 " returned the internal pointer!");
+		} else {
+			FAIL("PyCapsule_GetPointer should have "
+				 "returned NULL pointer but did not!");
+		}
+	}
+	PyCapsule_SetDestructor(object, NULL);
+	Py_DECREF(object);
+	if (capsule_destructor_call_count) {
+		FAIL("destructor called when it should not have been!");
+	}
+
+	for (known = &known_capsules[0]; known->module != NULL; known++) {
+		/* yeah, ordinarily I wouldn't do this either,
+		   but it's fine for this test harness.
+		*/
+		static char buffer[256];
+#undef FAIL
+#define FAIL(x) \
+		{ \
+		sprintf(buffer, "%s module: \"%s\" attribute: \"%s\"", \
+			x, known->module, known->attribute); \
+		error = buffer; \
+		goto exit; \
+		} \
+
+		PyObject *module = PyImport_ImportModule(known->module);
+		if (module) {
+			pointer = PyCapsule_Import(known->name, 0);
+			if (!pointer) {
+				Py_DECREF(module);
+				FAIL("PyCapsule_GetPointer returned NULL unexpectedly!");
+			}
+			object = PyObject_GetAttrString(module, known->attribute);
+			if (!object) {
+				Py_DECREF(module);
+				return NULL;
+			}
+			pointer2 = PyCapsule_GetPointer(object,
+						"weebles wobble but they don't fall down");
+			if (!PyErr_Occurred()) {
+				Py_DECREF(object);
+				Py_DECREF(module);
+				FAIL("PyCapsule_GetPointer should have failed but did not!");
+			}
+			PyErr_Clear();
+			if (pointer2) {
+				Py_DECREF(module);
+				Py_DECREF(object);
+				if (pointer2 == pointer) {
+					FAIL("PyCapsule_GetPointer should not have" 
+						 " returned its internal pointer!");
+				} else {
+					FAIL("PyCapsule_GetPointer should have" 
+						 " returned NULL pointer but did not!");
+				}
+			}
+			Py_DECREF(object);
+			Py_DECREF(module);
+		}
+	}
+
+  exit:
+	if (error) {
+		return raiseTestError("test_capsule", error);
+	}
+	Py_RETURN_NONE;
+#undef FAIL
+}
+
 #ifdef HAVE_GETTIMEOFDAY
 /* Profiling of integer performance */
 static void print_delta(int test, struct timeval *s, struct timeval *e)
@@ -1280,9 +1429,10 @@
 	{"test_empty_argparse", (PyCFunction)test_empty_argparse,METH_NOARGS},
 	{"test_null_strings",	(PyCFunction)test_null_strings,	 METH_NOARGS},
 	{"test_string_from_format", (PyCFunction)test_string_from_format, METH_NOARGS},
-	{"test_string_to_double", (PyCFunction)test_string_to_double, METH_NOARGS},
 	{"test_with_docstring", (PyCFunction)test_with_docstring, METH_NOARGS,
 	 PyDoc_STR("This is a pretty normal docstring.")},
+	{"test_string_to_double", (PyCFunction)test_string_to_double, METH_NOARGS},
+	{"test_capsule", (PyCFunction)test_capsule, METH_NOARGS},
 
 	{"getargs_tuple",	getargs_tuple,			 METH_VARARGS},
 	{"getargs_keywords", (PyCFunction)getargs_keywords,