Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit a5e3ca1

Browse files
committed
Add Python 2.7 support
Add tests/utils.py
1 parent efb61d4 commit a5e3ca1

8 files changed

Lines changed: 146 additions & 51 deletions

File tree

.travis.yml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,12 @@ dist: bionic
44
jobs:
55
fast_finish: true
66
include:
7+
- python: '2.7'
8+
- python: '3.5'
79
- python: '3.6'
810
- python: '3.7'
911
- python: '3.8'
10-
- python: '3.9-dev'
12+
- python: '3.9'
1113
- python: nightly
1214

1315
script:

README.rst

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,14 +9,15 @@ Python C API compatibility
99
The Python C API compatibility project is made of two parts:
1010

1111
* ``pythoncapi_compat.h``: Header file providing new functions of the Python C
12-
API to Python 3.6.
12+
API to old Python versions.
1313
* ``upgrade_pythoncapi.py``: Script upgrading C extension modules to newer
14-
Python API without losing support for Python 3.6. It relies on
14+
Python API without losing support for old Python versions. It relies on
1515
``pythoncapi_compat.h``.
1616

17-
Python 3.6 to Python 3.10 are supported. A subset of C99 is required, like
17+
Python 2.7 to Python 3.10 are supported. A subset of C99 is required, like
1818
``static inline`` functions: see `PEP 7
19-
<https://www.python.org/dev/peps/pep-0007/>`_.
19+
<https://www.python.org/dev/peps/pep-0007/>`_. ISO C90 is partially supported
20+
for Python 2.7.
2021

2122
Homepage:
2223
https://github.com/pythoncapi/pythoncapi_compat
@@ -194,7 +195,7 @@ PyThreadState::
194195

195196
PyFrameObject* PyThreadState_GetFrame(PyThreadState *tstate);
196197
PyInterpreterState* PyThreadState_GetInterpreter(PyThreadState *tstate);
197-
// Availability: Python 3.7+
198+
// Availability: Python 3.7
198199
uint64_t PyThreadState_GetID(PyThreadState *tstate);
199200

200201
PyInterpreterState::
@@ -204,7 +205,7 @@ PyInterpreterState::
204205
GC protocol::
205206

206207
int PyObject_GC_IsTracked(PyObject* obj);
207-
// Availability: Python 3.4+
208+
// Availability: Python 3.4
208209
int PyObject_GC_IsFinalized(PyObject *obj);
209210

210211
Module helper::

pythoncapi_compat.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
// Header file providing new functions of the Python C API to Python 3.6.
1+
// Header file providing new functions of the Python C API to old Python
2+
// versions.
23
//
34
// File distributed under the MIT license.
45
//

runtests.py

Lines changed: 24 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -7,18 +7,31 @@
77
python3 test_matrix.py
88
python3 test_matrix.py -v # verbose mode
99
"""
10+
from __future__ import absolute_import
11+
from __future__ import print_function
1012
import argparse
1113
import os.path
1214
import shutil
1315
import subprocess
1416
import sys
17+
try:
18+
from shutil import which
19+
except ImportError:
20+
# Python 2
21+
from distutils.spawn import find_executable as which
22+
23+
24+
from tests.utils import run_command
1525

1626

1727
TEST_DIR = os.path.join(os.path.dirname(__file__), 'tests')
1828
TEST_COMPAT = os.path.join(TEST_DIR, "test_pythoncapi_compat.py")
1929
TEST_UPGRADE = os.path.join(TEST_DIR, "test_upgrade_pythoncapi.py")
2030

2131
PYTHONS = (
32+
"python2.7",
33+
"python3.4",
34+
"python3.5",
2235
"python3.6",
2336
"python3.7",
2437
"python3.8",
@@ -29,14 +42,6 @@
2942
)
3043

3144

32-
def run_command(cmd):
33-
proc = subprocess.Popen(cmd)
34-
proc.wait()
35-
exitcode = proc.returncode
36-
if exitcode:
37-
sys.exit(exitcode)
38-
39-
4045
def run_tests_exe(executable, verbose, tested):
4146
executable = os.path.realpath(executable)
4247
if executable in tested:
@@ -50,9 +55,9 @@ def run_tests_exe(executable, verbose, tested):
5055

5156

5257
def run_tests(python, verbose, tested):
53-
executable = shutil.which(python)
58+
executable = which(python)
5459
if not executable:
55-
print(f"Ignore missing: {python}")
60+
print("Ignore missing: %s" % python)
5661
return
5762
run_tests_exe(executable, verbose, tested)
5863

@@ -70,10 +75,14 @@ def parse_args():
7075
def main():
7176
args = parse_args()
7277

73-
cmd = [sys.executable, TEST_UPGRADE]
74-
if args.verbose:
75-
cmd.append('-v')
76-
run_command(cmd)
78+
# upgrade_pythoncapi.py requires Python 3.6 or newer
79+
if sys.version_info >= (3, 6):
80+
cmd = [sys.executable, TEST_UPGRADE]
81+
if args.verbose:
82+
cmd.append('-v')
83+
run_command(cmd)
84+
else:
85+
print("Don't test upgrade_pythoncapi.py: it requires Python 3.6")
7786
print()
7887

7988
tested = set()
@@ -83,7 +92,7 @@ def main():
8392
run_tests_exe(sys.executable, args.verbose, tested)
8493

8594
print()
86-
print(f"Tested: {len(tested)} Python executables")
95+
print("Tested: %s Python executables" % len(tested))
8796
else:
8897
run_tests_exe(sys.executable, args.verbose, tested)
8998

tests/__init__.py

Whitespace-only changes.

tests/test_pythoncapi_compat.py

Lines changed: 23 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -7,21 +7,23 @@
77
python3 run_tests.py
88
python3 run_tests.py -v # verbose mode
99
"""
10-
import faulthandler
10+
from __future__ import absolute_import
11+
from __future__ import print_function
1112
import os.path
1213
import shutil
1314
import subprocess
1415
import sys
16+
try:
17+
import faulthandler
18+
except ImportError:
19+
# Python 2
20+
faulthandler = None
1521

16-
17-
VERBOSE = False
22+
# test.utils
23+
from utils import run_command, command_stdout
1824

1925

20-
def run_command(args, **kw):
21-
proc = subprocess.run(args)
22-
if proc.returncode:
23-
sys.exit(proc.returncode)
24-
return proc
26+
VERBOSE = False
2527

2628

2729
def display_title(title):
@@ -43,13 +45,10 @@ def build_ext():
4345
run_command(cmd)
4446
print()
4547
else:
46-
proc = subprocess.run(cmd,
47-
stdout=subprocess.PIPE,
48-
stderr=subprocess.STDOUT,
49-
universal_newlines=True)
50-
if proc.returncode:
51-
print(proc.stdout.rstrip())
52-
sys.exit(proc.returncode)
48+
exitcode, stdout = command_stdout(cmd, stderr=subprocess.STDOUT)
49+
if exitcode:
50+
print(stdout.rstrip())
51+
sys.exit(exitcode)
5352

5453

5554
def import_tests():
@@ -68,7 +67,8 @@ def import_tests():
6867
def _run_tests(tests, verbose):
6968
for name, test_func in tests:
7069
if verbose:
71-
print(f"{name}()", flush=True)
70+
print("%s()" % name)
71+
sys.stdout.flush()
7272
test_func()
7373

7474

@@ -78,14 +78,14 @@ def _check_refleak(test_func, verbose):
7878
if verbose:
7979
if i > 1:
8080
print()
81-
print(f"Run {i}/{nrun}:")
81+
print("Run %s/%s:" % (i, nrun))
8282

8383
init_refcnt = sys.gettotalrefcount()
8484
test_func()
8585
diff = sys.gettotalrefcount() - init_refcnt
8686

8787
if i > 3 and diff:
88-
raise AssertionError(f"refcnt leak, diff: {diff}")
88+
raise AssertionError("refcnt leak, diff: %s" % diff)
8989

9090

9191
def run_tests(testmod):
@@ -110,18 +110,19 @@ def test_func():
110110

111111
ver = sys.version_info
112112
build = 'debug' if hasattr(sys, 'gettotalrefcount') else 'release'
113-
msg = f"{len(tests)} tests succeeded!"
114-
msg = f"Python {ver.major}.{ver.minor} ({build} build): {msg}"
113+
msg = "%s tests succeeded!" % len(tests)
114+
msg = "Python %s.%s (%s build): %s" % (ver.major, ver.minor, build, msg)
115115
if check_refleak:
116-
msg = f"{msg} (no reference leak detected)"
116+
msg = "%s (no reference leak detected)" % msg
117117
print(msg)
118118

119119

120120
def main():
121121
global VERBOSE
122122
VERBOSE = "-v" in sys.argv[1:]
123123

124-
faulthandler.enable()
124+
if faulthandler is not None:
125+
faulthandler.enable()
125126

126127
src_dir = os.path.dirname(__file__)
127128
if src_dir:

tests/test_pythoncapi_compat_cext.c

Lines changed: 46 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,10 @@
33

44
#include "pythoncapi_compat.h"
55

6+
#if PY_VERSION_HEX >= 0x03000000
7+
# define PYTHON3 1
8+
#endif
9+
610
static PyObject*
711
ASSERT_FAILED(const char *err_msg)
812
{
@@ -196,31 +200,44 @@ test_gc(PyObject *self, PyObject *ignored)
196200
static PyObject *
197201
test_module(PyObject *self, PyObject *ignored)
198202
{
199-
PyObject *module = self;
203+
PyObject *module = PyImport_ImportModule("sys");
204+
if (module == NULL) {
205+
return NULL;
206+
}
200207
assert(PyModule_Check(module));
201208

202209
// test PyModule_AddType()
203210
PyTypeObject *type = &PyUnicode_Type;
211+
#ifdef PYTHON3
212+
const char *type_name = "str";
213+
#else
214+
const char *type_name = "unicode";
215+
#endif
204216
Py_ssize_t refcnt = Py_REFCNT(type);
205217

206218
if (PyModule_AddType(module, type) < 0) {
207-
return NULL;
219+
goto error;
208220
}
209221
assert(Py_REFCNT(type) == refcnt + 1);
210222

211-
PyObject *attr = PyObject_GetAttrString(module, "str");
223+
PyObject *attr = PyObject_GetAttrString(module, type_name);
212224
if (attr == NULL) {
213-
return NULL;
225+
goto error;
214226
}
215227
assert(attr == (PyObject *)type);
216228
Py_DECREF(attr);
217229

218-
if (PyObject_DelAttrString(module, "str") < 0) {
219-
return NULL;
230+
if (PyObject_DelAttrString(module, type_name) < 0) {
231+
goto error;
220232
}
221233
assert(Py_REFCNT(type) == refcnt);
222234

235+
Py_DECREF(module);
223236
Py_RETURN_NONE;
237+
238+
error:
239+
Py_DECREF(module);
240+
return NULL;
224241
}
225242

226243

@@ -236,15 +253,38 @@ static struct PyMethodDef methods[] = {
236253
};
237254

238255

256+
#ifdef PYTHON3
239257
static struct PyModuleDef module = {
240258
PyModuleDef_HEAD_INIT,
241259
.m_name = "test_pythoncapi_compat_cext",
242260
.m_methods = methods,
243261
};
244262

245263

264+
#if PY_VERSION_HEX >= 0x03050000
246265
PyMODINIT_FUNC
247266
PyInit_test_pythoncapi_compat_cext(void)
248267
{
249268
return PyModuleDef_Init(&module);
250269
}
270+
#else
271+
// Python 3.4
272+
PyMODINIT_FUNC
273+
PyInit_test_pythoncapi_compat_cext(void)
274+
{
275+
return PyModule_Create(&module);
276+
}
277+
#endif
278+
279+
#else
280+
// Python 2
281+
PyMODINIT_FUNC
282+
inittest_pythoncapi_compat_cext(void)
283+
{
284+
Py_InitModule4("test_pythoncapi_compat_cext",
285+
methods,
286+
NULL,
287+
NULL,
288+
PYTHON_API_VERSION);
289+
}
290+
#endif

tests/utils.py

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
import subprocess
2+
import sys
3+
4+
5+
PYTHON3 = (sys.version_info >= (3,))
6+
7+
8+
def run_command(cmd, **kw):
9+
if hasattr(subprocess, 'run'):
10+
proc = subprocess.run(cmd, **kw)
11+
else:
12+
kw['shell'] = False
13+
proc = subprocess.Popen(cmd, **kw)
14+
try:
15+
proc.wait()
16+
except:
17+
proc.kill()
18+
proc.wait()
19+
raise
20+
21+
exitcode = proc.returncode
22+
if exitcode:
23+
sys.exit(exitcode)
24+
25+
26+
def command_stdout(cmd, **kw):
27+
kw['stdout'] = subprocess.PIPE
28+
kw['universal_newlines'] = True
29+
if hasattr(subprocess, 'run'):
30+
proc = subprocess.run(cmd, **kw)
31+
return (proc.returncode, proc.stdout)
32+
else:
33+
kw['shell'] = False
34+
proc = subprocess.Popen(cmd, **kw)
35+
try:
36+
stdout = proc.communicate()[0]
37+
except:
38+
proc.kill()
39+
proc.wait()
40+
raise
41+
return (proc.returncode, stdout)

0 commit comments

Comments
 (0)