diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..d709909 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,13 @@ +language: python +jdk: oraclejdk8 +python: + - "3.6" +branches: + only: + - master + - "/[0-9]+\\.[0-9]+\\.[0-9]+.*/" +install: true +script: ".travis/build.sh" +cache: + directories: + - "~/.m2/repository" diff --git a/.travis/build.sh b/.travis/build.sh new file mode 100755 index 0000000..797c22f --- /dev/null +++ b/.travis/build.sh @@ -0,0 +1,5 @@ +python setup.py test +pip install . +python -c 'import sys; sys.path.remove(""); import jgo' +which jgo +test "$(jgo org.scijava:parsington 1+3)" -eq 4 diff --git a/jgo/jgo.py b/jgo/jgo.py index 5d18791..a02880c 100644 --- a/jgo/jgo.py +++ b/jgo/jgo.py @@ -188,6 +188,8 @@ def launch_java( main_class, *app_args, additional_jars=[], + stdout=None, + stderr=None, **subprocess_run_kwargs, ): java = executable_path('java') @@ -197,7 +199,7 @@ def launch_java( cp = classpath_separator().join([os.path.join(jar_dir, '*')] + additional_jars) _logger.debug("class path: %s", cp) jvm_args = tuple(arg for arg in jvm_args) if jvm_args else tuple() - return subprocess.run((java, '-cp', cp) + jvm_args + (main_class,) + app_args, **subprocess_run_kwargs) + return subprocess.run((java, '-cp', cp) + jvm_args + (main_class,) + app_args, stdout=stdout, stderr=stderr, **subprocess_run_kwargs) def run_and_combine_outputs(command, *args): return subprocess.check_output((command,) + args, stderr=subprocess.STDOUT) @@ -212,7 +214,7 @@ def find_endpoint(argv, shortcuts={}): indices.append(index) return -1 if len(indices) == 0 else indices[-1] -def jgo_main(argv=sys.argv[1:]): +def jgo_parser(): epilog=''' The endpoint should have one of the following formats: @@ -245,9 +247,14 @@ def jgo_main(argv=sys.argv[1:]): parser.add_argument('--ignore-jgorc', action='store_true', help='Ignore ~/.jgorc') parser.add_argument('--link-type', default=None, type=str, help='How to link from local maven repository into jgo cache. Defaults to the `links\' setting in ~/.jrunrc or \'auto\' if not specified.', choices=('hard', 'soft', 'copy', 'auto')) + return parser + +def jgo_main(argv=sys.argv[1:], stdout=None, stderr=None): + + parser = jgo_parser() try: - completed_process = run(parser, argv=argv) + completed_process = run(parser, argv=argv, stdout=stdout, stderr=stderr) completed_process.check_returncode() except subprocess.CalledProcessError as e: _logger.error("Error in `%s': %d", ' '.join(e.cmd), e.returncode) @@ -486,7 +493,7 @@ def resolve_dependencies( return primary_endpoint, workspace -def run(parser, argv=sys.argv[1:]): +def run(parser, argv=sys.argv[1:], stdout=None, stderr=None): config = default_config() if not '--ignore-jgorc' in argv: @@ -548,7 +555,7 @@ def run(parser, argv=sys.argv[1:]): try: with open(main_class_file, 'r') as f: main_class = f.readline() - return launch_java(workspace, jvm_args, main_class, *program_args, additional_jars=args.additional_jars, check=False) + return launch_java(workspace, jvm_args, main_class, *program_args, additional_jars=args.additional_jars, stdout=stdout, stderr=stderr, check=False) except FileNotFoundError: pass @@ -574,8 +581,7 @@ def run(parser, argv=sys.argv[1:]): with open(main_class_file, 'w') as f: f.write(main_class) - - return launch_java(workspace, jvm_args, main_class, *program_args, additional_jars=args.additional_jars, check=False) + return launch_java(workspace, jvm_args, main_class, *program_args, additional_jars=args.additional_jars, stdout=stdout, stderr=stderr, check=False) diff --git a/setup.py b/setup.py index 6a08526..c974140 100644 --- a/setup.py +++ b/setup.py @@ -17,10 +17,12 @@ license='Public domain', url='https://github.com/scijava/jgo', packages=['jgo'], + test_suite='nose.collector', entry_points={ 'console_scripts': [ 'jgo=jgo.jgo:jgo_main' ] }, python_requires='>=3', + tests_require=['nose>=1.0'] ) diff --git a/tests/test_parsington.py b/tests/test_parsington.py new file mode 100644 index 0000000..5cfb590 --- /dev/null +++ b/tests/test_parsington.py @@ -0,0 +1,107 @@ +import glob +import io +import jgo +import os +import pathlib +import unittest +import shutil +import subprocess +import tempfile + +import logging +_logger = logging.getLogger(__name__) + + +IGNORE_JGORC = '--ignore-jgorc' +LINK_TYPE = '--link-type' +PARSINGTON_VERSION = '1.0.4' +PARSINGTON_ENDPOINT = 'org.scijava:parsington:{}'.format(PARSINGTON_VERSION) + +def run_parsington(cache_dir, link_type, parsington_args): + parser = jgo.jgo.jgo_parser() + argv = (IGNORE_JGORC, LINK_TYPE, link_type, PARSINGTON_ENDPOINT) + parsington_args + os.environ[jgo.jgo.jgo_cache_dir_environment_variable()] = cache_dir + return jgo.jgo.run(parser=parser, argv=argv, stdout=subprocess.PIPE) + +def resolve_parsington(cache_dir, link_type, m2_repo): + return jgo.resolve_dependencies( + PARSINGTON_ENDPOINT, + m2_repo=m2_repo, + cache_dir=cache_dir, + link_type=link_type) + +class ParsingtonTest(unittest.TestCase): + + def test_resolve_hard(self): + tmp_dir = tempfile.mkdtemp(prefix='jgo-test-cache-dir') + m2_repo = os.path.join(str(pathlib.Path.home()), '.m2', 'repository') + try: + _, workspace = resolve_parsington(cache_dir=tmp_dir, link_type='hard', m2_repo=m2_repo) + jars = glob.glob(os.path.join(workspace, '*jar')) + self.assertEqual(len(jars), 1, 'Expected exactly one jar in workspace') + self.assertEqual(jars[0], os.path.join(workspace, 'parsington-%s.jar' % PARSINGTON_VERSION), 'Expected parsington jar') + self.assertFalse(os.path.islink(jars[0]), 'Expected hard link but found symbolic link.') + self.assertTrue(os.path.isfile(jars[0])) + self.assertGreaterEqual(os.stat(jars[0]).st_nlink, 2, 'Expected ref count of at least 2 for hard link.') + except OSError as e: + if e.errno == 18: + _logger.warning('Unable to cross-device hard link, skipping hard link test: %s', str(e)) + else: + raise e + finally: + shutil.rmtree(tmp_dir) + + def test_resolve_soft(self): + tmp_dir = tempfile.mkdtemp(prefix='jgo-test-cache-dir') + m2_repo = os.path.join(str(pathlib.Path.home()), '.m2', 'repository') + try: + _, workspace = resolve_parsington(cache_dir=tmp_dir, link_type='soft', m2_repo=m2_repo) + jars = glob.glob(os.path.join(workspace, '*jar')) + self.assertEqual(len(jars), 1, 'Expected exactly one jar in workspace') + self.assertEqual(jars[0], os.path.join(workspace, 'parsington-%s.jar' % PARSINGTON_VERSION), 'Expected parsington jar') + self.assertTrue(os.path.islink(jars[0]), 'Expected soft link.') + finally: + shutil.rmtree(tmp_dir) + + def test_resolve_copy(self): + tmp_dir = tempfile.mkdtemp(prefix='jgo-test-cache-dir') + m2_repo = os.path.join(str(pathlib.Path.home()), '.m2', 'repository') + try: + _, workspace = resolve_parsington(cache_dir=tmp_dir, link_type='copy', m2_repo=m2_repo) + jars = glob.glob(os.path.join(workspace, '*jar')) + self.assertEqual(len(jars), 1, 'Expected exactly one jar in workspace') + self.assertEqual(jars[0], os.path.join(workspace, 'parsington-%s.jar' % PARSINGTON_VERSION), 'Expected parsington jar') + self.assertFalse(os.path.islink(jars[0]), 'Expected copied file but found symbolic link.') + self.assertTrue(os.path.isfile(jars[0])) + self.assertEqual(os.stat(jars[0]).st_nlink, 1, 'Expected ref count of exactly 1 for copied file.') + finally: + shutil.rmtree(tmp_dir) + + def test_resolve_auto(self): + tmp_dir = tempfile.mkdtemp(prefix='jgo-test-cache-dir') + m2_repo = os.path.join(str(pathlib.Path.home()), '.m2', 'repository') + try: + _, workspace = resolve_parsington(cache_dir=tmp_dir, link_type='auto', m2_repo=m2_repo) + jars = glob.glob(os.path.join(workspace, '*jar')) + self.assertEqual(len(jars), 1, 'Expected exactly one jar in workspace') + self.assertEqual(jars[0], os.path.join(workspace, 'parsington-%s.jar' % PARSINGTON_VERSION), 'Expected parsington jar') + finally: + shutil.rmtree(tmp_dir) + + def test_run_jgo(self): + tmp_dir = tempfile.mkdtemp(prefix='jgo-test-cache-dir') + + try: + completed_process = run_parsington(cache_dir=tmp_dir, link_type='auto', parsington_args=('1+3',)) + self.assertIsNotNone(completed_process) + self.assertEqual(completed_process.returncode, 0, 'Expected return code zero.') + self.assertEqual(completed_process.stdout.decode('ascii').strip(), str(1+3)) + finally: + shutil.rmtree(tmp_dir) + + +if __name__ == '__main__': + unittest.main() + + +