From jython-checkins at python.org Tue Oct 23 03:05:16 2018 From: jython-checkins at python.org (jeff.allen) Date: Tue, 23 Oct 2018 07:05:16 +0000 Subject: [Jython-checkins] =?utf-8?q?jython=3A_Treat_illegal_reflective_a?= =?utf-8?q?ccess_in_test=5Fcodecs=5Fjy=2E?= Message-ID: <20181023070516.1.CB00CA3EB0D4F708@mg.python.org> https://hg.python.org/jython/rev/5820f8a915f1 changeset: 8187:5820f8a915f1 user: Jeff Allen date: Sat Oct 20 15:12:51 2018 +0100 summary: Treat illegal reflective access in test_codecs_jy. There seems no good reason not to respect Java accessibility in test_codecs_jy, so we shall. We also fix CodecsTestCase.test_print_sans_lib so it does what it says. (Presumably changes since it was devised had invalidated it.) files: Lib/test/print_sans_lib.py | 7 ++++++- Lib/test/test_codecs_jy.py | 10 ++++------ 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/Lib/test/print_sans_lib.py b/Lib/test/print_sans_lib.py --- a/Lib/test/print_sans_lib.py +++ b/Lib/test/print_sans_lib.py @@ -1,4 +1,9 @@ +# Used by test_codec_jy::CodecsTestCase.test_print_sans_lib. +# Run without importing site module so codec registry is not initialised yet. import sys -sys.path = [path for path in sys.path if not path.startswith('/')] +from os.path import basename +# Hide standard library from import mechanism so the Python codec cannot be found. +sys.path = [p for p in sys.path if basename(p).lower() != 'lib'] +# Can we still encode and decode utf-8? encoded = u'hi'.encode("utf-8") encoded.decode('utf-8') diff --git a/Lib/test/test_codecs_jy.py b/Lib/test/test_codecs_jy.py --- a/Lib/test/test_codecs_jy.py +++ b/Lib/test/test_codecs_jy.py @@ -6,13 +6,11 @@ class CodecsTestCase(unittest.TestCase): def test_print_sans_lib(self): - """Encodes and decodes using utf-8 after in an environment - without the standard library - - Checks that the builtin utf-8 codec is always available: - http://bugs.jython.org/issue1458""" + # Encode and decode using utf-8 in an environment without the standard + # library, to check that a utf-8 codec is always available. See: + # http://bugs.jython.org/issue1458 subprocess.call([sys.executable, "-J-Dpython.cachedir.skip=true", - "-J-Dpython.security.respectJavaAccessibility=false", + "-S", # No site module: avoid codec registry initialised too soon test_support.findfile('print_sans_lib.py')]) def test_string_escape_1502(self): -- Repository URL: https://hg.python.org/jython From jython-checkins at python.org Tue Oct 23 03:05:16 2018 From: jython-checkins at python.org (jeff.allen) Date: Tue, 23 Oct 2018 07:05:16 +0000 Subject: [Jython-checkins] =?utf-8?q?jython=3A_Treat_illegal_reflective_a?= =?utf-8?q?ccess_in_test=5Fjava=5Fvisibility=2E?= Message-ID: <20181023070516.1.2B92355FE88B6B80@mg.python.org> https://hg.python.org/jython/rev/677009e86ce2 changeset: 8188:677009e86ce2 user: Jeff Allen date: Sun Oct 21 14:04:07 2018 +0100 summary: Treat illegal reflective access in test_java_visibility. The test is testing python.security.respectJavaAccessibility=false, but from Java 9 the JVM produces ugly warnings about the access that follows. These are suppressed in the test by appropriate --add-opens options. Since --add-opens is only valid from Java 9, this change also requires test.test_support.get_java_version to support JEP-223 version numbers. files: Lib/test/test_java_visibility.py | 32 ++++++++++++++++++- Lib/test/test_ssl.py | 4 +- Lib/test/test_support.py | 24 ++++++++++----- 3 files changed, 47 insertions(+), 13 deletions(-) diff --git a/Lib/test/test_java_visibility.py b/Lib/test/test_java_visibility.py --- a/Lib/test/test_java_visibility.py +++ b/Lib/test/test_java_visibility.py @@ -239,12 +239,38 @@ class RespectJavaAccessibilityTest(unittest.TestCase): + def setUp(self): + self.command = [sys.executable] + + # NOTE: from Java 9 onwards, the JVM will complain about (but by default still allow) + # reflective access that does not respect Java accessibility rules. In order to avoid: + # WARNING: Illegal reflective access by org.python.core.PyJavaType ... + # and a threat that "illegal access operations will be denied in a future release", + # we add --add-opens specifications for all the packages needed in this test. + + def add_opens(self, module, package): + self.command.append("-J--add-opens") + self.command.append("-J{}/{}=ALL-UNNAMED".format(module, package)) + def run_accessibility_script(self, script, error=AttributeError): fn = test_support.findfile(script) + # Check expected error in current environment self.assertRaises(error, execfile, fn) - self.assertEquals(subprocess.call([sys.executable, "-J-Dpython.cachedir.skip=true", - "-J-Dpython.security.respectJavaAccessibility=false", fn]), - 0) + + # Prepare to break the rules + self.command.append("-J-Dpython.cachedir.skip=true") + self.command.append("-J-Dpython.security.respectJavaAccessibility=false") + if test_support.get_java_version() >= (9,): + # See all the cases for which we have forgotten --add-opens + self.command.append("-J--illegal-access=warn") + # Open the packages used in the scripts + self.add_opens("java.desktop", "java.awt.geom") + for package in ("lang", "util", "nio", "nio.charset"): + self.add_opens("java.base", "java." + package) + + self.command.append(fn) + self.assertEquals(subprocess.call(self.command), 0) + def test_method_access(self): self.run_accessibility_script("call_protected_method.py") diff --git a/Lib/test/test_ssl.py b/Lib/test/test_ssl.py --- a/Lib/test/test_ssl.py +++ b/Lib/test/test_ssl.py @@ -337,7 +337,7 @@ self.assertRaisesRegexp(ValueError, "certfile must be specified for server-side operations", ssl.wrap_socket, sock, server_side=True, certfile="") - if support.get_java_version() < (1, 9): + if support.get_java_version() < (9,): # Possible FIXME similar issue as seen in # test_load_cert_chain - apparently this RSA 1024 cert is too weak and gets a # java.security.KeyStoreException: Key protection algorithm not found before the @@ -786,7 +786,7 @@ ctx = ssl.SSLContext(ssl.PROTOCOL_TLSv1) # Combined key and cert in a single file ctx.load_cert_chain(CERTFILE, keyfile=None) - if support.get_java_version() < (1, 9): + if support.get_java_version() < (9,): # Possible FIXME we may be skipping this test on Java 9 unnecessarily. # CERTFILE as generated uses RSA 1024, which is considered too weak. # This may be why this raises an error on Java 9: diff --git a/Lib/test/test_support.py b/Lib/test/test_support.py --- a/Lib/test/test_support.py +++ b/Lib/test/test_support.py @@ -56,16 +56,24 @@ if is_jython: def get_java_version(version=None): - # returns (1, 8, 0, 121) for version = "1.8.0_121", meaning - # Java 8 update 121, etc.. Conforms to: - # http://www.oracle.com/technetwork/java/javase/versioning-naming-139433.html - # and not yet http://openjdk.java.net/jeps/223 . + """return a tuple of int encoding the Java version (as in java.version). + + "1.8.0_121" -> (1, 8, 0, 121) + "9.0.4" -> (9, 0, 4) + "11" -> (11,) + "12-ea" -> (12,) + This parses strings (java.version properties) that conform to: + http://www.oracle.com/technetwork/java/javase/versioning-naming-139433.html + and http://openjdk.java.net/jeps/223 (but doesn't validate them). + """ if version is None: version = platform.java_ver()[0] - parse = re.match("(\d+)\.(\d+)\.(\d+)_(\d+)", version) - if parse: - return tuple((int(x) for x in parse.groups())) - else: + version = version.split('-')[0] # discard optional pre-release indicator + parts = version.split('_') # pre-JEP-223 format like 1.8.0_121 + parts[0:1] = parts[0].split('.') + try: + return tuple(int(x) for x in parts) + except: return () -- Repository URL: https://hg.python.org/jython From jython-checkins at python.org Tue Oct 23 03:05:17 2018 From: jython-checkins at python.org (jeff.allen) Date: Tue, 23 Oct 2018 07:05:17 +0000 Subject: [Jython-checkins] =?utf-8?q?jython=3A_Treat_illegal_reflective_a?= =?utf-8?q?ccess_in_test=5Fgc=5Fjy=2E?= Message-ID: <20181023070517.1.FBB1C1AB3C70AD1B@mg.python.org> https://hg.python.org/jython/rev/dc1ffb710882 changeset: 8189:dc1ffb710882 user: Jeff Allen date: Tue Oct 23 06:56:02 2018 +0100 summary: Treat illegal reflective access in test_gc_jy. By default we no longer use reflection when traversing cyclic isolates (cyclic garbage) so that reflective access warnings will not be produced. However, we enable it, for one class of test case only, on Java versions less than 9. The clauses of the original test are made individual tests to aid debug. files: Lib/test/test_gc_jy.py | 73 ++++++++++++--------- src/org/python/modules/gc.java | 2 +- 2 files changed, 41 insertions(+), 34 deletions(-) diff --git a/Lib/test/test_gc_jy.py b/Lib/test/test_gc_jy.py --- a/Lib/test/test_gc_jy.py +++ b/Lib/test/test_gc_jy.py @@ -33,7 +33,7 @@ gc.stopMonitoring() except Exception: pass - + @classmethod def tearDownClass(cls): try: @@ -371,7 +371,7 @@ class Test_Resurrection(object): def __init__(self, name): self.name = name - + def __repr__(self): return "<"+self.name+">" @@ -385,7 +385,7 @@ c = Test_Resurrection("c") c.a = a c.toResurrect = Test_Finalizable("d") - + del a del c self.assertNotEqual(gc.collect(), 0) @@ -552,7 +552,7 @@ class Test_Resurrection(object): def __init__(self, name): self.name = name - + def __repr__(self): return "<"+self.name+">" @@ -566,7 +566,7 @@ c = Test_Resurrection("c") c.a = a c.toResurrect = Test_Finalizable("d") - + del a del c self.assertNotEqual(gc.collect(), 0) @@ -616,31 +616,31 @@ def test_raw_forced_delayedFinalization(self): #print "test_raw_forced_delayedFinalization" comments = [] - + class Test_JavaAbortFinalizable(Object): def __init__(self, name, toAbort): self.name = name self.toAbort = toAbort - + def __repr__(self): return "<"+self.name+">" - + def finalize(self): gc.notifyPreFinalization() comments.append("del "+self.name) gc.abortDelayedFinalization(self.toAbort) gc.notifyPostFinalization() - + class Test_Finalizable(object): def __init__(self, name): self.name = name - + def __repr__(self): return "<"+self.name+">" - + def __del__(self): comments.append("del "+self.name) - + def callback(obj): comments.append("callback0") @@ -666,12 +666,12 @@ def test_raw_forced_delayedWeakrefCallback(self): comments = [] resurrected = [] - + class Test_JavaResurrectFinalizable(Object): def __init__(self, name, toResurrect): self.name = name self.toResurrect = toResurrect - + def __repr__(self): return "<"+self.name+">" @@ -684,20 +684,20 @@ # We manually restore weak references: gc.restoreWeakReferences(self.toResurrect) gc.notifyPostFinalization() - + class Test_Finalizable(object): def __init__(self, name): self.name = name - + def __repr__(self): return "<"+self.name+">" - + def __del__(self): comments.append("del "+self.name) - + def callback(obj): comments.append("callback") - + a = Test_Finalizable("a") b = Test_JavaResurrectFinalizable("b", a) wa = weakref.ref(a, callback) @@ -723,15 +723,15 @@ def test_raw_forced_delayed(self): comments = [] - + class Test_JavaAbortFinalizable(Object): def __init__(self, name, toAbort): self.name = name self.toAbort = toAbort - + def __repr__(self): return "<"+self.name+">" - + def finalize(self): gc.notifyPreFinalization() comments.append("del "+self.name) @@ -826,7 +826,7 @@ class Test_Resurrection(object): def __init__(self, name): self.name = name - + def __repr__(self): return "<"+self.name+">" @@ -926,7 +926,7 @@ class Test_Resurrection(object): def __init__(self, name): self.name = name - + def __repr__(self): return "<"+self.name+">" @@ -976,7 +976,7 @@ class Test_Resurrection(object): def __init__(self, name): self.name = name - + def __repr__(self): return "<"+self.name+">" @@ -1035,7 +1035,7 @@ class Test_Resurrection(object): def __init__(self, name): self.name = name - + def __repr__(self): return "<"+self.name+">" @@ -1071,11 +1071,10 @@ self.assertEqual(wc(), None) - at unittest.skipUnless(test_support.is_jython, - ''' - The test involves Java-classes and is thus not supported by - non-Jython interpreters. - ''') + at unittest.skipUnless(test_support.is_jython and test_support.get_java_version() < (9,), + "Test is specific to Java versions <9") + # From Java 9 onwards we get ugly warnings. + # See discussion in http://bugs.jython.org/issue2656 class GCTests_Jy_TraverseByReflection(unittest.TestCase): @classmethod @@ -1083,6 +1082,7 @@ #Jython-specific block: try: cls.savedJythonGCFlags = gc.getJythonGCFlags() + gc.removeJythonGCFlags(gc.DONT_TRAVERSE_BY_REFLECTION) # i.e. enable ... gc.addJythonGCFlags(gc.SUPPRESS_TRAVERSE_BY_REFLECTION_WARNING) gc.setMonitorGlobal(True) except Exception: @@ -1096,25 +1096,32 @@ except Exception: pass - def test_TraverseByReflection(self): + def test_Field(self): gc.collect() - prt = GCTestHelper.reflectionTraverseTestField() del prt self.assertEqual(gc.collect(), 1) + def test_List(self): + gc.collect() prt = GCTestHelper.reflectionTraverseTestList() del prt self.assertEqual(gc.collect(), 1) + def test_Array(self): + gc.collect() prt = GCTestHelper.reflectionTraverseTestArray() del prt self.assertEqual(gc.collect(), 1) + def test_PyList(self): + gc.collect() prt = GCTestHelper.reflectionTraverseTestPyList() del prt self.assertEqual(gc.collect(), 2) + def test_Cycle(self): + gc.collect() prt = GCTestHelper.reflectionTraverseTestCycle() del prt self.assertEqual(gc.collect(), 0) diff --git a/src/org/python/modules/gc.java b/src/org/python/modules/gc.java --- a/src/org/python/modules/gc.java +++ b/src/org/python/modules/gc.java @@ -469,7 +469,7 @@ DEBUG_OBJECTS | DEBUG_SAVEALL; - private static short gcFlags = 0; + private static short gcFlags = DONT_TRAVERSE_BY_REFLECTION; private static int debugFlags = 0; private static boolean monitorNonTraversable = false; private static boolean waitingForFinalizers = false; -- Repository URL: https://hg.python.org/jython From jython-checkins at python.org Fri Oct 26 14:13:27 2018 From: jython-checkins at python.org (jeff.allen) Date: Fri, 26 Oct 2018 18:13:27 +0000 Subject: [Jython-checkins] =?utf-8?q?jython=3A_Conform_to_Python_2_byte_a?= =?utf-8?q?nd_unicode_definitions_of_white_space=2E?= Message-ID: <20181026181327.1.273853405BF4C57B@mg.python.org> https://hg.python.org/jython/rev/a1f68d091a1c changeset: 8190:a1f68d091a1c user: Jeff Allen date: Thu Oct 25 23:32:11 2018 +0100 summary: Conform to Python 2 byte and unicode definitions of white space. Jython traditionally accepted Java's definition of "white space", but in fact Java has several and they evolve with Unicode versions. This change set brings Jython into line with Python 2 in str, unicode and the re module. It requires we no longer share some code between PyString and PyUnicode. (It also cleans up some trailing white space in SRE_STATE.java.) files: Lib/test/test_bytes_jy.py | 2 +- Lib/test/test_unicode_jy.py | 13 +- src/org/python/core/PyString.java | 82 ++-- src/org/python/core/PyUnicode.java | 166 +++++++++- src/org/python/modules/sre/SRE_STATE.java | 60 +- 5 files changed, 235 insertions(+), 88 deletions(-) diff --git a/Lib/test/test_bytes_jy.py b/Lib/test/test_bytes_jy.py --- a/Lib/test/test_bytes_jy.py +++ b/Lib/test/test_bytes_jy.py @@ -70,7 +70,7 @@ LOWER = b'\xe0\xe7\xe9\xff' # Uppercase in Latin-1 but not ascii UPPER = b'\xc0\xc7\xc9\xdd' # Lowercase in Latin-1 but not ascii DIGIT = b'\xb9\xb2\xb3' # sup 1, 2, 3: numeric in Python (not Java) - SPACE = b'\x85\xa0' # NEXT LINE, NBSP: space in Python (not Java) + SPACE = b'\x85\xa0' # NEXT LINE, NBSP: space in unicode (not in str/bytes) def test_isalpha(self): for c in self.UPPER + self.LOWER: diff --git a/Lib/test/test_unicode_jy.py b/Lib/test/test_unicode_jy.py --- a/Lib/test/test_unicode_jy.py +++ b/Lib/test/test_unicode_jy.py @@ -854,7 +854,7 @@ class UnicodeSpaceTest(unittest.TestCase): - # Test classification of characters as whitespace (some Jython divergence) + # Test classification of characters as whitespace (strictly as observed in CPython) def checkequal(self, expected, obj, methodname, *args): "check that object.method() returns expected result" @@ -863,15 +863,10 @@ self.assertEqual(expected, realresult, grumble) # print grumble, 'x' if realresult != expected else '.' - # The set of Unicode characters that are spaces according to CPython 2.7.8 - SPACE = u'\t\n\x0b\x0c\r\x1c\x1d\x1e\x1f\x20\x85\xa0\u1680\u180e' + \ - u'\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a' + \ + # The set of Unicode characters that are spaces according to CPython 2.7.15 + SPACE = u'\t\n\x0b\x0c\r\x1c\x1d\x1e\x1f\x20\x85\xa0\u1680\u180e' \ + u'\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a' \ u'\u2028\u2029\u202f\u205f\u3000' - if test_support.is_jython: - # Not whitespace in Jython based on java.lang.Character.isWhitespace. - # This test documents the divergence, until we decide to remove it. - for c in u'\x85\xa0\u2007\u202f': - SPACE = SPACE.replace(c, u'') def test_isspace(self): for c in self.SPACE: diff --git a/src/org/python/core/PyString.java b/src/org/python/core/PyString.java --- a/src/org/python/core/PyString.java +++ b/src/org/python/core/PyString.java @@ -762,7 +762,7 @@ if (c == Character.TYPE || c == Character.class) { if (getString().length() == 1) { - return new Character(getString().charAt(0)); + return getString().charAt(0); } } @@ -1233,16 +1233,15 @@ * @return a new String, stripped of the whitespace characters/bytes */ protected final String _strip() { - String s = getString(); // Rightmost non-whitespace - int right = _stripRight(s); + int right = _findRight(); if (right < 0) { // They're all whitespace return ""; } else { // Leftmost non-whitespace character: right known not to be a whitespace - int left = _stripLeft(s, right); - return s.substring(left, right + 1); + int left = _findLeft(right); + return getString().substring(left, right + 1); } } @@ -1264,16 +1263,15 @@ // Divert to the whitespace version return _strip(); } else { - String s = getString(); // Rightmost non-matching character - int right = _stripRight(s, stripChars); + int right = _findRight(stripChars); if (right < 0) { // They all match return ""; } else { // Leftmost non-matching character: right is known not to match - int left = _stripLeft(s, stripChars, right); - return s.substring(left, right + 1); + int left = _findLeft(stripChars, right); + return getString().substring(left, right + 1); } } } @@ -1281,13 +1279,13 @@ /** * Helper for strip, lstrip implementation, when stripping whitespace. * - * @param s string to search (only s[0:right] is searched). * @param right rightmost extent of string search - * @return index of lefttmost non-whitespace character or right if they all are. + * @return index of leftmost non-whitespace character or right if they all are. */ - private static final int _stripLeft(String s, int right) { + protected int _findLeft(int right) { + String s = getString(); for (int left = 0; left < right; left++) { - if (!Character.isWhitespace(s.charAt(left))) { + if (!BaseBytes.isspace((byte) s.charAt(left))) { return left; } } @@ -1298,13 +1296,13 @@ * Helper for strip, lstrip implementation, when stripping specified * characters. * - * @param s string to search (only s[0:right] is searched). * @param stripChars specifies set of characters to strip * @param right rightmost extent of string search * @return index of leftmost character not in stripChars or right if * they all are. */ - private static final int _stripLeft(String s, String stripChars, int right) { + private int _findLeft(String stripChars, int right) { + String s = getString(); for (int left = 0; left < right; left++) { if (stripChars.indexOf(s.charAt(left)) < 0) { return left; @@ -1316,12 +1314,12 @@ /** * Helper for strip, rstrip implementation, when stripping whitespace. * - * @param s string to search. * @return index of rightmost non-whitespace character or -1 if they all are. */ - private static final int _stripRight(String s) { + protected int _findRight() { + String s = getString(); for (int right = s.length(); --right >= 0;) { - if (!Character.isWhitespace(s.charAt(right))) { + if (!BaseBytes.isspace((byte) s.charAt(right))) { return right; } } @@ -1332,11 +1330,11 @@ * Helper for strip, rstrip implementation, when stripping specified * characters. * - * @param s string to search. * @param stripChars specifies set of characters to strip * @return index of rightmost character not in stripChars or -1 if they all are. */ - private static final int _stripRight(String s, String stripChars) { + private int _findRight(String stripChars) { + String s = getString(); for (int right = s.length(); --right >= 0;) { if (stripChars.indexOf(s.charAt(right)) < 0) { return right; @@ -1405,7 +1403,7 @@ protected final String _lstrip() { String s = getString(); // Leftmost non-whitespace character: cannot exceed length - int left = _stripLeft(s, s.length()); + int left = _findLeft(s.length()); return s.substring(left); } @@ -1429,7 +1427,7 @@ } else { String s = getString(); // Leftmost matching character: cannot exceed length - int left = _stripLeft(s, stripChars, s.length()); + int left = _findLeft(stripChars, s.length()); return s.substring(left); } } @@ -1492,15 +1490,14 @@ * @return a new String, stripped of the whitespace characters/bytes */ protected final String _rstrip() { - String s = getString(); // Rightmost non-whitespace - int right = _stripRight(s); + int right = _findRight(); if (right < 0) { // They're all whitespace return ""; } else { // Substring up to and including this rightmost non-whitespace - return s.substring(0, right + 1); + return getString().substring(0, right + 1); } } @@ -1522,11 +1519,10 @@ // Divert to the whitespace version return _rstrip(); } else { - String s = getString(); // Rightmost non-matching character - int right = _stripRight(s, stripChars); + int right = _findRight(stripChars); // Substring up to and including this rightmost non-matching character (or "") - return s.substring(0, right + 1); + return getString().substring(0, right + 1); } } @@ -1631,16 +1627,15 @@ } /** - * Helper function for .split, in str and unicode, - * splitting on white space and returning a list of the separated parts. If there are more than - * maxsplit feasible the last element of the list is the remainder of the original - * (this) string. The split sections will be {@link PyUnicode} if this object is a - * PyUnicode. + * Helper function for .split, in str and (when overridden) in + * unicode, splitting on white space and returning a list of the separated parts. + * If there are more than maxsplit feasible splits the last element of the list is + * the remainder of the original (this) string. * * @param maxsplit limit on the number of splits (if >=0) * @return PyList of split sections */ - private PyList splitfields(int maxsplit) { + protected PyList splitfields(int maxsplit) { /* * Result built here is a list of split parts, exactly as required for s.split(None, * maxsplit). If there are to be n splits, there will be n+1 elements in L. @@ -1660,7 +1655,7 @@ // Find the next occurrence of non-whitespace while (start < length) { - if (!Character.isWhitespace(s.charAt(start))) { + if (!BaseBytes.isspace((byte) s.charAt(start))) { // Break leaving start pointing at non-whitespace break; } @@ -1678,7 +1673,7 @@ } else { // The next segment runs up to the next next whitespace or end for (index = start; index < length; index++) { - if (Character.isWhitespace(s.charAt(index))) { + if (BaseBytes.isspace((byte) s.charAt(index))) { // Break leaving index pointing at whitespace break; } @@ -1883,16 +1878,15 @@ } /** - * Helper function for .rsplit, in str and unicode, - * splitting on white space and returning a list of the separated parts. If there are more than - * maxsplit feasible the first element of the list is the remainder of the original - * (this) string. The split sections will be {@link PyUnicode} if this object is a - * PyUnicode. + * Helper function for .rsplit, in str and (when overridden) in + * unicode, splitting on white space and returning a list of the separated parts. + * If there are more than maxsplit feasible splits the first element of the list is + * the remainder of the original (this) string. * * @param maxsplit limit on the number of splits (if >=0) * @return PyList of split sections */ - private PyList rsplitfields(int maxsplit) { + protected PyList rsplitfields(int maxsplit) { /* * Result built here (in reverse) is a list of split parts, exactly as required for * s.rsplit(None, maxsplit). If there are to be n splits, there will be n+1 elements. @@ -1912,7 +1906,7 @@ // Find the next occurrence of non-whitespace (working leftwards) while (end >= 0) { - if (!Character.isWhitespace(s.charAt(end))) { + if (!BaseBytes.isspace((byte) s.charAt(end))) { // Break leaving end pointing at non-whitespace break; } @@ -1930,7 +1924,7 @@ } else { // The next segment runs back to the next next whitespace or beginning for (index = end; index >= 0; --index) { - if (Character.isWhitespace(s.charAt(index))) { + if (BaseBytes.isspace((byte) s.charAt(index))) { // Break leaving index pointing at whitespace break; } diff --git a/src/org/python/core/PyUnicode.java b/src/org/python/core/PyUnicode.java --- a/src/org/python/core/PyUnicode.java +++ b/src/org/python/core/PyUnicode.java @@ -1209,6 +1209,15 @@ return new PyUnicode(buffer); } + /** Define what characters are to be treated as a space according to Python 2. */ + private static boolean isPythonSpace(int ch) { + // Use the Java built-in methods as far as possible + return Character.isWhitespace(ch) // catches the ASCII spaces and some others + || Character.isSpaceChar(ch) // catches remaining Unicode spaces + || ch == 0x0085 // NEXT LINE (not a space in Java) + || ch == 0x180e; // MONGOLIAN VOWEL SEPARATOR (not a space in Java 9+ or Python 3) + } + private static class StripIterator implements Iterator { private final Iterator iter; @@ -1231,7 +1240,7 @@ } else { while (iter.hasNext()) { int codePoint = iter.next(); - if (!Character.isWhitespace(codePoint)) { + if (!isPythonSpace(codePoint)) { lookahead = codePoint; return; } @@ -1350,6 +1359,30 @@ new StripIterator(sep, new ReversedIterator<>(newSubsequenceIterator())))); } + /** {@inheritDoc} */ + @Override + protected int _findLeft(int right) { + String s = getString(); + for (int left = 0; left < right; left++) { + if (!isPythonSpace(s.charAt(left))) { + return left; + } + } + return right; + } + + /** {@inheritDoc} */ + @Override + protected int _findRight() { + String s = getString(); + for (int right = s.length(); --right >= 0;) { + if (!isPythonSpace(s.charAt(right))) { + return right; + } + } + return -1; + } + @Override public PyTuple partition(PyObject sep) { return unicode_partition(sep); @@ -1418,7 +1451,7 @@ while (iter.hasNext()) { int codepoint = iter.next(); - if (Character.isWhitespace(codepoint)) { + if (isPythonSpace(codepoint)) { completeSeparator = true; if (!atBeginning) { inSeparator = true; @@ -1624,6 +1657,67 @@ } } + /** + * {@inheritDoc} The split sections will be {@link PyUnicode} and use the Python + * unicode definition of "space". + */ + @Override + protected PyList splitfields(int maxsplit) { + /* + * Result built here is a list of split parts, exactly as required for s.split(None, + * maxsplit). If there are to be n splits, there will be n+1 elements in L. + */ + PyList list = new PyList(); + + String s = getString(); + int length = s.length(), start = 0, splits = 0, index; + + if (maxsplit < 0) { + // Make all possible splits: there can't be more than: + maxsplit = length; + } + + // start is always the first character not consumed into a piece on the list + while (start < length) { + + // Find the next occurrence of non-whitespace + while (start < length) { + if (!isPythonSpace(s.charAt(start))) { + // Break leaving start pointing at non-whitespace + break; + } + start++; + } + + if (start >= length) { + // Only found whitespace so there is no next segment + break; + + } else if (splits >= maxsplit) { + // The next segment is the last and contains all characters up to the end + index = length; + + } else { + // The next segment runs up to the next next whitespace or end + for (index = start; index < length; index++) { + if (isPythonSpace(s.charAt(index))) { + // Break leaving index pointing at whitespace + break; + } + } + } + + // Make a piece from start up to index + list.append(fromSubstring(start, index)); + splits++; + + // Start next segment search at that point + start = index; + } + + return list; + } + @ExposedMethod(defaults = {"null", "-1"}, doc = BuiltinDocs.unicode_rsplit_doc) final PyList unicode_rsplit(PyObject sepObj, int maxsplit) { String sep = coerceToString(sepObj, true); @@ -1634,6 +1728,68 @@ } } + /** + * {@inheritDoc} The split sections will be {@link PyUnicode} and use the Python + * unicode definition of "space". + */ + @Override + protected PyList rsplitfields(int maxsplit) { + /* + * Result built here (in reverse) is a list of split parts, exactly as required for + * s.rsplit(None, maxsplit). If there are to be n splits, there will be n+1 elements. + */ + PyList list = new PyList(); + + String s = getString(); + int length = s.length(), end = length - 1, splits = 0, index; + + if (maxsplit < 0) { + // Make all possible splits: there can't be more than: + maxsplit = length; + } + + // end is always the rightmost character not consumed into a piece on the list + while (end >= 0) { + + // Find the next occurrence of non-whitespace (working leftwards) + while (end >= 0) { + if (!isPythonSpace(s.charAt(end))) { + // Break leaving end pointing at non-whitespace + break; + } + --end; + } + + if (end < 0) { + // Only found whitespace so there is no next segment + break; + + } else if (splits >= maxsplit) { + // The next segment is the last and contains all characters back to the beginning + index = -1; + + } else { + // The next segment runs back to the next next whitespace or beginning + for (index = end; index >= 0; --index) { + if (isPythonSpace(s.charAt(index))) { + // Break leaving index pointing at whitespace + break; + } + } + } + + // Make a piece from index+1 start up to end+1 + list.append(fromSubstring(index + 1, end + 1)); + splits++; + + // Start next segment search at that point + end = index; + } + + list.reverse(); + return list; + } + @ExposedMethod(defaults = "false", doc = BuiltinDocs.unicode___getslice___doc) final PyList unicode_splitlines(boolean keepends) { return new PyList(new LineSplitIterator(keepends)); @@ -2089,7 +2245,7 @@ return false; } for (Iterator iter = newSubsequenceIterator(); iter.hasNext();) { - if (!Character.isWhitespace(iter.next())) { + if (!isPythonSpace(iter.next())) { return false; } } @@ -2190,7 +2346,7 @@ int i = 0; for (Iterator iter = newSubsequenceIterator(); iter.hasNext(); i++) { int codePoint = iter.next(); - if (Character.isWhitespace(codePoint)) { + if (isPythonSpace(codePoint)) { sb.append(' '); continue; } @@ -2221,7 +2377,7 @@ StringBuilder sb = new StringBuilder(); for (int i = 0; i < getString().length(); i++) { char ch = getString().charAt(i); - if (Character.isWhitespace(ch)) { + if (isPythonSpace(ch)) { sb.append(' '); continue; } diff --git a/src/org/python/modules/sre/SRE_STATE.java b/src/org/python/modules/sre/SRE_STATE.java --- a/src/org/python/modules/sre/SRE_STATE.java +++ b/src/org/python/modules/sre/SRE_STATE.java @@ -25,16 +25,16 @@ import org.python.core.PyString; public class SRE_STATE { - + /* * Generated from Python-2.4.5 like 'python headerToJava.py < Modules/sre_constants.h' - * where headerToJava.py contains the following code + * where headerToJava.py contains the following code import sys for line in sys.stdin: if line.startswith('#define'): line = line.replace('#define', 'public static final int').strip() segs = line.split(' ') - print '%s = %s;' % (' '.join(segs[:-1]), segs[-1]) + print '%s = %s;' % (' '.join(segs[:-1]), segs[-1]) */ //BEGIN generated code public static final int SRE_MAGIC = 20031017; @@ -114,7 +114,7 @@ //From here we're including things from _sre.c in the order they're defined there public static final int USE_RECURSION_LIMIT = 5000; - + /* error codes */ public static final int SRE_ERROR_ILLEGAL = -1; public static final int SRE_ERROR_STATE = -2; @@ -194,7 +194,7 @@ return false; } } - + final boolean sre_category(int category, int ch) { switch (category) { @@ -230,9 +230,11 @@ return !Character.isDigit(ch); case SRE_CATEGORY_UNI_SPACE: - return Character.isSpaceChar(ch) || Character.isWhitespace(ch) || ch == 0x0085; + return Character.isSpaceChar(ch) || Character.isWhitespace(ch) || + ch == 0x0085 || ch == 0x180e; case SRE_CATEGORY_UNI_NOT_SPACE: - return !(Character.isSpaceChar(ch) || Character.isWhitespace(ch) || ch == 0x0085); + return !(Character.isSpaceChar(ch) || Character.isWhitespace(ch) || + ch == 0x0085 || ch == 0x180e); case SRE_CATEGORY_UNI_WORD: return Character.isLetterOrDigit(ch) || ch == '_'; @@ -293,7 +295,7 @@ } private void mark_restore(int lo, int hi, int mark_stack_base) { - + if (hi <= lo) return; @@ -305,7 +307,7 @@ System.arraycopy(mark_stack, this.mark_stack_base, mark, lo, size); } - + final boolean SRE_AT(int ptr, int at) { /* check if pointer is at given position. */ @@ -376,7 +378,7 @@ case SRE_OP_FAILURE: // TRACE(setidx, ch, "CHARSET FAILURE"); return !ok; - + case SRE_OP_LITERAL: // TRACE(setidx, ch, "CHARSET LITERAL " + set[setidx]); /* */ @@ -384,7 +386,7 @@ return ok; setidx++; break; - + case SRE_OP_CATEGORY: /* */ // TRACE(setidx, ch, "CHARSET CHARSET " + set[setidx]); @@ -400,13 +402,13 @@ // (set[setidx + (ch >> 4)] & (1 << (ch & 15))) != 0) // return ok; // setidx += 16; - + /* (32 bits per code word) */ if (ch < 256 && (set[setidx + (ch >> 5)] & (1 << (ch & 31))) != 0) return ok; setidx += 8; break; - + case SRE_OP_RANGE: /* */ // TRACE(setidx, ch, "CHARSET RANGE " + set[setidx] + " " + set[setidx+1]); @@ -419,11 +421,11 @@ // TRACE(setidx, ch, "CHARSET NEGATE"); ok = !ok; break; - + case SRE_OP_BIGCHARSET: /* <256 blockindices> */ // TRACE(setidx, ch, "CHARSET BIGCHARSET "); - + // count = *(set++); // if (!(ch & ~65535)) // block = ((unsigned char*)set)[ch >> 8]; @@ -434,7 +436,7 @@ // (set[block*8 + ((ch & 255)>>5)] & (1 << (ch & 31)))) // return ok; // set += count*8; - + int count = set[setidx++]; int block; if (ch < 65536) @@ -444,7 +446,7 @@ setidx += 64; if (block >= 0 && (set[setidx + block*8 + ((ch & 255)>>5)] & (1 << (ch & 31))) != 0) return ok; - setidx += count * 8; + setidx += count * 8; break; default: @@ -455,7 +457,7 @@ } } } - + private int SRE_COUNT(int[] pattern, int pidx, int maxcount, int level) { int chr; int ptr = this.ptr; @@ -474,7 +476,7 @@ while (ptr < end && SRE_CHARSET(pattern, pidx + 2, str[ptr])) ptr++; break; - + case SRE_OP_ANY: /* repeated dot wildcard. */ // TRACE(pidx, ptr, "COUNT ANY"); @@ -600,7 +602,7 @@ pidx++; ptr++; break; - + case SRE_OP_SUCCESS: /* end of pattern */ // TRACE(pidx, ptr, "SUCCESS"); @@ -758,7 +760,7 @@ } lastmark = this.lastmark; lastindex = this.lastindex; - + if (pattern[pidx + pattern[pidx]] == SRE_OP_LITERAL) { /* tail starts with a literal. skip positions where the rest of the pattern cannot possibly match */ @@ -796,7 +798,7 @@ } } return 0; - + case SRE_OP_MIN_REPEAT_ONE: /* match repeated sequence (minimizing regexp) */ @@ -962,7 +964,7 @@ this.ptr = ptr; return 0; } - + lastmark = this.lastmark; lastindex = this.lastindex; @@ -989,7 +991,7 @@ this.ptr = ptr; return 0; - + case SRE_OP_GROUPREF: /* match backreference */ i = pattern[pidx]; @@ -1023,7 +1025,7 @@ } pidx++; break; - + case SRE_OP_GROUPREF_EXISTS: i = pattern[pidx]; // TRACE(pidx, ptr, "GROUPREF_EXISTS " + i); @@ -1035,7 +1037,7 @@ } pidx += 2; break; - + case SRE_OP_ASSERT: /* assert subpattern */ /* args: */ @@ -1064,7 +1066,7 @@ } pidx += pattern[pidx]; break; - + case SRE_OP_FAILURE: /* immediate failure */ // TRACE(pidx, ptr, "FAILURE"); @@ -1087,7 +1089,7 @@ this.lastindex = lastindex; } } - + int SRE_SEARCH(int[] pattern, int pidx) { int ptr = this.start; int end = this.end; @@ -1328,7 +1330,7 @@ } // XXX - this is not UTF-16 compliant; also depends on whether from PyString or PyUnicode - + String getslice(int index, String string, boolean empty) { int i, j; -- Repository URL: https://hg.python.org/jython From jython-checkins at python.org Fri Oct 26 14:13:28 2018 From: jython-checkins at python.org (jeff.allen) Date: Fri, 26 Oct 2018 18:13:28 +0000 Subject: [Jython-checkins] =?utf-8?q?jython=3A_Skip_some_tests_that_fail_?= =?utf-8?b?b24gSmF2YSA5KyAoaXNzdWVzICMyMzYyLCAjMjYzMCwgIzI3MTAsICMyNzEx?= =?utf-8?b?KS4=?= Message-ID: <20181026181328.1.035B2F44CE10670C@mg.python.org> https://hg.python.org/jython/rev/5aa64ce0b8b8 changeset: 8191:5aa64ce0b8b8 user: Jeff Allen date: Fri Oct 26 17:56:54 2018 +0100 summary: Skip some tests that fail on Java 9+ (issues #2362, #2630, #2710, #2711). The aim is to keep build-bots green so we can see other problems as they arise, and later to fix the issues captured at b.j.o.. files: Lib/test/test_classpathimporter.py | 3 +++ Lib/test/test_dict.py | 2 ++ Lib/test/test_import_jy.py | 3 +++ Lib/test/test_socket_jy.py | 9 ++++++--- Lib/test/test_ssl.py | 6 +++--- 5 files changed, 17 insertions(+), 6 deletions(-) diff --git a/Lib/test/test_classpathimporter.py b/Lib/test/test_classpathimporter.py --- a/Lib/test/test_classpathimporter.py +++ b/Lib/test/test_classpathimporter.py @@ -121,6 +121,9 @@ self.assertFalse(loader.is_package('jar_pkg.prefer_compiled')) @unittest.skipIf(test_support.is_jython_posix, "FIXME: failing on Linux issue #2422") + @unittest.skipIf(test_support.get_java_version() >= (9,), + "Fails on Java 9+. See b.j.o. issue #2362") # FIXME + # Probably related to Java modules: ensure also works outside java.base def test_loader_get_code(self): # Execute Python code out of the JAR jar = self.prepareJar('classimport.jar') diff --git a/Lib/test/test_dict.py b/Lib/test/test_dict.py --- a/Lib/test/test_dict.py +++ b/Lib/test/test_dict.py @@ -312,6 +312,8 @@ x.fail = True self.assertRaises(Exc, d.setdefault, x, []) + @unittest.skipIf(test_support.is_jython and test_support.get_java_version() >= (9,), + "Fails on Java 9+. See b.j.o. issue 2711.") def test_setdefault_atomic(self): # Issue #13521: setdefault() calls __hash__ and __eq__ only once. class Hashed(object): diff --git a/Lib/test/test_import_jy.py b/Lib/test/test_import_jy.py --- a/Lib/test/test_import_jy.py +++ b/Lib/test/test_import_jy.py @@ -172,6 +172,9 @@ # causes a stack overflow if the bug occurs self.assertRaises(Exception, getattr, anygui, 'abc') + @unittest.skipIf(test_support.get_java_version() >= (9,), + "Fails on Java 9+. See b.j.o. issue #2362") # FIXME + # Probably related to Java modules: ensure also works outside java.base def test_import_star(self): self.assertEquals(0, subprocess.call( [sys.executable, test_support.findfile("import_star_from_java.py")])) diff --git a/Lib/test/test_socket_jy.py b/Lib/test/test_socket_jy.py --- a/Lib/test/test_socket_jy.py +++ b/Lib/test/test_socket_jy.py @@ -67,7 +67,7 @@ target=self.do_nonblocking_connection, name="socket-workout-%s" % i, args=(connect_results, i))) - + for thread in connect_threads: thread.start() for thread in connect_threads: @@ -79,6 +79,7 @@ # Tests fix for http://bugs.jython.org/issue2428; based in part on the # code showing failure that was submitted with that bug for result in self.do_workout(): + if len(result) == 0: self.fail("A socket-workout thread failed to run") self.assertIn(result[0], {errno.EINPROGRESS, errno.EISCONN}) self.assertEqual(result[-1], errno.EISCONN) for code in result[1:-1]: @@ -124,25 +125,27 @@ target=self.do_nonblocking_connection, name="socket-workout-%s" % i, args=(connect_results, i))) - + for thread in connect_threads: thread.start() for thread in connect_threads: thread.join() return connect_results + @unittest.skipIf(test_support.is_jython and test_support.get_java_version() >= (9,), # FIXME + "Fails on Java 9+. See b.j.o. issue #2710") def test_connect_ex_workout(self): """Verify connect_ex states go through EINPROGRESS?, EALREADY*, EISCONN""" # Tests fix for http://bugs.jython.org/issue2428; based in part on the # code showing failure that was submitted with that bug for result in self.do_workout(): + if len(result) == 0: self.fail("A socket-workout thread failed to run") self.assertIn(result[0], {errno.EINPROGRESS, errno.EISCONN}) self.assertEqual(result[-1], errno.EISCONN) for code in result[1:-1]: self.assertEqual(code, errno.EALREADY) - class SocketOptionsTest(unittest.TestCase): def test_socket_options_defined(self): diff --git a/Lib/test/test_ssl.py b/Lib/test/test_ssl.py --- a/Lib/test/test_ssl.py +++ b/Lib/test/test_ssl.py @@ -338,7 +338,7 @@ "certfile must be specified for server-side operations", ssl.wrap_socket, sock, server_side=True, certfile="") if support.get_java_version() < (9,): - # Possible FIXME similar issue as seen in + # FIXME: Fails on Java 9+. See b.j.o. issue #2710. A similar issue is seen in # test_load_cert_chain - apparently this RSA 1024 cert is too weak and gets a # java.security.KeyStoreException: Key protection algorithm not found before the # ValueError raised on earlier versions of Java; @@ -787,8 +787,8 @@ # Combined key and cert in a single file ctx.load_cert_chain(CERTFILE, keyfile=None) if support.get_java_version() < (9,): - # Possible FIXME we may be skipping this test on Java 9 unnecessarily. - # CERTFILE as generated uses RSA 1024, which is considered too weak. + # FIXME: Fails on Java 9+. See b.j.o. issue #2710. A similar issue is seen in + # test_errors. CERTFILE as generated uses RSA 1024, which is considered too weak. # This may be why this raises an error on Java 9: # java.security.KeyStoreException: Key protection algorithm not found: # java.security.KeyStoreException: Certificate chain is not valid -- Repository URL: https://hg.python.org/jython From jython-checkins at python.org Fri Oct 26 14:13:28 2018 From: jython-checkins at python.org (jeff.allen) Date: Fri, 26 Oct 2018 18:13:28 +0000 Subject: [Jython-checkins] =?utf-8?q?jython=3A_Use_python=2Epath_instead_?= =?utf-8?q?of_JYTHONPATH_=232706=2E?= Message-ID: <20181026181328.1.4EB995BC0FB1A517@mg.python.org> https://hg.python.org/jython/rev/4057a7c55133 changeset: 8192:4057a7c55133 user: Jeff Allen date: Fri Oct 26 17:59:50 2018 +0100 summary: Use python.path instead of JYTHONPATH #2706. The change restricts the influence of JYTHONPATH to org.python.util.jython (the jython command), and makes it subject to the -E option (ignore environment). JYTHONPATH will no longer affect sys.path in applications that create their own interpreter. The system/registry property python.path continues to have that role. files: Lib/test/test_java_integration.py | 11 ++++------- NEWS | 6 ++++++ src/org/python/core/PySystemState.java | 9 --------- src/org/python/util/jython.java | 4 ++++ 4 files changed, 14 insertions(+), 16 deletions(-) diff --git a/Lib/test/test_java_integration.py b/Lib/test/test_java_integration.py --- a/Lib/test/test_java_integration.py +++ b/Lib/test/test_java_integration.py @@ -728,14 +728,12 @@ jars = find_jython_jars() jars.append(proxies_jar_path) classpath = os.pathsep.join(jars) - env = dict(os.environ) - env.update(JYTHONPATH=os.path.dirname(__file__)) cmd = [os.path.join(System.getProperty("java.home"), "bin", "java"), + "-Dpython.path=" + os.path.dirname(__file__), "-classpath", classpath, "javatests.ProxyDeserialization", cat_path] - self.assertEqual(subprocess.check_output(cmd, env=env, universal_newlines=True), - "meow\n") + self.assertEqual(subprocess.check_output(cmd, universal_newlines=True), "meow\n") finally: org.python.core.Options.proxyDebugDirectory = old_proxy_debug_dir shutil.rmtree(tempdir) @@ -792,11 +790,10 @@ # the proxy classpath += os.pathsep + tempdir cmd = [os.path.join(System.getProperty("java.home"), "bin", "java"), + "-Dpython.path=" + os.path.dirname(__file__), "-classpath", classpath, "BarkTheDog"] - env = dict(os.environ) - env.update(JYTHONPATH=os.path.dirname(__file__)) self.assertRegexpMatches( - subprocess.check_output(cmd, env=env, universal_newlines=True, + subprocess.check_output(cmd, universal_newlines=True, stderr=subprocess.STDOUT), r"^Class defined on CLASSPATH \n" "Rover barks 42 times$") diff --git a/NEWS b/NEWS --- a/NEWS +++ b/NEWS @@ -4,6 +4,7 @@ Development tip Bugs fixed + - [ 2706 ] Use python.path instead of JYTHONPATH - [ 2410 ] Regression in PySystemStateTest (leading slash) - [ 2639 ] Incorrect result when using != comparison against Java {List, Set, Map} - [ 2672 ] Integer formatting emits two minus signs with -2^31 @@ -26,6 +27,11 @@ treatment of the -i option. This simplifies support, and may also make it unnecessary for users to work around differences from CPython. - python.startup registry property (and JYTHONSTARTUP environment variable) added. + - Only the Jython command (class org.python.util.jython) now reads the environment variable + JYTHONPATH, not the core runtime, abd it respects the -E option (ignore environment). This + change is for consistency with CPython and with our handling of other environment variables. + A pure Java application that creates its own interpreter may use the system or registry + key "python.path" to add to sys.path, as documented. Jython 2.7.2a1 Bugs fixed diff --git a/src/org/python/core/PySystemState.java b/src/org/python/core/PySystemState.java --- a/src/org/python/core/PySystemState.java +++ b/src/org/python/core/PySystemState.java @@ -955,15 +955,6 @@ if (exec_prefix != null) { PySystemState.exec_prefix = Py.fileSystemEncode(exec_prefix); } - try { - // XXX: Respect or ignore Options.ignore_environment? - String jythonpath = System.getenv("JYTHONPATH"); - if (jythonpath != null) { - registry.setProperty("python.path", jythonpath); - } - } catch (SecurityException e) { - // Continue - } // Now the post properties (possibly set by custom JythonInitializer). registry.putAll(postProperties); diff --git a/src/org/python/util/jython.java b/src/org/python/util/jython.java --- a/src/org/python/util/jython.java +++ b/src/org/python/util/jython.java @@ -608,6 +608,10 @@ * @param registry to be (possibly) updated */ private static void addDefaultsFromEnvironment(Properties registry) { + + // Pick up the path from the environment + addDefault(registry, "python.path", getenv("JYTHONPATH")); + // Runs at the start of each (wholly) interactive session. addDefault(registry, "python.startup", getenv("JYTHONSTARTUP")); // Go interactive after script. (PYTHONINSPECT because Python scripts may set it.) -- Repository URL: https://hg.python.org/jython