##// END OF EJS Templates
parsers: fail fast if Python has wrong minor version (issue4110)...
Chris Jerdonek -
r20742:3681de20 default
parent child Browse files
Show More
@@ -14,6 +14,8 b''
14
14
15 #include "util.h"
15 #include "util.h"
16
16
17 static char *versionerrortext = "Python minor version mismatch";
18
17 static int8_t hextable[256] = {
19 static int8_t hextable[256] = {
18 -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
20 -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
19 -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
21 -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
@@ -1911,6 +1913,16 b' void dirs_module_init(PyObject *mod);'
1911
1913
1912 static void module_init(PyObject *mod)
1914 static void module_init(PyObject *mod)
1913 {
1915 {
1916 /* This module constant has two purposes. First, it lets us unit test
1917 * the ImportError raised without hard-coding any error text. This
1918 * means we can change the text in the future without breaking tests,
1919 * even across changesets without a recompile. Second, its presence
1920 * can be used to determine whether the version-checking logic is
1921 * present, which also helps in testing across changesets without a
1922 * recompile. Note that this means the pure-Python version of parsers
1923 * should not have this module constant. */
1924 PyModule_AddStringConstant(mod, "versionerrortext", versionerrortext);
1925
1914 dirs_module_init(mod);
1926 dirs_module_init(mod);
1915
1927
1916 indexType.tp_new = PyType_GenericNew;
1928 indexType.tp_new = PyType_GenericNew;
@@ -1928,6 +1940,24 b' static void module_init(PyObject *mod)'
1928 dirstate_unset = Py_BuildValue("ciii", 'n', 0, -1, -1);
1940 dirstate_unset = Py_BuildValue("ciii", 'n', 0, -1, -1);
1929 }
1941 }
1930
1942
1943 static int check_python_version(void)
1944 {
1945 PyObject *sys = PyImport_ImportModule("sys");
1946 long hexversion = PyInt_AsLong(PyObject_GetAttrString(sys, "hexversion"));
1947 /* sys.hexversion is a 32-bit number by default, so the -1 case
1948 * should only occur in unusual circumstances (e.g. if sys.hexversion
1949 * is manually set to an invalid value). */
1950 if ((hexversion == -1) || (hexversion >> 16 != PY_VERSION_HEX >> 16)) {
1951 PyErr_Format(PyExc_ImportError, "%s: The Mercurial extension "
1952 "modules were compiled with Python " PY_VERSION ", but "
1953 "Mercurial is currently using Python with sys.hexversion=%ld: "
1954 "Python %s\n at: %s", versionerrortext, hexversion,
1955 Py_GetVersion(), Py_GetProgramFullPath());
1956 return -1;
1957 }
1958 return 0;
1959 }
1960
1931 #ifdef IS_PY3K
1961 #ifdef IS_PY3K
1932 static struct PyModuleDef parsers_module = {
1962 static struct PyModuleDef parsers_module = {
1933 PyModuleDef_HEAD_INIT,
1963 PyModuleDef_HEAD_INIT,
@@ -1939,6 +1969,8 b' static struct PyModuleDef parsers_module'
1939
1969
1940 PyMODINIT_FUNC PyInit_parsers(void)
1970 PyMODINIT_FUNC PyInit_parsers(void)
1941 {
1971 {
1972 if (check_python_version() == -1)
1973 return;
1942 PyObject *mod = PyModule_Create(&parsers_module);
1974 PyObject *mod = PyModule_Create(&parsers_module);
1943 module_init(mod);
1975 module_init(mod);
1944 return mod;
1976 return mod;
@@ -1946,6 +1978,8 b' PyMODINIT_FUNC PyInit_parsers(void)'
1946 #else
1978 #else
1947 PyMODINIT_FUNC initparsers(void)
1979 PyMODINIT_FUNC initparsers(void)
1948 {
1980 {
1981 if (check_python_version() == -1)
1982 return;
1949 PyObject *mod = Py_InitModule3("parsers", methods, parsers_doc);
1983 PyObject *mod = Py_InitModule3("parsers", methods, parsers_doc);
1950 module_init(mod);
1984 module_init(mod);
1951 }
1985 }
@@ -1,8 +1,13 b''
1 """This unit test tests parsers.parse_index2()."""
1 """This unit test primarily tests parsers.parse_index2().
2
3 It also checks certain aspects of the parsers module as a whole.
4 """
2
5
3 from mercurial import parsers
6 from mercurial import parsers
4 from mercurial.node import nullid, nullrev
7 from mercurial.node import nullid, nullrev
5 import struct
8 import struct
9 import subprocess
10 import sys
6
11
7 # original python implementation
12 # original python implementation
8 def gettype(q):
13 def gettype(q):
@@ -95,7 +100,70 b' def parse_index2(data, inline):'
95 index, chunkcache = parsers.parse_index2(data, inline)
100 index, chunkcache = parsers.parse_index2(data, inline)
96 return list(index), chunkcache
101 return list(index), chunkcache
97
102
103 def importparsers(hexversion):
104 """Import mercurial.parsers with the given sys.hexversion."""
105 # The file parsers.c inspects sys.hexversion to determine the version
106 # of the currently-running Python interpreter, so we monkey-patch
107 # sys.hexversion to simulate using different versions.
108 code = ("import sys; sys.hexversion=%s; "
109 "import mercurial.parsers" % hexversion)
110 cmd = "python -c \"%s\"" % code
111 # We need to do these tests inside a subprocess because parser.c's
112 # version-checking code happens inside the module init function, and
113 # when using reload() to reimport an extension module, "The init function
114 # of extension modules is not called a second time"
115 # (from http://docs.python.org/2/library/functions.html?#reload).
116 p = subprocess.Popen(cmd, shell=True,
117 stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
118 return p.communicate() # returns stdout, stderr
119
120 def printhexfail(testnumber, hexversion, stdout, expected):
121 try:
122 hexstring = hex(hexversion)
123 except TypeError:
124 hexstring = None
125 print ("FAILED: version test #%s with Python %s and patched "
126 "sys.hexversion %r (%r):\n Expected %s but got:\n-->'%s'\n" %
127 (testnumber, sys.version_info, hexversion, hexstring, expected,
128 stdout))
129
130 def testversionokay(testnumber, hexversion):
131 stdout, stderr = importparsers(hexversion)
132 if stdout:
133 printhexfail(testnumber, hexversion, stdout, expected="no stdout")
134
135 def testversionfail(testnumber, hexversion):
136 stdout, stderr = importparsers(hexversion)
137 # We include versionerrortext to distinguish from other ImportErrors.
138 errtext = "ImportError: %s" % parsers.versionerrortext
139 if errtext not in stdout:
140 printhexfail(testnumber, hexversion, stdout,
141 expected="stdout to contain %r" % errtext)
142
143 def makehex(major, minor, micro):
144 return int("%x%02x%02x00" % (major, minor, micro), 16)
145
146 def runversiontests():
147 """Check the version-detection logic when importing parsers."""
148 info = sys.version_info
149 major, minor, micro = info[0], info[1], info[2]
150 # Test same major-minor versions.
151 testversionokay(1, makehex(major, minor, micro))
152 testversionokay(2, makehex(major, minor, micro + 1))
153 # Test different major-minor versions.
154 testversionfail(3, makehex(major + 1, minor, micro))
155 testversionfail(4, makehex(major, minor + 1, micro))
156 testversionfail(5, "'foo'")
157
98 def runtest() :
158 def runtest() :
159 # Only test the version-detection logic if it is present.
160 try:
161 parsers.versionerrortext
162 except AttributeError:
163 pass
164 else:
165 runversiontests()
166
99 # Check that parse_index2() raises TypeError on bad arguments.
167 # Check that parse_index2() raises TypeError on bad arguments.
100 try:
168 try:
101 parse_index2(0, True)
169 parse_index2(0, True)
General Comments 0
You need to be logged in to leave comments. Login now