Skip to content

Commit

Permalink
Merge pull request #555 from Thrameos/coverage
Browse files Browse the repository at this point in the history
Work on increasing coverage
  • Loading branch information
marscher committed Jan 19, 2020
2 parents afe8a8b + 6519a4e commit e1741e2
Show file tree
Hide file tree
Showing 90 changed files with 2,248 additions and 1,568 deletions.
35 changes: 0 additions & 35 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,6 @@ os:
language: python

python:
- '2.7'
- '3.4'
- '3.5'
- '3.6'
- 'nightly'
Expand Down Expand Up @@ -34,27 +32,6 @@ matrix:
- python: pypy3

include:
- name: "Python 2.7 on Xenial Linux (w/o NUMPY)"
python: 2.7
dist: xenial
language: python
env:
- NUMPY="--global-option=--disable-numpy"

- name: "Python 2.7 on Xenial Linux"
python: 2.7
dist: xenial
language: python

# TODO: remove in 0.8
- name: "Python 2.7 on Xenial Linux"
python: 2.7
dist: xenial
language: python
script:
- $PYTHON -c "import jpype"
- $PYTHON -m pytest -v test/jpypetest --convertStrings

- name: "Python 3.7 on Xenial Linux (w/o NUMPY)"
python: 3.7
dist: xenial
Expand Down Expand Up @@ -104,18 +81,6 @@ matrix:
- $PYTHON -c "import jpype"
- $PYTHON -m pytest -v test/jpypetest --convertStrings

- name: "Python 2.7 on macOS"
os: osx
osx_image: xcode10.2
language: java
env:
- PYTHON=python
- INST=--user
addons:
homebrew:
packages:
- ant

- name: "Python 3.7 on macOS"
os: osx
osx_image: xcode10.2
Expand Down
38 changes: 0 additions & 38 deletions appveyor.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,49 +12,11 @@ environment:

matrix:

## This one is failing from gcc died with sig 11.
## This error is apparently common when building python 2 modules with cygwin32.
## The failure depends on the windows version and will cause a failure
## on appveyor depending on which image is used.
## We will disable it for now
#
# - PYTHON: "python2"
# CYGWIN: "C:\\cygwin"
# ARCH: x86
# CYGSH: C:\Cygwin\bin\bash -c

## This one is failing due to a segfault when executing the first test in
## the test suite. We have been unable to replicate it outside of appveyor
## We will disable it for now
#
# - PYTHON: "python3"
# CYGWIN: "C:\\cygwin"
# ARCH: x86
# CYGSH: C:\Cygwin\bin\bash -c
# CINST_OPTS: --x86

## Python 2 on this platform is failing in the multi-threaded test.
## It is not clear if the issue is with the python build or incorrect
## locking code in jpype.
## We will disable it for now
# - PYTHON: "python2"
# CYGWIN: "C:\\cygwin64"
# ARCH: x86_64
# CYGSH: C:\Cygwin64\bin\bash -c

- PYTHON: "python3.7"
CYGWIN: "C:\\cygwin64"
ARCH: x86_64
CYGSH: C:\Cygwin64\bin\bash -c

- PYTHON: "C:\\Miniconda"
ARCH: x86
CONDA_PY: "2-latest"

- PYTHON: "C:\\Miniconda-x64"
ARCH: x86_64
CONDA_PY: "2-latest"

- PYTHON: "C:\\Miniconda3"
ARCH: x86
CONDA_PY: "3-latest"
Expand Down
78 changes: 62 additions & 16 deletions doc/userguide.rst
Original file line number Diff line number Diff line change
Expand Up @@ -616,6 +616,9 @@ tied to the JVM that is started or attached. Thus operating more than one
JVM does not appear to be possible under the current implementation.
Difficulties that would need to be overcome to remove this limitation include:

- All available JVM implementations support on one JVM instance per
process. Thus a communication layer would have to proxy JNI
class from JPype to another process.
- Which JVM would a static class method call. Thus the class types
would need to be JVM specific (ie. ``JClass('org.MyObject', jvm=JVM1)``)
- How would can a wrapper for two different JVM coexist in the
Expand All @@ -630,6 +633,51 @@ Difficulties that would need to be overcome to remove this limitation include:
Thus it appears prohibitive to support multiple JVMs in the JPype
class model.

Working with Multiprocessing
~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Because only one JVM can be started per process, JPype cannot be used
with processes created with fork. Forks copy all memory including the
JVM. The copied JVM usually will not function properly thus
JPype cannot support multiprocessing using fork.

To use multiprocessing with JPype, processes must be created with
"spawn". As the multiprocessing context is usually selected at
the start and the default for unix is fork, this requires the creating
the appropraite spawn context. To launch multiprocessing properly
the following receipe can be used.

.. code-block:: python
import multiprocessing as mp
ctx = mp.get_context("spawn")
process = ctx.Process(...)
queue = ctx.Queue()
...
Also when using multiprocessing, Java objects cannot be sent
through the default Queue methods as it used pickle without
any Java support. This can be overcome by wrapping Queue to first
encode to a byte stream using the JPickle package. By wrapping
a Queue with the Java pickler any serializable Java object can
be transferred between processes.

In addition, a standard Queue will not produce an error if is unable to
pickle a Java object. This can cause deadlocks when using
multiprocessing IPC thus wrapping any Queue is required.


Errors reported by Python fault handler
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
The JVM takes over the standard fault handlers resulting in unusual
behavior if Python handlers are installed. As part of normal operations
the JVM will trigger a segmentation fault when starting and when
interrupting threads. Pythons faulthandler can intercept these operations
thus reporting extraneous fault messages or preventing normal JVM
operations if Python handles it. When operating with JPype, Python
faulthandler module should be disabled.


Unloading the JVM
~~~~~~~~~~~~~~~~~
Expand All @@ -648,9 +696,7 @@ advantage of it. As the time of writing, the latest stable Sun JVM was
Unsupported Python versions
~~~~~~~~~~~~~~~~~~~~~~~~~~~

PyPy 2.7 has issues with the Python meta class programming. PyPy 3 appears
to work, but does not have very aggressive memory deallocation. Thus PyPy
3 fails the leak test.
CPython 2 support was removed starting in 2020.


Unsupported Java virtual machines
Expand All @@ -662,8 +708,8 @@ Cygwin
~~~~~~

Cygwin is currently usable in JPype, but has a number of issues for
which there is no current solution. The python2 compilation on cygwin has
bugs in a threading implementation that lead to crashes in the test bench.
which there is no current solution.

Cygwin does not appear to pass environment variables to the JVM properly
resulting in unusual behavior with certain windows calls. The path
separator for Cygwin does not match that of the Java dll, thus specification
Expand All @@ -672,7 +718,7 @@ of class paths must account for this. Subject to these issues JPype is usable.
PyPy
~~~~

The GC routine in PyPy does not play well with Java. It runs when it thinks
The GC routine in PyPy 3 does not play well with Java. It runs when it thinks
that Python is running out of resources. Thus a code that allocates a lot
of Java memory and deletes the Python objects will still be holding the
Java memory until Python is garbage collected. This means that out of
Expand All @@ -684,10 +730,10 @@ Advanced Topics
Using JPype for debugging Java code
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

One common use of JPype is not to develop programs in Python, but rather to
One common use of JPype is not to develop programs in Python, but rather to
function as a Read-Eval-Print Loop for Java. When operating Java though
Python as a method of developing or debugging Java there are a few tricks
that can be used to simplify the job, beyond being able to probe and plot the
that can be used to simplify the job, beyond being able to probe and plot the
Java data structures interactively. These methods include:

1) Attaching a debugger to the Java JVM being run under JPype.
Expand All @@ -702,18 +748,18 @@ Attaching a Debugger

Interacting with Java through a shell is great, but sometimes it is necessary
to drop down to a debugger. To make this happen we need to start the JVM
with options to support remote debugging.
with options to support remote debugging.

.. code-block:: python
jpype.startJVM("-Xint", "-Xdebug", "-Xnoagent",
jpype.startJVM("-Xint", "-Xdebug", "-Xnoagent",
"-Xrunjdwp:transport=dt_socket,server=y,address=12999,suspend=n")
Then add a marker in your program when it is time to attach the debugger
in the form of a pause statement.

.. code-block:: python
input("pause to attach debugger")
myobj.callProblematicMethod()
Expand All @@ -729,11 +775,11 @@ a breakpoint is hit.
Attach data to an Exception
:::::::::::::::::::::::::::

Sometimes getting to the level of a debugger is challenging especially if the
Sometimes getting to the level of a debugger is challenging especially if the
code is large and error occurs rarely. In this case it is often benefitial to
simply attach data to an exception. To do this, we need to write a small
utility class. Java exceptions are not strictly speaking expandable, but
they can be chained. Thus, it we create a dummy exception holding a
utility class. Java exceptions are not strictly speaking expandable, but
they can be chained. Thus, it we create a dummy exception holding a
``java.util.Map`` and attach it to as the cause of the exception, it will be
passed back down the call stack until it reaches Python. We can then use
``getCause()`` to retrieve the map containing the relevant data.
Expand All @@ -749,12 +795,12 @@ use this option, we simply make sure all of the classes in Java that we are
using are Serializable, then add a condition that detects the faulty algorithm
state. When the fault occurs, we create a ``java.util.HashMap`` and populate
it with the values we wish to be able to examine from within Python. We then
use Java serialization to write this state file to disk. We then execute the
use Java serialization to write this state file to disk. We then execute the
program and collect the resulting state files.

We can then return later with an interactive Python shell, and launch JPype
with a classpath for the jars and possibly a connection to debugger.
We load the state file into memory and we can then probe or execute the
We load the state file into memory and we can then probe or execute the
methods that lead up to the fault.


6 changes: 4 additions & 2 deletions jpype/_classpath.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ def _splitpath(path):
return parts

def _posix2win(directory):
if len(directory)>3 and directory[1:3]==":\\":
return directory
root = _get_root()
directory = _os.path.abspath(directory)
paths = _splitpath(directory)
Expand All @@ -55,7 +57,7 @@ def addClassPath(path1):
glob pattern.
Arguments:
path(str):
path(str):
"""
global _CLASSPATHS
Expand All @@ -72,7 +74,7 @@ def getClassPath(env=True):
Includes user added paths and the environment CLASSPATH.
Arguments:
env(Optional, bool): If true then environment is included.
env(Optional, bool): If true then environment is included.
(default True)
"""
global _CLASSPATHS
Expand Down
12 changes: 7 additions & 5 deletions jpype/_core.py
Original file line number Diff line number Diff line change
Expand Up @@ -106,13 +106,15 @@ def _hasClassPath(args):
def _handleClassPath(clsList):
out = []
for s in clsList:
if not isinstance(s, (str, _jtypes._unicode)):
if not isinstance(s, str):
raise TypeError("Classpath elements must be strings")
if s.endswith('*'):
import glob
out.extend(glob.glob(s+'.jar'))
else:
out.append(s)
if _sys.platform == "cygwin":
out = [ _classpath._posix2win(i) for i in out]
return _classpath._SEP.join(out)


Expand Down Expand Up @@ -143,10 +145,10 @@ def startJVM(*args, **kwargs):
cast to Python strings. This option is to support legacy code
for which conversion of Python strings was the default. This
will globally change the behavior of all calls using
strings, and a value of True is NOT recommended for newly
strings, and a value of True is NOT recommended for newly
developed code.
The default value for this option during 0.7 series is
The default value for this option during 0.7 series is
True. The option will be False starting in 0.8. A
warning will be issued if this option is not specified
during the transition period.
Expand Down Expand Up @@ -196,7 +198,7 @@ def startJVM(*args, **kwargs):

# Handle strings and list of strings.
if classpath:
if isinstance(classpath, (str, _jtypes._unicode)):
if isinstance(classpath, str):
args.append('-Djava.class.path=%s' % _handleClassPath([classpath]))
elif hasattr(classpath, '__iter__'):
args.append('-Djava.class.path=%s' % _handleClassPath(classpath))
Expand Down Expand Up @@ -367,7 +369,7 @@ def getJVMVersion():
runtime = _jclass.JClass('java.lang.Runtime')
version = runtime.class_.getPackage().getImplementationVersion()

# Java 9+ has a version method
# Java 9+ has a version method
if not version:
version = runtime.version()
version = (re.match("([0-9.]+)", str(version)).group(1))
Expand Down
Loading

0 comments on commit e1741e2

Please sign in to comment.