#!/usr/bin/env python3 """Compile the exact odict_or as C89 and exercise its operation ordering.""" import re import subprocess import sys import tempfile from pathlib import Path source = Path(sys.argv[1]).read_text() match = re.search( r"static PyObject \*\nodict_or\(PyObject \*left, PyObject \*right\)\n\{.*?\n\}", source, re.S, ) assert match, "odict_or definition not found" harness = r''' #include #include typedef struct { int ordered; int dict; } PyObject; typedef PyObject PyTypeObject; static PyObject left_obj, right_obj, result_obj, not_implemented; static PyObject *expected_type; static int stage, fail_call, fail_update; static int PyODict_Check(PyObject *obj) { assert(stage == 0 && obj == &left_obj); stage = 1; return obj->ordered; } static PyTypeObject *Py_TYPE(PyObject *obj) { assert(stage == 1 && obj == expected_type); stage = 2; return obj; } static int PyDict_Check(PyObject *obj) { assert(stage == 2); assert(obj == (left_obj.ordered ? &right_obj : &left_obj)); stage = 3; return obj->dict; } #define Py_RETURN_NOTIMPLEMENTED return ¬_implemented static PyObject *PyObject_CallOneArg(PyObject *type, PyObject *arg) { assert(stage == 3 && type == expected_type && arg == &left_obj); stage = 4; return fail_call ? NULL : &result_obj; } static int mutablemapping_update_arg(PyObject *obj, PyObject *arg) { assert(stage == 4 && obj == &result_obj && arg == &right_obj); stage = 5; return fail_update ? -1 : 0; } static void Py_DECREF(PyObject *obj) { assert(stage == 5 && fail_update && obj == &result_obj); stage = 6; } ''' harness += match.group(0) harness += r''' int main(void) { int ordered, scenario; PyObject *result; for (ordered = 0; ordered < 2; ++ordered) { for (scenario = 0; scenario < 4; ++scenario) { left_obj.ordered = ordered; right_obj.ordered = 1; left_obj.dict = right_obj.dict = scenario != 0; expected_type = ordered ? &left_obj : &right_obj; fail_call = scenario == 1; fail_update = scenario == 2; stage = 0; result = odict_or(&left_obj, &right_obj); if (scenario == 0) { assert(result == ¬_implemented && stage == 3); } else if (scenario == 1) { assert(result == NULL && stage == 4); } else if (scenario == 2) { assert(result == NULL && stage == 6); } else { assert(result == &result_obj && stage == 5); } } } return 0; } ''' with tempfile.TemporaryDirectory(prefix="vc6-odict-or-") as directory: path = Path(directory) (path / "test.c").write_text(harness) compiled = subprocess.run([ "cc", "-std=c89", "-pedantic-errors", "-Wall", "-Wextra", "-Werror", str(path / "test.c"), "-o", str(path / "test"), ]) if compiled.returncode: sys.exit(compiled.returncode) subprocess.run([str(path / "test")], check=True) print("odict_or C89 compile and operand/rejection/construction/update/cleanup order: PASS (8 cases)")