From 286dbed5f596f90700245d75eea0479284d3c3d8 Mon Sep 17 00:00:00 2001 From: Roger Binns Date: Wed, 15 Feb 2023 14:15:32 -0800 Subject: [PATCH] More work in progress faultinject.h will need to be under source control because it isn't possible to define a cpp macro that does #include --- .gitignore | 1 - MANIFEST.in | 1 + Makefile | 2 +- fi.py | 128 ++++++++++++++--- src/apsw.c | 21 +-- src/connection.c | 2 + src/faultinject.h | 309 ++++++++++++++++++++++++++++++++++++++++ src/util.c | 3 + tools/genfaultinject.py | 127 +++++++++++++++-- 9 files changed, 552 insertions(+), 42 deletions(-) create mode 100644 src/faultinject.h diff --git a/.gitignore b/.gitignore index f469b41b..8c4b9661 100644 --- a/.gitignore +++ b/.gitignore @@ -2,7 +2,6 @@ testdb2 apsw/*.pyd TAGS -src/faultinject.h sqlite3async.h sqlite3async.c callgrind.out.* diff --git a/MANIFEST.in b/MANIFEST.in index 48fb96ae..4de38f2a 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,6 +1,7 @@ # The C source include src/*.c +include src/faultinject.h include src/apswtypes.py include src/apswversion.h include src/apsw.docstrings diff --git a/Makefile b/Makefile index 1d7ee256..7e6505af 100644 --- a/Makefile +++ b/Makefile @@ -73,7 +73,7 @@ build_ext: src/apswversion.h ## Fetches SQLite and builds the extension src/faultinject.h: tools/genfaultinject.py -rm src/faultinject.h - tools/genfaultinject.py src/faultinject + tools/genfaultinject.py src/faultinject.h build_ext_debug: src/apswversion.h src/faultinject.h ## Fetches SQLite and builds the extension in debug mode env $(PYTHON) setup.py fetch --version=$(SQLITEVERSION) --all build_ext --inplace --force --enable-all-extensions --debug diff --git a/fi.py b/fi.py index 93d79e7f..bfee4df7 100644 --- a/fi.py +++ b/fi.py @@ -23,6 +23,38 @@ class ReturnCode(enum.IntEnum): "clear exception, keep going, call with result" +# should be same as in genfaultinject.py +returns = { + "pyobject": "PySet_New convert_value_to_pyobject getfunctionargs PyModule_Create2 PyErr_NewExceptionWithDoc".split(), + "int_no_gil": "sqlite3_threadsafe".split(), + "int": "PyType_Ready PyModule_AddObject PyModule_AddIntConstant".split(), +} + +expect_exception = set() + +FAULT = ZeroDivisionError, "Fault injection synthesized failure" + + +def FaultCall(key): + try: + if key[0] in returns["pyobject"] or key[0] == "PyModule_Create2": + expect_exception.add(MemoryError) + raise MemoryError() + if key[0] == "sqlite3_threadsafe": + expect_exception.add(EnvironmentError) + return 0 + if key[0] in returns["int"]: + # for ones returning -1 on error + expect_exception.add(FAULT[0]) + return (-1, *FAULT) + finally: + to_fault.discard(key) + has_faulted.add(key) + + print("Unhandled", key) + breakpoint() + + def called(is_call, fault_function, callid, call_location, exc_info, retval): if False: d = { @@ -46,21 +78,14 @@ def called(is_call, fault_function, callid, call_location, exc_info, retval): key = (fault_function, call_location) if is_call: - if key in has_faulted: + if expect_exception: + # already have faulted this round + if key not in has_faulted: + to_fault.add(key) return ReturnCode.Proceed else: - if fault_function in ("PySet_New", ): - has_faulted.add(key) - raise MemoryError() - return ReturnCode.ProceedAndCallBack - if fault_function in ("PySet_New", ): - breakpoint() - fault = retval is None or all(e is not None for e in exc_info) - if fault: - has_faulted.add(key) - to_fault.remove(key) - else: - assert False, f"unknown { fault_function }" + return FaultCall(key) + breakpoint() return None @@ -93,6 +118,15 @@ def BestIndexObject(self, iio): def Open(self): return Source.Cursor() + def UpdateDeleteRow(self, rowid): + pass + + def UpdateInsertRow(self, rowid, fields): + return 77 + + def UpdateChangeRow(self, rowid, newrowid, fields): + pass + class Cursor: def Filter(self, *args): @@ -102,34 +136,92 @@ def Eof(self): return self.pos >= 7 def Column(self, n): - return self.pos + return [None, ' ' * n, b"aa" * n, 3.14 * n][n] def Next(self): self.pos += 1 + def Rowid(self): + return self.pos + + def Close(self): + pass + con.createmodule("vtable", Source(), use_bestindex_object=True, iVersion=3, eponymous=True) con.execute("select * from vtable where c2>2 and c1 in (1,2,3)") + con.execute("create virtual table fred using vtable()") + con.execute("delete from fred where c3>5") + n = 2 + con.execute("insert into fred values(?,?,?,?)", [None, ' ' * n, b"aa" * n, 3.14 * n]) + con.execute("insert into fred(ROWID, c1) values (99, NULL)") + con.execute("update fred set c2=c3 where rowid=3; update fred set rowid=990 where c2=2") + + def func(*args): + return 3.14 + + con.createscalarfunction("func", func) + con.execute("select func(1,null,'abc',x'aabb')") + + class SumInt: + + def __init__(self): + self.v = 0 + + def step(self, arg): + self.v += arg + + def inverse(self, arg): + self.v -= arg + + def final(self): + return self.v + + def value(self): + return self.v + + con.create_window_function("sumint", SumInt) + + for row in con.execute(""" + CREATE TABLE t3(x, y); + INSERT INTO t3 VALUES('a', 4), + ('b', 5), + ('c', 3), + ('d', 8), + ('e', 1); + -- Use the window function + SELECT x, sumint(y) OVER ( + ORDER BY x ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING + ) AS sum_y + FROM t3 ORDER BY x; + """): + pass # we reached the end return True last = None -complete = False -while not complete: +while True: print("remaining", len(to_fault), "done", len(has_faulted)) + expect_exception = set() try: - complete = exercise() - except Exception: + exercise() + break + except Exception as e: complete = False + assert sys.exc_info( + )[0] in expect_exception, f"Expected { type(e) }/{ sys.exc_info()[1] } to be in { expect_exception }" + now = set(to_fault), set(has_faulted) if now == last and len(to_fault): print("Unable to make progress") exercise() + break else: last = now +assert not to_fault, "Remaining { to_fault }" print("Complete") for n in sorted(has_faulted): diff --git a/src/apsw.c b/src/apsw.c index a3c76cab..8c4385e9 100644 --- a/src/apsw.c +++ b/src/apsw.c @@ -92,9 +92,10 @@ API Reference #include #include "structmember.h" -#ifdef APSW_TESTFIXTURES #include "faultinject.h" +#ifdef APSW_TESTFIXTURES + /* Fault injection */ #define APSW_FAULT_INJECT(faultName, good, bad) \ do \ @@ -128,7 +129,6 @@ static int APSW_Should_Fault(const char *); { \ good; \ } while (0) - #endif /* The module object */ @@ -1623,7 +1623,7 @@ PyInit_apsw(void) if (PyType_Ready(&ConnectionType) < 0 || PyType_Ready(&APSWCursorType) < 0 || PyType_Ready(&ZeroBlobBindType) < 0 || PyType_Ready(&APSWBlobType) < 0 || PyType_Ready(&APSWVFSType) < 0 || PyType_Ready(&APSWVFSFileType) < 0 || PyType_Ready(&APSWURIFilenameType) < 0 || PyType_Ready(&FunctionCBInfoType) < 0 || PyType_Ready(&APSWBackupType) < 0 || PyType_Ready(&SqliteIndexInfoType) < 0 || PyType_Ready(&apsw_no_change_object) < 0) goto fail; - m = apswmodule = PyModule_Create(&apswmoduledef); + m = apswmodule = PyModule_Create2(&apswmoduledef, PYTHON_API_VERSION); if (m == NULL) goto fail; @@ -2281,7 +2281,7 @@ modules etc. For example:: PyModule_AddObject(m, "compile_options", get_compile_options()); PyModule_AddObject(m, "keywords", get_keywords()); - if(!PyErr_Occurred()) + if (!PyErr_Occurred()) { PyObject *mod = PyImport_ImportModule("collections.abc"); if (mod) @@ -2313,7 +2313,8 @@ PyInit___init__(void) #endif #ifdef APSW_TESTFIXTURES - +#define APSW_FAULT_CLEAR +#include "faultinject.h" static FaultInjectControlVerb APSW_FaultInjectControl(int is_call, const char *faultfunction, const char *filename, const char *funcname, int linenum, const char *args, PyObject **obj) { @@ -2420,7 +2421,11 @@ APSW_FaultInjectControl(int is_call, const char *faultfunction, const char *file } else { - assert(!PyErr_Occurred()); + if (PyErr_Occurred()) + { + PyErr_PrintEx(0); + assert(0); /* PyErr_Print clears the exception */ + } assert(Py_IsNone(res)); Py_CLEAR(res); /* return ignored */ @@ -2434,7 +2439,7 @@ APSW_Should_Fault(const char *name) PyGILState_STATE gilstate; PyObject *res, *callable; PyObject *errsave1 = NULL, *errsave2 = NULL, *errsave3 = NULL; - int callres=0; + int callres = 0; gilstate = PyGILState_Ensure(); @@ -2464,7 +2469,7 @@ APSW_Should_Fault(const char *name) Py_DECREF(res); PyErr_Restore(errsave1, errsave2, errsave3); - end: +end: PyGILState_Release(gilstate); return callres; } diff --git a/src/connection.c b/src/connection.c index ef0f7a55..451d0ce3 100644 --- a/src/connection.c +++ b/src/connection.c @@ -2409,9 +2409,11 @@ set_context_result(sqlite3_context *context, PyObject *obj) } /* Returns a new reference to a tuple formed from function parameters */ +#undef getfunctionargs static PyObject * getfunctionargs(sqlite3_context *context, PyObject *firstelement, int argc, sqlite3_value **argv) { +#include "faultinject.h" PyObject *pyargs = NULL; int i; int extra = firstelement ? 1 : 0; diff --git a/src/faultinject.h b/src/faultinject.h new file mode 100644 index 00000000..b626cd74 --- /dev/null +++ b/src/faultinject.h @@ -0,0 +1,309 @@ +/* DO NOT EDIT THIS FILE + This file is generated by tools/genfaultinject.py + Edit that not this */ +#ifdef APSW_TESTFIXTURES + +#ifndef APSW_FAULT_INJECT_INCLUDED + +typedef enum +{ + FICProceed = 7, + FICProceed_And_Call_With_Result = 8, + FICReturnThis = 9, +} FaultInjectControlVerb; + +static FaultInjectControlVerb +APSW_FaultInjectControl(int is_call, const char *faultfunction, const char *filename, const char *funcname, int linenum, const char *args, PyObject **obj); + +#define APSW_FAULT_INJECT_INCLUDED +#endif + +#ifdef APSW_FAULT_CLEAR + +#undef PyErr_NewExceptionWithDoc +#undef PyModule_AddIntConstant +#undef PyModule_AddObject +#undef PyModule_Create2 +#undef PySet_New +#undef PyType_Ready +#undef convert_value_to_pyobject +#undef getfunctionargs +#undef sqlite3_threadsafe + +#else + +#define PyErr_NewExceptionWithDoc(...) \ +({ \ + PyObject *_res = 0; \ + PyGILState_STATE gilstate = PyGILState_Ensure(); \ + switch (APSW_FaultInjectControl(1, "PyErr_NewExceptionWithDoc", __FILE__, __func__, __LINE__, #__VA_ARGS__, &_res)) \ + { \ + case FICProceed: \ + assert(_res == 0); \ + _res = PyErr_NewExceptionWithDoc(__VA_ARGS__); \ + break; \ + case FICProceed_And_Call_With_Result: \ + assert(_res == 0); \ + _res = PyErr_NewExceptionWithDoc(__VA_ARGS__); \ + APSW_FaultInjectControl(0, "PyErr_NewExceptionWithDoc", __FILE__, __func__, __LINE__, #__VA_ARGS__, &_res); \ + /* fallthrough */ \ + case FICReturnThis: \ + default: \ + assert(_res || PyErr_Occurred()); \ + assert(!(_res && PyErr_Occurred())); \ + break; \ + } \ + PyGILState_Release(gilstate); \ + _res; \ +}) +#define PyModule_AddIntConstant(...) \ +({ \ + PyObject *_res2=0; \ + int _res = 0; \ + PyGILState_STATE gilstate = PyGILState_Ensure(); \ + switch (APSW_FaultInjectControl(1, "PyModule_AddIntConstant", __FILE__, __func__, __LINE__, #__VA_ARGS__, &_res2)) \ + { \ + case FICProceed: \ + assert(_res == 0); \ + _res = PyModule_AddIntConstant(__VA_ARGS__); \ + break; \ + case FICProceed_And_Call_With_Result: \ + assert(_res2 == 0); \ + _res = PyModule_AddIntConstant(__VA_ARGS__); \ + _res2 = PyLong_FromLong(_res); \ + APSW_FaultInjectControl(0, "PyModule_AddIntConstant", __FILE__, __func__, __LINE__, #__VA_ARGS__, &_res2); \ + /* fallthrough */ \ + case FICReturnThis: \ + default: \ + if(PyTuple_Check(_res2)) \ + { \ + assert(3 == PyTuple_GET_SIZE(_res2)); \ + _res = PyLong_AsLong(PyTuple_GET_ITEM(_res2, 0)); \ + assert(PyUnicode_Check(PyTuple_GET_ITEM(_res2, 2))); \ + PyErr_Format(PyTuple_GET_ITEM(_res2, 1), "%s", PyUnicode_AsUTF8(PyTuple_GET_ITEM(_res2, 2))); \ + } \ + else \ + { \ + assert(PyLong_Check(_res2)); \ + _res = PyLong_AsLong(_res2); \ + } \ + break; \ + } \ + Py_XDECREF(_res2); \ + PyGILState_Release(gilstate); \ + _res; \ +}) +#define PyModule_AddObject(...) \ +({ \ + PyObject *_res2=0; \ + int _res = 0; \ + PyGILState_STATE gilstate = PyGILState_Ensure(); \ + switch (APSW_FaultInjectControl(1, "PyModule_AddObject", __FILE__, __func__, __LINE__, #__VA_ARGS__, &_res2)) \ + { \ + case FICProceed: \ + assert(_res == 0); \ + _res = PyModule_AddObject(__VA_ARGS__); \ + break; \ + case FICProceed_And_Call_With_Result: \ + assert(_res2 == 0); \ + _res = PyModule_AddObject(__VA_ARGS__); \ + _res2 = PyLong_FromLong(_res); \ + APSW_FaultInjectControl(0, "PyModule_AddObject", __FILE__, __func__, __LINE__, #__VA_ARGS__, &_res2); \ + /* fallthrough */ \ + case FICReturnThis: \ + default: \ + if(PyTuple_Check(_res2)) \ + { \ + assert(3 == PyTuple_GET_SIZE(_res2)); \ + _res = PyLong_AsLong(PyTuple_GET_ITEM(_res2, 0)); \ + assert(PyUnicode_Check(PyTuple_GET_ITEM(_res2, 2))); \ + PyErr_Format(PyTuple_GET_ITEM(_res2, 1), "%s", PyUnicode_AsUTF8(PyTuple_GET_ITEM(_res2, 2))); \ + } \ + else \ + { \ + assert(PyLong_Check(_res2)); \ + _res = PyLong_AsLong(_res2); \ + } \ + break; \ + } \ + Py_XDECREF(_res2); \ + PyGILState_Release(gilstate); \ + _res; \ +}) +#define PyModule_Create2(...) \ +({ \ + PyObject *_res = 0; \ + PyGILState_STATE gilstate = PyGILState_Ensure(); \ + switch (APSW_FaultInjectControl(1, "PyModule_Create2", __FILE__, __func__, __LINE__, #__VA_ARGS__, &_res)) \ + { \ + case FICProceed: \ + assert(_res == 0); \ + _res = PyModule_Create2(__VA_ARGS__); \ + break; \ + case FICProceed_And_Call_With_Result: \ + assert(_res == 0); \ + _res = PyModule_Create2(__VA_ARGS__); \ + APSW_FaultInjectControl(0, "PyModule_Create2", __FILE__, __func__, __LINE__, #__VA_ARGS__, &_res); \ + /* fallthrough */ \ + case FICReturnThis: \ + default: \ + assert(_res || PyErr_Occurred()); \ + assert(!(_res && PyErr_Occurred())); \ + break; \ + } \ + PyGILState_Release(gilstate); \ + _res; \ +}) +#define PySet_New(...) \ +({ \ + PyObject *_res = 0; \ + PyGILState_STATE gilstate = PyGILState_Ensure(); \ + switch (APSW_FaultInjectControl(1, "PySet_New", __FILE__, __func__, __LINE__, #__VA_ARGS__, &_res)) \ + { \ + case FICProceed: \ + assert(_res == 0); \ + _res = PySet_New(__VA_ARGS__); \ + break; \ + case FICProceed_And_Call_With_Result: \ + assert(_res == 0); \ + _res = PySet_New(__VA_ARGS__); \ + APSW_FaultInjectControl(0, "PySet_New", __FILE__, __func__, __LINE__, #__VA_ARGS__, &_res); \ + /* fallthrough */ \ + case FICReturnThis: \ + default: \ + assert(_res || PyErr_Occurred()); \ + assert(!(_res && PyErr_Occurred())); \ + break; \ + } \ + PyGILState_Release(gilstate); \ + _res; \ +}) +#define PyType_Ready(...) \ +({ \ + PyObject *_res2=0; \ + int _res = 0; \ + PyGILState_STATE gilstate = PyGILState_Ensure(); \ + switch (APSW_FaultInjectControl(1, "PyType_Ready", __FILE__, __func__, __LINE__, #__VA_ARGS__, &_res2)) \ + { \ + case FICProceed: \ + assert(_res == 0); \ + _res = PyType_Ready(__VA_ARGS__); \ + break; \ + case FICProceed_And_Call_With_Result: \ + assert(_res2 == 0); \ + _res = PyType_Ready(__VA_ARGS__); \ + _res2 = PyLong_FromLong(_res); \ + APSW_FaultInjectControl(0, "PyType_Ready", __FILE__, __func__, __LINE__, #__VA_ARGS__, &_res2); \ + /* fallthrough */ \ + case FICReturnThis: \ + default: \ + if(PyTuple_Check(_res2)) \ + { \ + assert(3 == PyTuple_GET_SIZE(_res2)); \ + _res = PyLong_AsLong(PyTuple_GET_ITEM(_res2, 0)); \ + assert(PyUnicode_Check(PyTuple_GET_ITEM(_res2, 2))); \ + PyErr_Format(PyTuple_GET_ITEM(_res2, 1), "%s", PyUnicode_AsUTF8(PyTuple_GET_ITEM(_res2, 2))); \ + } \ + else \ + { \ + assert(PyLong_Check(_res2)); \ + _res = PyLong_AsLong(_res2); \ + } \ + break; \ + } \ + Py_XDECREF(_res2); \ + PyGILState_Release(gilstate); \ + _res; \ +}) +#define convert_value_to_pyobject(...) \ +({ \ + PyObject *_res = 0; \ + PyGILState_STATE gilstate = PyGILState_Ensure(); \ + switch (APSW_FaultInjectControl(1, "convert_value_to_pyobject", __FILE__, __func__, __LINE__, #__VA_ARGS__, &_res)) \ + { \ + case FICProceed: \ + assert(_res == 0); \ + _res = convert_value_to_pyobject(__VA_ARGS__); \ + break; \ + case FICProceed_And_Call_With_Result: \ + assert(_res == 0); \ + _res = convert_value_to_pyobject(__VA_ARGS__); \ + APSW_FaultInjectControl(0, "convert_value_to_pyobject", __FILE__, __func__, __LINE__, #__VA_ARGS__, &_res); \ + /* fallthrough */ \ + case FICReturnThis: \ + default: \ + assert(_res || PyErr_Occurred()); \ + assert(!(_res && PyErr_Occurred())); \ + break; \ + } \ + PyGILState_Release(gilstate); \ + _res; \ +}) +#define getfunctionargs(...) \ +({ \ + PyObject *_res = 0; \ + PyGILState_STATE gilstate = PyGILState_Ensure(); \ + switch (APSW_FaultInjectControl(1, "getfunctionargs", __FILE__, __func__, __LINE__, #__VA_ARGS__, &_res)) \ + { \ + case FICProceed: \ + assert(_res == 0); \ + _res = getfunctionargs(__VA_ARGS__); \ + break; \ + case FICProceed_And_Call_With_Result: \ + assert(_res == 0); \ + _res = getfunctionargs(__VA_ARGS__); \ + APSW_FaultInjectControl(0, "getfunctionargs", __FILE__, __func__, __LINE__, #__VA_ARGS__, &_res); \ + /* fallthrough */ \ + case FICReturnThis: \ + default: \ + assert(_res || PyErr_Occurred()); \ + assert(!(_res && PyErr_Occurred())); \ + break; \ + } \ + PyGILState_Release(gilstate); \ + _res; \ +}) +#define sqlite3_threadsafe(...) \ +({ \ + PyObject *_res2 = 0; \ + int _res = 0; \ + PyGILState_STATE gilstate = PyGILState_Ensure(); \ + switch (APSW_FaultInjectControl(1, "sqlite3_threadsafe", __FILE__, __func__, __LINE__, #__VA_ARGS__, &_res2)) \ + { \ + case FICProceed: \ + assert(_res2 == 0); \ + PyGILState_Release(gilstate); \ + _res = sqlite3_threadsafe(__VA_ARGS__); \ + gilstate = PyGILState_Ensure(); \ + break; \ + case FICProceed_And_Call_With_Result: \ + assert(_res2 == 0); \ + PyGILState_Release(gilstate); \ + _res = sqlite3_threadsafe(__VA_ARGS__); \ + gilstate = PyGILState_Ensure(); \ + _res2 = PyLong_FromLong(_res); \ + APSW_FaultInjectControl(0, "sqlite3_threadsafe", __FILE__, __func__, __LINE__, #__VA_ARGS__, &_res2); \ + /* fallthrough */ \ + case FICReturnThis: \ + default: \ + assert(_res2); \ + if(PyTuple_Check(_res2)) \ + { \ + assert(3 == PyTuple_GET_SIZE(_res2)); \ + _res = PyLong_AsLong(PyTuple_GET_ITEM(_res2, 0)); \ + assert(PyUnicode_Check(PyTuple_GET_ITEM(_res2, 2))); \ + PyErr_Format(PyTuple_GET_ITEM(_res2, 1), "%s", PyUnicode_AsUTF8(PyTuple_GET_ITEM(_res2, 2))); \ + } \ + else \ + { \ + assert(PyLong_Check(_res2)); \ + _res = PyLong_AsLong(_res2); \ + } \ + break; \ + } \ + Py_XDECREF(_res2); \ + PyGILState_Release(gilstate); \ + _res; \ +}) +#endif +#endif \ No newline at end of file diff --git a/src/util.c b/src/util.c index e450ef9d..ea30c896 100644 --- a/src/util.c +++ b/src/util.c @@ -202,10 +202,13 @@ apsw_write_unraisable(PyObject *hookobject) recursion_level--; } +#undef convert_value_to_pyobject /* Converts sqlite3_value to PyObject. Returns a new reference. */ static PyObject * convert_value_to_pyobject(sqlite3_value *value, int in_constraint_possible, int no_change_possible) { +#include "faultinject.h" + int coltype = sqlite3_value_type(value); sqlite3_value *in_value; diff --git a/tools/genfaultinject.py b/tools/genfaultinject.py index a0d367b6..7373c6e9 100755 --- a/tools/genfaultinject.py +++ b/tools/genfaultinject.py @@ -28,6 +28,7 @@ APSW_FaultInjectControl(0, "PySet_New", __FILE__, __func__, __LINE__, #__VA_ARGS__, &_res); /* fallthrough */ case FICReturnThis: + default: assert(_res || PyErr_Occurred()); assert(!(_res && PyErr_Occurred())); break; @@ -37,47 +38,145 @@ }) """ +# for int returns, the fault injection can either return a number +# (PyLong) or a tuple(PyLong, AType, str) in which case +# PyErr_Format(AType, "%s", str) sets Python exception indicator and +# the PyLong is returned + + +# note this releases the gil around calls. +int_return_no_gil = """ +({ + PyObject *_res2 = 0; + int _res = 0; + PyGILState_STATE gilstate = PyGILState_Ensure(); + switch (APSW_FaultInjectControl(1, "sqlite3_threadsafe", __FILE__, __func__, __LINE__, #__VA_ARGS__, &_res2)) + { + case FICProceed: + assert(_res2 == 0); + PyGILState_Release(gilstate); + _res = sqlite3_threadsafe(__VA_ARGS__); + gilstate = PyGILState_Ensure(); + break; + case FICProceed_And_Call_With_Result: + assert(_res2 == 0); + PyGILState_Release(gilstate); + _res = sqlite3_threadsafe(__VA_ARGS__); + gilstate = PyGILState_Ensure(); + _res2 = PyLong_FromLong(_res); + APSW_FaultInjectControl(0, "sqlite3_threadsafe", __FILE__, __func__, __LINE__, #__VA_ARGS__, &_res2); + /* fallthrough */ + case FICReturnThis: + default: + assert(_res2); + if(PyTuple_Check(_res2)) + { + assert(3 == PyTuple_GET_SIZE(_res2)); + _res = PyLong_AsLong(PyTuple_GET_ITEM(_res2, 0)); + assert(PyUnicode_Check(PyTuple_GET_ITEM(_res2, 2))); + PyErr_Format(PyTuple_GET_ITEM(_res2, 1), "%s", PyUnicode_AsUTF8(PyTuple_GET_ITEM(_res2, 2))); + } + else + { + assert(PyLong_Check(_res2)); + _res = PyLong_AsLong(_res2); + } + break; + } + Py_XDECREF(_res2); + PyGILState_Release(gilstate); + _res; +}) +""" + +int_return = """ +({ + PyObject *_res2=0; + int _res = 0; + PyGILState_STATE gilstate = PyGILState_Ensure(); + switch (APSW_FaultInjectControl(1, "PyType_Ready", __FILE__, __func__, __LINE__, #__VA_ARGS__, &_res2)) + { + case FICProceed: + assert(_res == 0); + _res = PyType_Ready(__VA_ARGS__); + break; + case FICProceed_And_Call_With_Result: + assert(_res2 == 0); + _res = PyType_Ready(__VA_ARGS__); + _res2 = PyLong_FromLong(_res); + APSW_FaultInjectControl(0, "PyType_Ready", __FILE__, __func__, __LINE__, #__VA_ARGS__, &_res2); + /* fallthrough */ + case FICReturnThis: + default: + if(PyTuple_Check(_res2)) + { + assert(3 == PyTuple_GET_SIZE(_res2)); + _res = PyLong_AsLong(PyTuple_GET_ITEM(_res2, 0)); + assert(PyUnicode_Check(PyTuple_GET_ITEM(_res2, 2))); + PyErr_Format(PyTuple_GET_ITEM(_res2, 1), "%s", PyUnicode_AsUTF8(PyTuple_GET_ITEM(_res2, 2))); + } + else + { + assert(PyLong_Check(_res2)); + _res = PyLong_AsLong(_res2); + } + break; + } + Py_XDECREF(_res2); + PyGILState_Release(gilstate); + _res; +}) +""" + def get_definition(s): if s in returns["pyobject"]: t = pyobject_return.replace("PySet_New", s) + elif s in returns["int_no_gil"]: + t = int_return_no_gil.replace("sqlite3_threadsafe", s) + elif s in returns["int"]: + t = int_return.replace("PyType_Ready", s) else: print("unknown template " + s) breakpoint() 1 / 0 t = t.strip().split("\n") maxlen = max(len(l) for l in t) - for i in range(len(t)): + for i in range(len(t) - 1): t[i] += " " * (maxlen - len(t[i])) + " \\\n" return "".join(t) def genfile(symbols): res = [] - res.append(f""" + res.append(f"""\ +/* DO NOT EDIT THIS FILE + This file is generated by tools/genfaultinject.py + Edit that not this */ +#ifdef APSW_TESTFIXTURES + #ifndef APSW_FAULT_INJECT_INCLUDED { proto } #define APSW_FAULT_INJECT_INCLUDED #endif -#ifdef APSW_FAULT_INJECT_OFF +#ifdef APSW_FAULT_CLEAR """) - for s in symbols: + for s in sorted(symbols): res.append(f"#undef { s }") - - res.append(""" -#undef APSW_FAULT_INJECT_OFF -#else -""") - for s in symbols: + res.append("\n#else\n") + for s in sorted(symbols): res.append(f"#define {s}(...) \\\n{ get_definition(s) }") - - res.append("#endif\n") - + res.append("#endif") + res.append("#endif") return "\n".join(res) -returns = {"pyobject": "PySet_New convert_value_to_pyobject".split()} +returns = { + "pyobject": "PySet_New convert_value_to_pyobject getfunctionargs PyModule_Create2 PyErr_NewExceptionWithDoc".split(), + "int_no_gil": "sqlite3_threadsafe".split(), + "int": "PyType_Ready PyModule_AddObject PyModule_AddIntConstant".split(), +} if __name__ == '__main__': import sys