Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow the creation of a global vertx #70

Open
ixtli opened this issue Dec 6, 2019 · 0 comments
Open

Allow the creation of a global vertx #70

ixtli opened this issue Dec 6, 2019 · 0 comments

Comments

@ixtli
Copy link

ixtli commented Dec 6, 2019

I am testing a Verticle which, while simple, can sometimes make a long-running network request at startup. As well, this network request counts against API limits, so I'd like to reduce the amount of times the startup procedure is called given that most of my tests don't care about this but require it to have run.

The correct behavior would be some sort of @BeforeAll annotation in JUnit5 but that runs only once for the ENTIRE run. There has been a lot of hand-wringing about this on junit5 github issues because, while you CAN achieve this effect, its not well documented. (c.f.: junit-team/junit5#1555 )

For vertx-junit, what we need is the ability to add a parameter to the root extension context, instead of the one passed directly by junit5. Specifically, this line https://github.com/vert-x3/vertx-junit5/blob/master/vertx-junit5/src/main/java/io/vertx/junit5/VertxExtension.java#L162, but

  private static Store rootStore(ExtensionContext extensionContext) {
    return extensionContext.getRoot().getStore(Namespace.GLOBAL);
  }

The difference is that if you attach a parameter to THAT store, it will not get closed until ALL of the tests are complete. This allows you to do something like this to, in effect, create a @BeforeEntireSuite.

By way of example I've created this little helper class that would be much better off inside vertx-junit5 as some convenience to get access to a Vertx that will not be torn down between sets of tests. You can extend this class and provide a keyname method and a method that starts the vertx/verticle pair as implementations of the abstract methods. Obviously there are a lot of kludges here, but i think it's a complete example.

package ai.brace.util;

import io.vertx.core.Vertx;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.junit.jupiter.api.extension.BeforeAllCallback;
import org.junit.jupiter.api.extension.ExtensionContext;

import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;

import static org.assertj.core.api.Assertions.assertThat;

abstract class GlobalVerticleExtension
        implements BeforeAllCallback, ExtensionContext.Store.CloseableResource, AutoCloseable
{
    private static final ExtensionContext.Namespace NAMESPACE = ExtensionContext.Namespace.GLOBAL;
    private static final Logger logger = LogManager.getLogger();

    /**
     * The verticle's vertx context.
     */
    private Vertx vertx = null;

    /**
     * Return a unique name for this verticle so that we can potentially register multiple with junit
     *
     * @return A unique name for the verticle.
     */
    public abstract String getName();

    /**
     * Start the verticle and give back a vertx instance that will be shut down at the end.
     *
     * @param future complete or cancel this depending on how the startup procedure goes.
     */
    public abstract void start(final CompletableFuture<Vertx> future);

    private GlobalVerticleExtension create(final String name)
    {
        final CompletableFuture<Vertx> future = new CompletableFuture<>();

        logger.info("Starting " + name);
        start(future);

        try
        {
            vertx = future.get(10, TimeUnit.SECONDS);
        }
        catch (InterruptedException e)
        {
            throw new RuntimeException(name + " startup interrupted.");
        }
        catch (ExecutionException e)
        {
            throw new RuntimeException(e);
        }
        catch (TimeoutException e)
        {
            throw new RuntimeException(name + " startup timeout.");
        }

        assertThat(vertx).isNotNull();
        assertThat(vertx.deploymentIDs()).isNotEmpty().hasSize(1);

        logger.info(name + " startup complete.");

        return this;
    }

    @Override
    public void beforeAll(ExtensionContext context)
    {
        // N.B.: If you don't include getRoot() here this will become a beforeAll / close handler for every annotated
        // test class. This might actually be very useful, but for this class we want code to exec once per test run.
        context.getRoot().getStore(NAMESPACE).getOrComputeIfAbsent(getName() + "_global_verticle", this::create);
    }

    @Override
    public void close()
    {
        if (vertx == null)
        {
            return;
        }

        final CompletableFuture<Boolean> future = new CompletableFuture<>();
        vertx.close(result -> future.complete(result.succeeded()));
        try
        {
            future.get(10, TimeUnit.SECONDS);
        }
        catch (InterruptedException | ExecutionException | TimeoutException e)
        {
            // This is an exit routine, so log then eat the exception.
            logger.error(e);
        }

        vertx = null;
    }
}

Note: the example at https://github.com/vert-x3/vertx-junit5/blob/master/vertx-junit5/src/test/java/io/vertx/junit5/OtherExtension.java#L38 does not quite apply because the function it's calling in VertxExtension.java doesn't give access to context.getRoot().

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant