cpython/Modules/_testinternalcapi.c
Steve Dower 99fcf15052
bpo-45582: Port getpath[p].c to Python (GH-29041)
The getpath.py file is frozen at build time and executed as code over a namespace. It is never imported, nor is it meant to be importable or reusable. However, it should be easier to read, modify, and patch than the previous code.

This commit attempts to preserve every previously tested quirk, but these may be changed in the future to better align platforms.
2021-12-03 00:08:42 +00:00

451 lines
12 KiB
C

/*
* C Extension module to test Python internal C APIs (Include/internal).
*/
#ifndef Py_BUILD_CORE_BUILTIN
# define Py_BUILD_CORE_MODULE 1
#endif
/* Always enable assertions */
#undef NDEBUG
#define PY_SSIZE_T_CLEAN
#include "Python.h"
#include "pycore_atomic_funcs.h" // _Py_atomic_int_get()
#include "pycore_bitutils.h" // _Py_bswap32()
#include "pycore_fileutils.h" // _Py_normpath
#include "pycore_gc.h" // PyGC_Head
#include "pycore_hashtable.h" // _Py_hashtable_new()
#include "pycore_initconfig.h" // _Py_GetConfigsAsDict()
#include "pycore_pathconfig.h" // _PyPathConfig_ClearGlobal()
#include "pycore_interp.h" // _PyInterpreterState_GetConfigCopy()
#include "pycore_pyerrors.h" // _Py_UTF8_Edit_Cost()
#include "pycore_pystate.h" // _PyThreadState_GET()
#include "osdefs.h" // MAXPATHLEN
static PyObject *
get_configs(PyObject *self, PyObject *Py_UNUSED(args))
{
return _Py_GetConfigsAsDict();
}
static PyObject*
get_recursion_depth(PyObject *self, PyObject *Py_UNUSED(args))
{
PyThreadState *tstate = _PyThreadState_GET();
/* subtract one to ignore the frame of the get_recursion_depth() call */
return PyLong_FromLong(tstate->recursion_limit - tstate->recursion_remaining - 1);
}
static PyObject*
test_bswap(PyObject *self, PyObject *Py_UNUSED(args))
{
uint16_t u16 = _Py_bswap16(UINT16_C(0x3412));
if (u16 != UINT16_C(0x1234)) {
PyErr_Format(PyExc_AssertionError,
"_Py_bswap16(0x3412) returns %u", u16);
return NULL;
}
uint32_t u32 = _Py_bswap32(UINT32_C(0x78563412));
if (u32 != UINT32_C(0x12345678)) {
PyErr_Format(PyExc_AssertionError,
"_Py_bswap32(0x78563412) returns %lu", u32);
return NULL;
}
uint64_t u64 = _Py_bswap64(UINT64_C(0xEFCDAB9078563412));
if (u64 != UINT64_C(0x1234567890ABCDEF)) {
PyErr_Format(PyExc_AssertionError,
"_Py_bswap64(0xEFCDAB9078563412) returns %llu", u64);
return NULL;
}
Py_RETURN_NONE;
}
static int
check_popcount(uint32_t x, int expected)
{
// Use volatile to prevent the compiler to optimize out the whole test
volatile uint32_t u = x;
int bits = _Py_popcount32(u);
if (bits != expected) {
PyErr_Format(PyExc_AssertionError,
"_Py_popcount32(%lu) returns %i, expected %i",
(unsigned long)x, bits, expected);
return -1;
}
return 0;
}
static PyObject*
test_popcount(PyObject *self, PyObject *Py_UNUSED(args))
{
#define CHECK(X, RESULT) \
do { \
if (check_popcount(X, RESULT) < 0) { \
return NULL; \
} \
} while (0)
CHECK(0, 0);
CHECK(1, 1);
CHECK(0x08080808, 4);
CHECK(0x10101010, 4);
CHECK(0x10204080, 4);
CHECK(0xDEADCAFE, 22);
CHECK(0xFFFFFFFF, 32);
Py_RETURN_NONE;
#undef CHECK
}
static int
check_bit_length(unsigned long x, int expected)
{
// Use volatile to prevent the compiler to optimize out the whole test
volatile unsigned long u = x;
int len = _Py_bit_length(u);
if (len != expected) {
PyErr_Format(PyExc_AssertionError,
"_Py_bit_length(%lu) returns %i, expected %i",
x, len, expected);
return -1;
}
return 0;
}
static PyObject*
test_bit_length(PyObject *self, PyObject *Py_UNUSED(args))
{
#define CHECK(X, RESULT) \
do { \
if (check_bit_length(X, RESULT) < 0) { \
return NULL; \
} \
} while (0)
CHECK(0, 0);
CHECK(1, 1);
CHECK(0x1000, 13);
CHECK(0x1234, 13);
CHECK(0x54321, 19);
CHECK(0x7FFFFFFF, 31);
CHECK(0xFFFFFFFF, 32);
Py_RETURN_NONE;
#undef CHECK
}
#define TO_PTR(ch) ((void*)(uintptr_t)ch)
#define FROM_PTR(ptr) ((uintptr_t)ptr)
#define VALUE(key) (1 + ((int)(key) - 'a'))
static Py_uhash_t
hash_char(const void *key)
{
char ch = (char)FROM_PTR(key);
return ch;
}
static int
hashtable_cb(_Py_hashtable_t *table,
const void *key_ptr, const void *value_ptr,
void *user_data)
{
int *count = (int *)user_data;
char key = (char)FROM_PTR(key_ptr);
int value = (int)FROM_PTR(value_ptr);
assert(value == VALUE(key));
*count += 1;
return 0;
}
static PyObject*
test_hashtable(PyObject *self, PyObject *Py_UNUSED(args))
{
_Py_hashtable_t *table = _Py_hashtable_new(hash_char,
_Py_hashtable_compare_direct);
if (table == NULL) {
return PyErr_NoMemory();
}
// Using an newly allocated table must not crash
assert(table->nentries == 0);
assert(table->nbuckets > 0);
assert(_Py_hashtable_get(table, TO_PTR('x')) == NULL);
// Test _Py_hashtable_set()
char key;
for (key='a'; key <= 'z'; key++) {
int value = VALUE(key);
if (_Py_hashtable_set(table, TO_PTR(key), TO_PTR(value)) < 0) {
_Py_hashtable_destroy(table);
return PyErr_NoMemory();
}
}
assert(table->nentries == 26);
assert(table->nbuckets > table->nentries);
// Test _Py_hashtable_get_entry()
for (key='a'; key <= 'z'; key++) {
_Py_hashtable_entry_t *entry = _Py_hashtable_get_entry(table, TO_PTR(key));
assert(entry != NULL);
assert(entry->key == TO_PTR(key));
assert(entry->value == TO_PTR(VALUE(key)));
}
// Test _Py_hashtable_get()
for (key='a'; key <= 'z'; key++) {
void *value_ptr = _Py_hashtable_get(table, TO_PTR(key));
assert((int)FROM_PTR(value_ptr) == VALUE(key));
}
// Test _Py_hashtable_steal()
key = 'p';
void *value_ptr = _Py_hashtable_steal(table, TO_PTR(key));
assert((int)FROM_PTR(value_ptr) == VALUE(key));
assert(table->nentries == 25);
assert(_Py_hashtable_get_entry(table, TO_PTR(key)) == NULL);
// Test _Py_hashtable_foreach()
int count = 0;
int res = _Py_hashtable_foreach(table, hashtable_cb, &count);
assert(res == 0);
assert(count == 25);
// Test _Py_hashtable_clear()
_Py_hashtable_clear(table);
assert(table->nentries == 0);
assert(table->nbuckets > 0);
assert(_Py_hashtable_get(table, TO_PTR('x')) == NULL);
_Py_hashtable_destroy(table);
Py_RETURN_NONE;
}
static PyObject *
test_get_config(PyObject *Py_UNUSED(self), PyObject *Py_UNUSED(args))
{
PyConfig config;
PyConfig_InitIsolatedConfig(&config);
if (_PyInterpreterState_GetConfigCopy(&config) < 0) {
PyConfig_Clear(&config);
return NULL;
}
PyObject *dict = _PyConfig_AsDict(&config);
PyConfig_Clear(&config);
return dict;
}
static PyObject *
test_set_config(PyObject *Py_UNUSED(self), PyObject *dict)
{
PyConfig config;
PyConfig_InitIsolatedConfig(&config);
if (_PyConfig_FromDict(&config, dict) < 0) {
goto error;
}
if (_PyInterpreterState_SetConfig(&config) < 0) {
goto error;
}
PyConfig_Clear(&config);
Py_RETURN_NONE;
error:
PyConfig_Clear(&config);
return NULL;
}
static PyObject *
test_reset_path_config(PyObject *Py_UNUSED(self), PyObject *Py_UNUSED(arg))
{
_PyPathConfig_ClearGlobal();
Py_RETURN_NONE;
}
static PyObject*
test_atomic_funcs(PyObject *self, PyObject *Py_UNUSED(args))
{
// Test _Py_atomic_size_get() and _Py_atomic_size_set()
Py_ssize_t var = 1;
_Py_atomic_size_set(&var, 2);
assert(_Py_atomic_size_get(&var) == 2);
Py_RETURN_NONE;
}
static int
check_edit_cost(const char *a, const char *b, Py_ssize_t expected)
{
int ret = -1;
PyObject *a_obj = NULL;
PyObject *b_obj = NULL;
a_obj = PyUnicode_FromString(a);
if (a_obj == NULL) {
goto exit;
}
b_obj = PyUnicode_FromString(b);
if (a_obj == NULL) {
goto exit;
}
Py_ssize_t result = _Py_UTF8_Edit_Cost(a_obj, b_obj, -1);
if (result != expected) {
PyErr_Format(PyExc_AssertionError,
"Edit cost from '%s' to '%s' returns %zd, expected %zd",
a, b, result, expected);
goto exit;
}
// Check that smaller max_edits thresholds are exceeded.
Py_ssize_t max_edits = result;
while (max_edits > 0) {
max_edits /= 2;
Py_ssize_t result2 = _Py_UTF8_Edit_Cost(a_obj, b_obj, max_edits);
if (result2 <= max_edits) {
PyErr_Format(PyExc_AssertionError,
"Edit cost from '%s' to '%s' (threshold %zd) "
"returns %zd, expected greater than %zd",
a, b, max_edits, result2, max_edits);
goto exit;
}
}
// Check that bigger max_edits thresholds don't change anything
Py_ssize_t result3 = _Py_UTF8_Edit_Cost(a_obj, b_obj, result * 2 + 1);
if (result3 != result) {
PyErr_Format(PyExc_AssertionError,
"Edit cost from '%s' to '%s' (threshold %zd) "
"returns %zd, expected %zd",
a, b, result * 2, result3, result);
goto exit;
}
ret = 0;
exit:
Py_XDECREF(a_obj);
Py_XDECREF(b_obj);
return ret;
}
static PyObject *
test_edit_cost(PyObject *self, PyObject *Py_UNUSED(args))
{
#define CHECK(a, b, n) do { \
if (check_edit_cost(a, b, n) < 0) { \
return NULL; \
} \
} while (0) \
CHECK("", "", 0);
CHECK("", "a", 2);
CHECK("a", "A", 1);
CHECK("Apple", "Aple", 2);
CHECK("Banana", "B@n@n@", 6);
CHECK("Cherry", "Cherry!", 2);
CHECK("---0---", "------", 2);
CHECK("abc", "y", 6);
CHECK("aa", "bb", 4);
CHECK("aaaaa", "AAAAA", 5);
CHECK("wxyz", "wXyZ", 2);
CHECK("wxyz", "wXyZ123", 8);
CHECK("Python", "Java", 12);
CHECK("Java", "C#", 8);
CHECK("AbstractFoobarManager", "abstract_foobar_manager", 3+2*2);
CHECK("CPython", "PyPy", 10);
CHECK("CPython", "pypy", 11);
CHECK("AttributeError", "AttributeErrop", 2);
CHECK("AttributeError", "AttributeErrorTests", 10);
#undef CHECK
Py_RETURN_NONE;
}
static PyObject *
normalize_path(PyObject *self, PyObject *filename)
{
Py_ssize_t size = -1;
wchar_t *encoded = PyUnicode_AsWideCharString(filename, &size);
if (encoded == NULL) {
return NULL;
}
PyObject *result = PyUnicode_FromWideChar(_Py_normpath(encoded, size), -1);
PyMem_Free(encoded);
return result;
}
static PyObject *
get_getpath_codeobject(PyObject *self, PyObject *Py_UNUSED(args)) {
return _Py_Get_Getpath_CodeObject();
}
static PyMethodDef TestMethods[] = {
{"get_configs", get_configs, METH_NOARGS},
{"get_recursion_depth", get_recursion_depth, METH_NOARGS},
{"test_bswap", test_bswap, METH_NOARGS},
{"test_popcount", test_popcount, METH_NOARGS},
{"test_bit_length", test_bit_length, METH_NOARGS},
{"test_hashtable", test_hashtable, METH_NOARGS},
{"get_config", test_get_config, METH_NOARGS},
{"set_config", test_set_config, METH_O},
{"reset_path_config", test_reset_path_config, METH_NOARGS},
{"test_atomic_funcs", test_atomic_funcs, METH_NOARGS},
{"test_edit_cost", test_edit_cost, METH_NOARGS},
{"normalize_path", normalize_path, METH_O, NULL},
{"get_getpath_codeobject", get_getpath_codeobject, METH_NOARGS, NULL},
{NULL, NULL} /* sentinel */
};
static struct PyModuleDef _testcapimodule = {
PyModuleDef_HEAD_INIT,
"_testinternalcapi",
NULL,
-1,
TestMethods,
NULL,
NULL,
NULL,
NULL
};
PyMODINIT_FUNC
PyInit__testinternalcapi(void)
{
PyObject *module = PyModule_Create(&_testcapimodule);
if (module == NULL) {
return NULL;
}
if (PyModule_AddObject(module, "SIZEOF_PYGC_HEAD",
PyLong_FromSsize_t(sizeof(PyGC_Head))) < 0) {
goto error;
}
return module;
error:
Py_DECREF(module);
return NULL;
}