#!/usr/bin/env python3 """Compile the exact update body as C89 and check evaluation/dispatch order.""" 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 \*\nmutablemapping_update\(PyObject \*self, PyObject \*args, PyObject \*kwargs\)\n\{.*?\n\}", source, re.S, ) assert match, "mutablemapping_update not found" harness = r''' #include #include #include typedef long Py_ssize_t; typedef struct { int unused; } PyObject; static PyObject self_obj, args_obj, kwargs_obj, other_obj, items_obj, none_obj; static int scenario, PyExc_TypeError; static char events[64]; static void event(char c) { size_t n = strlen(events); assert(n + 1 < sizeof(events)); events[n] = c; events[n + 1] = '\0'; } static int PyTuple_Check(PyObject *obj) { assert(obj == &args_obj); event('T'); return 1; } static Py_ssize_t PyTuple_GET_SIZE(PyObject *obj) { assert(obj == &args_obj && strcmp(events, "T") == 0); event('L'); return scenario == 2 ? 2 : scenario == 1 ? 0 : 1; } static void PyErr_Format(int exc, const char *msg, Py_ssize_t len) { assert(exc == PyExc_TypeError && len == 2); assert(strcmp(msg, "update() takes at most 1 positional argument (%zd given)") == 0); event('E'); } static PyObject *PyTuple_GET_ITEM(PyObject *obj, int index) { assert(obj == &args_obj && index == 0); event('G'); return &other_obj; } static void Py_INCREF(PyObject *obj) { assert(obj == &other_obj); event('I'); } static void Py_DECREF(PyObject *obj) { assert(obj == &other_obj || obj == &items_obj); event(obj == &other_obj ? 'D' : 'd'); } static int mutablemapping_update_arg(PyObject *self, PyObject *other) { assert(self == &self_obj && other == &other_obj); event('U'); return scenario == 3 ? -1 : 0; } static int PyDict_Check(PyObject *obj) { assert(obj == &kwargs_obj); event('K'); return 1; } static int PyDict_GET_SIZE(PyObject *obj) { assert(obj == &kwargs_obj); event('S'); return scenario != 5; } static PyObject *PyDict_Items(PyObject *obj) { assert(obj == &kwargs_obj); event('M'); return scenario == 6 ? NULL : &items_obj; } static int mutablemapping_add_pairs(PyObject *self, PyObject *items) { assert(self == &self_obj && items == &items_obj); event('A'); return scenario == 7 ? -1 : 0; } #define Py_RETURN_NONE return &none_obj ''' harness += match.group(0) harness += r''' int main(void) { static const char *expected[] = { "", "TL", "TLE", "TLGIUD", "TLGIUD", "TLGIUDKS", "TLGIUDKSM", "TLGIUDKSMAd", "TLGIUDKSMAd", "KSMAd" }; PyObject *result; for (scenario = 0; scenario < 10; ++scenario) { events[0] = '\0'; result = mutablemapping_update(&self_obj, scenario == 0 || scenario == 9 ? NULL : &args_obj, scenario >= 5 ? &kwargs_obj : NULL); assert(result == (scenario == 2 || scenario == 3 || scenario == 6 || scenario == 7 ? NULL : &none_obj)); assert(strcmp(events, expected[scenario]) == 0); } return 0; } ''' with tempfile.TemporaryDirectory(prefix="vc6-odict-update-len-") as directory: path = Path(directory) (path / "test.c").write_text(harness) result = subprocess.run([ "cc", "-std=c89", "-pedantic-errors", "-Wall", "-Wextra", "-Werror", str(path / "test.c"), "-o", str(path / "test"), ]) if result.returncode: sys.exit(result.returncode) subprocess.run([str(path / "test")], check=True) print("mutablemapping_update C89 compile and evaluation/dispatch order: PASS (10 cases)")