Skip to content

Commit 9f8c25f

Browse files
committed
[GR-17357] Add Jython emulation flag to make imports and catching Java exceptions work more like Jython
PullRequest: graalpython/614
2 parents a61abd6 + 511d9ff commit 9f8c25f

File tree

29 files changed

+829
-677
lines changed

29 files changed

+829
-677
lines changed

CHANGELOG.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,8 @@ language runtime. The main focus is on user-observable behavior of the engine.
1515
* Fix `dict.__contains__` for dictionaries with only `str` keys for subclasses of `str`
1616
* Support NumPy 1.16.4 and Pandas 0.25.0
1717
* Support `timeit` module
18-
* Support importing Java classes using normal Python import syntax when the package is known
1918
* Improve performance across many Python benchmarks
19+
* Add a new `--python.EmulateJython` flag to support importing Java classes using normal Python import syntax when the package is known and to catch Java exceptions from Python code
2020

2121
## Version 19.2.0
2222

ci.jsonnet

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ local builder = import 'ci_common/builder.libsonnet';
2727
builder.testGateTime(type="tagged-unittest", platform="darwin", timelimit=const.TIME_LIMIT["2h"]),
2828
builder.testGate(type="svm-unittest", platform="linux"),
2929
builder.testGate(type="svm-unittest", platform="darwin"),
30+
builder.testGate(type="unittest-jython", platform="linux"),
3031

3132
// junit
3233
builder.testGate(type="junit", platform="linux"),

doc/CONTRIBUTING.md

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,3 +98,67 @@ For debugging java implemented code execute:
9898

9999
The command will also start a debug server, which can be used in an IDE. If the IDE was initialized properly
100100
by using the command mentioned above, the existing `GraalDebug` run configuration can be used to debug.
101+
102+
### Advanced commands to develop and debug
103+
104+
Here are some advanced commands to debug test failures and fix issues.
105+
106+
First, we have three sets of unittests in the base repository:
107+
1. Our own Python-bases unittests
108+
2. JUnit tests
109+
3. Python's standard library tests
110+
111+
To run the first, you can use this command:
112+
113+
mx python-gate --tags python-unittest
114+
115+
If some of the tests fail, you can re-run just a single test like this,
116+
substituting TEST-PATTERN (and possibly the file glob on the third line) with
117+
the test you want to run. Note that you can insert `-d` to debug on the Java
118+
level or use `--inspect` to debug in the Chrome debugger.
119+
120+
mx [-d] python3 [--inspect] \
121+
graalpython/com.oracle.graal.python.test/src/graalpytest.py \
122+
graalpython/com.oracle.graal.python.test/src/tests/test_*.py \
123+
-k TEST-PATTERN
124+
125+
To run the JUnit tests, you can use this command:
126+
127+
mx python-gate --tags python-junit
128+
129+
To run a subset of the tests, you can use the following. Again, you can use `-d`
130+
to attach with a Java debugger.
131+
132+
mx [-d] unittest JAVA-TEST-CLASSNAME
133+
134+
To run the Python standard library tests, you can use the following:
135+
136+
mx python-gate --tags python-tagged-unittest
137+
138+
Note that we use "tag files", small `.txt` files that select which tests to run,
139+
so we only run tests that we know should pass. To run a subset of those tests,
140+
use the following command. However, the way we run those tests is by spawning a
141+
sub-process for every stdlib tests, to avoid interference while our
142+
implementation isn't quite ready, so you have to put the flags somewhere else to
143+
debug. You can see `-debug-java` and `--inspect` below, to debug in Java
144+
debugger or Chromium, respectively.
145+
146+
ENABLE_CPYTHON_TAGGED_UNITTESTS=true mx python3 \
147+
graalpython/com.oracle.graal.python.test/src/graalpytest.py \
148+
[-debug-java] [--inspect] \
149+
graalpython/com.oracle.graal.python.test/src/tests/test_tagged_unittests.py \
150+
-k NAME-OF-CPYTHON-UNITTEST
151+
152+
There's also multiple other gates that may fail with changes. One of these is
153+
our *style* gate, which checks formatting rules and copyrights. To auto-fix most
154+
issues, run the following command. Anything that's reported as error after this
155+
command you have to fix manually.
156+
157+
mx python-style --fix
158+
159+
Another important gate is the gate that checks if you broke the native image
160+
building. To test if building a native image still works, you can use the
161+
following command. This will create a native executable called `graalpython` and
162+
print its path as the last output, if successful.
163+
164+
mx python-svm

doc/JYTHON.md

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
# About Jython Compatibility
2+
3+
Full Jython compatibility is not a goal of this project. One major reason for
4+
this is that most Jython code that uses Java integration will be based on a
5+
stable Jython release, and these only come in Python 2.x versions. The GraalVM
6+
implementation of Python, in contrast, is only targeting Python 3.x.
7+
8+
Nonetheless, there are certain features of Jython's Java integration that we can
9+
offer similarly. Some features are more expensive to offer, and thus are hidden
10+
behind a commandline flag, `--python.EmulateJython`.
11+
12+
## Importing Java classes and packages
13+
14+
In the default mode, Java classes can only be imported through the `java`
15+
package. Additionally, only Java classes can be imported, not packages. In
16+
Jython compatibility mode, importing works more directly, at the cost worse
17+
performance for import failures in the general case.
18+
19+
#### Normal mode
20+
21+
Suppose you want to import the class `sun.misc.Signal`, normal usage looks like
22+
this:
23+
24+
import java.sun.misc.Signal as Signal
25+
26+
For the `java` namespace, you do not have to repeat the `java` name (but can):
27+
28+
import java.lang.System as System
29+
30+
#### Jython mode
31+
32+
In Jython mode, Java packages can be imported and traversed, and they can be
33+
imported directly without going through the `java` module.
34+
35+
import sun.misc.Signal as Signal
36+
37+
import org.antlr
38+
39+
type(org.antlr.v4.runtime) # => module
40+
org.antlr.v4.runtime.Token
41+
42+
The downside of this is that everytime an import fails (and there are many
43+
speculative imports in the standard library), we ask the Java classloader to
44+
list currently available packages and traverse them to check if we should create
45+
a Java package. This slows down startup significantly.
46+
47+
## Interacting with Java objects
48+
49+
Once you get hold of a Java object or class, interaction in both modes works
50+
naturally. Public fields and methods can be read and invoked as expected.
51+
52+
## Subclassing Java classes and implementing interfaces with Python classes
53+
54+
This is not supported at all right now, there's no emulation available even in
55+
Jython compatibility mode. We have not seen many uses of this in the wild. Let
56+
us know if this is of interest to you!
57+
58+
## Catching Java exceptions
59+
60+
By default this is not allowed, because of the additional cost of checking for
61+
Java exceptions in the except statement execution. However, in Jython
62+
compatibility mode, the common case of catching a Java exception directly works:
63+
64+
import java.lang.NumberFormatException as NumberFormatException
65+
import java.lang.Integer as Integer
66+
67+
try:
68+
Integer.parseInt("99", 8)
69+
except NumberFormatException as e:
70+
pass
71+
72+
Note that even in this mode, Java exceptions are never caught by generic except
73+
handlers, so this *will not* work:
74+
75+
import java.lang.Integer as Integer
76+
77+
try:
78+
Integer.parseInt("99", 8)
79+
except:
80+
pass

graalpython/com.oracle.graal.python.cext/src/abstract.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -546,5 +546,5 @@ PyObject* PySequence_InPlaceRepeat(PyObject *o, Py_ssize_t count) {
546546

547547
UPCALL_ID(PySequence_InPlaceConcat);
548548
PyObject* PySequence_InPlaceConcat(PyObject *s, PyObject *o) {
549-
return UPCALL_CEXT_O(_jls_PySequence_Concat, native_to_java(s), native_to_java(o));
549+
return UPCALL_CEXT_O(_jls_PySequence_InPlaceConcat, native_to_java(s), native_to_java(o));
550550
}

graalpython/com.oracle.graal.python.test/src/graalpytest.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,9 +60,11 @@ class ThreadPool():
6060
if os.environ.get(b"ENABLE_THREADED_GRAALPYTEST") == b"true":
6161
maxcnt = min(os.cpu_count(), 16)
6262
sleep = time.sleep
63+
start_new_thread = _thread.start_new_thread
6364
print("Running with %d threads" % maxcnt)
6465
else:
6566
sleep = lambda x: x
67+
start_new_thread = lambda f, args: f(*args)
6668
maxcnt = 1
6769

6870
@classmethod
@@ -73,7 +75,7 @@ def runner():
7375
function()
7476
finally:
7577
self.release_token()
76-
_thread.start_new_thread(runner, ())
78+
self.start_new_thread(runner, ())
7779
self.sleep(0.5)
7880

7981
@classmethod

graalpython/com.oracle.graal.python.test/src/tests/cpyext/test_abstract.py

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -366,8 +366,8 @@ def compile_module(self, name):
366366

367367
test_PyNumber_Lshift = CPyExtFunction(
368368
lambda args: args[0] << args[1],
369-
lambda: (
370-
(0, 0),
369+
lambda: (
370+
(0, 0),
371371
(0, -1),
372372
(3, 2),
373373
(10, 5),
@@ -875,7 +875,7 @@ def compile_module(self, name):
875875
)
876876

877877
test_PySequence_InPlaceConcat = CPyExtFunction(
878-
lambda args: args[0] + args[1],
878+
lambda args: args[0] + list(args[1]) if isinstance(args[0], list) else args[0] + args[1],
879879
lambda: (
880880
((1,), tuple()),
881881
((1,), list()),
@@ -895,4 +895,3 @@ def compile_module(self, name):
895895
arguments=["PyObject* s", "PyObject* o"],
896896
cmpfunc=unhandled_error_compare
897897
)
898-

graalpython/com.oracle.graal.python.test/src/tests/test_interop.py

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -338,14 +338,24 @@ def test_java_imports():
338338
from java.util import ArrayList
339339
assert repr(ArrayList()) == "[]"
340340

341-
assert java.util.ArrayList == ArrayList
341+
if sys.graal_python_jython_emulation_enabled:
342+
assert java.util.ArrayList == ArrayList
342343

343-
import sun
344-
assert type(sun.misc) is type(java)
344+
import sun
345+
assert type(sun.misc) is type(java)
345346

346-
import sun.misc.Signal
347-
assert sun.misc.Signal is not None
347+
import sun.misc.Signal
348+
assert sun.misc.Signal is not None
348349

350+
def test_java_exceptions():
351+
if sys.graal_python_jython_emulation_enabled:
352+
from java.lang import Integer, NumberFormatException
353+
try:
354+
Integer.parseInt("99", 8)
355+
except NumberFormatException as e:
356+
assert True
357+
else:
358+
assert False
349359

350360
def test_foreign_object_does_not_leak_Javas_toString():
351361
try:

graalpython/com.oracle.graal.python.test/src/tests/test_re.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,8 @@ def test_none_value():
9090
if special or text ])
9191
n = next(stream)
9292
assert not n[0]
93-
assert str(n[0]) == 'None'
93+
# GR-17928
94+
# assert str(n[0]) == 'None'
9495

9596
class S(str):
9697
def __getitem__(self, index):
@@ -457,5 +458,3 @@ def test_finditer_empty_string(self):
457458
r"(//?| ==?)|([[]]+)")
458459
for m in regex.finditer(''):
459460
self.fail()
460-
461-

graalpython/com.oracle.graal.python.test/src/tests/test_tagged_unittests.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,12 @@ class TestAllWorkingTests():
8080
for idx, working_test in enumerate(WORKING_TESTS):
8181
def make_test_func(working_test):
8282
def fun(self):
83-
cmd = [sys.executable, "-S", "-m", "unittest"]
83+
cmd = [sys.executable]
84+
if "--inspect" in sys.argv:
85+
cmd.append("--inspect")
86+
if "-debug-java" in sys.argv:
87+
cmd.append("-debug-java")
88+
cmd += ["-S", "-m", "unittest"]
8489
for testpattern in working_test[1]:
8590
cmd.extend(["-k", testpattern])
8691
testmod = working_test[0].rpartition(".")[2]

0 commit comments

Comments
 (0)