Skip to content

Commit 500f65d

Browse files
authored
Add a way to register ConfigurationObject globally (#1183)
* Cleanup closeQuietly, Remove unused methods in IoUtil
1 parent 12f23d1 commit 500f65d

File tree

12 files changed

+131
-137
lines changed

12 files changed

+131
-137
lines changed

docs/extensions.adoc

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -720,9 +720,11 @@ type. Spock will then automatically create exactly one instance of the configura
720720
settings from the configuration file to it (before the `start()` methods of global extensions are called) and inject
721721
that instance into the extension class instances.
722722

723-
A configuration object cannot be used exclusively in an annotation driven local extension, but it has to be used in at
724-
least one global extension to properly get initialized and populated with the settings from the configuration file. But
725-
if the configuration object is used in a global extension, you can also use it just fine in an annotation driven local
723+
If a configuration object should be used exclusively in an annotation driven local extension you must register it in
724+
`META-INF/services/spock.config.ConfigurationObject`.
725+
This is similar to a global extension, put the fully-qualified class name of the annotated class on a new line in the file.
726+
This will cause the configuration object to properly get initialized and populated with the settings from the configuration file.
727+
However, if the configuration object is used in a global extension, you can also use it just fine in an annotation driven local
726728
extension. If the configuration object is only used in an annotation driven local extension, you will get an exception
727729
when then configuration object is to be injected into the extension and you will also get an error when the
728730
configuration file is evaluated and it contains the section, as the configuration object is not properly registered yet.

docs/release_notes.adoc

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ include::include.adoc[]
33

44
== 2.0-M4 (tbd)
55

6+
- Add a way to register `ConfigurationObject` globally without the need for a Global Extension
7+
68
== 2.0-M3 (2020-06-11)
79

810
=== Breaking Changes

spock-core/src/main/groovy/spock/util/EmbeddedSpecRunner.groovy

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ class EmbeddedSpecRunner {
4545

4646
Closure configurationScript = null
4747
List<Class> extensionClasses = []
48+
List<Class> configClasses = []
4849
boolean inheritParentExtensions = true
4950

5051
void configurationScript(Closure configurationScript) {
@@ -85,7 +86,7 @@ class EmbeddedSpecRunner {
8586
}
8687
}
8788

88-
SummarizedEngineExecutionResults runClasses(List classes) {
89+
SummarizedEngineExecutionResults runClasses(List<Class<?>> classes) {
8990
withNewContext {
9091
doRunRequest(classes.findAll { !Modifier.isAbstract(it.modifiers) }.collect {selectClass(it)})
9192
}
@@ -125,8 +126,8 @@ class EmbeddedSpecRunner {
125126
def newSpockUserHome = new File(context.spockUserHome, "EmbeddedSpecRunner")
126127
def script = configurationScript ?
127128
new ConfigurationScriptLoader(newSpockUserHome).loadClosureBasedScript(configurationScript) : null
128-
RunContext.withNewContext(newContextName, newSpockUserHome, script,
129-
extensionClasses, inheritParentExtensions, block as IThrowableFunction)
129+
(T)RunContext.withNewContext(newContextName, newSpockUserHome, script,
130+
extensionClasses, configClasses, inheritParentExtensions, block as IThrowableFunction)
130131
}
131132

132133
private SummarizedEngineExecutionResults doRunRequest(List<DiscoverySelector> selectors) {

spock-core/src/main/java/org/spockframework/runtime/ExtensionClassesLoader.java

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
package org.spockframework.runtime;
1616

1717
import org.spockframework.runtime.extension.*;
18-
import org.spockframework.util.IoUtil;
18+
import spock.config.ConfigurationObject;
1919

2020
import java.io.*;
2121
import java.net.URL;
@@ -26,11 +26,16 @@
2626
*/
2727
public class ExtensionClassesLoader {
2828
public static final String EXTENSION_DESCRIPTOR_PATH = "META-INF/services/" + IGlobalExtension.class.getName();
29+
public static final String CONFIG_DESCRIPTOR_PATH = "META-INF/services/" + ConfigurationObject.class.getName();
2930

30-
public List<Class<?>> loadClassesFromDefaultLocation() {
31+
public List<Class<?>> loadExtensionClassesFromDefaultLocation() {
3132
return loadClasses(EXTENSION_DESCRIPTOR_PATH);
3233
}
3334

35+
public List<Class<?>> loadConfigClassesFromDefaultLocation() {
36+
return loadClasses(CONFIG_DESCRIPTOR_PATH);
37+
}
38+
3439
public List<Class<?>> loadClasses(String descriptorPath) {
3540
Map<String, URL> discoveredClasses = new HashMap<>();
3641
List<Class<?>> extClasses = new ArrayList<>();
@@ -57,10 +62,7 @@ private List<URL> locateDescriptors(String descriptorPath) {
5762
}
5863

5964
private List<String> readDescriptor(URL url) {
60-
BufferedReader reader = null;
61-
62-
try {
63-
reader = new BufferedReader(new InputStreamReader(url.openStream()));
65+
try(BufferedReader reader = new BufferedReader(new InputStreamReader(url.openStream()))) {
6466
List<String> lines = new ArrayList<>();
6567
String line = reader.readLine();
6668
while (line != null) {
@@ -72,8 +74,6 @@ private List<String> readDescriptor(URL url) {
7274
return lines;
7375
} catch (IOException e) {
7476
throw new ExtensionException("Failed to read extension descriptor '%s'", e).withArgs(url);
75-
} finally {
76-
IoUtil.closeQuietly(reader);
7777
}
7878
}
7979

spock-core/src/main/java/org/spockframework/runtime/GlobalExtensionRegistry.java

Lines changed: 22 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@
2323
import java.lang.reflect.Field;
2424
import java.util.*;
2525

26+
import org.jetbrains.annotations.NotNull;
27+
2628
/**
2729
* Maintains a registry of global Spock extensions and their configuration objects,
2830
* which can be used to configure other extensions.
@@ -36,19 +38,20 @@ public class GlobalExtensionRegistry implements IExtensionRegistry, IConfigurati
3638

3739
private final List<IGlobalExtension> globalExtensions = new ArrayList<>();
3840

39-
GlobalExtensionRegistry(List<Class<?>> globalExtensionClasses, List<?> initialConfigurations) {
41+
GlobalExtensionRegistry(List<Class<?>> globalExtensionClasses, List<Class<?>> initialConfigurations) {
4042
this.globalExtensionClasses = globalExtensionClasses;
4143
initializeConfigurations(initialConfigurations);
4244
}
4345

44-
private void initializeConfigurations(List<?> initialConfigurations) {
45-
for (Object configuration : initialConfigurations) {
46-
ConfigurationObject annotation = configuration.getClass().getAnnotation(ConfigurationObject.class);
46+
private void initializeConfigurations(List<Class<?>> initialConfigurations) {
47+
for (Class<?> configuration : initialConfigurations) {
48+
ConfigurationObject annotation = configuration.getAnnotation(ConfigurationObject.class);
4749
if (annotation == null) {
48-
throw new InternalSpockError("Not a @ConfigurationObject: %s").withArgs(configuration.getClass());
50+
throw new InternalSpockError("Not a @ConfigurationObject: %s").withArgs(configuration);
4951
}
50-
configurationsByType.put(configuration.getClass(), configuration);
51-
configurationsByName.put(annotation.value(), configuration);
52+
Object instance = createConfiguration(configuration);
53+
configurationsByType.put(configuration, instance);
54+
configurationsByName.put(annotation.value(), instance);
5255
}
5356
}
5457

@@ -85,7 +88,7 @@ private void verifyGlobalExtension(Class<?> clazz) {
8588

8689
private IGlobalExtension instantiateGlobalExtension(Class<?> clazz) {
8790
try {
88-
return (IGlobalExtension) clazz.newInstance();
91+
return (IGlobalExtension) clazz.getDeclaredConstructor().newInstance();
8992
} catch (Exception e) {
9093
throw new ExtensionException("Failed to instantiate extension '%s'", e).withArgs(clazz.getName());
9194
}
@@ -139,12 +142,19 @@ private Object createConfiguration(Class<?> type, Object extension) {
139142
.withArgs(extension.getClass(), type);
140143
}
141144

145+
return createConfiguration(type);
146+
}
147+
148+
@NotNull
149+
private Object createConfiguration(Class<?> type) {
142150
try {
143-
return type.newInstance();
151+
return type.getDeclaredConstructor().newInstance();
144152
} catch (InstantiationException e) {
145-
throw new ExtensionException("Cannot instantiate configuration class %s").withArgs(type);
146-
} catch (IllegalAccessException e) {
147-
throw new ExtensionException("Configuration class '%s' has no public no-arg constructor").withArgs(type);
153+
throw new ExtensionException("Cannot instantiate configuration class %s", e).withArgs(type);
154+
} catch (IllegalAccessException | NoSuchMethodException e) {
155+
throw new ExtensionException("Configuration class '%s' has no public no-arg constructor", e).withArgs(type);
156+
} catch (Exception e) {
157+
throw new ExtensionException("Failed to instantiate configuration '%s'", e).withArgs(type.getName());
148158
}
149159
}
150160
}

spock-core/src/main/java/org/spockframework/runtime/RunContext.java

Lines changed: 30 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -33,17 +33,21 @@ public class RunContext implements EngineExecutionContext {
3333
private final File spockUserHome;
3434
private final DelegatingScript configurationScript;
3535
private final List<Class<?>> globalExtensionClasses;
36+
private final List<Class<?>> globalConfigClasses;
3637
private final GlobalExtensionRegistry globalExtensionRegistry;
3738

3839
private final IObjectRenderer<Object> diffedObjectRenderer = createDiffedObjectRenderer();
3940

4041
private RunContext(String name, File spockUserHome,
41-
@Nullable DelegatingScript configurationScript, List<Class<?>> globalExtensionClasses) {
42+
@Nullable DelegatingScript configurationScript,
43+
List<Class<?>> globalExtensionClasses,
44+
List<Class<?>> globalConfigClasses) {
4245
this.name = name;
4346
this.spockUserHome = spockUserHome;
4447
this.configurationScript = configurationScript;
4548
this.globalExtensionClasses = globalExtensionClasses;
46-
globalExtensionRegistry = new GlobalExtensionRegistry(globalExtensionClasses, Collections.singletonList(new RunnerConfiguration()));
49+
this.globalConfigClasses = globalConfigClasses;
50+
globalExtensionRegistry = new GlobalExtensionRegistry(globalExtensionClasses, globalConfigClasses);
4751
}
4852

4953
private void start() {
@@ -76,7 +80,7 @@ public ExtensionRunner createExtensionRunner(SpecInfo spec) {
7680

7781
public PlatformParameterizedSpecRunner createSpecRunner(SpecInfo spec) {
7882
return new PlatformParameterizedSpecRunner(
79-
new MasterRunSupervisor(spec, createStackTraceFilter(spec), diffedObjectRenderer));
83+
new MasterRunSupervisor(spec, createStackTraceFilter(spec), diffedObjectRenderer));
8084
}
8185

8286
@Nullable
@@ -122,13 +126,19 @@ private IObjectRenderer<Object> createDiffedObjectRenderer() {
122126
}
123127

124128
public static <T, U extends Throwable> T withNewContext(String name, File spockUserHome,
125-
@Nullable DelegatingScript configurationScript,
126-
List<Class<?>> extensionClasses, boolean inheritParentExtensions,
127-
IThrowableFunction<RunContext, T, U> command) throws U {
129+
@Nullable DelegatingScript configurationScript,
130+
List<Class<?>> extensionClasses,
131+
List<Class<?>> configClasses,
132+
boolean inheritParentExtensions,
133+
IThrowableFunction<RunContext, T, U> command) throws U {
128134
List<Class<?>> allExtensionClasses = new ArrayList<>(extensionClasses);
129-
if (inheritParentExtensions) allExtensionClasses.addAll(getCurrentExtensions());
135+
List<Class<?>> allConfigClasses = new ArrayList<>(configClasses);
136+
if (inheritParentExtensions) {
137+
allExtensionClasses.addAll(getCurrentExtensions());
138+
allConfigClasses.addAll(getCurrentConfigs());
139+
}
130140

131-
RunContext context = new RunContext(name, spockUserHome, configurationScript, allExtensionClasses);
141+
RunContext context = new RunContext(name, spockUserHome, configurationScript, allExtensionClasses, allConfigClasses);
132142
Deque<RunContext> contextStack = contextStacks.get();
133143
contextStack.addFirst(context);
134144
try {
@@ -148,12 +158,8 @@ public static RunContext get() {
148158
contextStack.addFirst(context);
149159
final RunContext bottomContext = context;
150160
try {
151-
Runtime.getRuntime().addShutdownHook(new Thread("org.spockframework.runtime.RunContext.stop()") {
152-
@Override
153-
public void run() {
154-
bottomContext.stop();
155-
}
156-
});
161+
Runtime.getRuntime().addShutdownHook(
162+
new Thread(bottomContext::stop, "org.spockframework.runtime.RunContext.stop()"));
157163
} catch (AccessControlException ignored) {
158164
// GAE doesn't support creating a new thread
159165
}
@@ -168,6 +174,12 @@ private static List<Class<?>> getCurrentExtensions() {
168174
return context.globalExtensionClasses;
169175
}
170176

177+
private static List<Class<?>> getCurrentConfigs() {
178+
RunContext context = contextStacks.get().peek();
179+
if (context == null) return Collections.emptyList();
180+
return context.globalConfigClasses;
181+
}
182+
171183
// This context will stay around until the thread dies.
172184
// It would be more accurate to remove the context once the test run
173185
// has finished, but the JUnit Runner SPI doesn't provide an adequate hook.
@@ -176,7 +188,9 @@ private static List<Class<?>> getCurrentExtensions() {
176188
static RunContext createBottomContext() {
177189
File spockUserHome = SpockUserHomeUtil.getSpockUserHome();
178190
DelegatingScript script = new ConfigurationScriptLoader(spockUserHome).loadAutoDetectedScript();
179-
List<Class<?>> classes = new ExtensionClassesLoader().loadClassesFromDefaultLocation();
180-
return new RunContext("default", spockUserHome, script, classes);
191+
ExtensionClassesLoader extensionClassesLoader = new ExtensionClassesLoader();
192+
List<Class<?>> classes = extensionClassesLoader.loadExtensionClassesFromDefaultLocation();
193+
List<Class<?>> configs = extensionClassesLoader.loadConfigClassesFromDefaultLocation();
194+
return new RunContext("default", spockUserHome, script, classes, configs);
181195
}
182196
}

spock-core/src/main/java/org/spockframework/runtime/SpecRunHistory.java

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -36,28 +36,24 @@ public String getSpecName() {
3636
}
3737

3838
public void loadFromDisk() throws IOException {
39-
ObjectInputStream in = new ObjectInputStream(new FileInputStream(getDataFile()));
40-
try {
39+
40+
try(ObjectInputStream in = new ObjectInputStream(new FileInputStream(getDataFile()))) {
4141
data = (Data) in.readObject();
4242
} catch (ClassNotFoundException e) {
4343
// in JDK 1.5, there is no IOException constructor that takes a cause
4444
IOException io = new IOException("deserialization error");
4545
io.initCause(e);
4646
throw io;
47-
} finally {
48-
IoUtil.closeQuietly(in);
4947
}
5048
}
5149

5250
public void saveToDisk() throws IOException {
5351
File file = getDataFile();
5452
IoUtil.createDirectory(file.getParentFile());
5553

56-
ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream(file));
57-
try {
54+
55+
try(ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream(file))) {
5856
out.writeObject(data);
59-
} finally {
60-
IoUtil.closeQuietly(out);
6157
}
6258
}
6359

0 commit comments

Comments
 (0)