You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@tapestry.apache.org by hl...@apache.org on 2012/06/21 19:28:19 UTC
[4/9] Refactor all the tapestry-ioc Spock specifications into the
ioc.specs package Convert some package-private classes and constructors to
public
http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/a1bef869/tapestry-ioc/src/test/groovy/ioc/specs/DefaultModuleDefImplSpec.groovy
----------------------------------------------------------------------
diff --git a/tapestry-ioc/src/test/groovy/ioc/specs/DefaultModuleDefImplSpec.groovy b/tapestry-ioc/src/test/groovy/ioc/specs/DefaultModuleDefImplSpec.groovy
new file mode 100644
index 0000000..d017dfc
--- /dev/null
+++ b/tapestry-ioc/src/test/groovy/ioc/specs/DefaultModuleDefImplSpec.groovy
@@ -0,0 +1,450 @@
+package ioc.specs
+
+import org.apache.tapestry5.internal.plastic.PlasticClassLoader
+import org.apache.tapestry5.internal.plastic.PlasticInternalUtils
+import org.apache.tapestry5.internal.plastic.asm.ClassWriter
+import org.apache.tapestry5.ioc.def.ServiceDef3
+import org.apache.tapestry5.ioc.internal.services.PlasticProxyFactoryImpl
+import org.apache.tapestry5.ioc.services.PlasticProxyFactory
+import org.slf4j.Logger
+import spock.lang.Shared
+import spock.lang.Specification
+import spock.lang.Unroll
+import org.apache.tapestry5.ioc.*
+import org.apache.tapestry5.ioc.internal.*
+
+import static org.apache.tapestry5.internal.plastic.asm.Opcodes.*
+
+class DefaultModuleDefImplSpec extends Specification {
+
+ @Shared
+ PlasticProxyFactory proxyFactory = new PlasticProxyFactoryImpl(Thread.currentThread().contextClassLoader, null)
+
+ @Shared
+ OperationTracker tracker = new QuietOperationTracker()
+
+ Logger logger = Mock()
+
+ def "toString() of module lists services in the module"() {
+ when:
+
+ def md = module SimpleModule
+
+ then:
+
+ md.toString() == "ModuleDef[$SimpleModule.name Barney, Fred, Wilma]"
+ }
+
+ def "serviceIds contains all service ids"() {
+ def md = module SimpleModule
+
+ expect:
+
+ md.serviceIds == ["Fred", "Barney", "Wilma"] as Set
+ }
+
+ def "ServiceDef obtainable by service id"() {
+ def md = module SimpleModule
+
+ when:
+
+ def sd = md.getServiceDef "fred"
+
+ then:
+
+ sd.serviceId == "Fred"
+ sd.serviceInterface == FieService
+ sd.toString().contains "${SimpleModule.name}.buildFred()"
+ sd.serviceScope == ScopeConstants.DEFAULT
+ !sd.eagerLoad
+ sd.markers.empty
+
+ when:
+
+ sd = md.getServiceDef("Wilma")
+
+ then:
+
+ sd.eagerLoad
+ }
+
+ def "ModuleDef exposes decorator methods as DecoratorDefs"() {
+ def md = module SimpleModule
+
+ when:
+
+ def decos = md.decoratorDefs
+
+ then:
+
+ decos.size() == 1
+
+ def deco = decos.find()
+
+ deco.decoratorId == "Logging"
+ deco.toString().contains "${SimpleModule.name}.decorateLogging(Class, Object)"
+ }
+
+ def "@ServiceId annotation on service builder method overrides naming convention"() {
+ when:
+
+ def md = module ServiceIdViaAnnotationModule
+
+ then:
+
+ md.getServiceDef("FooService") != null
+ }
+
+ def "@ServiceId on implementation class overrides default id from ServiceBinder.bind() default"() {
+ when:
+
+ def md = module ServiceIdViaAnnotationModule
+
+ then:
+
+ md.getServiceDef("BarneyService") != null
+ }
+
+ def "@Named annotation on service builder method overrides naming convention"() {
+ when:
+
+ def md = module NamedServiceModule
+
+ then:
+
+ md.getServiceDef("BazService") != null
+ }
+
+ def "@Named annotation on service implementation class overrides ServiceBinder.bind() default"() {
+ when:
+
+ def md = module NamedServiceModule
+
+ then:
+
+ md.getServiceDef("QuuxService") != null
+ }
+
+ def "naming convention for a service builder method named build() is derived from the return type"() {
+ when:
+
+ def md = module DefaultServiceIdModule
+
+ then:
+
+ md.getServiceDef("FieService") != null
+ }
+
+ def "conflicting service ids result in an exception"() {
+ when:
+
+ module ServiceIdConflictMethodModule
+
+ then:
+
+ RuntimeException ex = thrown()
+
+ ex.message.contains "Service Fred (defined by ${ServiceIdConflictMethodModule.name}.buildFred()"
+ ex.message.contains "conflicts with previously defined service defined by ${ServiceIdConflictMethodModule.name}.buildFred(Object)"
+ }
+
+ def "a service builder method may not return void"() {
+ when:
+
+ module VoidBuilderMethodModule
+
+ then:
+
+ RuntimeException ex = thrown()
+
+ ex.message.contains "${VoidBuilderMethodModule.name}.buildNull()"
+ ex.message.contains "but the return type (void) is not acceptable"
+ }
+
+ def "a service builder method may not return an array"() {
+ when:
+
+ module BuilderMethodModule
+
+ then:
+
+ RuntimeException ex = thrown()
+
+ ex.message.contains "${BuilderMethodModule.name}.buildStringArray()"
+ ex.message.contains "but the return type (java.lang.String[])"
+ }
+
+ @Unroll
+ def "A decorator method #desc"() {
+ when:
+
+ module moduleClass
+
+ then:
+
+ RuntimeException e = thrown()
+
+ e.message.contains expectedText
+
+ where:
+
+ moduleClass | expectedText | desc
+ PrimitiveDecoratorMethodModule | "decoratePrimitive" | "may not return a primitive type"
+ ArrayDecoratorMethodModule | "decorateArray" | "may not return an array"
+ }
+
+ @Unroll
+ def "#desc"() {
+ when:
+
+ def md = module moduleClass
+
+ then:
+
+ def defs = md.contributionDefs
+
+ defs.size() == 1
+
+ def cd = defs.find()
+
+ cd.serviceId == serviceId
+
+ cd.toString().contains "${moduleClass.name}.$methodSignature"
+
+ where:
+
+ moduleClass | serviceId | methodSignature | desc
+ SimpleModule | "Barney" | "contributeBarney(Configuration)" | "contribution without annotation to configuration"
+ OrderedConfigurationModule | "Ordered" | "contributeOrdered(OrderedConfiguration)" | "contribution to ordered configuration"
+ MappedConfigurationModule | "Mapped" | "contributeMapped(MappedConfiguration)" | "contribution to mapped configuration"
+ }
+
+ @Unroll
+ def "service contribution method that #desc throws an exception"() {
+
+ when:
+
+ module moduleClass
+
+ then:
+
+ RuntimeException e = thrown()
+
+ e.message.contains message
+
+ where:
+
+ moduleClass | message | desc
+
+ NoUsableContributionParameterModule | "does not contain a parameter of type Configuration, OrderedConfiguration or MappedConfiguration" | "does not include configuration parameter"
+ TooManyContributionParametersModule | "contains more than one parameter of type Configuration, OrderedConfiguration, or MappedConfiguration" | "includes more than one configuration parameter"
+ }
+
+ def "using defaults for ServiceBinder.bind()"() {
+
+ when:
+
+ def md = module AutobuildModule
+ ServiceDef3 sd = md.getServiceDef "stringholder"
+
+ then:
+
+ sd.serviceInterface == StringHolder
+ sd.serviceId == "StringHolder"
+ sd.serviceScope == ScopeConstants.DEFAULT
+ !sd.isEagerLoad()
+ sd.markers.empty
+ !sd.preventDecoration
+ }
+
+ def "overriding defaults for ServiceBinder.bind()"() {
+
+ when:
+
+ def md = module ComplexAutobuildModule
+ ServiceDef3 sd = md.getServiceDef "sh"
+
+ then:
+
+ sd.serviceInterface == StringHolder
+ sd.serviceId == "SH"
+ sd.serviceScope == "magic"
+ sd.eagerLoad
+ sd.preventDecoration
+ }
+
+ def "implementation class for ServiceBinder.bind() must have a public constructor"() {
+ when:
+
+ module UninstantiableAutobuildServiceModule
+
+ then:
+
+ RuntimeException e = thrown()
+
+ e.message.contains "Class org.apache.tapestry5.ioc.internal.RunnableServiceImpl (implementation of service 'Runnable') does not contain any public constructors."
+ }
+
+ def "the bind() method of a module class must be a static method"() {
+ when:
+
+ module NonStaticBindMethodModule
+
+ then:
+
+ RuntimeException e = thrown()
+
+ e.message.contains "Method org.apache.tapestry5.ioc.internal.NonStaticBindMethodModule.bind(ServiceBinder)"
+ e.message.contains "appears to be a service binder method, but is an instance method, not a static method"
+ }
+
+ def "when autobuilding a service implementation, the constructor with the most parameters is chosen"() {
+ ServiceBuilderResources resources = Mock()
+
+ when:
+
+ def md = module MutlipleAutobuildServiceConstructorsModule
+
+ def sd = md.getServiceDef "stringholder"
+
+ then:
+
+ sd != null
+
+ 0 * _
+
+ when:
+
+ def oc = sd.createServiceCreator(resources)
+ def holder = oc.createObject()
+
+ holder.value = "foo"
+
+ then:
+
+ holder instanceof StringHolder
+ holder.value == "FOO"
+
+ _ * resources.serviceId >> "StringHolder"
+ _ * resources.logger >> logger
+ _ * resources.serviceInterface >> StringHolder
+ 1 * resources.getService("ToUpperCaseStringHolder", StringHolder) >> new ToUpperCaseStringHolder()
+ _ * resources.tracker >> tracker
+
+ 1 * logger.debug(_) >> { args ->
+ assert args[0].contains(
+ "Invoking constructor org.apache.tapestry5.ioc.internal.MultipleConstructorsAutobuildService(StringHolder)")
+ }
+
+ 0 * _
+ }
+
+ def "an exception inside a bind() method bubbles up"() {
+ when:
+
+ module ExceptionInBindMethod
+
+ then:
+
+ RuntimeException e = thrown()
+
+ e.message.contains "Error invoking service binder method org.apache.tapestry5.ioc.internal.ExceptionInBindMethod.bind(ServiceBinder)"
+ e.message.contains "at ExceptionInBindMethod.java"
+ e.message.contains "Really, how often is this going to happen?"
+ }
+
+ def "@EagerLoad annotation on service implementation class is reflected in the ServiceDef"() {
+ when:
+
+ def md = module EagerLoadViaAnnotationModule
+ def sd = md.getServiceDef "runnable"
+
+ then:
+
+ sd.eagerLoad
+ }
+
+ private DefaultModuleDefImpl module(moduleClass) {
+ new DefaultModuleDefImpl(moduleClass, logger, proxyFactory)
+ }
+
+ def "marker annotations on the service builder method are available in the ServiceDef"() {
+
+ when:
+
+ def md = module MarkerModule
+ def sd = md.getServiceDef "greeter"
+
+ then:
+
+ sd.markers == [BlueMarker] as Set
+ }
+
+ def "marker annotations specified via ServiceBinder is available in the ServiceDef"() {
+ when:
+
+ def md = module MarkerModule
+ def sd = md.getServiceDef "redgreeter"
+
+ then:
+
+ sd.markers == [RedMarker] as Set
+ }
+
+ def "marker annotation on the implementation class is available in the ServiceDef"() {
+ when:
+
+ def md = module MarkerModule
+ def sd = md.getServiceDef "SecondRedGreeter"
+
+ then:
+
+ sd.markers == [RedMarker] as Set
+ }
+
+ def "marker annotation from ServiceBinder and implementation class are merged"() {
+ when:
+
+ def md = module MarkerModule
+ def sd = md.getServiceDef "SurprisinglyBlueGreeter"
+
+ then:
+
+ sd.markers == [RedMarker, BlueMarker] as Set
+ }
+
+ def "public synthetic methods on module class are ignored"() {
+ def moduleClass = createSyntheticModuleClass()
+
+ when:
+
+ def md = module moduleClass
+
+ then:
+
+ md.serviceIds.size() == 1
+ }
+
+ private createSyntheticModuleClass() {
+
+ def cw = new ClassWriter(ClassWriter.COMPUTE_MAXS + ClassWriter.COMPUTE_FRAMES)
+
+ cw.visit(V1_5, ACC_PUBLIC, "EnhancedSyntheticMethodModule", null,
+ PlasticInternalUtils.toInternalName(SyntheticMethodModule.name), null);
+
+ def mv = cw.visitMethod ACC_PUBLIC | ACC_STATIC | ACC_SYNTHETIC, "synth", "()V", null, null
+ mv.visitCode()
+ mv.visitInsn RETURN
+ mv.visitEnd()
+
+ cw.visitEnd()
+
+ def bytecode = cw.toByteArray()
+
+ ClassLoader loader = Thread.currentThread().contextClassLoader
+
+ PlasticClassLoader plasticLoader = new PlasticClassLoader(loader, new NoopClassLoaderDelegate())
+
+ return plasticLoader.defineClassWithBytecode("EnhancedSyntheticMethodModule", bytecode)
+ }
+}
http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/a1bef869/tapestry-ioc/src/test/groovy/ioc/specs/DummyLockSpec.groovy
----------------------------------------------------------------------
diff --git a/tapestry-ioc/src/test/groovy/ioc/specs/DummyLockSpec.groovy b/tapestry-ioc/src/test/groovy/ioc/specs/DummyLockSpec.groovy
new file mode 100644
index 0000000..8e6dadd
--- /dev/null
+++ b/tapestry-ioc/src/test/groovy/ioc/specs/DummyLockSpec.groovy
@@ -0,0 +1,28 @@
+package ioc.specs
+
+import org.apache.tapestry5.ioc.internal.util.DummyLock
+import spock.lang.Specification
+
+import java.util.concurrent.locks.Lock
+
+class DummyLockSpec extends Specification {
+
+ def "all methods are no-ops"() {
+ Lock lock = new DummyLock()
+
+ when:
+
+ lock.lock()
+ lock.unlock()
+ lock.lockInterruptibly()
+
+ then:
+
+ noExceptionThrown()
+
+ expect:
+ lock.newCondition() == null
+ lock.tryLock()
+ lock.tryLock(0, null)
+ }
+}
http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/a1bef869/tapestry-ioc/src/test/groovy/ioc/specs/EagerLoadSpec.groovy
----------------------------------------------------------------------
diff --git a/tapestry-ioc/src/test/groovy/ioc/specs/EagerLoadSpec.groovy b/tapestry-ioc/src/test/groovy/ioc/specs/EagerLoadSpec.groovy
new file mode 100644
index 0000000..563d2c7
--- /dev/null
+++ b/tapestry-ioc/src/test/groovy/ioc/specs/EagerLoadSpec.groovy
@@ -0,0 +1,22 @@
+package ioc.specs
+
+import org.apache.tapestry5.ioc.EagerProxyReloadModule
+
+class EagerLoadSpec extends AbstractRegistrySpecification {
+
+ def "proxied service does eager load"() {
+ expect:
+
+ EagerProxyReloadModule.eagerLoadServiceDidLoad == false
+
+ when:
+
+ buildRegistry EagerProxyReloadModule
+
+ performRegistryStartup()
+
+ then:
+
+ EagerProxyReloadModule.eagerLoadServiceDidLoad == true
+ }
+}
http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/a1bef869/tapestry-ioc/src/test/groovy/ioc/specs/ExceptionAnalyzerImplSpec.groovy
----------------------------------------------------------------------
diff --git a/tapestry-ioc/src/test/groovy/ioc/specs/ExceptionAnalyzerImplSpec.groovy b/tapestry-ioc/src/test/groovy/ioc/specs/ExceptionAnalyzerImplSpec.groovy
new file mode 100644
index 0000000..f865c1e
--- /dev/null
+++ b/tapestry-ioc/src/test/groovy/ioc/specs/ExceptionAnalyzerImplSpec.groovy
@@ -0,0 +1,214 @@
+package ioc.specs
+
+import org.apache.tapestry5.ioc.Location
+import org.apache.tapestry5.ioc.internal.util.TapestryException
+import org.apache.tapestry5.ioc.services.ExceptionAnalyzer
+
+class WriteOnlyPropertyException extends Exception {
+
+ private String code;
+
+ public String getCode() {
+ return code;
+ }
+
+ public void setFaultCode(int code) {
+ this.code = String.format("%04d", code);
+ }
+}
+
+class SelfCausedException extends RuntimeException {
+
+ SelfCausedException(String message) {
+ super(message);
+ }
+
+ public Throwable getCause() {
+ return this;
+ }
+}
+
+class ExceptionAnalyzerImplSpec extends AbstractSharedRegistrySpecification {
+
+ ExceptionAnalyzer analyzer = getService(ExceptionAnalyzer)
+
+ def "analysis of a simple exception"() {
+ when:
+ def ea = analyzer.analyze(t)
+
+ then:
+
+ ea.exceptionInfos.size() == 1
+
+ def ei = ea.exceptionInfos[0]
+
+ ei.className == RuntimeException.name
+ ei.message == message
+
+ ei.propertyNames.empty
+ !ei.stackTrace.empty
+
+ where:
+
+ message = "Hey! We've Got No Tomatoes"
+ t = new RuntimeException(message)
+ }
+
+ def "access to properties of exception"() {
+ Location l = Mock()
+ def t = new TapestryException("Message", l, null)
+
+ when:
+ def ea = analyzer.analyze(t)
+
+ then:
+
+ ea.exceptionInfos.size() == 1
+
+ def ei = ea.exceptionInfos[0]
+
+ ei.propertyNames == ["location"]
+ ei.getProperty("location").is(l)
+ }
+
+ def "access to nested exceptions"() {
+ when:
+
+ def ea = analyzer.analyze(outer)
+
+ then:
+
+ ea.exceptionInfos.size() == 2
+
+ def ei = ea.exceptionInfos[0]
+
+ ei.message == "Outer"
+ ei.stackTrace.empty
+
+ when:
+
+ ei = ea.exceptionInfos[1]
+
+ then:
+
+ ei.message == "Inner"
+ !ei.stackTrace.empty
+
+ where:
+
+ inner = new RuntimeException("Inner")
+ outer = new RuntimeException("Outer", inner)
+ }
+
+ def "middle exception that adds no value is removed"() {
+ when:
+
+ def ea = analyzer.analyze(outer)
+
+ then:
+
+ ea.exceptionInfos.size() == 2
+
+ def ei = ea.exceptionInfos[0]
+
+ ei.message == "Outer: Middle"
+ ei.stackTrace.empty
+
+ when:
+
+ ei = ea.exceptionInfos[1]
+
+ then:
+
+ ei.message == "Inner"
+
+ !ei.stackTrace.empty
+
+ where:
+
+ inner = new RuntimeException("Inner");
+ middle = new RuntimeException("Middle", inner);
+ outer = new RuntimeException("Outer: Middle", middle);
+ }
+
+ def "a middle exception that adds extra information is retained"() {
+ Location l = Mock()
+ def inner = new RuntimeException("Inner");
+ def middle = new TapestryException("Middle", l, inner);
+ def outer = new RuntimeException("Outer: Middle", middle);
+
+ when:
+
+ def ea = analyzer.analyze(outer)
+
+ then:
+
+ ea.exceptionInfos.size() == 3
+
+ def ei = ea.exceptionInfos[0]
+
+ ei.message == "Outer: Middle"
+ ei.stackTrace.empty
+
+ when:
+
+ ei = ea.exceptionInfos[1]
+
+ then:
+
+ ei.message == "Middle"
+ ei.getProperty("location").is(l)
+ ei.stackTrace.empty
+
+ when:
+
+ ei = ea.exceptionInfos[2]
+
+ then:
+
+ ei.message == "Inner"
+ !ei.stackTrace.empty
+ }
+
+ def "write only properties are omitted"() {
+ WriteOnlyPropertyException ex = new WriteOnlyPropertyException();
+
+ ex.setFaultCode(99);
+
+ when:
+
+ def ea = analyzer.analyze(ex);
+
+ then:
+
+ def ei = ea.exceptionInfos[0]
+
+ ei.propertyNames.contains("code")
+ !ei.propertyNames.contains("faultCode")
+ ei.getProperty("code") == "0099"
+ }
+
+ def "an exception that is its own cause does not cause an endless loop"() {
+ when:
+
+ def ea = analyzer.analyze(t)
+
+ then:
+
+ ea.exceptionInfos.size() == 1
+
+ def ei = ea.exceptionInfos[0]
+
+ ei.className == SelfCausedException.name
+ ei.message == message
+
+ !ei.propertyNames.contains("cause")
+
+ !ei.stackTrace.empty
+
+ where:
+
+ message = "Who you lookin at?"
+ t = new SelfCausedException(message)
+ }
+}
http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/a1bef869/tapestry-ioc/src/test/groovy/ioc/specs/ExceptionTrackerImplSpec.groovy
----------------------------------------------------------------------
diff --git a/tapestry-ioc/src/test/groovy/ioc/specs/ExceptionTrackerImplSpec.groovy b/tapestry-ioc/src/test/groovy/ioc/specs/ExceptionTrackerImplSpec.groovy
new file mode 100644
index 0000000..136f3aa
--- /dev/null
+++ b/tapestry-ioc/src/test/groovy/ioc/specs/ExceptionTrackerImplSpec.groovy
@@ -0,0 +1,32 @@
+package ioc.specs
+
+import org.apache.tapestry5.ioc.internal.services.ExceptionTrackerImpl
+import spock.lang.Specification
+
+class ExceptionTrackerImplSpec extends Specification {
+
+ def "exceptions are tracked"() {
+
+ def t1 = new RuntimeException()
+ def t2 = new RuntimeException()
+
+ when: "with a new tracker"
+
+ def et = new ExceptionTrackerImpl()
+
+ then: "never logged exceptions return false"
+
+ !et.exceptionLogged(t1)
+ !et.exceptionLogged(t2)
+
+ then: "subsequently, the same exceptions return true"
+
+ et.exceptionLogged(t1)
+ et.exceptionLogged(t2)
+
+ then: "and again"
+
+ et.exceptionLogged(t1)
+ et.exceptionLogged(t2)
+ }
+}
http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/a1bef869/tapestry-ioc/src/test/groovy/ioc/specs/ExceptionUtilsSpec.groovy
----------------------------------------------------------------------
diff --git a/tapestry-ioc/src/test/groovy/ioc/specs/ExceptionUtilsSpec.groovy b/tapestry-ioc/src/test/groovy/ioc/specs/ExceptionUtilsSpec.groovy
new file mode 100644
index 0000000..200bd9e
--- /dev/null
+++ b/tapestry-ioc/src/test/groovy/ioc/specs/ExceptionUtilsSpec.groovy
@@ -0,0 +1,53 @@
+package ioc.specs
+
+import org.apache.tapestry5.ioc.internal.services.PropertyAccessImpl
+import org.apache.tapestry5.ioc.internal.util.TapestryException
+import org.apache.tapestry5.ioc.util.ExceptionUtils
+import org.apache.tapestry5.ioc.util.ExceptionWrapper
+import spock.lang.Shared
+import spock.lang.Specification
+
+class ExceptionUtilsSpec extends Specification {
+
+ @Shared
+ def access = new PropertyAccessImpl()
+
+ def "find cause with match"() {
+ when:
+ def inner = new TapestryException("foo", null)
+ def outer = new RuntimeException(inner)
+
+ then:
+
+ ExceptionUtils.findCause(outer, TapestryException).is(inner)
+ ExceptionUtils.findCause(outer, TapestryException, access).is(inner)
+ }
+
+ def "find cause with no match"() {
+
+ when:
+
+ def re = new RuntimeException("No cause for you.")
+
+ then:
+
+ ExceptionUtils.findCause(re, TapestryException) == null
+ ExceptionUtils.findCause(re, TapestryException, access) == null
+ }
+
+ def "find a hidden exception"() {
+ when:
+
+ def inner = new RuntimeException()
+ def outer = new ExceptionWrapper(inner)
+
+ then:
+
+ // TAP5-1639: The old code can't find inner
+ ExceptionUtils.findCause(outer, RuntimeException) == null
+
+ // The new reflection-based on can:
+
+ ExceptionUtils.findCause(outer, RuntimeException, access).is(inner)
+ }
+}
http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/a1bef869/tapestry-ioc/src/test/groovy/ioc/specs/FilterMethodAnalyzerSpec.groovy
----------------------------------------------------------------------
diff --git a/tapestry-ioc/src/test/groovy/ioc/specs/FilterMethodAnalyzerSpec.groovy b/tapestry-ioc/src/test/groovy/ioc/specs/FilterMethodAnalyzerSpec.groovy
new file mode 100644
index 0000000..7e0e626
--- /dev/null
+++ b/tapestry-ioc/src/test/groovy/ioc/specs/FilterMethodAnalyzerSpec.groovy
@@ -0,0 +1,37 @@
+package ioc.specs
+
+import org.apache.tapestry5.ioc.internal.services.FilterMethodAnalyzer
+import org.apache.tapestry5.ioc.internal.services.MethodSignature
+import org.apache.tapestry5.ioc.internal.services.SampleFilter
+import org.apache.tapestry5.ioc.internal.services.SampleService
+import spock.lang.Specification
+import spock.lang.Unroll
+
+class FilterMethodAnalyzerSpec extends Specification {
+
+ private MethodSignature find(clazz, name) {
+ new MethodSignature(clazz.methods.find { it.name == name })
+ }
+
+ @Unroll
+ def "position of delegate parameter for #methodName should be #position"() {
+ def analyzer = new FilterMethodAnalyzer(SampleService)
+
+ def mainMethod = find SampleService, methodName
+ def filterMethod = find SampleFilter, methodName
+
+ expect:
+
+ analyzer.findServiceInterfacePosition(mainMethod, filterMethod) == position
+
+ where:
+
+ methodName | position
+ "simpleMatch" | 0
+ "mismatchParameterCount" | -1
+ "mismatchReturnType" | -1
+ "missingServiceInterface" | -1
+ "complexMatch" | 2
+
+ }
+}
http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/a1bef869/tapestry-ioc/src/test/groovy/ioc/specs/GeneralIntegrationSpec.groovy
----------------------------------------------------------------------
diff --git a/tapestry-ioc/src/test/groovy/ioc/specs/GeneralIntegrationSpec.groovy b/tapestry-ioc/src/test/groovy/ioc/specs/GeneralIntegrationSpec.groovy
new file mode 100644
index 0000000..1e30463
--- /dev/null
+++ b/tapestry-ioc/src/test/groovy/ioc/specs/GeneralIntegrationSpec.groovy
@@ -0,0 +1,25 @@
+package ioc.specs
+
+import org.apache.tapestry5.ioc.internal.services.Bean
+import org.apache.tapestry5.ioc.services.PropertyAccess
+
+class GeneralIntegrationSpec extends AbstractSharedRegistrySpecification {
+
+ def "PropertyAccess service is available"() {
+
+ PropertyAccess pa = getService "PropertyAccess", PropertyAccess
+
+ Bean b = new Bean()
+
+ when:
+
+ pa.set(b, "value", 99)
+
+ then:
+
+ b.value == 99
+ pa.get(b, "value") == 99
+ }
+
+
+}
http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/a1bef869/tapestry-ioc/src/test/groovy/ioc/specs/GenericUtilsSpec.groovy
----------------------------------------------------------------------
diff --git a/tapestry-ioc/src/test/groovy/ioc/specs/GenericUtilsSpec.groovy b/tapestry-ioc/src/test/groovy/ioc/specs/GenericUtilsSpec.groovy
new file mode 100644
index 0000000..6b527d2
--- /dev/null
+++ b/tapestry-ioc/src/test/groovy/ioc/specs/GenericUtilsSpec.groovy
@@ -0,0 +1,40 @@
+package ioc.specs
+
+import org.apache.tapestry5.ioc.internal.util.GenericsUtils
+import org.apache.tapestry5.ioc.internal.util.NonGenericBean
+import org.apache.tapestry5.ioc.internal.util.StringBean
+import org.apache.tapestry5.ioc.internal.util.StringLongPair
+import spock.lang.Specification
+import spock.lang.Unroll
+
+class GenericUtilsSpec extends Specification {
+
+ def find(clazz, name) {
+ def method = clazz.methods.find { it.name.equalsIgnoreCase(name) }
+
+ if (method == null) {
+ throw new IllegalArgumentException("Unable to find method '$name' of ${clazz.name}.")
+ }
+
+ return method
+ }
+
+ @Unroll
+ def "generic return type for #method is #expected"() {
+
+ expect:
+
+ GenericsUtils.extractGenericReturnType(clazz, method).is(expected)
+
+ where:
+
+ clazz | name | expected
+ NonGenericBean | "getvalue" | String
+ StringBean | "getvalue" | String
+ StringLongPair | "getkey" | String
+ StringLongPair | "getvalue" | Long
+
+ method = find(clazz, name)
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/a1bef869/tapestry-ioc/src/test/groovy/ioc/specs/GlobPatternMatcherSpec.groovy
----------------------------------------------------------------------
diff --git a/tapestry-ioc/src/test/groovy/ioc/specs/GlobPatternMatcherSpec.groovy b/tapestry-ioc/src/test/groovy/ioc/specs/GlobPatternMatcherSpec.groovy
new file mode 100644
index 0000000..9cb0a94
--- /dev/null
+++ b/tapestry-ioc/src/test/groovy/ioc/specs/GlobPatternMatcherSpec.groovy
@@ -0,0 +1,63 @@
+package ioc.specs
+
+import org.apache.tapestry5.ioc.internal.GlobPatternMatcher
+import spock.lang.Specification
+import spock.lang.Unroll
+
+@Unroll
+class GlobPatternMatcherSpec extends Specification {
+
+ def "input '#input' matches pattern '#pattern'"() {
+
+ def matcher = new GlobPatternMatcher(pattern)
+
+ expect:
+
+ matcher.matches(input)
+
+ where:
+
+ input | pattern
+ "fred" | "fred"
+ "fred" | "FRED"
+ "fred" | "*"
+ "" | "*"
+ "fred.Barney" | "*Barney"
+ "fred.Barney" | "*BARNEY"
+ "fred.Barney" | "fred*"
+ "fred.Barney" | "FRED*"
+ "fredBarney" | "*dB*"
+ "fredBarney" | "*DB*"
+ "fred.Barney" | "*Barney*"
+ "fred.Barney" | "*fred*"
+ "fred.Barney" | "*FRED*"
+ "MyEntityDAO" | ".*dao"
+ "FredDAO" | "(fred|barney)dao"
+ }
+
+ def "input '#input' does not match pattern '#pattern'"() {
+
+ def matcher = new GlobPatternMatcher(pattern)
+
+ expect:
+
+ !matcher.matches(input)
+
+ where:
+
+ input | pattern
+ "xfred" | "fred"
+ "fredx" | "fred"
+ "fred" | "xfred"
+ "fred" | "fredx"
+ "fred.Barneyx" | "*Barney"
+ "fred.Barney" | "*Barneyx"
+ "fred.Barney" | "*xBarney"
+ "xfred.Barney" | "fred*"
+ "fred.Barney" | "fredx*"
+ "fred.Barney" | "xfred*"
+ "fred.Barney" | "*flint*"
+ "MyEntityDAL" | ".*dao"
+ "WilmaDAO" | "(fred|barney)dao"
+ }
+}
http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/a1bef869/tapestry-ioc/src/test/groovy/ioc/specs/IdAllocatorSpec.groovy
----------------------------------------------------------------------
diff --git a/tapestry-ioc/src/test/groovy/ioc/specs/IdAllocatorSpec.groovy b/tapestry-ioc/src/test/groovy/ioc/specs/IdAllocatorSpec.groovy
new file mode 100644
index 0000000..643aa3a
--- /dev/null
+++ b/tapestry-ioc/src/test/groovy/ioc/specs/IdAllocatorSpec.groovy
@@ -0,0 +1,163 @@
+package ioc.specs
+
+import org.apache.tapestry5.ioc.util.IdAllocator
+import spock.lang.Specification
+
+class IdAllocatorSpec extends Specification {
+
+ def "id is not allocated until it is allocated"() {
+ when:
+
+ IdAllocator a = new IdAllocator()
+
+ then:
+
+ !a.isAllocated("name")
+
+ when:
+
+ def actual = a.allocateId("name")
+
+ then:
+
+ actual == "name"
+ a.isAllocated("name")
+ }
+
+ def "repeatedly allocated ids are uniqued with a suffix"() {
+
+ IdAllocator a = new IdAllocator()
+
+ a.allocateId("name")
+
+ expect:
+
+ 10.times {
+
+ def expected = "name_$it"
+
+ assert !a.isAllocated(expected)
+
+ assert a.allocateId("name") == expected
+ }
+ }
+
+ def "access to allocated ids"() {
+ IdAllocator a = new IdAllocator()
+
+ when:
+
+ a.allocateId("name")
+
+ then:
+
+ a.allocatedIds == ["name"]
+
+ when:
+
+ a.allocateId("name")
+
+ then:
+
+ a.allocatedIds == ["name", "name_0"]
+ }
+
+ def "allocation using a namespace"() {
+
+ IdAllocator a = new IdAllocator("_NS")
+
+ expect:
+
+ a.allocateId("name") == "name_NS"
+
+ a.allocateId("name") == "name_NS_0"
+
+ // This is current behavior, but is probably something
+ // that could be improved.
+
+ a.allocateId("name_NS") == "name_NS_NS"
+
+ a.allocateId("name_NS") == "name_NS_NS_0"
+ }
+
+ def "degenerate id allocation"() {
+ IdAllocator a = new IdAllocator()
+
+ expect:
+
+ a.allocateId("d_1") == "d_1"
+ a.allocateId("d") == "d"
+ a.allocateId("d") == "d_0"
+ a.allocateId("d") == "d_2"
+
+ a.allocateId("d") == "d_3"
+
+ // It's a collision, so a unique number is appended.
+ a.allocateId("d_1") == "d_1_0"
+ }
+
+ def "degenerate id allocation (with a namespace)"() {
+
+ IdAllocator a = new IdAllocator("_NS")
+
+ expect:
+
+ a.allocateId("d_1") == "d_1_NS"
+
+ a.allocateId("d") == "d_NS"
+ a.allocateId("d") == "d_NS_0"
+ a.allocateId("d") == "d_NS_1"
+ a.allocateId("d") == "d_NS_2"
+ a.allocateId("d") == "d_NS_3"
+
+ a.allocateId("d_1") == "d_1_NS_0"
+
+ // This is very degenerate, and maybe something that needs fixing.
+
+ a.allocateId("d_1_NS") == "d_1_NS_NS"
+ }
+
+ def "clearing an allocator forgets prior ids"() {
+ when:
+
+ IdAllocator a = new IdAllocator()
+
+
+ then:
+
+ a.allocateId("foo") == "foo"
+ a.allocateId("foo") == "foo_0"
+
+ when:
+
+ a.clear()
+
+ then:
+
+ a.allocateId("foo") == "foo"
+ a.allocateId("foo") == "foo_0"
+ }
+
+ def "cloning an id allocator does not share data with the new allocator"() {
+
+ when:
+
+ IdAllocator a = new IdAllocator();
+
+ then:
+
+ a.allocateId("foo") == "foo"
+ a.allocateId("foo") == "foo_0"
+
+ when:
+
+ IdAllocator b = a.clone()
+
+ then:
+
+ ["bar", "baz", "foo", "foo"].each {
+ assert a.allocateId(it) == b.allocateId(it)
+ }
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/a1bef869/tapestry-ioc/src/test/groovy/ioc/specs/InheritanceSearchSpec.groovy
----------------------------------------------------------------------
diff --git a/tapestry-ioc/src/test/groovy/ioc/specs/InheritanceSearchSpec.groovy b/tapestry-ioc/src/test/groovy/ioc/specs/InheritanceSearchSpec.groovy
new file mode 100644
index 0000000..eabedeb
--- /dev/null
+++ b/tapestry-ioc/src/test/groovy/ioc/specs/InheritanceSearchSpec.groovy
@@ -0,0 +1,68 @@
+package ioc.specs
+
+import org.apache.tapestry5.plastic.PlasticUtils
+import spock.lang.Specification
+import spock.lang.Unroll
+import org.apache.tapestry5.ioc.internal.util.*
+
+class InheritanceSearchSpec extends Specification {
+
+ def "remove() is always a failure"() {
+ when:
+
+ new InheritanceSearch(Object).remove()
+
+ then:
+
+ thrown(UnsupportedOperationException)
+ }
+
+ def "exception thrown when invoking next() after Object has been reached"() {
+ def s = new InheritanceSearch(Object)
+
+ expect:
+
+ s.next() == Object
+ !s.hasNext()
+
+ when:
+
+ s.next()
+
+ then:
+
+ thrown(IllegalStateException)
+ }
+
+ @Unroll
+ def "inheritance of #className is #expectedNames"() {
+ def search = new InheritanceSearch(clazz)
+ def result = []
+ while (search.hasNext()) {
+ result << search.next()
+ }
+
+ expect:
+
+ result == expected
+
+ where:
+
+ clazz | expected
+ Object | [Object]
+ String | [String, Serializable, Comparable, CharSequence, Object]
+ Comparable | [Comparable, Object]
+ FooBar | [FooBar, Foo, Bar, Object]
+ FooBarImpl | [FooBarImpl, FooImpl, BarImpl, Bar, FooBar, Foo, Object]
+ long | [long, Long, Number, Comparable, Serializable, Object]
+ void | [void, Object]
+ long[] | [long[], Cloneable, Serializable, Object]
+ int[][] | [int[][], Cloneable, Serializable, Object]
+ String[] | [String[], Object[], Cloneable, Serializable, Object]
+ String[][] | [String[][], Object[], Cloneable, Serializable, Object]
+
+ className = PlasticUtils.toTypeName(clazz)
+ expectedNames = expected.collect { PlasticUtils.toTypeName(it) }.join(", ")
+
+ }
+}
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/a1bef869/tapestry-ioc/src/test/groovy/ioc/specs/InjectionSpec.groovy
----------------------------------------------------------------------
diff --git a/tapestry-ioc/src/test/groovy/ioc/specs/InjectionSpec.groovy b/tapestry-ioc/src/test/groovy/ioc/specs/InjectionSpec.groovy
new file mode 100644
index 0000000..7c7d549
--- /dev/null
+++ b/tapestry-ioc/src/test/groovy/ioc/specs/InjectionSpec.groovy
@@ -0,0 +1,125 @@
+package ioc.specs
+
+import org.apache.tapestry5.ioc.*
+
+class InjectionSpec extends AbstractRegistrySpecification {
+
+ def "symbol in @Inject is expanded"() {
+
+ buildRegistry GreeterModule
+
+ when:
+
+ def greeter = getService "Greeter", Greeter
+
+ then:
+
+ greeter.greeting == "Hello"
+ greeter.toString() == "<Proxy for Greeter(org.apache.tapestry5.ioc.Greeter)>"
+ }
+
+ def "injection by marker with single match"() {
+
+ buildRegistry GreeterModule
+
+ when:
+
+ def greeter = getService "InjectedBlueGreeter", Greeter
+
+ then:
+
+ greeter.greeting == "Blue"
+ }
+
+ def "verify exception for inject by marker with multiple matches"() {
+ buildRegistry GreeterModule
+
+ def greeter = getService "InjectedRedGreeter", Greeter
+
+ when:
+
+ greeter.greeting
+
+ then:
+
+ RuntimeException e = thrown()
+
+ e.message.contains "Unable to locate a single service assignable to type org.apache.tapestry5.ioc.Greeter with marker annotation(s) org.apache.tapestry5.ioc.RedMarker"
+ e.message.contains "org.apache.tapestry5.ioc.GreeterModule.buildRedGreeter1()"
+ e.message.contains "org.apache.tapestry5.ioc.GreeterModule.buildRedGreeter2()"
+ }
+
+ def "verify exception for injection by marker and no matches"() {
+ buildRegistry GreeterModule
+
+ def greeter = getService "InjectedYellowGreeter", Greeter
+
+ when:
+
+ greeter.greeting
+
+ then:
+
+ RuntimeException e = thrown()
+
+ e.message.contains "Exception constructing service"
+ e.message.contains "Unable to locate any service assignable to type org.apache.tapestry5.ioc.Greeter with marker annotation(s) org.apache.tapestry5.ioc.YellowMarker."
+ }
+
+ def "recursion handling injections (due to MasterObjectProvider) is detected"() {
+
+ buildRegistry CyclicMOPModule
+
+ def trigger = getService "Trigger", Runnable
+
+ when:
+
+ trigger.run()
+
+ then:
+
+ RuntimeException e = thrown()
+
+ e.message.contains "Construction of service 'TypeCoercer' has failed due to recursion"
+ }
+
+ def "A field may be annotated with @InjectResource to receive resources"() {
+
+ buildRegistry FieldResourceInjectionModule
+
+ when:
+
+ def s = getService FieldResourceService
+
+ then:
+
+ s.serviceId == "FieldResourceService"
+
+ s.labels == ["Barney", "Betty", "Fred", "Wilma"]
+ }
+
+ def "methods with @PostInjection are invoked and can be passed further injections"() {
+ buildRegistry PostInjectionMethodModule
+
+ when:
+
+ def g = getService Greeter
+
+ then:
+
+ g.greeting == "Greetings from ServiceIdGreeter."
+ }
+
+ def "a service may be overridden by contributing to ServiceOverride"() {
+ buildRegistry GreeterServiceOverrideModule
+
+ when:
+
+ def g = getObject Greeter, null
+
+ then:
+
+ g.greeting == "Override Greeting"
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/a1bef869/tapestry-ioc/src/test/groovy/ioc/specs/InternalUtilsSpec.groovy
----------------------------------------------------------------------
diff --git a/tapestry-ioc/src/test/groovy/ioc/specs/InternalUtilsSpec.groovy b/tapestry-ioc/src/test/groovy/ioc/specs/InternalUtilsSpec.groovy
new file mode 100644
index 0000000..0666198
--- /dev/null
+++ b/tapestry-ioc/src/test/groovy/ioc/specs/InternalUtilsSpec.groovy
@@ -0,0 +1,607 @@
+package ioc.specs
+
+import org.apache.tapestry5.func.F
+import org.apache.tapestry5.func.Predicate
+import org.apache.tapestry5.ioc.annotations.Inject
+import org.apache.tapestry5.ioc.def.ServiceDef
+import org.apache.tapestry5.ioc.def.ServiceDef2
+import org.apache.tapestry5.ioc.internal.QuietOperationTracker
+import org.apache.tapestry5.ioc.services.Builtin
+import org.apache.tapestry5.ioc.services.Coercion
+import org.apache.tapestry5.ioc.services.SymbolSource
+import spock.lang.Shared
+import spock.lang.Specification
+import spock.lang.Unroll
+
+import java.lang.reflect.Method
+
+import org.apache.tapestry5.ioc.*
+import org.apache.tapestry5.ioc.internal.util.*
+
+class InternalUtilsSpec extends Specification {
+
+ private static class PrivateInnerClass {
+
+ public PrivateInnerClass() {
+ }
+ }
+
+ static class PublicInnerClass {
+
+ protected PublicInnerClass() {
+ }
+ }
+
+ @Shared
+ def tracker = new QuietOperationTracker();
+
+
+ @Unroll
+ def "asString(): #desc"() {
+
+ when:
+
+ Method m = clazz.getMethod(methodName, * paramTypes)
+
+ then:
+
+ InternalUtils.asString(m) == expected
+
+ where:
+
+ clazz | methodName | paramTypes | expected | desc
+ Object | "toString" | [] | "java.lang.Object.toString()" | "method with no arguments"
+ Collections | "sort" | [List, Comparator] | "java.util.Collections.sort(List, Comparator)" | "method with multiple argments"
+ Object | "wait" | [long] | "java.lang.Object.wait(long)" | "method with primitive argument"
+ Arrays | "sort" | [int[]] | "java.util.Arrays.sort(int[])" | "method with primitive array argument"
+ Arrays | "sort" | [Object[]] | "java.util.Arrays.sort(Object[])" | "method with object array argument"
+ }
+
+ @Unroll
+ def "size(): #desc"() {
+ expect:
+
+ InternalUtils.size(array as Object[]) == expected
+
+ where:
+
+ array | expected | desc
+ [] | 0 | "empty array"
+ null | 0 | "null is size 0"
+ [1, 2, 3] | 3 | "non-empty array"
+ }
+
+ @Unroll
+ def "stripMemberName('#input') should be '#expected'"() {
+ expect:
+
+ InternalUtils.stripMemberName(input) == expected
+
+ where:
+
+ input | expected
+ "simple" | "simple"
+ "_name" | "name"
+ '$name' | "name"
+ '__$ruby_style_' | "ruby_style"
+ '$_$__$__$_$____$_$_$_$$name' | "name"
+ "foo_" | "foo"
+ "_foo_" | "foo"
+ }
+
+ def "invalid input to stripMemberName() is an exception"() {
+ when:
+
+ InternalUtils.stripMemberName("!foo")
+
+ then:
+
+ IllegalArgumentException e = thrown()
+
+ e.message == "Input '!foo' is not a valid Java identifier."
+ }
+
+ def "toList(Enumeration) is a sorted list"() {
+ when:
+
+ def e = Collections.enumeration(["wilma", "fred", "barney"])
+
+ then:
+
+ InternalUtils.toList(e) == ["barney", "fred", "wilma"]
+ }
+
+ @Unroll
+ def "join(): #desc"() {
+ expect:
+
+ InternalUtils.join(list) == expected
+
+ where:
+
+ list | expected | desc
+ ["barney"] | "barney" | "single value"
+ ["fred", "barney", "wilma"] | "fred, barney, wilma" | "multiple values"
+ ["fred", "barney", "", "wilma"] | "fred, barney, (blank), wilma" | "empty string converted to '(blank)'"
+ }
+
+ @Unroll
+ def "joinSorted(): #desc"() {
+ InternalUtils.joinSorted(list) == expected
+
+ where:
+
+ list | expected | desc
+ null | "(none)" | "null list is '(none)'"
+ [] | "(none)" | "empty list is '(none)'"
+ ["barney"] | "barney" | "single value"
+ ["fred", "barney", "wilma"] | "barney, fred, wilma" | "multiple values"
+ ["fred", "barney", "", "wilma"] | "(blank), barney, fred, wilma" | "empty string converted to '(blank)'"
+ }
+
+ @Unroll
+ def "capitalize('#input') is '#expected'"() {
+ expect:
+
+ InternalUtils.capitalize(input) == expected
+
+ where:
+
+ input | expected
+ "hello" | "Hello"
+ "Goodbye" | "Goodbye"
+ "" | ""
+ "a" | "A"
+ "A" | "A"
+ }
+
+ def "locationOf(Object)"() {
+ Locatable locatable = Mock()
+ Location l = Mock()
+
+ expect:
+
+ InternalUtils.locationOf(null) == null
+ InternalUtils.locationOf("La! La!") == null
+
+ InternalUtils.locationOf(l).is(l)
+
+ when:
+
+ def actual = InternalUtils.locationOf(locatable)
+
+ then:
+
+ _ * locatable.location >> l
+
+ actual.is(l)
+ }
+
+ @Unroll
+ def "sortedKeys(): #desc"() {
+ expect:
+
+ InternalUtils.sortedKeys(map) == expected
+
+ where:
+
+ map | expected | desc
+ null | [] | "null map"
+ [:] | [] | "empty map"
+ ["fred": "flintstone", "barney": "rubble"] | ["barney", "fred"] | "standard map"
+ }
+
+ @Unroll
+ def "get(Map,Object): #desc"() {
+ expect:
+
+ InternalUtils.get(map, key) == expected
+
+ where:
+
+ map | key | expected | desc
+ null | null | null | "null key and map"
+ null | "foo" | null | "null map"
+ ["fred": "flintstone"] | "fred" | "flintstone" | "real map and key"
+ ["fred": "flintstone"] | "barney" | null | "real map with missing key"
+ }
+
+ def "reverseIterator(List)"() {
+ when:
+
+ def i = InternalUtils.reverseIterator(["a", "b", "c"])
+
+ then:
+
+ i.hasNext()
+ i.next() == "c"
+
+ i.hasNext()
+ i.next() == "b"
+
+ i.hasNext()
+ i.next() == "a"
+
+ !i.hasNext()
+ }
+
+ def "remove() not supported by reverse Iterator"() {
+ def i = InternalUtils.reverseIterator(["a", "b", "c"])
+
+ when:
+
+ i.remove()
+
+ then:
+
+ thrown(UnsupportedOperationException)
+ }
+
+ @Unroll
+ def "lastTerm(): #desc"() {
+ expect:
+
+ InternalUtils.lastTerm(input) == expected
+
+ where:
+
+ input | expected | desc
+ "simple" | "simple" | "single term"
+ "fee.fie.foe.fum" | "fum" | "dotted name sequence"
+ }
+
+ def "simple value passed to lastTerm() returns the exact input value"() {
+ def input = "simple"
+
+ expect:
+
+ InternalUtils.lastTerm(input).is(input)
+ }
+
+ def "addToMapList()"() {
+ def map = [:]
+
+ when:
+
+ InternalUtils.addToMapList(map, "fred", 1)
+
+ then:
+
+ map == ["fred": [1]]
+
+ when:
+
+ InternalUtils.addToMapList(map, "fred", 2)
+
+ then:
+
+ map == ["fred": [1, 2]]
+ }
+
+ def "validateMarkerAnnotation()"() {
+
+ when:
+
+ InternalUtils.validateMarkerAnnotation(Inject)
+
+ then:
+
+ noExceptionThrown()
+
+ when:
+
+ InternalUtils.validateMarkerAnnotations([Inject, NotRetainedRuntime] as Class[])
+
+ then:
+
+ IllegalArgumentException e = thrown()
+
+ e.message == "Marker annotation class org.apache.tapestry5.ioc.internal.util.NotRetainedRuntime is not valid because it is not visible at runtime. Add a @Retention(RetentionPolicy.RUNTIME) to the class."
+ }
+
+ def "close(Closable) for null does nothing"() {
+ when:
+ InternalUtils.close(null)
+
+ then:
+ noExceptionThrown()
+ }
+
+ def "close(Closable) for success case"() {
+ Closeable c = Mock()
+
+ when:
+
+ InternalUtils.close(c)
+
+ then:
+
+ 1 * c.close()
+ }
+
+ def "close(Closable) ignores exceptions"() {
+ Closeable c = Mock()
+
+ when:
+
+ InternalUtils.close(c)
+
+ then:
+
+ 1 * c.close() >> {
+ throw new IOException("ignored")
+ }
+ }
+
+ def "constructor with Tapestry @Inject annotation"() {
+ when:
+
+ def c = InternalUtils.findAutobuildConstructor(InjectoBean)
+
+ then:
+
+ c.parameterTypes == [String]
+ }
+
+ def "constructor with javax @Inject annotation"() {
+ when:
+
+ def c = InternalUtils.findAutobuildConstructor(JavaxInjectBean)
+
+ then:
+
+ c.parameterTypes == [String]
+ }
+
+ def "too many autobuild constructors"() {
+ when:
+
+ InternalUtils.findAutobuildConstructor(TooManyAutobuildConstructorsBean)
+
+ then:
+
+ IllegalArgumentException e = thrown()
+
+ e.message == "Too many autobuild constructors found: use either @org.apache.tapestry5.ioc.annotations.Inject or @javax.inject.Inject annotation to mark a single constructor for autobuilding."
+ }
+
+ def "validateConstructorForAutobuild(): ensure check that the class itself is public"() {
+ def cons = PrivateInnerClass.constructors[0]
+
+ when:
+
+ InternalUtils.validateConstructorForAutobuild(cons)
+
+ then:
+
+ IllegalArgumentException e = thrown()
+
+ e.message == "Class ${PrivateInnerClass.name} is not a public class and may not be autobuilt."
+ }
+
+ def "validateConstructorForAutobuild(): ensure check that constructor is public"() {
+ def cons = PublicInnerClass.declaredConstructors[0]
+
+ when:
+
+ InternalUtils.validateConstructorForAutobuild(cons)
+
+ then:
+
+ IllegalArgumentException e = thrown()
+
+ e.message == "Constructor protected ${PublicInnerClass.name}() is not public and may not be used for autobuilding an instance of the class. " +
+ "You should make the constructor public, or mark an alternate public constructor with the @Inject annotation."
+ }
+
+ def "@Inject service annotation on a field"() {
+ ObjectLocator ol = Mock()
+ def target = new FieldInjectionViaInjectService()
+ Runnable fred = Mock()
+
+ when:
+
+ InternalUtils.injectIntoFields(target, ol, null, tracker)
+
+ then:
+
+ target.fred.is(fred)
+
+ 1 * ol.getService("FredService", Runnable) >> fred
+ }
+
+ def "@javax.annotations.Inject / @Named annotation on field"() {
+ ObjectLocator ol = Mock()
+ def target = new FieldInjectionViaJavaxNamed()
+ Runnable fred = Mock()
+
+ when:
+
+ InternalUtils.injectIntoFields(target, ol, null, tracker)
+
+ then:
+
+ target.fred.is(fred)
+
+ 1 * ol.getService("BarneyService", Runnable) >> fred
+ }
+
+ def "@Inject annotation on field"() {
+ ObjectLocator ol = Mock()
+ def target = new FieldInjectionViaInject()
+ SymbolSource source = Mock()
+ InjectionResources resources = Mock()
+
+ when:
+
+ InternalUtils.injectIntoFields(target, ol, resources, tracker)
+
+ then:
+
+ target.symbolSource.is(source)
+
+ 1 * resources.findResource(SymbolSource, SymbolSource) >> null
+ 1 * ol.getObject(SymbolSource, _) >> { type, ap ->
+ assert ap.getAnnotation(Builtin) != null
+
+ return source
+ }
+ }
+
+ def "@javax.annotation.Inject annotation on field"() {
+ ObjectLocator ol = Mock()
+ def target = new FieldInjectionViaJavaxInject()
+ SymbolSource source = Mock()
+ InjectionResources resources = Mock()
+
+ when:
+
+ InternalUtils.injectIntoFields(target, ol, resources, tracker)
+
+ then:
+
+ target.symbolSource.is(source)
+
+ 1 * resources.findResource(SymbolSource, SymbolSource) >> null
+ 1 * ol.getObject(SymbolSource, _) >> { type, ap ->
+ assert ap.getAnnotation(Builtin) != null
+
+ return source
+ }
+ }
+
+ def "check handling of exception while injecting into a field"() {
+ ObjectLocator ol = Mock()
+ def target = new FieldInjectionViaInjectService()
+
+ when:
+
+ InternalUtils.injectIntoFields(target, ol, null, tracker)
+
+ then:
+
+ Exception e = thrown()
+
+ 1 * ol.getService("FredService", Runnable) >> "NotTheRightType"
+
+ e.message.contains "Unable to set field 'fred' of <FieldInjectionViaInjectService> to NotTheRightType"
+ }
+
+ @Unroll
+ def "keys(Map): #desc"() {
+ expect:
+
+ InternalUtils.keys(map) == (expected as Set)
+
+ where:
+
+ map | expected | desc
+ null | [] | "null map"
+ [:] | [] | "empty map"
+ ["fred": "flintstone", "barney": "rubble"] | ["fred", "barney"] | "non-empty map"
+ }
+
+ @Unroll
+ def "size(Collection): #desc"() {
+ expect:
+
+ InternalUtils.size(coll) == expected
+
+ where:
+
+ coll | expected | desc
+ null | 0 | "null collection"
+ [] | 0 | "empty collection"
+ [1, 2, 3] | 3 | "non-empty collection"
+ }
+
+ def "toServiceDef2() delegates most methods to ServiceDef instance"() {
+ ServiceDef delegate = Mock()
+ ServiceBuilderResources resources = Mock()
+ ObjectCreator creator = Mock()
+ def serviceId = "fred"
+ def markers = [] as Set
+
+ ServiceDef2 sd = InternalUtils.toServiceDef2(delegate)
+
+ when:
+
+ def actual = sd.createServiceCreator(resources)
+
+ then:
+
+ actual.is creator
+
+ 1 * delegate.createServiceCreator(resources) >> creator
+
+
+ when:
+
+ actual = sd.getServiceId()
+
+ then:
+ actual.is serviceId
+
+ 1 * delegate.serviceId >> serviceId
+
+ when:
+
+ actual = sd.markers
+
+ then:
+
+ actual.is markers
+ 1 * delegate.markers >> markers
+
+
+ when:
+
+ actual = sd.serviceInterface
+
+ then:
+
+ actual == Runnable
+ 1 * delegate.serviceInterface >> Runnable
+
+ when:
+
+ actual = sd.serviceScope
+
+ then:
+
+ actual == ScopeConstants.PERTHREAD
+ 1 * delegate.serviceScope >> ScopeConstants.PERTHREAD
+
+ when:
+
+ actual = sd.eagerLoad
+
+ then:
+
+ actual == true
+ 1 * delegate.eagerLoad >> true
+
+ expect:
+
+ !sd.preventDecoration
+ }
+
+ def "matchAndSort()"() {
+ def pred = { !it.startsWith(".") } as Predicate
+
+ expect:
+
+ InternalUtils.matchAndSort(["Fred", "Barney", "..", ".hidden", "Wilma"], pred) == ["Barney", "Fred", "Wilma"]
+ }
+
+ def "toMapper(Coercion)"() {
+ def coercion = { it.toUpperCase() } as Coercion
+
+ def flow = F.flow("Mary", "had", "a", "little", "lamb")
+
+ expect:
+
+ flow.map(InternalUtils.toMapper(coercion)).toList() == ["MARY", "HAD", "A", "LITTLE", "LAMB"]
+ }
+}
+
http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/a1bef869/tapestry-ioc/src/test/groovy/ioc/specs/JustInTimeObjectCreatorSpec.groovy
----------------------------------------------------------------------
diff --git a/tapestry-ioc/src/test/groovy/ioc/specs/JustInTimeObjectCreatorSpec.groovy b/tapestry-ioc/src/test/groovy/ioc/specs/JustInTimeObjectCreatorSpec.groovy
new file mode 100644
index 0000000..bfd1c59
--- /dev/null
+++ b/tapestry-ioc/src/test/groovy/ioc/specs/JustInTimeObjectCreatorSpec.groovy
@@ -0,0 +1,53 @@
+package ioc.specs
+
+import org.apache.tapestry5.ioc.ObjectCreator
+import org.apache.tapestry5.ioc.internal.ServiceActivityTracker
+import org.apache.tapestry5.ioc.internal.services.JustInTimeObjectCreator
+import org.apache.tapestry5.ioc.services.Status
+import spock.lang.Specification
+
+class JustInTimeObjectCreatorSpec extends Specification {
+
+ static final String SERVICE_ID = "FooBar";
+
+ def "can not create object after shutdown"() {
+
+ ObjectCreator creator = Mock()
+
+ def jit = new JustInTimeObjectCreator(null, creator, SERVICE_ID)
+
+ // Simulate the invocation from the Registry when it shuts down.
+ jit.run()
+
+ when:
+
+ jit.createObject()
+
+ then:
+
+ RuntimeException e = thrown()
+
+ e.message.contains "Proxy for service FooBar is no longer active because the IOC Registry has been shut down."
+ }
+
+ def "lazily instantiates the object via its delegate creator"() {
+
+ ObjectCreator creator = Mock()
+ Object service = new Object()
+ ServiceActivityTracker tracker = Mock()
+
+ def jit = new JustInTimeObjectCreator(tracker, creator, SERVICE_ID)
+
+ when:
+
+ jit.eagerLoadService()
+
+ then:
+
+ 1 * creator.createObject() >> service
+ 1 * tracker.setStatus(SERVICE_ID, Status.REAL)
+ 0 * _
+
+ jit.createObject().is service
+ }
+}
http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/a1bef869/tapestry-ioc/src/test/groovy/ioc/specs/LazyAdvisorImplSpec.groovy
----------------------------------------------------------------------
diff --git a/tapestry-ioc/src/test/groovy/ioc/specs/LazyAdvisorImplSpec.groovy b/tapestry-ioc/src/test/groovy/ioc/specs/LazyAdvisorImplSpec.groovy
new file mode 100644
index 0000000..cfc6fc0
--- /dev/null
+++ b/tapestry-ioc/src/test/groovy/ioc/specs/LazyAdvisorImplSpec.groovy
@@ -0,0 +1,140 @@
+package ioc.specs
+
+import org.apache.tapestry5.ioc.Greeter
+import org.apache.tapestry5.ioc.annotations.NotLazy
+import org.apache.tapestry5.ioc.services.AspectDecorator
+import org.apache.tapestry5.ioc.services.LazyAdvisor
+
+import java.sql.SQLException
+
+public interface LazyService {
+
+ void notLazyBecauseVoid();
+
+ String notLazyBecauseOfReturnValue();
+
+ /**
+ * The only lazy method.
+ */
+ Greeter createGreeter() throws RuntimeException;
+
+ Greeter safeCreateCreator();
+
+ @NotLazy
+ Greeter notLazyFromAnnotationGreeter();
+
+ Greeter notLazyCreateGreeter() throws SQLException;
+}
+
+class LazyAdvisorImplSpec extends AbstractSharedRegistrySpecification {
+
+ def LazyService advise(LazyService base) {
+ def decorator = getService AspectDecorator
+ def advisor = getService LazyAdvisor
+
+ def builder = decorator.createBuilder LazyService, base, "<LazyService Proxy>"
+
+
+ advisor.addLazyMethodInvocationAdvice builder
+
+ builder.build()
+ }
+
+ LazyService service = Mock()
+ LazyService advised = advise service
+
+ def "void methods are not lazy"() {
+
+ when:
+
+ advised.notLazyBecauseVoid()
+
+ then:
+
+ service.notLazyBecauseVoid()
+ }
+
+ def "methods with a non-interface return type are not lazy"() {
+
+ when:
+
+ assert advised.notLazyBecauseOfReturnValue() == "so true"
+
+ then:
+
+ 1 * service.notLazyBecauseOfReturnValue() >> "so true"
+ }
+
+ def "returned thunks cache the return value"() {
+
+ Greeter greeter = Mock()
+
+ when:
+
+ def thunk = advised.createGreeter()
+
+ then:
+
+ 0 * _
+
+ when:
+
+ assert thunk.greeting == "Lazy!"
+
+ then:
+
+ 1 * service.createGreeter() >> greeter
+ 1 * greeter.greeting >> "Lazy!"
+ 0 * _
+
+ when:
+
+ assert thunk.greeting == "Still Lazy!"
+
+ then: "the greeter instance is cached"
+
+ 1 * greeter.greeting >> "Still Lazy!"
+ 0 * _
+ }
+
+ def "a checked exception will prevent laziness"() {
+
+ Greeter greeter = Mock()
+
+ when:
+
+ assert advised.notLazyCreateGreeter().is(greeter)
+
+ then:
+
+ 1 * service.notLazyCreateGreeter() >> greeter
+ 0 * _
+ }
+
+ def "the @NotLazy annotation prevents laziness"() {
+
+ Greeter greeter = Mock()
+
+ when:
+
+ assert advised.notLazyFromAnnotationGreeter().is(greeter)
+
+ then:
+
+ 1 * service.notLazyFromAnnotationGreeter() >> greeter
+ 0 * _
+ }
+
+ def "thunk class is cached"() {
+
+ when:
+
+ def g1 = advised.createGreeter()
+ def g2 = advised.safeCreateCreator()
+
+ then:
+
+ g1.class == g2.class
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/a1bef869/tapestry-ioc/src/test/groovy/ioc/specs/LocalizedNamesGeneratorSpec.groovy
----------------------------------------------------------------------
diff --git a/tapestry-ioc/src/test/groovy/ioc/specs/LocalizedNamesGeneratorSpec.groovy b/tapestry-ioc/src/test/groovy/ioc/specs/LocalizedNamesGeneratorSpec.groovy
new file mode 100644
index 0000000..474ef00
--- /dev/null
+++ b/tapestry-ioc/src/test/groovy/ioc/specs/LocalizedNamesGeneratorSpec.groovy
@@ -0,0 +1,39 @@
+package ioc.specs
+
+import org.apache.tapestry5.ioc.util.LocalizedNameGenerator
+import spock.lang.Specification
+import spock.lang.Unroll
+
+class LocalizedNamesGeneratorSpec extends Specification {
+
+ @Unroll
+ def "Localized names for #path and #locale are '#expected'"() {
+
+ when:
+
+ LocalizedNameGenerator g = new LocalizedNameGenerator(path, locale)
+
+ then:
+
+ expected.tokenize().each {
+ assert g.hasNext()
+ assert g.next() == it
+ }
+
+ !g.hasNext()
+
+ where:
+
+ path | locale | expected
+
+ "basic.test" | Locale.US | "basic_en_US.test basic_en.test basic.test"
+ "noCountry.zap" | Locale.FRENCH | "noCountry_fr.zap noCountry.zap"
+ "fred.foo" | new Locale("en", "", "GEEK") | "fred_en__GEEK.foo fred_en.foo fred.foo"
+ "context:/blah" | Locale.FRENCH | "context:/blah_fr context:/blah"
+ "context:/blah" | new Locale("fr", "", "GEEK") | "context:/blah_fr__GEEK context:/blah_fr context:/blah"
+
+ // The double-underscore is correct, it's a kind of placeholder for the null country. JDK1.3 always converts the locale to upper case. JDK 1.4
+ // does not. To keep this test happyt, we selected an all-uppercase locale.
+
+ }
+}
http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/a1bef869/tapestry-ioc/src/test/groovy/ioc/specs/LocationImplSpec.groovy
----------------------------------------------------------------------
diff --git a/tapestry-ioc/src/test/groovy/ioc/specs/LocationImplSpec.groovy b/tapestry-ioc/src/test/groovy/ioc/specs/LocationImplSpec.groovy
new file mode 100644
index 0000000..ea71e00
--- /dev/null
+++ b/tapestry-ioc/src/test/groovy/ioc/specs/LocationImplSpec.groovy
@@ -0,0 +1,89 @@
+package ioc.specs
+
+import org.apache.tapestry5.ioc.internal.util.ClasspathResource
+import org.apache.tapestry5.ioc.internal.util.LocationImpl
+import spock.lang.Shared
+import spock.lang.Specification
+
+class LocationImplSpec extends Specification {
+
+ @Shared
+ def random = new Random()
+
+ @Shared
+ def resource = new ClasspathResource("/foo/Bar.xml")
+
+ def "toString() with all three parameters"() {
+ def line = random.nextInt()
+ def column = random.nextInt()
+
+ when:
+
+ def location = new LocationImpl(resource, line, column)
+
+ then:
+
+ location.resource.is(resource)
+ location.line == line
+ location.column == column
+
+ location.toString() == "$resource, line $line, column $column"
+ }
+
+ def "toString() with unknown column"() {
+ def line = random.nextInt()
+
+ when:
+
+ def location = new LocationImpl(resource, line)
+
+ then:
+
+ location.resource.is(resource)
+ location.line == line
+ location.toString() == "$resource, line $line"
+ }
+
+ def "unknown line and column"() {
+ when:
+
+ def location = new LocationImpl(resource,)
+
+ then:
+
+ location.resource.is(resource)
+ location.toString() == resource.toString()
+ }
+
+ def "equality"() {
+
+ when:
+
+ def l1 = new LocationImpl(resource, 22, 7)
+ def l2 = new LocationImpl(resource, 22, 7)
+ def l3 = new LocationImpl(null, 22, 7)
+ def l4 = new LocationImpl(resource, 99, 7)
+ def l5 = new LocationImpl(resource, 22, 99)
+ def l6 = new LocationImpl(new ClasspathResource("/baz/Biff.txt"), 22, 7)
+
+ then:
+
+ l1 == l1
+ l1 != null
+
+ l1 == l2
+ l2.hashCode() == l1.hashCode()
+
+ l3 != l1
+ l3.hashCode() != l1.hashCode()
+
+ l4 != l1
+ l4.hashCode() != l1.hashCode()
+
+ l5 != l1
+ l5.hashCode() != l1.hashCode()
+
+ l6 != l1
+ l6.hashCode() != l1.hashCode()
+ }
+}
http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/a1bef869/tapestry-ioc/src/test/groovy/ioc/specs/LoggingDecoratorImplSpec.groovy
----------------------------------------------------------------------
diff --git a/tapestry-ioc/src/test/groovy/ioc/specs/LoggingDecoratorImplSpec.groovy b/tapestry-ioc/src/test/groovy/ioc/specs/LoggingDecoratorImplSpec.groovy
new file mode 100644
index 0000000..248cb2a
--- /dev/null
+++ b/tapestry-ioc/src/test/groovy/ioc/specs/LoggingDecoratorImplSpec.groovy
@@ -0,0 +1,173 @@
+package ioc.specs
+
+import org.apache.tapestry5.ioc.internal.ToStringService
+import org.apache.tapestry5.ioc.internal.UpcaseService
+import org.apache.tapestry5.ioc.services.LoggingDecorator
+import org.slf4j.Logger
+import org.xml.sax.SAXParseException
+
+interface AdderService {
+
+ long add(long operand1, long operand2);
+}
+
+interface ExceptionService {
+
+ void parse() throws SAXParseException;
+}
+
+class LoggingDecoratorImplSpec extends AbstractSharedRegistrySpecification {
+
+ LoggingDecorator decorator = getService LoggingDecorator
+
+ Logger logger = Mock()
+
+ def "logging of void method"() {
+
+ _ * logger.debugEnabled >> true
+
+ Runnable delegate = Mock()
+
+ Runnable interceptor = decorator.build(Runnable, delegate, "foo.Bar", logger)
+
+ when:
+
+ interceptor.run()
+
+ then:
+
+ 1 * logger.debug("[ENTER] run()")
+
+ then:
+
+ 1 * delegate.run()
+
+ then:
+
+ 1 * logger.debug("[ EXIT] run")
+
+ interceptor.toString() == "<Logging interceptor for foo.Bar(java.lang.Runnable)>"
+ }
+
+ def "runtime exception inside method is logged"() {
+ _ * logger.debugEnabled >> true
+
+ Runnable delegate = Mock()
+
+ Runnable interceptor = decorator.build(Runnable, delegate, "foo.Bar", logger)
+
+ def t = new RuntimeException("From delegate.")
+
+ when:
+
+ interceptor.run()
+
+ then:
+
+ 1 * logger.debug("[ENTER] run()")
+
+ then:
+
+ 1 * delegate.run() >> {
+ throw t
+ }
+
+ then:
+
+ 1 * logger.debug("[ FAIL] run -- ${RuntimeException.name}", t)
+
+ then:
+
+ RuntimeException e = thrown()
+
+ e.is t
+ }
+
+ def "method throws checked exception"() {
+ Throwable t = new SAXParseException("From delegate.", null)
+ _ * logger.debugEnabled >> true
+ ExceptionService delegate = Mock()
+
+ ExceptionService service = decorator.build(ExceptionService, delegate, "MyService", logger)
+
+ when:
+
+ service.parse()
+
+ then:
+
+ Throwable actual = thrown()
+
+ actual.is(t)
+
+ 1 * logger.debug("[ENTER] parse()")
+
+ 1 * delegate.parse() >> { throw t }
+
+ 1 * logger.debug("[ FAIL] parse -- ${SAXParseException.name}", t)
+ }
+
+ def "handling of object parameter and return type"() {
+ _ * logger.debugEnabled >> true
+
+ UpcaseService delegate = Mock()
+
+ UpcaseService service = decorator.build(UpcaseService, delegate, "MyService", logger)
+
+ when:
+
+ assert service.upcase("barney") == "BARNEY"
+
+ then:
+
+ 1 * logger.debug('[ENTER] upcase("barney")')
+
+ 1 * delegate.upcase(_) >> { args -> args[0].toUpperCase() }
+
+ 1 * logger.debug('[ EXIT] upcase ["BARNEY"]')
+ }
+
+ def "handling of primitive parameter and return type"() {
+ _ * logger.debugEnabled >> true
+
+ AdderService delegate = Mock()
+
+ AdderService service = decorator.build(AdderService, delegate, "Adder", logger)
+
+ when:
+
+ assert service.add(6, 13) == 19
+
+ then:
+
+ 1 * logger.debug("[ENTER] add(6, 13)")
+
+ 1 * delegate.add(_, _) >> { args -> args[0] + args[1] }
+
+ 1 * logger.debug("[ EXIT] add [19]")
+ }
+
+ def "toString() method of service interface is delegated"() {
+ _ * logger.debugEnabled >> true
+
+ // Spock's Mocking doesn't seem to be as savvy as Tapestry's about letting toString()
+ // delegate through, so we can't implement ToStringService as a Mock
+
+ ToStringService delegate = new ToStringService() {
+
+ String toString() { "FROM DELEGATE" }
+ }
+
+ ToStringService service = decorator.build(ToStringService, delegate, "ToString", logger)
+
+ when:
+
+ assert service.toString() == "FROM DELEGATE"
+
+ then:
+
+ 1 * logger.debug("[ENTER] toString()")
+ 1 * logger.debug('[ EXIT] toString ["FROM DELEGATE"]')
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/a1bef869/tapestry-ioc/src/test/groovy/ioc/specs/LoggingSourceImplSpec.groovy
----------------------------------------------------------------------
diff --git a/tapestry-ioc/src/test/groovy/ioc/specs/LoggingSourceImplSpec.groovy b/tapestry-ioc/src/test/groovy/ioc/specs/LoggingSourceImplSpec.groovy
new file mode 100644
index 0000000..14c61c7
--- /dev/null
+++ b/tapestry-ioc/src/test/groovy/ioc/specs/LoggingSourceImplSpec.groovy
@@ -0,0 +1,30 @@
+package ioc.specs
+
+import org.apache.tapestry5.ioc.LoggerSource
+import org.apache.tapestry5.ioc.internal.LoggerSourceImpl
+import org.slf4j.LoggerFactory
+import spock.lang.Specification
+
+class LoggingSourceImplSpec extends Specification {
+
+ LoggerSource loggerSource = new LoggerSourceImpl()
+
+ def "get logger by class"() {
+ Class clazz = getClass()
+
+ expect:
+
+ loggerSource.getLogger(clazz).is(LoggerFactory.getLogger(clazz))
+ }
+
+ def "get logger by name"() {
+ String name = "foo.Bar"
+
+ expect:
+
+ loggerSource.getLogger(name).is(LoggerFactory.getLogger(name))
+
+ }
+
+
+}
http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/a1bef869/tapestry-ioc/src/test/groovy/ioc/specs/ManifestProcessingSpec.groovy
----------------------------------------------------------------------
diff --git a/tapestry-ioc/src/test/groovy/ioc/specs/ManifestProcessingSpec.groovy b/tapestry-ioc/src/test/groovy/ioc/specs/ManifestProcessingSpec.groovy
new file mode 100644
index 0000000..8c13398
--- /dev/null
+++ b/tapestry-ioc/src/test/groovy/ioc/specs/ManifestProcessingSpec.groovy
@@ -0,0 +1,37 @@
+package ioc.specs
+
+import org.apache.tapestry5.ioc.IOCUtilities
+import org.apache.tapestry5.ioc.RegistryBuilder
+import spock.lang.Specification
+
+class ManifestProcessingSpec extends Specification {
+
+ def "invalid class in manifest"() {
+
+ File fakejar = new File("src/test/fakejar")
+
+ expect:
+
+ // This is more to verify the module execution environment
+ fakejar.exists()
+ fakejar.isDirectory()
+
+ when:
+
+ URL url = fakejar.toURL()
+ URLClassLoader loader = new URLClassLoader([url] as URL[], Thread.currentThread().contextClassLoader)
+
+ RegistryBuilder builder = new RegistryBuilder(loader)
+
+ IOCUtilities.addDefaultModules(builder)
+
+ then:
+
+ RuntimeException e = thrown()
+
+ e.message.contains "Exception loading module(s) from manifest"
+ e.message.contains "Failure loading Tapestry IoC module class does.not.exist.Module"
+
+
+ }
+}
http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/a1bef869/tapestry-ioc/src/test/groovy/ioc/specs/MasterObjectProviderImplSpec.groovy
----------------------------------------------------------------------
diff --git a/tapestry-ioc/src/test/groovy/ioc/specs/MasterObjectProviderImplSpec.groovy b/tapestry-ioc/src/test/groovy/ioc/specs/MasterObjectProviderImplSpec.groovy
new file mode 100644
index 0000000..f8fbc84
--- /dev/null
+++ b/tapestry-ioc/src/test/groovy/ioc/specs/MasterObjectProviderImplSpec.groovy
@@ -0,0 +1,110 @@
+package ioc.specs
+
+import org.apache.tapestry5.ioc.AnnotationProvider
+import org.apache.tapestry5.ioc.ObjectLocator
+import org.apache.tapestry5.ioc.ObjectProvider
+import org.apache.tapestry5.ioc.OperationTracker
+import org.apache.tapestry5.ioc.internal.QuietOperationTracker
+import org.apache.tapestry5.ioc.internal.services.MasterObjectProviderImpl
+import org.apache.tapestry5.ioc.services.MasterObjectProvider
+import spock.lang.Shared
+import spock.lang.Specification
+
+class MasterObjectProviderImplSpec extends Specification {
+
+ @Shared
+ OperationTracker tracker = new QuietOperationTracker()
+
+ def "found match via first provider"() {
+ ObjectProvider prov1 = Mock()
+ ObjectProvider prov2 = Mock()
+ AnnotationProvider ap = Mock()
+ ObjectLocator locator = Mock()
+ Runnable expected = Mock()
+
+ MasterObjectProvider mop = new MasterObjectProviderImpl([prov1, prov2], tracker)
+
+ when:
+
+ assert mop.provide(Runnable, ap, locator, true).is(expected)
+
+ then:
+
+ 1 * prov1.provide(Runnable, ap, locator) >> expected
+ 0 * _
+ }
+
+ def "found match after first provider"() {
+ ObjectProvider prov1 = Mock()
+ ObjectProvider prov2 = Mock()
+ AnnotationProvider ap = Mock()
+ ObjectLocator locator = Mock()
+ Runnable expected = Mock()
+
+ MasterObjectProvider mop = new MasterObjectProviderImpl([prov1, prov2], tracker)
+
+ when:
+
+ assert mop.provide(Runnable, ap, locator, true).is(expected)
+
+ then:
+
+ 1 * prov1.provide(Runnable, ap, locator) >> null
+
+ then:
+
+ 1 * prov2.provide(Runnable, ap, locator) >> expected
+ 0 * _
+ }
+
+ def "no match found on optional search returns null"() {
+ ObjectProvider prov1 = Mock()
+ ObjectProvider prov2 = Mock()
+ AnnotationProvider ap = Mock()
+ ObjectLocator locator = Mock()
+
+ MasterObjectProvider mop = new MasterObjectProviderImpl([prov1, prov2], tracker)
+
+ when:
+
+ assert mop.provide(Runnable, ap, locator, false) == null
+
+ then:
+
+ 1 * prov1.provide(Runnable, ap, locator) >> null
+
+ then:
+
+ 1 * prov2.provide(Runnable, ap, locator) >> null
+ 0 * _
+ }
+
+ def "no match for a required search delegates to the ObjectLocator.getService(Class)"() {
+ ObjectProvider prov1 = Mock()
+ ObjectProvider prov2 = Mock()
+ AnnotationProvider ap = Mock()
+ ObjectLocator locator = Mock()
+ Runnable expected = Mock()
+
+ MasterObjectProvider mop = new MasterObjectProviderImpl([prov1, prov2], tracker)
+
+ when:
+
+ assert mop.provide(Runnable, ap, locator, true).is(expected)
+
+ then:
+
+ 1 * prov1.provide(Runnable, ap, locator) >> null
+
+ then:
+
+ 1 * prov2.provide(Runnable, ap, locator) >> null
+
+ then:
+
+ 1 * locator.getService(Runnable) >> expected
+
+ 0 * _
+
+ }
+}
http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/a1bef869/tapestry-ioc/src/test/groovy/ioc/specs/MessageFormatterImplSpec.groovy
----------------------------------------------------------------------
diff --git a/tapestry-ioc/src/test/groovy/ioc/specs/MessageFormatterImplSpec.groovy b/tapestry-ioc/src/test/groovy/ioc/specs/MessageFormatterImplSpec.groovy
new file mode 100644
index 0000000..5e5c0f1
--- /dev/null
+++ b/tapestry-ioc/src/test/groovy/ioc/specs/MessageFormatterImplSpec.groovy
@@ -0,0 +1,28 @@
+package ioc.specs
+
+import org.apache.tapestry5.ioc.internal.util.MessageFormatterImpl
+import spock.lang.Specification
+import spock.lang.Unroll
+
+class MessageFormatterImplSpec extends Specification {
+
+ @Unroll
+ def "standard formatting: #desc"() {
+
+ def mf = new MessageFormatterImpl(format, null)
+
+ expect:
+
+ mf.format(* args) == expected
+
+ where:
+
+ format | args | expected | desc
+
+ "Tapestry is %s." | ["cool"] | "Tapestry is cool." | "simple substition"
+ "Tapestry release #%d." | [5] | "Tapestry release #5." | "numeric conversion"
+ "%s is %s at version %d." | ["Tapestry", "cool", 5] | "Tapestry is cool at version 5." | "multiple conversions"
+ "%s failed: %s" | ["Something", new RuntimeException("bad wolf")] | "Something failed: bad wolf" | "expansion of exception message"
+ "%s failed: %s" | ["Another", new NullPointerException()] | "Another failed: java.lang.NullPointerException" | "expansion of exception without message is exception class name"
+ }
+}
http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/a1bef869/tapestry-ioc/src/test/groovy/ioc/specs/MessagesImplSpec.groovy
----------------------------------------------------------------------
diff --git a/tapestry-ioc/src/test/groovy/ioc/specs/MessagesImplSpec.groovy b/tapestry-ioc/src/test/groovy/ioc/specs/MessagesImplSpec.groovy
new file mode 100644
index 0000000..d063a09
--- /dev/null
+++ b/tapestry-ioc/src/test/groovy/ioc/specs/MessagesImplSpec.groovy
@@ -0,0 +1,76 @@
+package ioc.specs
+
+import org.apache.tapestry5.ioc.Messages
+import org.apache.tapestry5.ioc.internal.util.MessagesImpl
+import org.apache.tapestry5.ioc.internal.util.TargetMessages
+import spock.lang.Shared
+import spock.lang.Specification
+import spock.lang.Unroll
+
+class MessagesImplSpec extends Specification {
+
+ @Shared
+ Messages messages = MessagesImpl.forClass(TargetMessages)
+
+ @Unroll
+ def "contains key: #desc"() {
+
+ expect:
+
+ messages.contains(key) == expectation
+
+ where:
+
+ key | expectation | desc
+ "no-args" | true | "base case"
+ "xyzzyz" | false | "key not present"
+ "No-Args" | true | "case insensitive"
+ }
+
+ @Unroll
+ def "get message from catalog: #desc"() {
+ expect:
+
+ messages.get(key) == expectation
+
+ where:
+
+ key | expectation | desc
+
+ "no-args" | "No arguments." | "base case"
+ "something-failed" | "Something failed: %s" | "does not attempt to expand conversions"
+ "No-Args" | "No arguments." | "access is case insensitive"
+ "does-not-exist" | "[[missing key: does-not-exist]]" | "fake value supplied for missing key"
+ }
+
+ @Unroll
+ def "format message:#desc"() {
+ expect:
+
+ messages.format(key, value) == expectation
+
+ where:
+
+ key | value | expectation | desc
+ "result" | "good" | "The result is 'good'." | "standard"
+ "Result" | "best" | "The result is 'best'." | "lookup is case insensitive"
+ "does-not-exist" | "xyzzyz" | "[[missing key: does-not-exist]]" | "fake value supplied for missing key"
+ }
+
+ def "access a MesageFormatter to format content"() {
+ def mf = messages.getFormatter("result")
+
+ expect:
+
+ mf.format("cool") == "The result is 'cool'."
+ }
+
+ def "MessageFormatters are cached"() {
+ def mf1 = messages.getFormatter("result")
+ def mf2 = messages.getFormatter("result")
+
+ expect:
+
+ mf1.is(mf2)
+ }
+}
http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/a1bef869/tapestry-ioc/src/test/groovy/ioc/specs/MethodIteratorSpec.groovy
----------------------------------------------------------------------
diff --git a/tapestry-ioc/src/test/groovy/ioc/specs/MethodIteratorSpec.groovy b/tapestry-ioc/src/test/groovy/ioc/specs/MethodIteratorSpec.groovy
new file mode 100644
index 0000000..a3b7af8
--- /dev/null
+++ b/tapestry-ioc/src/test/groovy/ioc/specs/MethodIteratorSpec.groovy
@@ -0,0 +1,115 @@
+package ioc.specs
+
+import org.apache.tapestry5.ioc.internal.services.MethodIterator
+import org.apache.tapestry5.ioc.internal.services.MethodSignature
+import spock.lang.Specification
+import spock.lang.Unroll
+
+interface Play extends Runnable {
+
+ void jump()
+}
+
+interface Runnable2 {
+
+ void run()
+}
+
+interface Runnable3 extends Runnable, Runnable2 {
+
+}
+
+interface Openable {
+
+ public void open();
+}
+
+interface OpenableWithError {
+
+ public void open() throws IOException;
+}
+
+interface CombinedOpeneable extends Openable, OpenableWithError {
+}
+
+class MethodIteratorSpec extends Specification {
+
+ def "iterate a simple (single-method) interface"() {
+
+ MethodIterator mi = new MethodIterator(Runnable)
+
+ expect:
+
+ mi.hasNext()
+
+ when: "iterate to first method"
+
+ def actual = mi.next()
+
+ then: "first method signature returned"
+
+ actual == new MethodSignature(void, "run", null, null)
+
+ !mi.hasNext()
+
+ when: "iterating when no method signatures left"
+
+ mi.next()
+
+ then: "throws exception"
+
+ thrown(NoSuchElementException)
+ }
+
+ def "method inherited from super interface are visible"() {
+
+ MethodIterator mi = new MethodIterator(Play)
+
+ expect:
+
+ mi.hasNext()
+
+ mi.next() == new MethodSignature(void, "jump", null, null)
+
+ mi.hasNext()
+
+ mi.next() == new MethodSignature(void, "run", null, null)
+
+ !mi.hasNext()
+ }
+
+ @Unroll
+ def "getToString() on #interfaceType.name should be #expected"() {
+
+ expect:
+
+ new MethodIterator(interfaceType).getToString() == expected
+
+ where:
+
+ interfaceType | expected
+ Runnable | false
+ Play | false
+ ToString | true
+ }
+
+ def "method duplicated from a base interface into a sub interface are filtered out"() {
+ MethodIterator mi = new MethodIterator(Runnable3)
+
+ expect:
+
+ mi.next() == new MethodSignature(void, "run", null, null)
+ !mi.hasNext()
+ }
+
+ def "inherited methods are filtered out if less specific"() {
+ MethodIterator mi = new MethodIterator(CombinedOpeneable)
+
+ expect:
+
+ mi.next() == new MethodSignature(void, "open", null, [IOException] as Class[])
+
+ !mi.hasNext()
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/a1bef869/tapestry-ioc/src/test/groovy/ioc/specs/MethodSignatureSpec.groovy
----------------------------------------------------------------------
diff --git a/tapestry-ioc/src/test/groovy/ioc/specs/MethodSignatureSpec.groovy b/tapestry-ioc/src/test/groovy/ioc/specs/MethodSignatureSpec.groovy
new file mode 100644
index 0000000..5745770
--- /dev/null
+++ b/tapestry-ioc/src/test/groovy/ioc/specs/MethodSignatureSpec.groovy
@@ -0,0 +1,179 @@
+package ioc.specs
+
+import org.apache.tapestry5.ioc.internal.services.MethodSignature
+import spock.lang.Specification
+import spock.lang.Unroll
+
+import java.lang.reflect.Method
+import java.sql.SQLException
+
+class MethodSignatureSpec extends Specification {
+
+ def MethodSignature find(Class sourceClass, String methodName) {
+ Method match = sourceClass.methods.find { it.name == methodName }
+
+ if (match == null) {
+ throw new IllegalStateException("Call $sourceClass.name has no method named '$methodName'.")
+ }
+
+ return new MethodSignature(match)
+ }
+
+ @Unroll
+ def "#firstClass.name and #secondClass.name have identical MethodSignatures for method #methodName"() {
+
+ when:
+
+ def m1 = find firstClass, methodName
+ def m2 = find secondClass, methodName
+
+ then:
+
+ m1.hashCode() == m2.hashCode()
+ m1 == m2
+
+ where:
+
+ firstClass | secondClass | methodName
+ Object | Boolean | "hashCode"
+ String | StringBuilder | "charAt"
+ ObjectInput | ObjectInputStream | "close"
+ }
+
+ def "a null parameter or exception list is equivalent to an empty one"() {
+ def m1 = new MethodSignature(void, "foo", null, null)
+ def m2 = new MethodSignature(void, "foo", [] as Class[], [] as Class[])
+
+ expect:
+
+ m1 == m2
+ m2 == m1
+
+ m1.hashCode() == m2.hashCode()
+ }
+
+ def "a mismatch of method name causes inequality"() {
+ def m1 = new MethodSignature(void, "foo", null, null)
+ def m2 = new MethodSignature(void, "bar", null, null)
+
+ expect:
+
+ m1 != m2
+ }
+
+ def "a mismatch of parameters causes inequality"() {
+ def m1 = new MethodSignature(void, "foo", [String] as Class[], null)
+ def m2 = new MethodSignature(void, "foo", [Boolean] as Class[], null)
+
+ expect:
+
+ m1 != m2
+ }
+
+ def "a MethodSignature never equals null"() {
+
+ expect:
+
+ new MethodSignature(void, "foo", null, null) != null
+ }
+
+ def "a MethodSignature may only equal another MethodSignature"() {
+
+ expect:
+
+ new MethodSignature(void, "foo", null, null) != "Any Old Thing"
+ }
+
+ @Unroll
+ def "MethodSignature.toString() for #clazz.name #methodName is '#toString'"() {
+
+ def sig = find(clazz, methodName)
+
+ expect:
+
+ sig.toString() == toString
+
+ where:
+
+ clazz | methodName | toString
+ String | "getChars" | "void getChars(int, int, char[], int)"
+ Class | "newInstance" | "java.lang.Object newInstance() throws java.lang.IllegalAccessException, java.lang.InstantiationException"
+ }
+
+ @Unroll
+ def "MethodSignature.uniqueId for #clazz.name #methodName is '#uniqueId'"() {
+ def sig = find(clazz, methodName)
+
+ expect:
+
+ sig.uniqueId == uniqueId
+
+ where:
+
+ clazz | methodName | uniqueId
+ String | "getChars" | "getChars(int,int,char[],int)"
+ Class | "newInstance" | "newInstance()"
+ }
+
+ def "different return types will prevent override"() {
+
+ def m1 = new MethodSignature(void, "foo", null, null)
+ def m2 = new MethodSignature(int, "foo", null, null)
+
+ expect:
+
+ !m1.isOverridingSignatureOf(m2)
+ }
+
+ def "different method names will prevent override"() {
+ def m1 = new MethodSignature(int, "foo", null, null)
+ def m2 = new MethodSignature(int, "bar", null, null)
+
+ expect:
+
+ !m1.isOverridingSignatureOf(m2)
+ }
+
+ def "different parameter types will prevent override"() {
+ def m1 = new MethodSignature(int, "foo", null, null)
+ def m2 = new MethodSignature(int, "foo", [String] as Class[], null)
+
+ expect:
+
+ !m1.isOverridingSignatureOf(m2)
+ }
+
+ def "a difference of exceptions thrown allows for override"() {
+ def m1 = new MethodSignature(int, "foo", null, [Exception] as Class[])
+ def m2 = new MethodSignature(int, "foo", null, [RuntimeException] as Class[])
+
+ expect:
+
+ // All of m2's exceptions are assignable to at least one of m1's exceptions
+ m1.isOverridingSignatureOf(m2)
+ !m2.isOverridingSignatureOf(m1)
+ }
+
+ def "signature with no exceptions will not override"() {
+ def m1 = new MethodSignature(int, "foo", null, null)
+ def m2 = new MethodSignature(int, "foo", null, [RuntimeException] as Class[])
+
+ expect:
+
+ !m1.isOverridingSignatureOf(m2)
+ m2.isOverridingSignatureOf(m1)
+ }
+
+ def "complex matching of signature exceptions when determining override"() {
+ def m1 = new MethodSignature(void, "close", null,
+ [SQLException, NumberFormatException] as Class[])
+ def m2 = new MethodSignature(void.class, "close", null,
+ [SQLException, IOException] as Class[])
+
+ expect:
+
+ // NumberFormatException and IOException don't fit in either direction
+ !m1.isOverridingSignatureOf(m2)
+ !m2.isOverridingSignatureOf(m1)
+ }
+}