From 08e2e04bcb07b69fa1508f080097cd6182b9cf5c Mon Sep 17 00:00:00 2001 From: cpburnz Date: Wed, 19 Nov 2014 23:15:44 -0500 Subject: [PATCH] Support class and method annotations. --- clamp/declarative.py | 174 +++++++++++++++++- clamp/proxymaker.py | 140 +++++++++++++- clamp/signature.py | 112 +++++++++++ tests/integ/clamp_samples/__init__.py | 2 +- tests/integ/clamp_samples/class_.py | 24 +++ tests/integ/clamp_samples/method.py | 51 +++++ .../integ/clamp_supports/MultiAnnotation.java | 10 + .../clamp_supports/SingleAnnotation.java | 9 + .../integ/clamp_supports/StubAnnotation.java | 7 + tests/integ/junit_tests/TestClass.java | 43 +++++ tests/integ/junit_tests/TestMethod.java | 105 +++++++++++ tests/integ/setup.py | 39 +++- 12 files changed, 711 insertions(+), 5 deletions(-) create mode 100644 tests/integ/clamp_samples/class_.py create mode 100644 tests/integ/clamp_samples/method.py create mode 100644 tests/integ/clamp_supports/MultiAnnotation.java create mode 100644 tests/integ/clamp_supports/SingleAnnotation.java create mode 100644 tests/integ/clamp_supports/StubAnnotation.java create mode 100644 tests/integ/junit_tests/TestClass.java create mode 100644 tests/integ/junit_tests/TestMethod.java diff --git a/clamp/declarative.py b/clamp/declarative.py index 6529c07..e07f440 100644 --- a/clamp/declarative.py +++ b/clamp/declarative.py @@ -4,7 +4,6 @@ from clamp.proxymaker import ClampProxyMaker from clamp.signature import Constant - def clamp_base(package, proxy_maker=ClampProxyMaker): """ A helper method that allows you to create clamped classes @@ -41,3 +40,176 @@ class ClampBase(object): __metaclass__ = _clamp_closure(package=package, proxy_maker=proxy_maker) return ClampBase + + + +import inspect +import logging + +from clamp.signature import ClassInfo, MethodInfo + +log = logging.getLogger(__name__) + +def clamp_class(package, proxy_maker=ClampProxyMaker): + """ + Clamp a class and rewrite as a Java class. + + Example:: + + @clamp_class('org.example.package') + class PythonClass(object): + ... + """ + assert isinstance(package, basestring), "package:{!r} is not a string.".format(package) + + def class_decorator(target): + log.debug("clamp_class({!r}, {!r})({!r})".format(package, proxy_maker, target)) + assert inspect.isclass(target), "target:{!r} is not a class.".format(target) + + # Check to see if the class was compiled by the default proxy maker. + # Format: + # + # org.python.proxies.$$ + # org.python.proxies..$$ + # + + try: + target_package, target_module = target.__module__.rsplit('.', 1) + except IndexError: + proxy_package = 'org.python.proxies' + proxy_module = target.__module__ + proxy_class = target.__name__ + else: + proxy_package = 'org.python.proxies.' + target_package + proxy_module = target_module + proxy_class = target.__name__ + + log.debug("Find proxy base: {}:{}:{}".format(proxy_package, proxy_module, proxy_class)) + + log.debug("{}".format(target)) + bases = [] + for base in target.__bases__: + log.debug("- {}".format(base)) + if base.__module__ == proxy_package and tuple(base.__name__.split('$')[:-1]) == (proxy_module, proxy_class): + # Found proxy base, extract its bases. + for sub_base in base.__bases__: + log.debug(" - {}".format(sub_base)) + bases.append(sub_base) + else: + # Normal base. + bases.append(base) + + log.debug(bases) + + class ClampProxyMakerMeta(type): + + def __new__(mcs, name, bases, dct): + log.debug("ClampProxyMakerMeta({!r}, {!r}, {!r})".format(name, bases, dct)) + newdct = dict(dct) + newdct['__metaclass__'] = mcs + newdct['__proxymaker__'] = proxy_maker(package=package) + return type.__new__(mcs, name, bases, newdct) + + # Clamp class. + return ClampProxyMakerMeta(target.__name__, tuple(bases), dict(vars(target))) + + return class_decorator + +def annotate(*args, **fields): + """ + Apply a Java annotation to a Python class or method. + + Example:: + + @annotate(JavaIterface, field1="Value 1", ...) + class PythonClass(object): + + @annotate(JavaInterface, field1="Value 1", ...) + def pythonMethod(self): + ... + + @annotate('arg', JavaInterface, field1="Value 1", ...) + def doSomething(self, arg): + ... + """ + if len(args) == 1: + interface = args[0] + return _annotate(interface, fields) + elif len(args) == 2: + arg, interface = args + return _annotate_arg(arg, interface, fields) + + raise TypeError("annotate() takes either 1 or 2 positional arguments ({} given).".format(len(args))) + +def _annotate(interface, fields): + + def annotate_decorator(target): + log.debug("annotate({!r}, {!r})({!r})".format(interface, fields, target)) + + assert inspect.isclass(target) or inspect.isfunction(target), "target:{!r} is not a class or method.".format(target) + + if inspect.isclass(target): + if not hasattr(target, '_clamp'): + target._clamp = ClassInfo(target) + info = target._clamp + elif inspect.isfunction(target): + # Require methods to be decorated with @method in order to detect + # missing type information early. + assert hasattr(target, '_clamp'), "method:{!r} has not been decorated with @method.".format(target) + info = target._clamp + + # Add annotation. + info.annotate(interface, **fields) + + return target + + return annotate_decorator + +def _annotate_arg(arg, interface, fields): + + def annotate_decorator(method): + log.debug("annotate({!r}, {!r}, {!r})({!r})".format(arg, interface, fields, method)) + method._clamp.annotate_arg(arg, interface, **fields) + return method + + return annotate_decorator + +def method(return_type, arg_types=None, name=None, access=None): + """ + Apply Java type information the method. This is required in order to + expose any Python methods in a clamped class. + + Example:: + + @method(java.lang.Void) + def doNothing(self): + pass + + @method(java.lang.String, (java.lang.Long, java.lang.Long)) + def someMethod(self, value1, value2): + return str(value1 + value2) + """ + def method_decorator(method): + log.debug("method({!r}, {!r}, {!r})({!r})".format(return_type, arg_types, name, method)) + method._clamp = MethodInfo(method, return_type, arg_types=arg_types, name=name, access=access) + return method + + return method_decorator + +def throws(*exception_types): + """ + Declare what Java exceptions the method throws. + + Example:: + + @throws(java.io.FileNotFoundException) + @method(java.lang.Void.TYPE) + def doNothing(self): + pass + """ + def throws_decorator(method): + log.debug("throws(*{!r})({!r})".format(exception_types, method)) + method._clamp.throws(*exception_types) + return method + + return throws_decorator diff --git a/clamp/proxymaker.py b/clamp/proxymaker.py index bf71aee..770be88 100644 --- a/clamp/proxymaker.py +++ b/clamp/proxymaker.py @@ -1,4 +1,6 @@ +import inspect import logging +import re import java from java.io import Serializable @@ -10,6 +12,7 @@ from clamp.build import get_builder from clamp.signature import Constant +_MATCH_PRIVATE_NAME = re.compile('^_(?P\w+)__(?P\w+)$') log = logging.getLogger(__name__) @@ -33,9 +36,10 @@ class SerializableProxyMaker(CustomMaker): # {'__init__': , '__module__': '__main__', 'call': , '__proxymaker__': }, 'clamped', {}) def __init__(self, superclass, interfaces, className, pythonModuleName, fullProxyName, mapping, package, kwargs): + self.mapping = mapping self.package = package self.kwargs = kwargs - + log.debug("superclass=%s, interfaces=%s, className=%s, pythonModuleName=%s, fullProxyName=%s, mapping=%s, " "package=%s, kwargs=%s", superclass, interfaces, className, pythonModuleName, fullProxyName, mapping, package, kwargs) @@ -104,13 +108,95 @@ def makeClass(self): raise TypeError("Cannot clamp proxy class {} without a defined builder".format(self.myClass)) return cls + def visitClassAnnotations(self): + # Find class annotations. + class_info = self.mapping.get('_clamp') + if class_info is not None: + for annotation in class_info.annotations: + self.addClassAnnotation(annotation) + + def visitMethods(self, *args): + # Only override the signature: + # + # void visitMethods() + # + if args: + return self.super__visitMethods(*args) + + # Add default methods. + self.super__visitMethods() + + # Add methods with type information. + for name, method in self.mapping.iteritems(): + if isinstance(method, (classmethod, staticmethod)): + log.warning("method:{!r} is not yet supported.".format(method)) + continue + + if not inspect.isfunction(method): + continue + + method_info = getattr(method, '_clamp', None) + if method_info is None: + continue + + if method_info.return_type is None or method_info.arg_types is None: + log.warning("method:{!r} info:{!r} is missing type information.".format(method, method_info)) + continue + + if method_info.access is not None: + access = method_info.access + else: + # Deduce method modifer from name and decorators. + # - TODO: How should @classmethod be handled? Should it be STATIC? + access = access_from_name(name) + + if isinstance(method, staticmethod): + # Interpret: + # + # @staticmethod + # def func(...) + # + # As: + # + # static func(...) + # + access |= Modifier.STATIC + + if getattr(method, '__isabstractmethod__', False): + # Interpret: + # + # @abc.abstractmethod + # def func(self, ...) + # + # As: + # + # abstract func(...) + # + access |= Modifier.ABSTRACT + + # Slice off the beginning *self* argument. + # - TODO: This will need to be modified once @staticmethod and + # @classmethod are supported. + argspec = inspect.getargspec(method) + args = argspec.args[1:] + + arg_annotations = [method_info.arg_annotations[arg] for arg in args] + + # HACK: This causes the methods to be generated without a call to + # the super-class. This prevents a `NullPointerException` from + # being raised because of a lack of passing the non-existant + # class. + access |= Modifier.ABSTRACT + + self.addMethod(method_info.name, name, method_info.return_type, method_info.arg_types, method_info.exception_types, access, None, method_info.annotations, arg_annotations) + class ClampProxyMaker(object): def __init__(self, package, **kwargs): self.package = package self.kwargs = kwargs - + def __call__(self, superclass, interfaces, className, pythonModuleName, fullProxyName, mapping): """Constructs a usable proxy name that does not depend on ordering""" log.debug("Called ClampProxyMaker: %s, %r, %r, %s, %s, %s, %r", self.package, superclass, interfaces, @@ -119,3 +205,53 @@ def __call__(self, superclass, interfaces, className, pythonModuleName, fullProx superclass, interfaces, className, pythonModuleName, self.package + "." + pythonModuleName + "." + className, mapping, self.package, self.kwargs) + + +def access_from_name(name): + """ + Derive the method or field access from its name. + """ + result = _MATCH_PRIVATE_NAME.match(name) + if result is not None: + # Interpret: + # + # def __func(self, ...) + # + # As: + # + # private __func(...) + # + return Modifier.PRIVATE + + elif name.startswith('__') and name.endswith('__'): + # Interpret: + # + # def __func__(self, ...) + # + # As: + # + # public __func__(...) + # + return Modifier.PUBLIC + + elif name.startswith('_'): + # Interpret: + # + # def _func(self, ...) + # + # As: + # + # protected _func(...) + # + return Modifier.PROTECTED + + else: + # Interpret: + # + # def func(self, ...) + # + # As: + # + # public func(...) + # + return Modifier.PUBLIC diff --git a/clamp/signature.py b/clamp/signature.py index 27da5d4..cbd65e6 100644 --- a/clamp/signature.py +++ b/clamp/signature.py @@ -1,3 +1,12 @@ +import collections +import inspect +import logging + +import java.lang.Class +from org.python.compiler.ProxyCodeHelpers import AnnotationDescr + +log = logging.getLogger(__name__) + class Constant(object): """ Use this class to declare class attributes as java const @@ -14,3 +23,106 @@ def __init__(self, value, type=None): raise NotImplementedError("type has to be set right now") self.value = value self.type = type + + +class ClassInfo(object): + """ + The ``ClassInfo`` class is used to store Java annotations on a class. + """ + + def __init__(self, cls): + assert inspect.isclass(cls), "cls:{!r} is not a class.".format(cls) + + self.annotations = [] + self.cls = cls + + def __repr__(self): + return "".format(self.annotations) + + def annotate(self, _interface, **fields): + """ + Apply a Java annotation to the class. + """ + assert isinstance(_interface, java.lang.Class) and _interface.isInterface(), "interface:{!r} is not a Java interface.".format(_interface) + + log.debug("annotate({!r}, {!r})".format(_interface, fields)) + + # Store annotation. + annotation = AnnotationDescr(_interface, fields or None) + self.annotations.append(annotation) + + +class MethodInfo(object): + """ + The ``MethodInfo`` class is used to store Java type information and + annotations on a method. + """ + + def __init__(self, method, return_type, arg_types, name=None, access=None): + if isinstance(method, (classmethod, staticmethod)): + raise NotImplementedError("method:{!r} is not yet supported.".format(method)) + assert inspect.isfunction(method), "method:{!r} is not a function.".format(method) + + assert inspect.isclass(return_type), "return_type:{!r} is not a class.".format(return_type) + + arg_types = tuple(arg_types) if arg_types is not None else () + for arg_type in arg_types: + assert inspect.isclass(arg_type), "arg_type:{!r} is not a class.".format(arg_type) + + argspec = inspect.getargspec(method) + expect_args = argspec.args[1:] + assert len(arg_types) == len(expect_args), "method:{!r} has {}, given {}.".format(method, len(expect_args), len(arg_types)) + + if not name: + name = method.__name__ + assert isinstance(name, basestring), "name:{!r} is not a string.".format(name) + + self.access = access + self.annotations = [] + self.arg_annotations = collections.defaultdict(list) + self.arg_types = arg_types + self.exception_types = [] + self.method = method + self.name = name + self.return_type = return_type + + def __repr__(self): + return "".format(self.access, self.annotations, dict(self.arg_annotations), self.arg_types, self.exception_types, self.name, self.return_type) + + def annotate(self, _interface, **fields): + """ + Apply a Java annotation to the method. + """ + assert isinstance(_interface, java.lang.Class) and _interface.isInterface(), "interface:{!r} is not a Java interface.".format(_interface) + + log.debug("annotate({!r}, {!r})".format(_interface, fields)) + + # Store annotation. + annotation = AnnotationDescr(_interface, fields or None) + self.annotations.append(annotation) + + def annotate_arg(self, _arg, _interface, **fields): + """ + Apply a Java annotation to the method argument. + """ + assert isinstance(_arg, basestring), "arg:{!r} is not a string.".format(_arg) + assert isinstance(_interface, java.lang.Class) and _interface.isInterface(), "interface:{!r} is not a Java interface.".format(_interface) + + argspec = inspect.getargspec(self.method) + expect_args = argspec.args[1:] + assert _arg in expect_args, "method:{!r} does not have argument:{!r}.".format(self.method, _arg) + + log.debug("annotate_arg({!r}, {!r}, {!r})".format(_arg, _interface, fields)) + + # Store annotation. + annotation = AnnotationDescr(_interface, fields or None) + self.arg_annotations[_arg].append(annotation) + + def throws(self, *exception_types): + """ + Declare what Java exceptions the methods throws. + """ + for exception_type in exception_types: + assert inspect.isclass(exception_type), "exception_type:{!r} is not a class.".format(exception_type) + + self.exception_types = exception_types diff --git a/tests/integ/clamp_samples/__init__.py b/tests/integ/clamp_samples/__init__.py index 8c360b6..3f14f63 100644 --- a/tests/integ/clamp_samples/__init__.py +++ b/tests/integ/clamp_samples/__init__.py @@ -1 +1 @@ -from . import callable, const_ \ No newline at end of file +from . import callable, class_, const_, method diff --git a/tests/integ/clamp_samples/class_.py b/tests/integ/clamp_samples/class_.py new file mode 100644 index 0000000..05a3bd7 --- /dev/null +++ b/tests/integ/clamp_samples/class_.py @@ -0,0 +1,24 @@ +from java.lang import Object +from org.clamp_supports import StubAnnotation, SingleAnnotation, MultiAnnotation + +from clamp.declarative import annotate, clamp_class + +@clamp_class('org') +@annotate(StubAnnotation) +class StubAnnotationSample(Object): + pass + +@clamp_class('org') +@annotate(SingleAnnotation, value='single') +class SingleAnnotationSample(Object): + pass + +@clamp_class('org') +@annotate(MultiAnnotation, value='all', extra='test') +class MultiAllAnnotationSample(Object): + pass + +@clamp_class('org') +@annotate(MultiAnnotation, value='default') +class MultiDefaultAnnotationSample(Object): + pass diff --git a/tests/integ/clamp_samples/method.py b/tests/integ/clamp_samples/method.py new file mode 100644 index 0000000..19df130 --- /dev/null +++ b/tests/integ/clamp_samples/method.py @@ -0,0 +1,51 @@ +from java.io import FileNotFoundException +from java.lang import Integer, Object, String, Void +from org.clamp_supports import StubAnnotation, SingleAnnotation, MultiAnnotation + +from clamp.declarative import annotate, clamp_class, method, throws + +@clamp_class('org') +class MethodSample(Object): + + @method(Integer.TYPE) + def returnZero(self): + return 0 + + @method(String) + def returnFoo(self): + return 'foo' + + @method(Integer.TYPE, (Integer.TYPE,)) + def returnIntArg(self, arg): + return arg + + @method(Integer.TYPE, (String, String)) + def returnIntConcat(self, arg1, arg2): + return int(arg1 + arg2) + + @annotate(StubAnnotation) + @method(Void.TYPE) + def stubAnnotation(self): + pass + + @annotate(SingleAnnotation, value='single') + @method(Void.TYPE) + def singleAnnotation(self): + pass + + @annotate(MultiAnnotation, value='multi') + @method(Void.TYPE) + def multiAnnotation(self): + pass + + @annotate('one', StubAnnotation) + @annotate('two', SingleAnnotation, value='single') + @annotate('three', MultiAnnotation, value='multi') + @method(Void.TYPE, (String, String, String)) + def argAnnotations(self, one, two, three): + pass + + @throws(FileNotFoundException) + @method(Void.TYPE) + def fileNotFound(self): + raise FileNotFoundException() diff --git a/tests/integ/clamp_supports/MultiAnnotation.java b/tests/integ/clamp_supports/MultiAnnotation.java new file mode 100644 index 0000000..791f90c --- /dev/null +++ b/tests/integ/clamp_supports/MultiAnnotation.java @@ -0,0 +1,10 @@ +package org.clamp_supports; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +@Retention(RetentionPolicy.RUNTIME) +public @interface MultiAnnotation { + String value(); + String extra() default ""; +} diff --git a/tests/integ/clamp_supports/SingleAnnotation.java b/tests/integ/clamp_supports/SingleAnnotation.java new file mode 100644 index 0000000..e775475 --- /dev/null +++ b/tests/integ/clamp_supports/SingleAnnotation.java @@ -0,0 +1,9 @@ +package org.clamp_supports; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +@Retention(RetentionPolicy.RUNTIME) +public @interface SingleAnnotation { + String value(); +} diff --git a/tests/integ/clamp_supports/StubAnnotation.java b/tests/integ/clamp_supports/StubAnnotation.java new file mode 100644 index 0000000..76ecafa --- /dev/null +++ b/tests/integ/clamp_supports/StubAnnotation.java @@ -0,0 +1,7 @@ +package org.clamp_supports; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +@Retention(RetentionPolicy.RUNTIME) +public @interface StubAnnotation {} diff --git a/tests/integ/junit_tests/TestClass.java b/tests/integ/junit_tests/TestClass.java new file mode 100644 index 0000000..52e591f --- /dev/null +++ b/tests/integ/junit_tests/TestClass.java @@ -0,0 +1,43 @@ +import java.lang.reflect.*; +import org.junit.Test; +import static org.junit.Assert.*; +import org.python.compiler.MTime; + +import org.clamp_samples.class_.StubAnnotationSample; +import org.clamp_samples.class_.SingleAnnotationSample; +import org.clamp_samples.class_.MultiAllAnnotationSample; +import org.clamp_samples.class_.MultiDefaultAnnotationSample; +import org.clamp_supports.StubAnnotation; +import org.clamp_supports.SingleAnnotation; +import org.clamp_supports.MultiAnnotation; + +public class TestClass { + @Test + public void testStubAnnotationSample() throws Exception { + StubAnnotation anno = StubAnnotationSample.class.getAnnotation(StubAnnotation.class); + assertNotNull(anno); + } + + @Test + public void testSingleAnnotationSample() throws Exception { + SingleAnnotation anno = SingleAnnotationSample.class.getAnnotation(SingleAnnotation.class); + assertNotNull(anno); + assertEquals(anno.value(), "single"); + } + + @Test + public void testMultiAllAnnotationSample() throws Exception { + MultiAnnotation anno = MultiAllAnnotationSample.class.getAnnotation(MultiAnnotation.class); + assertNotNull(anno); + assertEquals(anno.value(), "all"); + assertEquals(anno.extra(), "test"); + } + + @Test + public void testMultiDefaultAnnotationSample() throws Exception { + MultiAnnotation anno = MultiDefaultAnnotationSample.class.getAnnotation(MultiAnnotation.class); + assertNotNull(anno); + assertEquals(anno.value(), "default"); + assertEquals(anno.extra(), ""); + } +} diff --git a/tests/integ/junit_tests/TestMethod.java b/tests/integ/junit_tests/TestMethod.java new file mode 100644 index 0000000..da78062 --- /dev/null +++ b/tests/integ/junit_tests/TestMethod.java @@ -0,0 +1,105 @@ +import java.io.FileNotFoundException; +import java.lang.String; +import java.lang.annotation.Annotation; +import java.lang.reflect.Method; +import org.junit.Test; +import static org.junit.Assert.*; + +import org.clamp_samples.method.MethodSample; +import org.clamp_supports.StubAnnotation; +import org.clamp_supports.SingleAnnotation; +import org.clamp_supports.MultiAnnotation; + +public class TestMethod { + + @Test + public void testReturnZero() throws Exception { + MethodSample methodObj = new MethodSample(); + assertEquals(0, methodObj.returnZero()); + } + + @Test + public void testReturnFoo() throws Exception { + MethodSample methodObj = new MethodSample(); + assertEquals("foo", methodObj.returnFoo()); + } + + @Test + public void testReturnIntArg() throws Exception { + MethodSample methodObj = new MethodSample(); + assertEquals(0, methodObj.returnIntArg(0)); + assertEquals(1234, methodObj.returnIntArg(1234)); + assertEquals(-1234, methodObj.returnIntArg(-1234)); + } + + @Test + public void testReturnIntConcat() throws Exception { + MethodSample methodObj = new MethodSample(); + assertEquals(0, methodObj.returnIntConcat("0", "0")); + assertEquals(123456, methodObj.returnIntConcat("123", "456")); + } + + @Test + public void testStubAnnotation() throws Exception { + Method method = MethodSample.class.getDeclaredMethod("stubAnnotation"); + assertNotNull(method); + + StubAnnotation anno = method.getAnnotation(StubAnnotation.class); + assertNotNull(anno); + } + + @Test + public void testSingleAnnotation() throws Exception { + Method method = MethodSample.class.getDeclaredMethod("singleAnnotation"); + assertNotNull(method); + + SingleAnnotation anno = method.getAnnotation(SingleAnnotation.class); + assertNotNull(anno); + assertEquals(anno.value(), "single"); + } + + @Test + public void testMultiAnnotation() throws Exception { + Method method = MethodSample.class.getDeclaredMethod("multiAnnotation"); + assertNotNull(method); + + MultiAnnotation anno = method.getAnnotation(MultiAnnotation.class); + assertNotNull(anno); + assertEquals(anno.value(), "multi"); + assertEquals(anno.extra(), ""); + } + + @Test + public void testArgAnnotations() throws Exception { + Method method = MethodSample.class.getDeclaredMethod("argAnnotations", String.class, String.class, String.class); + assertNotNull(method); + Annotation[][] argAnnos = method.getParameterAnnotations(); + assertEquals(argAnnos.length, 3); + + assertEquals(argAnnos[0].length, 1); + StubAnnotation stubAnno = (StubAnnotation)argAnnos[0][0]; + assertNotNull(stubAnno); + + assertEquals(argAnnos[1].length, 1); + SingleAnnotation singleAnno = (SingleAnnotation)argAnnos[1][0]; + assertNotNull(singleAnno); + assertEquals(singleAnno.value(), "single"); + + assertEquals(argAnnos[2].length, 1); + MultiAnnotation multiAnno = (MultiAnnotation)argAnnos[2][0]; + assertNotNull(multiAnno); + assertEquals(multiAnno.value(), "multi"); + assertEquals(multiAnno.extra(), ""); + } + + @Test(expected=FileNotFoundException.class) + public void testFileNotFound() throws Exception { + Method method = MethodSample.class.getDeclaredMethod("fileNotFound"); + Class[] exceptions = method.getExceptionTypes(); + assertEquals(exceptions.length, 1); + assertEquals(exceptions[0], FileNotFoundException.class); + + MethodSample methodObj = new MethodSample(); + methodObj.fileNotFound(); + } +} diff --git a/tests/integ/setup.py b/tests/integ/setup.py index 946d3a0..2c81dce 100644 --- a/tests/integ/setup.py +++ b/tests/integ/setup.py @@ -3,6 +3,7 @@ import sys import os +import zipfile from setuptools import setup, find_packages, Command from glob import glob @@ -15,6 +16,7 @@ from org.junit.runner import JUnitCore from javax.tools import ToolProvider +from java.io import File from java.net import URLClassLoader from java.net import URL @@ -34,18 +36,38 @@ def initialize_options(self): def finalize_options(self): self.testjar = os.path.join(self.tempdir, 'tests.jar') self.test_classesdir = os.path.join(self.tempdir, 'classes') + self.supportjar = os.path.join(self.tempdir, 'support.jar') + self.support_classesdir = os.path.join(self.tempdir, 'support_classes') + self.support_sourcesdir = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'clamp_supports') def build_jar(self): build_jar_cmd = self.distribution.get_command_obj('build_jar') build_jar_cmd.output = os.path.join(self.testjar) self.run_command('build_jar') + def build_support_jar(self): + with zipfile.ZipFile(self.supportjar, 'w') as fh: + for path, _dirs, files in os.walk(self.support_classesdir): + for fname in files: + fh.write(os.path.join(path, fname), os.path.relpath(os.path.join(path, fname), self.support_classesdir)) + + def import_support_jar(self): + addURL = URLClassLoader.getDeclaredMethod('addURL', [URL]) + addURL.accessible = True + addURL.invoke(URLClassLoader.getSystemClassLoader(), [File(os.path.abspath(self.supportjar)).toURL()]) + def get_classpath(self): jython_dir = os.path.split(os.path.split(sys.executable)[0])[0] junit = glob(os.path.join(jython_dir, 'javalib/junit-*.jar'))[0] return ":".join([os.path.join(jython_dir, sys.JYTHON_DEV_JAR), - junit, self.testjar]) + junit, self.supportjar, self.testjar]) + + def get_support_classpath(self): + jython_dir = os.path.split(os.path.split(sys.executable)[0])[0] + junit = glob(os.path.join(jython_dir, 'javalib/junit-*.jar'))[0] + + return ":".join([os.path.join(jython_dir, sys.JYTHON_DEV_JAR), junit]) def get_test_classes(self): urls = [URL('file:' + os.path.abspath(self.test_classesdir) + '/'), @@ -60,6 +82,9 @@ def get_test_classes(self): def get_java_files(self): return [fname for fname in os.listdir(self.junit_testdir) if fname.endswith('java')] + def get_support_files(self): + return [os.path.relpath(os.path.join(path, fname), self.support_sourcesdir) for path, _dirs, files in os.walk(self.support_sourcesdir) for fname in files if fname.endswith('.java')] + def run_javac(self): javac = ToolProvider.getSystemJavaCompiler() for fname in self.get_java_files(): @@ -68,6 +93,14 @@ def run_javac(self): if err: sys.exit() + def run_support_javac(self): + javac = ToolProvider.getSystemJavaCompiler() + for fname in self.get_support_files(): + destdir = os.path.join(self.support_classesdir, os.path.dirname(fname)) + self.mkpath(destdir) + err = javac.run(None, None, None, ['-cp', self.get_support_classpath(), '-d', destdir, + os.path.join(self.support_sourcesdir, fname)]) + def run_junit(self): result = JUnitCore.runClasses(list(self.get_test_classes())) print "Ran {} tests in {}s, failures: {}".format(result.runCount, result.runTime, result.failureCount) @@ -78,6 +111,10 @@ def run_junit(self): print failure def run(self): + self.mkpath(self.support_classesdir) + self.run_support_javac() + self.build_support_jar() + self.import_support_jar() self.mkpath(os.path.join(self.tempdir, 'classes')) self.build_jar() self.run_javac()