You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@tapestry.apache.org by th...@apache.org on 2021/05/31 17:23:08 UTC
[tapestry-5] branch master updated: TAP5-2668: Move spock-tapestry
extension to tapestry-test
This is an automated email from the ASF dual-hosted git repository.
thiagohp pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/tapestry-5.git
The following commit(s) were added to refs/heads/master by this push:
new f9576b8 TAP5-2668: Move spock-tapestry extension to tapestry-test
f9576b8 is described below
commit f9576b8a5a548ed3edd547d1b495f6d2a68195cb
Author: Thiago H. de Paula Figueiredo <th...@arsmachina.com.br>
AuthorDate: Mon May 31 14:20:42 2021 -0300
TAP5-2668: Move spock-tapestry extension to tapestry-test
Thanks Volker Lamp for the patch!
---
settings.gradle | 1 +
tapestry-spock/README.md | 43 +++++
tapestry-spock/build.gradle | 13 ++
.../apache/tapestry5/spock/ExtensionModule.java | 37 +++++
.../org/apache/tapestry5/spock/SpockTapestry.java | 37 +++++
.../tapestry5/spock/TapestryInterceptor.java | 182 +++++++++++++++++++++
.../tapestry5/spock/TapestrySpockExtension.java | 148 +++++++++++++++++
...ockframework.runtime.extension.IGlobalExtension | 1 +
.../spock/BeforeRegistryCreatedMethod.groovy | 83 ++++++++++
.../tapestry5/spock/InjectionExamples.groovy | 119 ++++++++++++++
.../spock/InjectionExamplesWithImportModule.groovy | 116 +++++++++++++
.../tapestry5/spock/TapestrySpecInheritance.groovy | 72 ++++++++
.../java/org/apache/tapestry5/spock/Module1.java | 39 +++++
.../java/org/apache/tapestry5/spock/Module2.java | 29 ++++
.../java/org/apache/tapestry5/spock/Service1.java | 22 +++
.../org/apache/tapestry5/spock/Service1Impl.java | 32 ++++
.../java/org/apache/tapestry5/spock/Service2.java | 22 +++
.../org/apache/tapestry5/spock/Service2Impl.java | 27 +++
.../java/org/apache/tapestry5/spock/Service3.java | 22 +++
.../org/apache/tapestry5/spock/Service3Impl.java | 36 ++++
20 files changed, 1081 insertions(+)
diff --git a/settings.gradle b/settings.gradle
index 9921957..afc6fbe 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -6,3 +6,4 @@ include "tapestry-test-data", 'tapestry-internal-test', "tapestry-ioc-junit"
include "tapestry-webresources", "tapestry-runner", "tapestry-test-constants"
include "tapestry-ioc-jcache", "beanmodel", "commons", "genericsresolver-guava", "tapestry-version-migrator"
// include "tapestry-cdi"
+include "tapestry-spock"
diff --git a/tapestry-spock/README.md b/tapestry-spock/README.md
new file mode 100644
index 0000000..d545dcb
--- /dev/null
+++ b/tapestry-spock/README.md
@@ -0,0 +1,43 @@
+# Support for Tapestry injections in Spock specifications
+This sub-project, `tapestry-spock`, provides support for Tapestry injections in [Spock](https://spockframework.org/) specifications. Spock is a testing and specification framework for Java and Groovy applications.
+
+## Usage example
+
+```java
+ @ImportModule(UniverseModule)
+ class UniverseSpec extends Specification {
+
+ @Inject
+ UniverseService service
+
+ UniverseService copy = service
+
+ def "service knows the answer to the universe"() {
+ expect:
+ copy == service // injection occurred before 'copy' was initialized
+ service.answer() == 42 // what else did you expect?!
+ }
+ }
+```
+
+`@ImportModule` indicates which Tapestry module(s) should be started (and subsequently shut down). The deprecated `@SubModule` annotation is still supported for compatibility reasons.
+
+`@Inject` marks fields which should be injected with a Tapestry service or symbol. Related Tapestry annotations, such as `@Service` and `@Symbol`, are also supported.
+
+## Drop-in replacement for spock-tapestry
+This software was originally part of the Spock project, known as `spock-tapestry`. The Tapestry project adopted the software as `tapestry-spock` to overcome the backwards incompatibilities introduced by Tapestry 5.7.
+
+`tapestry-spock` works as a drop-in replacement for `spock-tapestry`. So, when upgrading to Tapestry 5.7, use
+
+`testRuntimeOnly "org.apache.tapestry:tapestry-spock:$tapestryVersion"` (Gradle)
+
+instead of
+
+`testRuntimeOnly "org.spockframework:spock-tapestry:$spockVersion"`.
+
+
+## References
+
+1. [https://issues.apache.org/jira/browse/TAP5-2668](https://issues.apache.org/jira/browse/TAP5-2668)
+2. [https://github.com/spockframework/spock/issues/1312](https://github.com/spockframework/spock/issues/1312)
+3. [https://github.com/spockframework/spock/pull/1315](https://github.com/spockframework/spock/pull/1315)
diff --git a/tapestry-spock/build.gradle b/tapestry-spock/build.gradle
new file mode 100644
index 0000000..5c94718
--- /dev/null
+++ b/tapestry-spock/build.gradle
@@ -0,0 +1,13 @@
+description = "Provides support Tapestry injections in Spock specifications"
+
+dependencies {
+ compile project(':commons')
+ compile project(':tapestry-ioc')
+ compile "org.spockframework:spock-core:${versions.spock}"
+
+ testCompile "javax.inject:javax.inject:1"
+}
+
+test {
+ useJUnit()
+}
diff --git a/tapestry-spock/src/main/java/org/apache/tapestry5/spock/ExtensionModule.java b/tapestry-spock/src/main/java/org/apache/tapestry5/spock/ExtensionModule.java
new file mode 100644
index 0000000..d678cbc
--- /dev/null
+++ b/tapestry-spock/src/main/java/org/apache/tapestry5/spock/ExtensionModule.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright 2009, 2011 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.tapestry5.spock;
+
+import org.apache.tapestry5.commons.ObjectLocator;
+import org.apache.tapestry5.ioc.annotations.Marker;
+
+/**
+ * A Tapestry module that is started for every specification which uses Spock's
+ * Tapestry extension.
+ *
+ * @author Peter Niederwieser
+ */
+@Marker(SpockTapestry.class)
+public class ExtensionModule
+{
+
+ public static ObjectLocator build(ObjectLocator locator)
+ {
+ return locator;
+ }
+
+}
diff --git a/tapestry-spock/src/main/java/org/apache/tapestry5/spock/SpockTapestry.java b/tapestry-spock/src/main/java/org/apache/tapestry5/spock/SpockTapestry.java
new file mode 100644
index 0000000..393b8c5
--- /dev/null
+++ b/tapestry-spock/src/main/java/org/apache/tapestry5/spock/SpockTapestry.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright 2011 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.tapestry5.spock;
+
+import static java.lang.annotation.ElementType.FIELD;
+import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.ElementType.PARAMETER;
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+/**
+ * Marker annotation for services that are specific to the Spock/Tapestry
+ * integration module.
+ *
+ * @see ExtensionModule
+ */
+@Target({ PARAMETER, FIELD, METHOD })
+@Retention(RUNTIME)
+@Documented
+public @interface SpockTapestry {}
\ No newline at end of file
diff --git a/tapestry-spock/src/main/java/org/apache/tapestry5/spock/TapestryInterceptor.java b/tapestry-spock/src/main/java/org/apache/tapestry5/spock/TapestryInterceptor.java
new file mode 100644
index 0000000..4fe0ac9
--- /dev/null
+++ b/tapestry-spock/src/main/java/org/apache/tapestry5/spock/TapestryInterceptor.java
@@ -0,0 +1,182 @@
+/*
+ * Copyright 2009 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.tapestry5.spock;
+
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Set;
+
+import org.apache.tapestry5.commons.AnnotationProvider;
+import org.apache.tapestry5.ioc.Registry;
+import org.apache.tapestry5.ioc.RegistryBuilder;
+import org.apache.tapestry5.ioc.annotations.Autobuild;
+import org.apache.tapestry5.ioc.annotations.Inject;
+import org.apache.tapestry5.ioc.annotations.InjectService;
+import org.spockframework.runtime.extension.AbstractMethodInterceptor;
+import org.spockframework.runtime.extension.IMethodInvocation;
+import org.spockframework.runtime.model.FieldInfo;
+import org.spockframework.runtime.model.SpecInfo;
+import org.spockframework.util.ReflectionUtil;
+
+import spock.lang.Shared;
+import spock.lang.Specification;
+
+/**
+ * Controls creation, startup and shutdown of the Tapestry container,
+ * and injects specifications with Tapestry-provided objects.
+ *
+ * @author Peter Niederwieser
+ */
+public class TapestryInterceptor extends AbstractMethodInterceptor
+{
+ private final SpecInfo spec;
+
+ private final Set<Class<?>> modules;
+
+ private Registry registry;
+
+ public TapestryInterceptor(SpecInfo spec, Set<Class<?>> modules)
+ {
+ this.spec = spec;
+ this.modules = modules;
+ }
+
+ @Override
+ public void interceptSharedInitializerMethod(IMethodInvocation invocation) throws Throwable
+ {
+ runBeforeRegistryCreatedMethods((Specification) invocation.getSharedInstance());
+
+ createAndStartupRegistry();
+
+ injectServices(invocation.getSharedInstance(), true);
+
+ invocation.proceed();
+ }
+
+ @Override
+ public void interceptCleanupSpecMethod(IMethodInvocation invocation) throws Throwable
+ {
+ try
+ {
+ invocation.proceed();
+ }
+ finally
+ {
+ shutdownRegistry();
+ }
+ }
+
+ @Override
+ public void interceptInitializerMethod(IMethodInvocation invocation) throws Throwable
+ {
+ injectServices(invocation.getInstance(), false);
+
+ invocation.proceed();
+ }
+
+ private void runBeforeRegistryCreatedMethods(Specification spec)
+ {
+ Object returnValue;
+
+ for (Method method : findAllBeforeRegistryCreatedMethods())
+ {
+ returnValue = ReflectionUtil.invokeMethod(spec, method);
+
+ // Return values of a type other than Registry are silently ignored.
+ // This avoids problems in case the method unintentionally returns a
+ // value (which is common due to Groovy's implicit return).
+ if (returnValue instanceof Registry)
+ registry = (Registry) returnValue;
+ }
+ }
+
+ private void createAndStartupRegistry()
+ {
+ if (registry != null) return;
+
+ RegistryBuilder builder = new RegistryBuilder();
+ builder.add(ExtensionModule.class);
+
+ for (Class<?> module : modules) builder.add(module);
+
+ registry = builder.build();
+ registry.performRegistryStartup();
+ }
+
+ private List<Method> findAllBeforeRegistryCreatedMethods()
+ {
+ List<Method> methods = new ArrayList<>();
+
+ for (SpecInfo curr : spec.getSpecsTopToBottom())
+ {
+ Method method = ReflectionUtil.getDeclaredMethodByName(curr.getReflection(), "beforeRegistryCreated");
+ if (method != null)
+ {
+ method.setAccessible(true);
+ methods.add(method);
+ }
+ }
+
+ return methods;
+ }
+
+ private void injectServices(Object target, boolean sharedFields) throws IllegalAccessException
+ {
+ for (final FieldInfo field : spec.getAllFields())
+ {
+ Field rawField = field.getReflection();
+
+ if ((rawField.isAnnotationPresent(Inject.class)
+ || ReflectionUtil.isAnnotationPresent(rawField, "javax.inject.Inject")
+ || rawField.isAnnotationPresent(Autobuild.class))
+ && rawField.isAnnotationPresent(Shared.class) == sharedFields)
+ {
+ Object value = registry.getObject(rawField.getType(), createAnnotationProvider(field));
+ rawField.setAccessible(true);
+ rawField.set(target, value);
+ }
+ else if (rawField.isAnnotationPresent(InjectService.class))
+ {
+ String serviceName = rawField.getAnnotation(InjectService.class).value();
+ Object value = registry.getService(serviceName, rawField.getType());
+ rawField.setAccessible(true);
+ rawField.set(target, value);
+ }
+ }
+ }
+
+ private AnnotationProvider createAnnotationProvider(final FieldInfo field)
+ {
+ return new AnnotationProvider()
+ {
+ @Override
+ public <T extends Annotation> T getAnnotation(Class<T> annotationClass)
+ {
+ return field.getAnnotation(annotationClass);
+ }
+ };
+ }
+
+ private void shutdownRegistry()
+ {
+ if (registry != null)
+ registry.shutdown();
+ }
+}
diff --git a/tapestry-spock/src/main/java/org/apache/tapestry5/spock/TapestrySpockExtension.java b/tapestry-spock/src/main/java/org/apache/tapestry5/spock/TapestrySpockExtension.java
new file mode 100644
index 0000000..22e3cb7
--- /dev/null
+++ b/tapestry-spock/src/main/java/org/apache/tapestry5/spock/TapestrySpockExtension.java
@@ -0,0 +1,148 @@
+/*
+ * Copyright 2009 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.tapestry5.spock;
+
+import org.spockframework.runtime.extension.*;
+import org.spockframework.runtime.model.SpecInfo;
+import org.spockframework.util.ReflectionUtil;
+
+import java.lang.annotation.Annotation;
+import java.util.*;
+
+import org.apache.tapestry5.ioc.annotations.*;
+
+/**
+ * Facilitates <a href="https://spockframework.org/">Spock</a>-based integration testing
+ * of Tapestry modules. Spock is a testing and specification framework for Java and Groovy
+ * applications.
+ *
+ * Supports injection of Tapestry services into Spock specifications.
+ *
+ * <p><b>Usage example:</b>
+ *
+ * <pre>
+ * @ImportModule(UniverseModule)
+ * class UniverseSpec extends Specification {
+ *
+ * @Inject
+ * UniverseService service
+ *
+ * UniverseService copy = service
+ *
+ * def "service knows the answer to the universe"() {
+ * expect:
+ * copy == service // injection occurred before 'copy' was initialized
+ * service.answer() == 42 // what else did you expect?!
+ * }
+ * }
+ * </pre>
+ *
+ * <p><tt>@ImportModule</tt> indicates which Tapestry module(s) should be started
+ * (and subsequently shut down). The deprecated <tt>@SubModule</tt> annotation
+ * is still supported for compatibility reasons.
+ *
+ * <p><tt>@Inject</tt> marks fields which should be injected with a Tapestry service or
+ * symbol. Related Tapestry annotations, such as <tt>@Service</tt> and <tt>@Symbol</tt>,
+ * are also supported.
+ *
+ * <p>Note: Only field (and no constructor) injection is supported.
+ *
+ * <p>To interact directly with the Tapestry registry, an injection point of type
+ * <tt>ObjectLocator</tt> may be defined. However, this should be rarely needed.
+ *
+ * <p>For every specification annotated with <tt>@ImportModule</tt> or
+ * <tt>@SubModule</tt>, the Tapestry registry will be started up (and subsequently shut down)
+ * once. Because fields are injected <em>before</em> field initializers and the <tt>setup()</tt>/
+ * <tt>setupSpec()</tt> methods are run, they can be safely accessed from these places.
+ *
+ * <p>Fields marked as <tt>@Shared</tt> are injected once per specification; regular
+ * fields once per feature (iteration). However, this does <em>not</em> mean that each
+ * feature will receive a fresh service instance; rather, it is left to the Tapestry
+ * registry to control the lifecycle of a service. Most Tapestry services use the default
+ * "singleton" scope, which results in the same service instance being shared between all
+ * features.
+ *
+ * <p>Features that require their own service instance(s) should be moved into separate
+ * specifications. To avoid code fragmentation and duplication, you might want to put
+ * multiple (micro-)specifications into the same source file, and factor out their
+ * commonalities into a base class.
+ *
+ *
+ * @author Peter Niederwieser
+ */
+public class TapestrySpockExtension extends AbstractGlobalExtension
+{
+
+ // since Tapestry 5.4
+ @SuppressWarnings("unchecked")
+ private static final Class<? extends Annotation> importModuleAnnotation =
+ (Class)ReflectionUtil.loadClassIfAvailable("org.apache.tapestry5.ioc.annotations.ImportModule");
+
+ // deprecated as of Tapestry 5.4
+ @SuppressWarnings("unchecked")
+ private static final Class<? extends Annotation> submoduleAnnotation =
+ (Class)ReflectionUtil.loadClassIfAvailable("org.apache.tapestry5.ioc.annotations.SubModule");
+
+ @Override
+ public void visitSpec(final SpecInfo spec)
+ {
+ Set<Class<?>> modules = collectModules(spec);
+ if (modules == null) return;
+
+ IMethodInterceptor interceptor = new TapestryInterceptor(spec, modules);
+ spec.addSharedInitializerInterceptor(interceptor);
+ spec.addInitializerInterceptor(interceptor);
+ spec.addCleanupSpecInterceptor(interceptor);
+ }
+
+ // Returns null if no ImportModule or SubModule annotation was found.
+ // Returns an empty list if one or more ImportModule or SubModule annotations were found,
+ // but they didn't specify any modules. This distinction is important to
+ // allow activation of the extension w/o specifying any modules.
+ private Set<Class<?>> collectModules(SpecInfo spec)
+ {
+ Set<Class<?>> modules = null;
+
+ for (SpecInfo curr : spec.getSpecsTopToBottom())
+ {
+ if (importModuleAnnotation != null && spec.isAnnotationPresent(importModuleAnnotation))
+ {
+ ImportModule importModule = curr.getAnnotation(ImportModule.class);
+ if (importModule != null)
+ {
+ if (modules == null) { modules = new HashSet<>(); }
+ modules.addAll(Arrays.<Class<?>>asList(importModule.value()));
+ }
+ }
+
+ // Support for deprecated @SubModule.
+ if (submoduleAnnotation != null && spec.isAnnotationPresent(submoduleAnnotation))
+ {
+ @SuppressWarnings("deprecation")
+ SubModule subModule = curr.getAnnotation(SubModule.class);
+ if (subModule != null)
+ {
+ if (modules == null) { modules = new HashSet<>(); }
+ modules.addAll(Arrays.<Class<?>>asList(subModule.value()));
+ }
+ }
+ }
+
+ return modules;
+ }
+
+}
diff --git a/tapestry-spock/src/main/resources/META-INF/services/org.spockframework.runtime.extension.IGlobalExtension b/tapestry-spock/src/main/resources/META-INF/services/org.spockframework.runtime.extension.IGlobalExtension
new file mode 100644
index 0000000..dae201e
--- /dev/null
+++ b/tapestry-spock/src/main/resources/META-INF/services/org.spockframework.runtime.extension.IGlobalExtension
@@ -0,0 +1 @@
+org.apache.tapestry5.spock.TapestrySpockExtension
\ No newline at end of file
diff --git a/tapestry-spock/src/test/groovy/org/apache/tapestry5/spock/BeforeRegistryCreatedMethod.groovy b/tapestry-spock/src/test/groovy/org/apache/tapestry5/spock/BeforeRegistryCreatedMethod.groovy
new file mode 100644
index 0000000..a83128a
--- /dev/null
+++ b/tapestry-spock/src/test/groovy/org/apache/tapestry5/spock/BeforeRegistryCreatedMethod.groovy
@@ -0,0 +1,83 @@
+/*
+ * Copyright 2010 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.tapestry5.spock
+
+import org.apache.tapestry5.ioc.RegistryBuilder
+import org.apache.tapestry5.ioc.annotations.*
+
+import spock.lang.*
+
+@SubModule(Module1)
+class BeforeRegistryCreatedMethod extends Specification {
+ @Shared
+ @Inject
+ Service1 sharedServiceField
+
+ @Inject
+ Service1 instanceServiceField
+
+ @Shared
+ String sharedField = "sharedField"
+
+ String instanceField = "instanceField"
+
+ @Shared
+ boolean beforeRegistryCreatedRun
+
+ def beforeRegistryCreated() {
+ assert sharedServiceField == null
+ assert instanceServiceField == null
+ assert sharedField == null
+ assert instanceField == null
+ assert !beforeRegistryCreatedRun
+ beforeRegistryCreatedRun = true
+ }
+
+ def beforeSpec() {
+ assert sharedServiceField instanceof Service1Impl
+ assert instanceServiceField == null
+ assert sharedField == "sharedField"
+ assert instanceField == null
+ assert beforeRegistryCreatedRun
+ }
+
+ def feature() {
+ expect:
+ sharedServiceField instanceof Service1
+ instanceServiceField instanceof Service1
+ sharedField == "sharedField"
+ instanceField == "instanceField"
+ beforeRegistryCreatedRun
+ }
+}
+
+@SubModule([])
+class CustomTapestryRegistry extends Specification {
+ @Inject
+ Service1 service1
+
+ def beforeRegistryCreated() {
+ def builder = new RegistryBuilder()
+ builder.add(Module1)
+ def registry = builder.build()
+ registry.performRegistryStartup()
+ registry
+ }
+
+ def "custom registry is used for injection"() {
+ expect:
+ service1 instanceof Service1
+ }
+}
\ No newline at end of file
diff --git a/tapestry-spock/src/test/groovy/org/apache/tapestry5/spock/InjectionExamples.groovy b/tapestry-spock/src/test/groovy/org/apache/tapestry5/spock/InjectionExamples.groovy
new file mode 100644
index 0000000..9537f5f
--- /dev/null
+++ b/tapestry-spock/src/test/groovy/org/apache/tapestry5/spock/InjectionExamples.groovy
@@ -0,0 +1,119 @@
+package org.apache.tapestry5.spock
+/*
+ * Copyright 2009 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+import org.apache.tapestry5.commons.ObjectLocator
+import org.apache.tapestry5.ioc.annotations.*
+
+import spock.lang.*
+
+@SubModule(Module1)
+class ServiceInjection extends Specification {
+ @Inject
+ Service1 service1
+
+ @Inject
+ Service2 service2
+
+ def "injected services"() {
+ expect:
+ service1.generateString() == service2.generateQuickBrownFox()
+ }
+}
+
+@SubModule(Module1)
+class ServiceInjectionWithJavaxInject extends Specification {
+ @javax.inject.Inject
+ org.apache.tapestry5.spock.Service1 service1
+
+ @javax.inject.Inject
+ org.apache.tapestry5.spock.Service2 service2
+
+ def "injected services"() {
+ expect:
+ service1.generateString() == service2.generateQuickBrownFox()
+ }
+}
+
+@SubModule(Module1)
+class ServiceInjectionWithServiceId extends Specification {
+ @InjectService("Service3")
+ Service3 service3
+
+ @Inject
+ Service2 service2
+
+ def "injected services"() {
+ expect:
+ service3.generateString() == service2.generateQuickBrownFox()
+ }
+}
+
+@SubModule(Module1)
+class ObjectLocatorInjection extends Specification {
+ @Inject
+ private ObjectLocator locator
+
+ def "injected object locator"() {
+ expect:
+ locator.getService(Service1).generateString() == locator.getService(Service2).generateQuickBrownFox()
+ }
+}
+
+@SubModule(Module1)
+class SymbolInjection extends Specification {
+ @Inject
+ @Symbol("java.version")
+ String javaVersion
+
+ @Inject
+ @Symbol("configKey") // Groovy doesn't yet support constants in annotations, so we have to use a literal
+ String configValue
+
+ @Inject
+ @Value("\${java.version} and \${configKey}")
+ String computedValue
+
+ def "injected system property"() {
+ expect:
+ javaVersion == System.getProperty("java.version")
+ }
+
+ def "injected application default"() {
+ expect:
+ configValue == "configValue"
+ }
+
+ def "injected computed value"() {
+ expect:
+ computedValue == "${System.getProperty("java.version")} and configValue"
+ }
+}
+
+@SubModule(Module2)
+class AutobuildInjection extends Specification {
+ @Autobuild
+ Service1Impl service1
+
+ def "auto-built service"() {
+ expect:
+ service1 instanceof Service1Impl
+ }
+}
+
+class InjectionExamples {
+}
diff --git a/tapestry-spock/src/test/groovy/org/apache/tapestry5/spock/InjectionExamplesWithImportModule.groovy b/tapestry-spock/src/test/groovy/org/apache/tapestry5/spock/InjectionExamplesWithImportModule.groovy
new file mode 100644
index 0000000..a8a006c
--- /dev/null
+++ b/tapestry-spock/src/test/groovy/org/apache/tapestry5/spock/InjectionExamplesWithImportModule.groovy
@@ -0,0 +1,116 @@
+/*
+ * Copyright 2009 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.tapestry5.spock
+
+import org.apache.tapestry5.commons.ObjectLocator
+import org.apache.tapestry5.ioc.annotations.*
+
+import spock.lang.*
+
+@ImportModule(Module1)
+class ServiceInjectionWithImportModule extends Specification {
+ @Inject
+ Service1 service1
+
+ @Inject
+ Service2 service2
+
+ def "injected services"() {
+ expect:
+ service1.generateString() == service2.generateQuickBrownFox()
+ }
+}
+
+@ImportModule(Module1)
+class ServiceInjectionWithJavaxInjectWithImportModule extends Specification {
+ @javax.inject.Inject
+ org.apache.tapestry5.spock.Service1 service1
+
+ @javax.inject.Inject
+ org.apache.tapestry5.spock.Service2 service2
+
+ def "injected services"() {
+ expect:
+ service1.generateString() == service2.generateQuickBrownFox()
+ }
+}
+
+@ImportModule(Module1)
+class ServiceInjectionWithServiceIdWithImportModule extends Specification {
+ @InjectService("Service3")
+ Service3 service3
+
+ @Inject
+ Service2 service2
+
+ def "injected services"() {
+ expect:
+ service3.generateString() == service2.generateQuickBrownFox()
+ }
+}
+
+@ImportModule(Module1)
+class ObjectLocatorInjectionWithImportModule extends Specification {
+ @Inject
+ private ObjectLocator locator
+
+ def "injected object locator"() {
+ expect:
+ locator.getService(Service1).generateString() == locator.getService(Service2).generateQuickBrownFox()
+ }
+}
+
+@ImportModule(Module1)
+class SymbolInjectionWithImportModule extends Specification {
+ @Inject
+ @Symbol("java.version")
+ String javaVersion
+
+ @Inject
+ @Symbol("configKey") // Groovy doesn't yet support constants in annotations, so we have to use a literal
+ String configValue
+
+ @Inject
+ @Value('${java.version} and ${configKey}')
+ String computedValue
+
+ def "injected system property"() {
+ expect:
+ javaVersion == System.getProperty("java.version")
+ }
+
+ def "injected application default"() {
+ expect:
+ configValue == "configValue"
+ }
+
+ def "injected computed value"() {
+ expect:
+ computedValue == "${System.getProperty("java.version")} and configValue"
+ }
+}
+
+@ImportModule(Module2)
+class AutobuildInjectionWithImportModule extends Specification {
+ @Autobuild
+ Service1Impl service1
+
+ def "auto-built service"() {
+ expect:
+ service1 instanceof Service1Impl
+ }
+}
diff --git a/tapestry-spock/src/test/groovy/org/apache/tapestry5/spock/TapestrySpecInheritance.groovy b/tapestry-spock/src/test/groovy/org/apache/tapestry5/spock/TapestrySpecInheritance.groovy
new file mode 100644
index 0000000..35c4c37
--- /dev/null
+++ b/tapestry-spock/src/test/groovy/org/apache/tapestry5/spock/TapestrySpecInheritance.groovy
@@ -0,0 +1,72 @@
+/*
+ * Copyright 2010 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.tapestry5.spock
+
+import org.apache.tapestry5.ioc.annotations.*
+
+import spock.lang.*
+
+@SubModule(Module1)
+abstract class BaseSpec extends Specification {
+ static beforeRegistryCreatedCount = 0
+
+ @Inject
+ Service1 service1
+
+ @Inject
+ Service2 service2
+
+ private beforeRegistryCreated() {
+ assert beforeRegistryCreatedCount == 0
+ beforeRegistryCreatedCount++
+ }
+
+ def setupSpec() {
+ assert beforeRegistryCreatedCount == 2
+ }
+
+ def setup() {
+ assert service1 instanceof Service1
+ assert service2 instanceof Service2
+ }
+}
+
+@SubModule(Module2)
+class TapestrySpecInheritance extends BaseSpec {
+ @Inject
+ Service2 anotherService2
+
+ def beforeRegistryCreated() {
+ assert beforeRegistryCreatedCount == 1
+ beforeRegistryCreatedCount++
+ }
+
+ def setup() {
+ assert service1 instanceof Service1
+ assert service2 instanceof Service2
+ assert anotherService2 instanceof Service2
+ }
+
+ def "fields of base class have been injected"() {
+ expect:
+ service1 instanceof Service1
+ service2 instanceof Service2
+ }
+
+ def "fields of derived class have been injected"() {
+ expect:
+ anotherService2 instanceof Service2
+ }
+}
diff --git a/tapestry-spock/src/test/java/org/apache/tapestry5/spock/Module1.java b/tapestry-spock/src/test/java/org/apache/tapestry5/spock/Module1.java
new file mode 100644
index 0000000..0dfbfad
--- /dev/null
+++ b/tapestry-spock/src/test/java/org/apache/tapestry5/spock/Module1.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2009, 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.tapestry5.spock;
+
+import org.apache.tapestry5.commons.MappedConfiguration;
+import org.apache.tapestry5.ioc.ServiceBinder;
+import org.apache.tapestry5.ioc.annotations.SubModule;
+
+// Tapestry module classes cannot currently be written in Groovy
+// See https://issues.apache.org/jira/browse/TAPESTRY-2746
+@SubModule(Module2.class)
+public class Module1
+{
+ public static void bind(ServiceBinder binder)
+ {
+ binder.bind(Service1.class, Service1Impl.class);
+ binder.bind(Service3.class, Service3Impl.class);
+ }
+
+ public void contributeApplicationDefaults(MappedConfiguration<String, String> configuration)
+ {
+ configuration.add("configKey", "configValue");
+ }
+
+}
\ No newline at end of file
diff --git a/tapestry-spock/src/test/java/org/apache/tapestry5/spock/Module2.java b/tapestry-spock/src/test/java/org/apache/tapestry5/spock/Module2.java
new file mode 100644
index 0000000..6ab9e3a
--- /dev/null
+++ b/tapestry-spock/src/test/java/org/apache/tapestry5/spock/Module2.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright 2009 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.tapestry5.spock;
+
+import org.apache.tapestry5.ioc.ServiceBinder;
+
+// Tapestry module classes cannot currently be written in Groovy
+// See https://issues.apache.org/jira/browse/TAPESTRY-2746
+public class Module2
+{
+ public static void bind(ServiceBinder binder)
+ {
+ binder.bind(Service2.class, Service2Impl.class);
+ }
+}
diff --git a/tapestry-spock/src/test/java/org/apache/tapestry5/spock/Service1.java b/tapestry-spock/src/test/java/org/apache/tapestry5/spock/Service1.java
new file mode 100644
index 0000000..a4384cf
--- /dev/null
+++ b/tapestry-spock/src/test/java/org/apache/tapestry5/spock/Service1.java
@@ -0,0 +1,22 @@
+/*
+ * Copyright 2009 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.tapestry5.spock;
+
+public interface Service1
+{
+ String generateString();
+}
diff --git a/tapestry-spock/src/test/java/org/apache/tapestry5/spock/Service1Impl.java b/tapestry-spock/src/test/java/org/apache/tapestry5/spock/Service1Impl.java
new file mode 100644
index 0000000..50ee5ac
--- /dev/null
+++ b/tapestry-spock/src/test/java/org/apache/tapestry5/spock/Service1Impl.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright 2009 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.tapestry5.spock;
+
+public class Service1Impl implements Service1
+{
+ private final Service2 service2;
+
+ public Service1Impl(Service2 service2)
+ {
+ this.service2 = service2;
+ }
+
+ public String generateString()
+ {
+ return service2.generateQuickBrownFox();
+ }
+}
diff --git a/tapestry-spock/src/test/java/org/apache/tapestry5/spock/Service2.java b/tapestry-spock/src/test/java/org/apache/tapestry5/spock/Service2.java
new file mode 100644
index 0000000..b4ff046
--- /dev/null
+++ b/tapestry-spock/src/test/java/org/apache/tapestry5/spock/Service2.java
@@ -0,0 +1,22 @@
+/*
+ * Copyright 2009 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.tapestry5.spock;
+
+public interface Service2
+{
+ String generateQuickBrownFox();
+}
\ No newline at end of file
diff --git a/tapestry-spock/src/test/java/org/apache/tapestry5/spock/Service2Impl.java b/tapestry-spock/src/test/java/org/apache/tapestry5/spock/Service2Impl.java
new file mode 100644
index 0000000..63ea6f4
--- /dev/null
+++ b/tapestry-spock/src/test/java/org/apache/tapestry5/spock/Service2Impl.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright 2009 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.tapestry5.spock;
+
+public class Service2Impl implements Service2
+{
+
+ public String generateQuickBrownFox()
+ {
+ return "The quick brown fox jumps over the lazy dog.";
+ }
+
+}
diff --git a/tapestry-spock/src/test/java/org/apache/tapestry5/spock/Service3.java b/tapestry-spock/src/test/java/org/apache/tapestry5/spock/Service3.java
new file mode 100644
index 0000000..d7f5b44
--- /dev/null
+++ b/tapestry-spock/src/test/java/org/apache/tapestry5/spock/Service3.java
@@ -0,0 +1,22 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.tapestry5.spock;
+
+public interface Service3
+{
+ String generateString();
+}
diff --git a/tapestry-spock/src/test/java/org/apache/tapestry5/spock/Service3Impl.java b/tapestry-spock/src/test/java/org/apache/tapestry5/spock/Service3Impl.java
new file mode 100644
index 0000000..e6aca66
--- /dev/null
+++ b/tapestry-spock/src/test/java/org/apache/tapestry5/spock/Service3Impl.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.tapestry5.spock;
+
+import org.apache.tapestry5.ioc.annotations.ServiceId;
+
+@ServiceId("Service3")
+public class Service3Impl implements Service3
+{
+ private final Service2 service2;
+
+ public Service3Impl(Service2 service2)
+ {
+ this.service2 = service2;
+ }
+
+ public String generateString()
+ {
+ return service2.generateQuickBrownFox();
+ }
+
+}