Skip to content

Commit

Permalink
feat: add global lifecycle
Browse files Browse the repository at this point in the history
  • Loading branch information
mjeanroy committed Aug 20, 2024
1 parent f4da781 commit cbfb89e
Show file tree
Hide file tree
Showing 25 changed files with 896 additions and 55 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -49,10 +49,12 @@
import java.lang.reflect.Parameter;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;

import static com.github.mjeanroy.junit.servers.commons.lang.Preconditions.notNull;
import static com.github.mjeanroy.junit.servers.jupiter.JunitServerExtensionLifecycle.PER_CLASS;
import static com.github.mjeanroy.junit.servers.jupiter.JunitServerExtensionLifecycle.PER_METHOD;
import static org.junit.platform.commons.support.AnnotationSupport.findAnnotation;

/**
* Extension for Junit Jupiter.
Expand Down Expand Up @@ -167,13 +169,32 @@ public class JunitServerExtension implements BeforeAllCallback, AfterAllCallback
*/
private final AbstractConfiguration configuration;

/**
* The extension lifecycle.
*/
private final JunitServerExtensionLifecycle lifecycle;

/**
* Create the jupiter with default server that will be automatically detected using the Service Provider
* API.
*/
public JunitServerExtension() {
this.server = null;
this.configuration = null;
this.lifecycle = null;
}

/**
* Create the jupiter with default server that will be automatically detected using the Service Provider
* API.
*
* @param lifecycle The extension lifecycle.
* @throws NullPointerException If {@code lifecycle} is {@code null}.
*/
public JunitServerExtension(JunitServerExtensionLifecycle lifecycle) {
this.server = null;
this.configuration = null;
this.lifecycle = notNull(lifecycle, "lifecycle");
}

/**
Expand All @@ -185,6 +206,21 @@ public JunitServerExtension() {
public JunitServerExtension(EmbeddedServer<?> server) {
this.server = notNull(server, "server");
this.configuration = null;
this.lifecycle = null;
}

/**
* Create the jupiter with given server to start/stop before/after tests.
*
* @param lifecycle The extension lifecycle.
* @param server The embedded server to use.
* @throws NullPointerException If {@code server} is {@code null}.
* @throws NullPointerException If {@code lifecycle} is {@code null}.
*/
public JunitServerExtension(JunitServerExtensionLifecycle lifecycle, EmbeddedServer<?> server) {
this.server = notNull(server, "server");
this.configuration = null;
this.lifecycle = notNull(lifecycle, "lifecycle");
}

/**
Expand All @@ -196,16 +232,41 @@ public JunitServerExtension(EmbeddedServer<?> server) {
public JunitServerExtension(AbstractConfiguration configuration) {
this.server = null;
this.configuration = configuration;
this.lifecycle = null;
}

/**
* Create the jupiter with given server configuration.
*
* @param lifecycle The extension lifecycle.
* @param configuration The embedded server configuration to use.
* @throws NullPointerException If {@code configuration} is {@code null}.
* @throws NullPointerException If {@code lifecycle} is {@code null}.
*/
public JunitServerExtension(JunitServerExtensionLifecycle lifecycle, AbstractConfiguration configuration) {
this.server = null;
this.configuration = configuration;
this.lifecycle = notNull(lifecycle, "lifecycle");
}

@Override
public void beforeAll(ExtensionContext context) {
// With nested class, the `beforeAll` is called, that could lead to multiple instances
// being instantiated.
Class<?> testClass = context.getRequiredTestClass();
JunitServerExtensionLifecycle actualLifecycle = getLifecycle(testClass, PER_CLASS);
if (actualLifecycle == PER_METHOD) {
return;
}

JunitServerExtensionContext ctx = findContextInStore(context);
if (ctx == null) {
start(context, PER_CLASS);
if (ctx != null) {
return;
}

start(
actualLifecycle.getExtensionContext(context),
testClass,
actualLifecycle
);
}

@Override
Expand All @@ -226,9 +287,14 @@ public void afterAll(ExtensionContext context) {
public void beforeEach(ExtensionContext context) {
JunitServerExtensionContext ctx = findContextInStore(context);

// The extension was not declared as a static extension.
if (ctx == null) {
ctx = start(context, PER_METHOD);
Class<?> testClass = context.getRequiredTestClass();
JunitServerExtensionLifecycle actualLifecycle = getLifecycle(testClass, PER_METHOD);
ctx = start(
actualLifecycle.getExtensionContext(context),
testClass,
actualLifecycle
);
}

ctx.getAnnotationsHandler().beforeEach(
Expand Down Expand Up @@ -323,17 +389,16 @@ private AbstractConfiguration findConfiguration(Class<?> testClass, AbstractConf
);
}

private JunitServerExtensionContext start(ExtensionContext context, JunitServerExtensionLifecycle lifecycle) {
log.debug("Register embedded server to junit extension context");
private JunitServerExtensionContext start(
ExtensionContext context,
Class<?> testClass,
JunitServerExtensionLifecycle lifecycle
) {
log.debug("Register embedded server to junit extension context using lifecycle: {}", lifecycle);

Class<?> testClass = context.getRequiredTestClass();
EmbeddedServer<?> server = this.server == null ? instantiateServer(testClass, configuration) : this.server;
EmbeddedServerRunner runner = new EmbeddedServerRunner(server);
JunitServerExtensionContext ctx = new JunitServerExtensionContext(
runner,
lifecycle,
testClass
);
JunitServerExtensionContext ctx = new JunitServerExtensionContext(runner);

ctx.getRunner().beforeAll();

Expand All @@ -342,13 +407,25 @@ private JunitServerExtensionContext start(ExtensionContext context, JunitServerE
return ctx;
}

private JunitServerExtensionLifecycle getLifecycle(Class<?> testClass, JunitServerExtensionLifecycle defaults) {
if (lifecycle != null) {
return lifecycle;
}

return findLifecycle(testClass).orElse(defaults);
}

private static JunitServerExtensionContext findContextInStore(ExtensionContext context) {
return getExtensionStore(context).get(
JunitServerExtensionContext.class.getName(),
JunitServerExtensionContext.class
);
}

protected Optional<JunitServerExtensionLifecycle> findLifecycle(Class<?> testClass) {
return findAnnotation(testClass, JunitServerTest.class).map(JunitServerTest::lifecycle);
}

private static void putContextInStore(ExtensionContext context, JunitServerExtensionContext ctx) {
log.debug("Store context to junit extension context");
getExtensionStore(context).put(ctx.getClass().getName(), ctx);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,18 +36,10 @@
final class JunitServerExtensionContext implements ExtensionContext.Store.CloseableResource {

private final EmbeddedServerRunner runner;
private final JunitServerExtensionLifecycle lifecycle;
private final Class<?> testClass;
private final AnnotationsHandlerRunner annotationsHandler;

JunitServerExtensionContext(
EmbeddedServerRunner runner,
JunitServerExtensionLifecycle lifecycle,
Class<?> testClass
) {
JunitServerExtensionContext(EmbeddedServerRunner runner) {
this.runner = notNull(runner, "runner");
this.lifecycle = notNull(lifecycle, "lifecycle");
this.testClass = notNull(testClass, "testClass");
this.annotationsHandler = new AnnotationsHandlerRunner(
runner.getServer(),
runner.getServer().getConfiguration()
Expand All @@ -58,14 +50,6 @@ EmbeddedServerRunner getRunner() {
return runner;
}

JunitServerExtensionLifecycle getLifecycle() {
return lifecycle;
}

Class<?> getTestClass() {
return testClass;
}

AnnotationsHandlerRunner getAnnotationsHandler() {
return annotationsHandler;
}
Expand All @@ -92,26 +76,21 @@ public boolean equals(Object o) {

if (o instanceof JunitServerExtensionContext) {
JunitServerExtensionContext that = (JunitServerExtensionContext) o;
return Objects.equals(runner, that.runner)
&& Objects.equals(lifecycle, that.lifecycle)
&& Objects.equals(testClass, that.testClass)
&& Objects.equals(annotationsHandler, that.annotationsHandler);
return Objects.equals(runner, that.runner) && Objects.equals(annotationsHandler, that.annotationsHandler);
}

return false;
}

@Override
public int hashCode() {
return Objects.hash(runner, lifecycle, testClass, annotationsHandler);
return Objects.hash(runner, annotationsHandler);
}

@Override
public String toString() {
return ToStringBuilder.create(getClass())
.append("runner", runner)
.append("lifecycle", lifecycle)
.append("testClass", testClass)
.append("annotationsHandler", annotationsHandler)
.build();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,14 +24,50 @@

package com.github.mjeanroy.junit.servers.jupiter;

enum JunitServerExtensionLifecycle {
import org.junit.jupiter.api.extension.ExtensionContext;

public enum JunitServerExtensionLifecycle {
/**
* Start/stop embedded server before/after all tests in all classes.
*/
GLOBAL {
@Override
ExtensionContext getExtensionContext(ExtensionContext context) {
return context.getRoot();
}
},

/**
* Start/stop embedded server before/after all tests in a class.
*/
PER_CLASS,
PER_CLASS {
@Override
ExtensionContext getExtensionContext(ExtensionContext context) {
ExtensionContext current = context;
ExtensionContext root = context.getRoot();

while (current != root) {
ExtensionContext parent = current.getParent().orElse(null);
if (parent == null || parent == root) {
return current;
}

current = parent;
}

return context;
}
},

/**
* Start/stop embedded server before/after any tests in a class.
*/
PER_METHOD
PER_METHOD {
@Override
ExtensionContext getExtensionContext(ExtensionContext context) {
return context;
}
};

abstract ExtensionContext getExtensionContext(ExtensionContext context);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
/**
* The MIT License (MIT)
*
* Copyright (c) 2014-2022 <mickael.jeanroy@gmail.com>
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/

package com.github.mjeanroy.junit.servers.jupiter;

import org.junit.jupiter.api.extension.ExtendWith;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

import static com.github.mjeanroy.junit.servers.jupiter.JunitServerExtensionLifecycle.PER_CLASS;

@Retention(RetentionPolicy.RUNTIME)
@Target({
ElementType.TYPE,
})
@Documented
@Inherited
@ExtendWith(JunitServerExtension.class)
public @interface JunitServerTest {
/**
* Lifecycle, defaults to {@link JunitServerExtensionLifecycle#PER_CLASS}.
*
* @return Lifecycle.
*/
JunitServerExtensionLifecycle lifecycle() default PER_CLASS;
}
Loading

0 comments on commit cbfb89e

Please sign in to comment.