You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@commons.apache.org by gg...@apache.org on 2018/02/06 00:04:14 UTC

commons-testing git commit: Add module commons-testing-junit4-mongodb.

Repository: commons-testing
Updated Branches:
  refs/heads/master 7fa3f2e77 -> a5c619156


Add module commons-testing-junit4-mongodb.

Project: http://git-wip-us.apache.org/repos/asf/commons-testing/repo
Commit: http://git-wip-us.apache.org/repos/asf/commons-testing/commit/a5c61915
Tree: http://git-wip-us.apache.org/repos/asf/commons-testing/tree/a5c61915
Diff: http://git-wip-us.apache.org/repos/asf/commons-testing/diff/a5c61915

Branch: refs/heads/master
Commit: a5c6191560329fec7d0a7639e518e08a3f206093
Parents: 7fa3f2e
Author: Gary Gregory <ga...@gmail.com>
Authored: Mon Feb 5 17:04:11 2018 -0700
Committer: Gary Gregory <ga...@gmail.com>
Committed: Mon Feb 5 17:04:11 2018 -0700

----------------------------------------------------------------------
 commons-testing-generic/pom.xml                 |   8 +
 .../java/org/apache/commons/testing/Closer.java |  60 +++++++
 .../testing/net/AvailableServerPortFinder.java  | 155 +++++++++++++++++++
 .../org/apache/commons/testing/CloserTest.java  |  57 +++++++
 .../net/AvailableServerPortFinderTest.java      |  30 ++++
 commons-testing-junit4-mongodb/pom.xml          | 113 ++++++++++++++
 .../testing/junit4/mongodb/MongoDbTestRule.java | 140 +++++++++++++++++
 .../junit4/mongodb/MongoDbTestRuleTest.java     |  42 +++++
 commons-testing-junit4/pom.xml                  |   5 +
 .../testing/junit4/RuleChainFactory.java        |  45 ++++++
 ...ailableServerPortSystemPropertyTestRule.java |  81 ++++++++++
 ...bleServerPortSystemPropertyTestRuleTest.java |  42 +++++
 pom.xml                                         |  29 +++-
 13 files changed, 803 insertions(+), 4 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/commons-testing/blob/a5c61915/commons-testing-generic/pom.xml
----------------------------------------------------------------------
diff --git a/commons-testing-generic/pom.xml b/commons-testing-generic/pom.xml
index b70c253..b50486a 100644
--- a/commons-testing-generic/pom.xml
+++ b/commons-testing-generic/pom.xml
@@ -62,6 +62,14 @@
     </site>
   </distributionManagement>
 
+  <dependencies>
+    <dependency>
+      <groupId>junit</groupId>
+      <artifactId>junit</artifactId>
+      <scope>test</scope>
+    </dependency>
+  </dependencies>
+
   <reporting>
     <plugins>
       <plugin>

http://git-wip-us.apache.org/repos/asf/commons-testing/blob/a5c61915/commons-testing-generic/src/main/java/org/apache/commons/testing/Closer.java
----------------------------------------------------------------------
diff --git a/commons-testing-generic/src/main/java/org/apache/commons/testing/Closer.java b/commons-testing-generic/src/main/java/org/apache/commons/testing/Closer.java
new file mode 100644
index 0000000..41bce1e
--- /dev/null
+++ b/commons-testing-generic/src/main/java/org/apache/commons/testing/Closer.java
@@ -0,0 +1,60 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.commons.testing;
+
+/**
+ * Helper class for closing resources.
+ */
+public final class Closer {
+
+    /**
+     * Closes an AutoCloseable or ignores if {@code null}.
+     *
+     * @param closeable
+     *            the resource to close; may be null
+     * @throws Exception
+     *             if the resource cannot be closed
+     * @since 1.0.0
+     */
+    public static void close(final AutoCloseable closeable) throws Exception {
+        if (closeable != null) {
+            closeable.close();
+        }
+    }
+
+    /**
+     * Closes an AutoCloseable and returns {@code true} if it closed without exception.
+     *
+     * @param closeable
+     *            the resource to close; may be null
+     * @return true if resource was closed successfully, or false if an exception was thrown
+     * @since 1.0.0
+     */
+    public static boolean closeSilently(final AutoCloseable closeable) {
+        try {
+            close(closeable);
+            return true;
+        } catch (final Exception ignored) {
+            return false;
+        }
+    }
+
+    private Closer() {
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/commons-testing/blob/a5c61915/commons-testing-generic/src/main/java/org/apache/commons/testing/net/AvailableServerPortFinder.java
----------------------------------------------------------------------
diff --git a/commons-testing-generic/src/main/java/org/apache/commons/testing/net/AvailableServerPortFinder.java b/commons-testing-generic/src/main/java/org/apache/commons/testing/net/AvailableServerPortFinder.java
new file mode 100644
index 0000000..9a276e9
--- /dev/null
+++ b/commons-testing-generic/src/main/java/org/apache/commons/testing/net/AvailableServerPortFinder.java
@@ -0,0 +1,155 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.commons.testing.net;
+
+import java.io.IOException;
+import java.net.DatagramSocket;
+import java.net.ServerSocket;
+import java.util.NoSuchElementException;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import org.apache.commons.testing.Closer;
+
+/**
+ * Finds currently available server ports.
+ */
+public final class AvailableServerPortFinder {
+
+    /**
+     * The minimum server currentMinPort number for IPv4.
+     * Set at 1100 to avoid returning privileged currentMinPort numbers.
+     */
+    public static final int MIN_PORT_NUMBER = 1100;
+
+    /**
+     * Incremented to the next lowest available port when getNextAvailable() is called.
+     */
+    private static AtomicInteger currentMinPort = new AtomicInteger(MIN_PORT_NUMBER);
+
+    /**
+     * We'll hold open the lowest port in this process
+     * so parallel processes won't use the same block
+     * of ports. They'll go up to the next block.
+     */
+    private static final ServerSocket LOCK;
+
+    /**
+     * The maximum server currentMinPort number for IPv4.
+     */
+    public static final int MAX_PORT_NUMBER = 65535;
+
+    static {
+        int port = MIN_PORT_NUMBER;
+        ServerSocket ss = null;
+
+        while (ss == null) {
+            try {
+                ss = new ServerSocket(port);
+            } catch (final Exception e) {
+                ss = null;
+                port += 200;
+            }
+        }
+        LOCK = ss;
+        Runtime.getRuntime().addShutdownHook(new Thread() {
+            @Override
+            public void run() {
+                close();
+            }
+        });
+        currentMinPort.set(port + 1);
+    }
+
+    /**
+     * Checks to see if a specific port is available.
+     *
+     * @param port the port number to check for availability
+     * @return {@code true} if the port is available, or {@code false} if not
+     * @throws IllegalArgumentException is thrown if the port number is out of range
+     */
+    public static synchronized boolean available(final int port) throws IllegalArgumentException {
+        if (port < currentMinPort.get() || port > MAX_PORT_NUMBER) {
+            throw new IllegalArgumentException("Invalid start currentMinPort: " + port);
+        }
+
+        ServerSocket ss = null;
+        DatagramSocket ds = null;
+        try {
+            ss = new ServerSocket(port);
+            ss.setReuseAddress(true);
+            ds = new DatagramSocket(port);
+            ds.setReuseAddress(true);
+            return true;
+        } catch (final IOException e) {
+            // Do nothing
+        } finally {
+            Closer.closeSilently(ds);
+            Closer.closeSilently(ss);
+        }
+
+        return false;
+    }
+
+    public static void close() {
+        Closer.closeSilently(LOCK);
+    }
+
+    /**
+     * Gets the next available port starting at the lowest number. This is the preferred
+     * method to use. The port return is immediately marked in use and doesn't rely on the caller actually opening
+     * the port.
+     *
+     * @throws IllegalArgumentException is thrown if the port number is out of range
+     * @throws NoSuchElementException if there are no ports available
+     * @return the available port
+     */
+    public static synchronized int getNextAvailable() {
+        final int next = getNextAvailable(currentMinPort.get());
+        currentMinPort.set(next + 1);
+        return next;
+    }
+
+    /**
+     * Gets the next available port starting at a given from port.
+     *
+     * @param fromPort the from port to scan for availability
+     * @throws IllegalArgumentException is thrown if the port number is out of range
+     * @throws NoSuchElementException if there are no ports available
+     * @return the available port
+     */
+    public static synchronized int getNextAvailable(final int fromPort) {
+        if (fromPort < currentMinPort.get() || fromPort > MAX_PORT_NUMBER) {
+            throw new IllegalArgumentException("From port number not in valid range: " + fromPort);
+        }
+
+        for (int i = fromPort; i <= MAX_PORT_NUMBER; i++) {
+            if (available(i)) {
+                return i;
+            }
+        }
+
+        throw new NoSuchElementException("Could not find an available port above " + fromPort);
+    }
+
+    /**
+     * Creates a new instance.
+     */
+    private AvailableServerPortFinder() {
+        // Do nothing
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/commons-testing/blob/a5c61915/commons-testing-generic/src/test/java/org/apache/commons/testing/CloserTest.java
----------------------------------------------------------------------
diff --git a/commons-testing-generic/src/test/java/org/apache/commons/testing/CloserTest.java b/commons-testing-generic/src/test/java/org/apache/commons/testing/CloserTest.java
new file mode 100644
index 0000000..b812292
--- /dev/null
+++ b/commons-testing-generic/src/test/java/org/apache/commons/testing/CloserTest.java
@@ -0,0 +1,57 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.commons.testing;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+public class CloserTest {
+
+    static class MarkAutoCloseable implements AutoCloseable {
+
+        private boolean closed;
+
+        @Override
+        public void close() throws Exception {
+            closed = true;
+        }
+
+        boolean isClosed() {
+            return closed;
+        }
+
+    }
+
+    @Test
+    public void testCloseAutoCloseable() throws Exception {
+        @SuppressWarnings("resource")
+        final MarkAutoCloseable closeable = new MarkAutoCloseable();
+        Assert.assertFalse(closeable.isClosed());
+        Closer.close(closeable);
+        Assert.assertTrue(closeable.isClosed());
+    }
+
+    @Test
+    public void testCloseSlientlyAutoCloseable() {
+        @SuppressWarnings("resource")
+        final MarkAutoCloseable closeable = new MarkAutoCloseable();
+        Assert.assertFalse(closeable.isClosed());
+        Closer.closeSilently(closeable);
+        Assert.assertTrue(closeable.isClosed());
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/commons-testing/blob/a5c61915/commons-testing-generic/src/test/java/org/apache/commons/testing/net/AvailableServerPortFinderTest.java
----------------------------------------------------------------------
diff --git a/commons-testing-generic/src/test/java/org/apache/commons/testing/net/AvailableServerPortFinderTest.java b/commons-testing-generic/src/test/java/org/apache/commons/testing/net/AvailableServerPortFinderTest.java
new file mode 100644
index 0000000..d1dde88
--- /dev/null
+++ b/commons-testing-generic/src/test/java/org/apache/commons/testing/net/AvailableServerPortFinderTest.java
@@ -0,0 +1,30 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.commons.testing.net;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+public class AvailableServerPortFinderTest {
+
+    @Test
+    public void testGetNextAvailable() {
+        Assert.assertTrue(AvailableServerPortFinder.getNextAvailable() > 0);
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/commons-testing/blob/a5c61915/commons-testing-junit4-mongodb/pom.xml
----------------------------------------------------------------------
diff --git a/commons-testing-junit4-mongodb/pom.xml b/commons-testing-junit4-mongodb/pom.xml
new file mode 100644
index 0000000..da8e5a7
--- /dev/null
+++ b/commons-testing-junit4-mongodb/pom.xml
@@ -0,0 +1,113 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+  Licensed to the Apache Software Foundation (ASF) under one
+  or more contributor license agreements. See the NOTICE file
+  distributed with this work for additional information
+  regarding copyright ownership. The ASF licenses this file
+  to you 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.
+
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+  <modelVersion>4.0.0</modelVersion>
+
+  <parent>
+    <groupId>org.apache.commons</groupId>
+    <artifactId>commons-testing-parent</artifactId>
+    <version>1.0.0-SNAPSHOT</version>
+    <relativePath>../</relativePath>
+  </parent>
+
+  <artifactId>commons-testing-junit4-mongodb</artifactId>
+  <packaging>jar</packaging>
+
+  <name>Apache Commons Testing for JUnit4 and MongoDB</name>
+  <description>Apache Commons Testing for JUnit4 and MongoDB</description>
+
+  <properties>
+    <testingParentDir>${basedir}/..</testingParentDir>
+    <commons.osgi.symbolicName>org.apache.commons.testing.junit4.mongodb.mongodb.mongodb</commons.osgi.symbolicName>
+  </properties>
+
+  <build>
+    <plugins>
+      <plugin>
+        <groupId>org.apache.felix</groupId>
+        <artifactId>maven-bundle-plugin</artifactId>
+        <configuration>
+          <instructions>
+            <Bundle-SymbolicName>org.apache.commons.testing.junit4.mongodb.mongodb.mongodb</Bundle-SymbolicName>
+            <Automatic-Module-Name>org.apache.commons.testing.junit4.mongodb.mongodb.mongodb</Automatic-Module-Name>
+          </instructions>
+        </configuration>
+      </plugin>
+    </plugins>
+  </build>
+
+  <distributionManagement>
+    <site>
+      <id>commonstesting-junit4-site</id>
+      <url>scm:svn:${commons.scmPubUrl}/junit4/</url>
+    </site>
+  </distributionManagement>
+
+  <dependencies>
+    <dependency>
+      <groupId>org.apache.commons</groupId>
+      <artifactId>commons-testing-junit4</artifactId>
+      <version>${project.version}</version>
+    </dependency>
+    <dependency>
+      <groupId>org.mongodb</groupId>
+      <artifactId>mongodb-driver</artifactId>
+    </dependency>
+    <dependency>
+      <groupId>org.mongodb</groupId>
+      <artifactId>bson</artifactId>
+    </dependency>
+    <dependency>
+      <groupId>de.flapdoodle.embed</groupId>
+      <artifactId>de.flapdoodle.embed.mongo</artifactId>
+    </dependency>
+  </dependencies>
+
+  <reporting>
+    <plugins>
+      <plugin>
+        <groupId>org.apache.maven.plugins</groupId>
+        <artifactId>maven-checkstyle-plugin</artifactId>
+        <version>${checkstyle.plugin.version}</version>
+        <configuration>
+          <configLocation>${testingParentDir}/checkstyle.xml</configLocation>
+          <suppressionsLocation>${testingParentDir}/checkstyle-suppressions.xml</suppressionsLocation>
+          <enableRulesSummary>false</enableRulesSummary>
+          <propertyExpansion>basedir=${basedir}</propertyExpansion>
+          <propertyExpansion>licensedir=${testingParentDir}/checkstyle-header.txt</propertyExpansion>
+        </configuration>
+      </plugin>
+      <plugin>
+        <groupId>org.codehaus.mojo</groupId>
+        <artifactId>findbugs-maven-plugin</artifactId>
+        <version>${findbugs.plugin.version}</version>
+        <configuration>
+          <fork>true</fork>
+          <jvmArgs>-Duser.language=en</jvmArgs>
+          <threshold>Normal</threshold>
+          <effort>Default</effort>
+          <excludeFilterFile>${testingParentDir}/findbugs-exclude-filter.xml</excludeFilterFile>
+        </configuration>
+      </plugin>
+    </plugins>
+  </reporting>
+</project>

http://git-wip-us.apache.org/repos/asf/commons-testing/blob/a5c61915/commons-testing-junit4-mongodb/src/main/java/org/apache/commons/testing/junit4/mongodb/MongoDbTestRule.java
----------------------------------------------------------------------
diff --git a/commons-testing-junit4-mongodb/src/main/java/org/apache/commons/testing/junit4/mongodb/MongoDbTestRule.java b/commons-testing-junit4-mongodb/src/main/java/org/apache/commons/testing/junit4/mongodb/MongoDbTestRule.java
new file mode 100644
index 0000000..9873466
--- /dev/null
+++ b/commons-testing-junit4-mongodb/src/main/java/org/apache/commons/testing/junit4/mongodb/MongoDbTestRule.java
@@ -0,0 +1,140 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.commons.testing.junit4.mongodb;
+
+import java.util.Objects;
+
+import org.junit.rules.TestRule;
+import org.junit.runner.Description;
+import org.junit.runners.model.Statement;
+
+import com.mongodb.MongoClient;
+
+import de.flapdoodle.embed.mongo.MongodExecutable;
+import de.flapdoodle.embed.mongo.MongodProcess;
+import de.flapdoodle.embed.mongo.MongodStarter;
+import de.flapdoodle.embed.mongo.config.MongodConfigBuilder;
+import de.flapdoodle.embed.mongo.config.Net;
+import de.flapdoodle.embed.mongo.config.Timeout;
+import de.flapdoodle.embed.mongo.distribution.Version;
+import de.flapdoodle.embed.mongo.distribution.Version.Main;
+import de.flapdoodle.embed.process.runtime.Network;
+
+/**
+ * A JUnit test rule to manage a MongoDB embedded instance.
+ *
+ * @author <a href="mailto:ggregory@rocketsoftware.com">Gary Gregory</a>
+ */
+public class MongoDbTestRule implements TestRule {
+
+    private static final Main DEFAULT_MONGODB_VERSION = Version.Main.V3_4;
+
+    private static final int BUILDER_TIMEOUT_MILLIS = 30000;
+
+    /**
+     * Store MongodStarter (or RuntimeConfig) in a static final field if you want to use artifact store caching (or else disable caching)
+     */
+    protected static final MongodStarter starter = MongodStarter.getDefaultInstance();
+
+    public static int getBuilderTimeoutMillis() {
+        return BUILDER_TIMEOUT_MILLIS;
+    }
+
+    public static MongodStarter getStarter() {
+        return starter;
+    }
+
+    protected final String portSystemPropertyName;
+    protected final Main mongoDbVersion;
+
+    protected MongoClient mongoClient;
+    protected MongodExecutable mongodExecutable;
+    protected MongodProcess mongodProcess;
+
+    public MongoDbTestRule(final String portSystemPropertyName) {
+        this(portSystemPropertyName, DEFAULT_MONGODB_VERSION);
+    }
+
+    public MongoDbTestRule(final String portSystemPropertyName, Main mongoDbVersion) {
+        this.portSystemPropertyName = Objects.requireNonNull(portSystemPropertyName, "portSystemPropertyName");
+        this.mongoDbVersion = mongoDbVersion;
+    }
+
+    @Override
+    public Statement apply(final Statement base, final Description description) {
+        return new Statement() {
+
+            @Override
+            public void evaluate() throws Throwable {
+                final String value = Objects.requireNonNull(System.getProperty(portSystemPropertyName), "System property '" + portSystemPropertyName + "' is null");
+                final int port = Integer.parseInt(value);
+                mongodExecutable = starter.prepare(
+                // @formatter:off
+                        new MongodConfigBuilder()
+                            .version(mongoDbVersion)
+                            .timeout(new Timeout(BUILDER_TIMEOUT_MILLIS))
+                            .net(
+                                    new Net("localhost", port, Network.localhostIsIPv6()))
+                            .build());
+                // @formatter:on
+                mongodProcess = mongodExecutable.start();
+                mongoClient = new MongoClient("localhost", port);
+                try {
+                    base.evaluate();
+                } finally {
+                    if (mongodProcess != null) {
+                        mongodProcess.stop();
+                        mongodProcess = null;
+                    }
+                    if (mongodExecutable != null) {
+                        mongodExecutable.stop();
+                        mongodExecutable = null;
+                    }
+                }
+            }
+        };
+    }
+
+    public MongoClient getMongoClient() {
+        return mongoClient;
+    }
+
+    public MongodExecutable getMongodExecutable() {
+        return mongodExecutable;
+    }
+
+    public MongodProcess getMongodProcess() {
+        return mongodProcess;
+    }
+
+    @Override
+    public String toString() {
+        final StringBuilder builder = new StringBuilder();
+        builder.append("MongoDbTestRule [portSystemPropertyName=");
+        builder.append(portSystemPropertyName);
+        builder.append(", mongoClient=");
+        builder.append(mongoClient);
+        builder.append(", mongodExecutable=");
+        builder.append(mongodExecutable);
+        builder.append(", mongodProcess=");
+        builder.append(mongodProcess);
+        builder.append("]");
+        return builder.toString();
+    }
+
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/commons-testing/blob/a5c61915/commons-testing-junit4-mongodb/src/test/java/org/apache/commons/testing/junit4/mongodb/MongoDbTestRuleTest.java
----------------------------------------------------------------------
diff --git a/commons-testing-junit4-mongodb/src/test/java/org/apache/commons/testing/junit4/mongodb/MongoDbTestRuleTest.java b/commons-testing-junit4-mongodb/src/test/java/org/apache/commons/testing/junit4/mongodb/MongoDbTestRuleTest.java
new file mode 100644
index 0000000..141286c
--- /dev/null
+++ b/commons-testing-junit4-mongodb/src/test/java/org/apache/commons/testing/junit4/mongodb/MongoDbTestRuleTest.java
@@ -0,0 +1,42 @@
+// Copyright 2017-2018 Rocket Software, Inc. All rights reserved.
+
+package org.apache.commons.testing.junit4.mongodb;
+
+import org.apache.commons.testing.junit4.RuleChainFactory;
+import org.apache.commons.testing.junit4.net.AvailableServerPortSystemPropertyTestRule;
+import org.junit.Assert;
+import org.junit.ClassRule;
+import org.junit.Test;
+import org.junit.rules.RuleChain;
+
+import com.mongodb.client.MongoIterable;
+
+/**
+ * Tests {@link MongoDbTestRule}.
+ */
+public class MongoDbTestRuleTest {
+
+    public static final AvailableServerPortSystemPropertyTestRule mongoDbPortTestRule = AvailableServerPortSystemPropertyTestRule
+            .create(MongoDbTestRuleTest.class.getName());
+
+    public static final MongoDbTestRule mongoDbTestRule = new MongoDbTestRule(mongoDbPortTestRule.getName());
+
+    @ClassRule
+    public static RuleChain mongoDbChain = RuleChainFactory.create(mongoDbPortTestRule, mongoDbTestRule);
+
+    @Test
+    public void testAccess() {
+        final MongoIterable<String> databaseNames = mongoDbTestRule.getMongoClient().listDatabaseNames();
+        Assert.assertNotNull(databaseNames);
+        Assert.assertNotNull(databaseNames.first());
+    }
+
+    @Test
+    public void testMongoDbRule() {
+        Assert.assertNotNull(MongoDbTestRule.getStarter());
+        Assert.assertNotNull(mongoDbTestRule);
+        Assert.assertNotNull(mongoDbTestRule.getMongoClient());
+        Assert.assertNotNull(mongoDbTestRule.getMongodExecutable());
+        Assert.assertNotNull(mongoDbTestRule.getMongodProcess());
+    }
+}

http://git-wip-us.apache.org/repos/asf/commons-testing/blob/a5c61915/commons-testing-junit4/pom.xml
----------------------------------------------------------------------
diff --git a/commons-testing-junit4/pom.xml b/commons-testing-junit4/pom.xml
index c12d50f..836c935 100644
--- a/commons-testing-junit4/pom.xml
+++ b/commons-testing-junit4/pom.xml
@@ -64,6 +64,11 @@
 
   <dependencies>
     <dependency>
+      <groupId>org.apache.commons</groupId>
+      <artifactId>commons-testing-generic</artifactId>
+      <version>${project.version}</version>
+    </dependency>
+    <dependency>
       <groupId>junit</groupId>
       <artifactId>junit</artifactId>
     </dependency>

http://git-wip-us.apache.org/repos/asf/commons-testing/blob/a5c61915/commons-testing-junit4/src/main/java/org/apache/commons/testing/junit4/RuleChainFactory.java
----------------------------------------------------------------------
diff --git a/commons-testing-junit4/src/main/java/org/apache/commons/testing/junit4/RuleChainFactory.java b/commons-testing-junit4/src/main/java/org/apache/commons/testing/junit4/RuleChainFactory.java
new file mode 100644
index 0000000..b63333f
--- /dev/null
+++ b/commons-testing-junit4/src/main/java/org/apache/commons/testing/junit4/RuleChainFactory.java
@@ -0,0 +1,45 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.commons.testing.junit4;
+
+import org.junit.rules.RuleChain;
+import org.junit.rules.TestRule;
+
+/**
+ * Builds JUnit {@link RuleChain}s.
+ */
+public class RuleChainFactory {
+
+    /**
+     * Creates a {@link RuleChain} where the rules are evaluated in the order you pass in.
+     * 
+     * @param testRules
+     *            test rules to evaluate
+     * @return a new rule chain.
+     */
+    public static RuleChain create(final TestRule... testRules) {
+        if (testRules == null || testRules.length == 0) {
+            return RuleChain.emptyRuleChain();
+        }
+        RuleChain ruleChain = RuleChain.outerRule(testRules[0]);
+        for (int i = 1; i < testRules.length; i++) {
+            ruleChain = ruleChain.around(testRules[i]);
+        }
+        return ruleChain;
+    }
+}

http://git-wip-us.apache.org/repos/asf/commons-testing/blob/a5c61915/commons-testing-junit4/src/main/java/org/apache/commons/testing/junit4/net/AvailableServerPortSystemPropertyTestRule.java
----------------------------------------------------------------------
diff --git a/commons-testing-junit4/src/main/java/org/apache/commons/testing/junit4/net/AvailableServerPortSystemPropertyTestRule.java b/commons-testing-junit4/src/main/java/org/apache/commons/testing/junit4/net/AvailableServerPortSystemPropertyTestRule.java
new file mode 100644
index 0000000..f8db859
--- /dev/null
+++ b/commons-testing-junit4/src/main/java/org/apache/commons/testing/junit4/net/AvailableServerPortSystemPropertyTestRule.java
@@ -0,0 +1,81 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.commons.testing.junit4.net;
+
+import org.apache.commons.testing.net.AvailableServerPortFinder;
+import org.junit.rules.TestRule;
+import org.junit.runner.Description;
+import org.junit.runners.model.Statement;
+
+/**
+ * A JUnit TestRule to discover an available port and save it in a system property.
+ */
+public class AvailableServerPortSystemPropertyTestRule implements TestRule {
+
+    public static AvailableServerPortSystemPropertyTestRule create(final String name) {
+        return new AvailableServerPortSystemPropertyTestRule(name);
+    }
+
+    protected final String name;
+    protected int port;
+
+    protected AvailableServerPortSystemPropertyTestRule(final String name) {
+        this.name = name;
+    }
+
+    @Override
+    public Statement apply(final Statement base, final Description description) {
+        return new Statement() {
+
+            @Override
+            public void evaluate() throws Throwable {
+                final String oldValue = System.getProperty(name);
+                try {
+                    port = AvailableServerPortFinder.getNextAvailable();
+                    System.setProperty(name, Integer.toString(port));
+                    base.evaluate();
+                } finally {
+                    // Restore if previously set
+                    if (oldValue != null) {
+                        System.setProperty(name, oldValue);
+                    }
+                }
+            }
+        };
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public int getPort() {
+        return port;
+    }
+
+    @Override
+    public String toString() {
+        final StringBuilder builder = new StringBuilder();
+        builder.append("AvailableServerPortSystemPropertyTestRule [name=");
+        builder.append(name);
+        builder.append(", port=");
+        builder.append(port);
+        builder.append("]");
+        return builder.toString();
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/commons-testing/blob/a5c61915/commons-testing-junit4/src/test/java/org/apache/commons/testing/junit4/net/AvailableServerPortSystemPropertyTestRuleTest.java
----------------------------------------------------------------------
diff --git a/commons-testing-junit4/src/test/java/org/apache/commons/testing/junit4/net/AvailableServerPortSystemPropertyTestRuleTest.java b/commons-testing-junit4/src/test/java/org/apache/commons/testing/junit4/net/AvailableServerPortSystemPropertyTestRuleTest.java
new file mode 100644
index 0000000..1a8a9e0
--- /dev/null
+++ b/commons-testing-junit4/src/test/java/org/apache/commons/testing/junit4/net/AvailableServerPortSystemPropertyTestRuleTest.java
@@ -0,0 +1,42 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.commons.testing.junit4.net;
+
+import java.io.IOException;
+import java.net.ServerSocket;
+
+import org.junit.Assert;
+import org.junit.Rule;
+import org.junit.Test;
+
+public class AvailableServerPortSystemPropertyTestRuleTest {
+
+    private static final String NAME = AvailableServerPortSystemPropertyTestRuleTest.class.getName();
+
+    @Rule
+    public AvailableServerPortSystemPropertyTestRule rule = new AvailableServerPortSystemPropertyTestRule(NAME);
+
+    @Test
+    public void test() throws IOException {
+        final int port = rule.getPort();
+        Assert.assertTrue(port > 0);
+        try (final ServerSocket ss = new ServerSocket(port)) {
+            // nothing
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/commons-testing/blob/a5c61915/pom.xml
----------------------------------------------------------------------
diff --git a/pom.xml b/pom.xml
index d3f50ee..909ad78 100644
--- a/pom.xml
+++ b/pom.xml
@@ -68,12 +68,27 @@
       <dependency>
         <groupId>junit</groupId>
         <artifactId>junit</artifactId>
-        <version>4.12</version>
+        <version>${junit4.version}</version>
       </dependency>
       <dependency>
         <groupId>org.apache.commons</groupId>
         <artifactId>commons-lang3</artifactId>
-        <version>3.7</version>
+        <version>${commons-lang.version}</version>
+      </dependency>
+      <dependency>
+        <groupId>org.mongodb</groupId>
+        <artifactId>mongodb-driver</artifactId>
+        <version>${mongodb3.version}</version>
+      </dependency>
+      <dependency>
+        <groupId>org.mongodb</groupId>
+        <artifactId>bson</artifactId>
+        <version>${mongodb3.version}</version>
+      </dependency>
+      <dependency>
+        <groupId>de.flapdoodle.embed</groupId>
+        <artifactId>de.flapdoodle.embed.mongo</artifactId>
+        <version>${embed.mongodb}</version>
       </dependency>
     </dependencies>
   </dependencyManagement>
@@ -114,11 +129,17 @@
     <commons.scmPubCheckoutDirectory>site-content</commons.scmPubCheckoutDirectory>
     <commons.encoding>utf-8</commons.encoding>
 
-    <!-- Override clirr version to be able to build the site on Java 7 -->
+    <!-- Plugin versions -->
     <commons.clirr.version>2.8</commons.clirr.version>
     <checkstyle.plugin.version>3.0.0</checkstyle.plugin.version>
     <findbugs.plugin.version>3.0.5</findbugs.plugin.version>
+    <embed.mongodb>2.0.1</embed.mongodb>
 
+    <!-- Component versions -->
+    <mongodb2.version>2.14.3</mongodb2.version>
+    <mongodb3.version>3.6.1</mongodb3.version>
+    <junit4.version>4.12</junit4.version>
+    <commons-lang.version>3.7</commons-lang.version>
   </properties>
 
 
@@ -265,7 +286,7 @@
     <module>commons-testing-generic</module>
     <module>commons-testing-junit4</module>
     <!--<module>commons-testing-junit5</module> -->
-    <!--<module>commons-testing-junit4-mongodb</module> -->
+    <module>commons-testing-junit4-mongodb</module>
   </modules>
 
   <profiles>