diff --git a/build.gradle b/build.gradle
index ba6870f20..5e07f3678 100644
--- a/build.gradle
+++ b/build.gradle
@@ -36,6 +36,7 @@ allprojects {
// build, causing unnecessary rebuilds.
builtDate = (new java.text.SimpleDateFormat("yyyy-MM-dd")).format(new Date())
oneLineDesc = 'An Open Source XMPP (Jabber) client library'
+ javadocAllProjects = subprojects - project(':smack-integration-test')
// A dirty hack used for Gradle's jacoco plugin, since is not
// hable to handle the case when a (sub)project has no unit
// tests. :-(
@@ -65,6 +66,7 @@ allprojects {
].collect{ project(it) }
androidBootClasspath = getAndroidRuntimeJar()
androidJavadocOffline = getAndroidJavadocOffline()
+ junitVersion = '4.11'
}
group = 'org.igniterealtime.smack'
sourceCompatibility = 1.7
@@ -147,7 +149,7 @@ gradle.taskGraph.whenReady { taskGraph ->
}
task javadocAll(type: Javadoc) {
- source subprojects.collect {project ->
+ source javadocAllProjects.collect {project ->
project.sourceSets.main.allJava }
destinationDir = javadocAllDir
// Might need a classpath
@@ -317,6 +319,9 @@ subprojects {
}
}
+// No need to ever clirr smack-integration-test
+project(':smack-integration-test').clirr.enabled = false
+
subprojects*.jar {
manifest {
from sharedManifest
@@ -350,6 +355,10 @@ task clirrRootReport(type: org.kordamp.gradle.clirr.ClirrReportTask) {
reports = files((subprojects.findAll { it.clirr.enabled == true }).tasks.clirr.xmlReport)
}
+task integrationTest {
+ dependsOn project(':smack-integration-test').tasks.run
+}
+
def getGitCommit() {
def dotGit = new File("$projectDir/.git")
if (!dotGit.isDirectory()) return 'non-git build'
diff --git a/documentation/developer/integrationtest.md b/documentation/developer/integrationtest.md
new file mode 100644
index 000000000..1ea008f10
--- /dev/null
+++ b/documentation/developer/integrationtest.md
@@ -0,0 +1,142 @@
+Smack's Integration Test Framework
+==================================
+
+Introduction
+------------
+
+Smack's Integration Test Framwork is used ot run a set of tests against a real XMPP service.
+The framework discovers on startup the available tests by reflection.
+
+Quickstart
+----------
+
+You can run the framework against an XMPP service with
+
+```bash
+$ gradle integrationTest -Dsinttest.service=my.xmppservice.org
+```
+
+Configuration
+-------------
+
+The framework is configured with a standard Java properties file.
+This file simply contains key/value pairs, which are separated by an equals sign ("=").
+The most important configuration value is the `service` value, it's also the only required setting.
+
+The file properties can be overridden with Java system properties.
+The name of a system property that is used by the framework needs to be prefixed with `sinttest.` (*S*mack *Int*egration *Test* Framework).
+For example the `service` property becomes `sinttest.service`.
+
+### Minimal example **properties** file
+
+```bash
+service=example.org
+```
+
+### Another example **properties** file
+
+```bash
+service=example.org
+serviceTlsPin=serviceTlsPin=CERTSHA256:2F:92:C9:4D:30:58:E1:05:21:9A:57:59:5F:6E:25:9A:0F:BF:FF:64:1A:C3:4B:EC:06:7D:4A:6F:0A:D5:21:85
+debug=true
+```
+
+### Framework properties
+
+| Name | |
+|----------------------|-------------------------------------------|
+| service | XMPP service to run the tests on |
+| serviceTlsPin | TLS Pin (used by java-pinning) |
+| securityMode | Either 'required' or disabled' |
+| replyTimeout | In milliseconds |
+| accountOneUsername | Username of the first XMPP account |
+| accountOnePassword | Password of the first XMPP account |
+| accountTwoUsername | Username of the second XMPP account |
+| accountTwoPassword | Password of the second XMPP account |
+| debug | 'true' to enable debug output |
+| enabledTests | List of enabled tests |
+| disabledTests | List of disabled tests |
+| testPackages | List of packages with tests |
+
+Overview of the components
+--------------------------
+
+Package `org.igniterealtime.smack.inttest`
+
+### `SmackIntegrationTestFramework`
+
+Contains `public static void main` method, i.e. the entry point for the framework.
+Here the available integration tests are discovered by means of reflections, the configured is read and a `IntegrationTestEnvironment` instance created, include the XMPPConnections.
+
+### `AbstractSmackIntegrationTest`
+
+The base class that integration tests need to subclass.
+
+### `AbstractSmackLowLevelIntegrationTest`
+
+Allows low level integration test, i.e. ever test method will have it's on exclusive XMPPTCPConnection instances.
+
+### `IntegrationTestEnvironment`
+
+The environment, e.g. the `XMPPConnections` provided to the integration tests by the framework. Note that for convenience `AbstractSmackIntegrationTest` contains some of those as protected members.
+
+### `SmackIntegrationTest`
+
+An annotation that needs to be added to all methods that represent a single integration test.
+Annotated integration test methods must not take any arguments (i.e. their parameter count is 0), and should return void as it's not evaluated in any way.
+The methods are supposed to throw an exception if their integration test fails.
+
+### `TestNotPossibleException`
+
+Can be thrown by test methods or constructors to signal that their test it no possible, e.g. because the service does not support the required feature.
+
+Running the integration tests
+-----------------------------
+
+Smack's Gradle build system is configured with a special task called `integrationTest`, which means you can run the tests simply with
+
+```bash
+$ gradle integrationTest
+```
+If one of `accountOneUsername`, `accountOnePassword`, `accountTwoUsername` or `accountTwoPassword` is not configured, then the framework will automatically create the accounts on the service (if account registration is supported and enabled).
+If the accounts got created, then they will also be deleted at the end of the test.
+
+Implementing Integration Tests
+------------------------------
+
+Create a new class which extends `AbstractSmackIntegrationTest`.
+Every non-static method, including the constructor, of this class will have two XMPPConnections available to perform the integration tests with: `conOne` and `conTwo`.
+You can use the constructor to check if the XMPP service does provide the required XMPP feature.
+If it does not, simply throw a `TestNotPossibleException`.
+
+Test methods must be `public`, take zero arguments i.e. declare no parameters and be annoated with `@SmackIntegrationTest`.
+If the test method is not able to perform a test then it should throw a `TestNotPossibleException`.
+
+### Rules for integration tests
+
+Tests should not leave any traces on the service if they are finished, i.e. the service state at the end of the test must be equal to the state of the beginning.
+It must be possible to run the tests in parallel.
+
+### Why are there two mechanisms to signal that the test is not possible?
+
+Because the XMPP service may provide a component that is required to perform a certain integration test, but that component may not support all features.
+For example, the XMPP service may provides a PubSub (XEP-60) component, but this component may not support all features of XEP-60.
+
+### Low-Level Integration Tests
+
+Classes that implement low-level integration tests need to sublcass `AbstractSmackLowLevelIntegrationTest`.
+The test methods can declare as many parameters as they need to, but every parameter must be of type `XMPPTCPConnection`.
+The framework will automatically create, register and login the connections.
+After the test is finished, the connections will be unregistered with the XMPP service and terminated.
+
+Running your own integration tests
+----------------------------------
+
+The framework can be used to run your own tests.
+Simply set the `testPackages` property to a comma separated list of package names where the framework should look for integration tests.
+
+Example:
+
+```bash
+$ gradle integrationTest -Dsinttest.service=my.xmppserivce.org -Dsinttest.testPackages=org.mypackage,org.otherpackage
+```
diff --git a/settings.gradle b/settings.gradle
index d83be147e..5759f000e 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -21,4 +21,5 @@ include 'smack-core',
'smack-bosh',
'smack-android',
'smack-android-extensions',
- 'smack-java7'
+ 'smack-java7',
+ 'smack-integration-test'
diff --git a/smack-core/build.gradle b/smack-core/build.gradle
index 9b2f6c973..46867ea91 100644
--- a/smack-core/build.gradle
+++ b/smack-core/build.gradle
@@ -11,7 +11,7 @@ dependencies {
compile "org.jxmpp:jxmpp-core:$jxmppVersion"
compile "org.jxmpp:jxmpp-jid:$jxmppVersion"
testCompile "org.jxmpp:jxmpp-jid:$jxmppVersion:tests"
- testCompile 'junit:junit:4.11'
+ testCompile "junit:junit:$junitVersion"
testCompile 'xmlunit:xmlunit:1.5'
testCompile 'org.powermock:powermock-module-junit4:1.5.5'
testCompile 'org.powermock:powermock-api-mockito:1.5.5'
diff --git a/smack-core/src/integration-test/java/org/jivesoftware/smack/LoginTest.java b/smack-core/src/integration-test/java/org/jivesoftware/smack/LoginTest.java
index c02c46d01..7adc45407 100644
--- a/smack-core/src/integration-test/java/org/jivesoftware/smack/LoginTest.java
+++ b/smack-core/src/integration-test/java/org/jivesoftware/smack/LoginTest.java
@@ -31,35 +31,6 @@ public class LoginTest extends SmackTestCase {
super(arg0);
}
- /**
- * Check that the server is returning the correct error when trying to login using an invalid
- * (i.e. non-existent) user.
- */
- public void testInvalidLogin() {
- try {
- XMPPTCPConnection connection = createConnection();
- connection.connect();
- try {
- // Login with an invalid user
- connection.login("invaliduser" , "invalidpass");
- connection.disconnect();
- fail("Invalid user was able to log into the server");
- }
- catch (XMPPException e) {
- if (e.getXMPPError() != null) {
- assertEquals("Incorrect error code while login with an invalid user", 401,
- e.getXMPPError().getCode());
- }
- }
- // Wait here while trying tests with exodus
- //Thread.sleep(300);
- }
- catch (Exception e) {
- e.printStackTrace();
- fail(e.getMessage());
- }
- }
-
/**
* Check that the server handles anonymous users correctly.
*/
diff --git a/smack-core/src/main/java/org/jivesoftware/smack/AbstractXMPPConnection.java b/smack-core/src/main/java/org/jivesoftware/smack/AbstractXMPPConnection.java
index db9bed25c..695c3f59b 100644
--- a/smack-core/src/main/java/org/jivesoftware/smack/AbstractXMPPConnection.java
+++ b/smack-core/src/main/java/org/jivesoftware/smack/AbstractXMPPConnection.java
@@ -50,6 +50,7 @@ import org.jivesoftware.smack.SmackException.NotConnectedException;
import org.jivesoftware.smack.SmackException.ConnectionException;
import org.jivesoftware.smack.SmackException.ResourceBindingNotOfferedException;
import org.jivesoftware.smack.SmackException.SecurityRequiredException;
+import org.jivesoftware.smack.XMPPException.StreamErrorException;
import org.jivesoftware.smack.XMPPException.XMPPErrorException;
import org.jivesoftware.smack.compress.packet.Compress;
import org.jivesoftware.smack.compression.XMPPInputOutputStream;
@@ -68,6 +69,7 @@ import org.jivesoftware.smack.packet.Presence;
import org.jivesoftware.smack.packet.Session;
import org.jivesoftware.smack.packet.StartTls;
import org.jivesoftware.smack.packet.PlainStreamElement;
+import org.jivesoftware.smack.packet.StreamError;
import org.jivesoftware.smack.packet.XMPPError;
import org.jivesoftware.smack.parsing.ParsingExceptionCallback;
import org.jivesoftware.smack.parsing.UnparsablePacket;
@@ -1195,7 +1197,19 @@ public abstract class AbstractXMPPConnection implements XMPPConnection {
}
protected void callConnectionClosedOnErrorListener(Exception e) {
- LOGGER.log(Level.WARNING, "Connection closed with error", e);
+ boolean logWarning = true;
+ if (e instanceof StreamErrorException) {
+ StreamErrorException see = (StreamErrorException) e;
+ if (see.getStreamError().getCondition() == StreamError.Condition.not_authorized
+ && wasAuthenticated) {
+ logWarning = false;
+ LOGGER.log(Level.FINE,
+ "Connection closed with not-authorized stream error after it was already authenticated. The account was likely deleted/unregistered on the server");
+ }
+ }
+ if (logWarning) {
+ LOGGER.log(Level.WARNING, "Connection closed with error", e);
+ }
for (ConnectionListener listener : connectionListeners) {
try {
listener.connectionClosedOnError(e);
diff --git a/smack-core/src/main/java/org/jivesoftware/smack/util/Async.java b/smack-core/src/main/java/org/jivesoftware/smack/util/Async.java
index d22e06858..86b6dae09 100644
--- a/smack-core/src/main/java/org/jivesoftware/smack/util/Async.java
+++ b/smack-core/src/main/java/org/jivesoftware/smack/util/Async.java
@@ -16,6 +16,9 @@
*/
package org.jivesoftware.smack.util;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
public class Async {
/**
@@ -50,4 +53,30 @@ public class Async {
thread.setDaemon(true);
return thread;
}
+
+ /**
+ * Like {@link Runnable}, but allows the runOrThrow() method to throw an exception.
+ *
+ * If the exception is an instance of {@link RuntimeException}, then it will be re-thrown, otherwise it will be
+ * simply logged.
+ */
+ public static abstract class ThrowingRunnable implements Runnable {
+
+ public static final Logger LOGGER = Logger.getLogger(ThrowingRunnable.class.getName());
+
+ @Override
+ public final void run() {
+ try {
+ runOrThrow();
+ }
+ catch (Exception e) {
+ if (e instanceof RuntimeException) {
+ throw (RuntimeException) e;
+ }
+ LOGGER.log(Level.WARNING, "Catched Exception", e);
+ }
+ }
+
+ public abstract void runOrThrow() throws Exception;
+ }
}
diff --git a/smack-core/src/main/java/org/jivesoftware/smack/util/Objects.java b/smack-core/src/main/java/org/jivesoftware/smack/util/Objects.java
index 0ee27afcb..a48696c60 100644
--- a/smack-core/src/main/java/org/jivesoftware/smack/util/Objects.java
+++ b/smack-core/src/main/java/org/jivesoftware/smack/util/Objects.java
@@ -24,4 +24,8 @@ public class Objects {
}
return obj;
}
+
+ public static T requireNonNull(T obj) {
+ return requireNonNull(obj, null);
+ }
}
diff --git a/smack-extensions/src/integration-test/java/org/jivesoftware/smackx/VersionTest.java b/smack-extensions/src/integration-test/java/org/jivesoftware/smackx/VersionTest.java
deleted file mode 100644
index d093a678b..000000000
--- a/smack-extensions/src/integration-test/java/org/jivesoftware/smackx/VersionTest.java
+++ /dev/null
@@ -1,67 +0,0 @@
-/**
- *
- * Copyright 2003-2007 Jive Software.
- *
- * 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.jivesoftware.smackx;
-
-import org.jivesoftware.smack.PacketCollector;
-import org.jivesoftware.smack.filter.PacketIDFilter;
-import org.jivesoftware.smack.packet.IQ;
-import org.jivesoftware.smack.test.SmackTestCase;
-import org.jivesoftware.smackx.packet.Version;
-
-/**
- * Test case to ensure that Smack is able to get and parse correctly iq:version packets.
- *
- * @author Gaston Dombiak
- */
-public class VersionTest extends SmackTestCase {
-
- public VersionTest(String arg0) {
- super(arg0);
- }
-
- /**
- * Get the version of the server and make sure that all the required data is present
- *
- * Note: This test expects the server to answer an iq:version packet.
- */
- public void testGetServerVersion() {
- Version version = new Version();
- version.setType(IQ.Type.get);
- version.setTo(getServiceName());
-
- // Create a packet collector to listen for a response.
- PacketCollector collector = getConnection(0).createPacketCollector(new PacketIDFilter(version.getStanzaId()));
-
- getConnection(0).sendStanza(version);
-
- // Wait up to 5 seconds for a result.
- IQ result = (IQ)collector.nextResult(5000);
- // Close the collector
- collector.cancel();
-
- assertNotNull("No result from the server", result);
-
- assertEquals("Incorrect result type", IQ.Type.result, result.getType());
- assertNotNull("No name specified in the result", ((Version)result).getName());
- assertNotNull("No version specified in the result", ((Version)result).getVersion());
- }
-
- protected int getMaxConnections() {
- return 1;
- }
-}
diff --git a/smack-integration-test/build.gradle b/smack-integration-test/build.gradle
new file mode 100644
index 000000000..a9a82f1aa
--- /dev/null
+++ b/smack-integration-test/build.gradle
@@ -0,0 +1,21 @@
+apply plugin: 'application'
+
+description = """\
+Smack integration tests."""
+
+mainClassName = 'org.igniterealtime.smack.inttest.SmackIntegrationTestFramework'
+
+dependencies {
+ compile project(':smack-java7')
+ compile project(':smack-tcp')
+ compile project(':smack-extensions')
+ compile 'org.reflections:reflections:0.9.9-RC1'
+ compile 'eu.geekplace.javapinning:java-pinning-jar:1.0.1'
+ compile "junit:junit:$junitVersion"
+ testCompile "org.jxmpp:jxmpp-jid:$jxmppVersion:tests"
+}
+
+run {
+ // Pass all system properties down to the "application" run
+ systemProperties System.getProperties()
+}
diff --git a/smack-integration-test/src/main/java/org/igniterealtime/smack/inttest/AbstractSmackIntTest.java b/smack-integration-test/src/main/java/org/igniterealtime/smack/inttest/AbstractSmackIntTest.java
new file mode 100644
index 000000000..176241075
--- /dev/null
+++ b/smack-integration-test/src/main/java/org/igniterealtime/smack/inttest/AbstractSmackIntTest.java
@@ -0,0 +1,25 @@
+/**
+ *
+ * Copyright 2015 Florian Schmaus
+ *
+ * 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.igniterealtime.smack.inttest;
+
+import java.util.logging.Logger;
+
+public abstract class AbstractSmackIntTest {
+
+ protected static final Logger LOGGER = Logger.getLogger(AbstractSmackIntTest.class.getName());
+
+}
diff --git a/smack-integration-test/src/main/java/org/igniterealtime/smack/inttest/AbstractSmackIntegrationTest.java b/smack-integration-test/src/main/java/org/igniterealtime/smack/inttest/AbstractSmackIntegrationTest.java
new file mode 100644
index 000000000..f9731f427
--- /dev/null
+++ b/smack-integration-test/src/main/java/org/igniterealtime/smack/inttest/AbstractSmackIntegrationTest.java
@@ -0,0 +1,52 @@
+/**
+ *
+ * Copyright 2015 Florian Schmaus
+ *
+ * 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.igniterealtime.smack.inttest;
+
+import org.jivesoftware.smack.XMPPConnection;
+
+public abstract class AbstractSmackIntegrationTest extends AbstractSmackIntTest {
+
+ /**
+ * The first connection.
+ */
+ protected final XMPPConnection conOne;
+
+ /**
+ * The second connection.
+ */
+ protected final XMPPConnection conTwo;
+
+ /**
+ * An alias for the first connection {@link #conOne}.
+ */
+ protected final XMPPConnection connection;
+
+ protected final long defaultTimeout;
+
+ protected final String testRunId;
+
+ public AbstractSmackIntegrationTest(SmackIntegrationTestEnvironment environment) {
+ this.connection = this.conOne = environment.conOne;
+ this.conTwo = environment.conTwo;
+ if (environment.configuration.replyTimeout > 0) {
+ this.defaultTimeout = environment.configuration.replyTimeout;
+ } else {
+ this.defaultTimeout = 2 * 60 * 1000;
+ }
+ this.testRunId = environment.testRunId;
+ }
+}
diff --git a/smack-integration-test/src/main/java/org/igniterealtime/smack/inttest/AbstractSmackLowLevelIntegrationTest.java b/smack-integration-test/src/main/java/org/igniterealtime/smack/inttest/AbstractSmackLowLevelIntegrationTest.java
new file mode 100644
index 000000000..ff33a2f3d
--- /dev/null
+++ b/smack-integration-test/src/main/java/org/igniterealtime/smack/inttest/AbstractSmackLowLevelIntegrationTest.java
@@ -0,0 +1,70 @@
+/**
+ *
+ * Copyright 2015 Florian Schmaus
+ *
+ * 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.igniterealtime.smack.inttest;
+
+import java.security.KeyManagementException;
+import java.security.NoSuchAlgorithmException;
+
+import javax.net.ssl.SSLContext;
+
+import org.jivesoftware.smack.tcp.XMPPTCPConnection;
+import org.jivesoftware.smack.tcp.XMPPTCPConnectionConfiguration;
+import org.jxmpp.jid.DomainBareJid;
+
+import eu.geekplace.javapinning.JavaPinning;
+
+public abstract class AbstractSmackLowLevelIntegrationTest extends AbstractSmackIntTest {
+
+ /**
+ * The configuration
+ */
+ protected final Configuration configuration;
+
+ protected final String testRunId;
+
+ protected final DomainBareJid service;
+
+ public AbstractSmackLowLevelIntegrationTest(Configuration configuration, String testRunId) {
+ this.configuration = configuration;
+ this.testRunId = testRunId;
+ this.service = configuration.service;
+ }
+
+ public final XMPPTCPConnectionConfiguration.Builder getConnectionConfiguration() throws KeyManagementException, NoSuchAlgorithmException {
+ XMPPTCPConnectionConfiguration.Builder builder = XMPPTCPConnectionConfiguration.builder();
+ if (configuration.serviceTlsPin != null) {
+ SSLContext sc = JavaPinning.forPin(configuration.serviceTlsPin);
+ builder.setCustomSSLContext(sc);
+ }
+ builder.setSecurityMode(configuration.securityMode);
+ builder.setServiceName(service);
+ return builder;
+ }
+
+ protected void performCheck(ConnectionCallback callback) throws Exception {
+ XMPPTCPConnection connection = SmackIntegrationTestFramework.getConnectedConnection(configuration);
+ try {
+ callback.connectionCallback(connection);
+ } finally {
+ connection.disconnect();
+ }
+ }
+
+ public interface ConnectionCallback {
+ public void connectionCallback(XMPPTCPConnection connection) throws Exception;
+ }
+}
diff --git a/smack-integration-test/src/main/java/org/igniterealtime/smack/inttest/Configuration.java b/smack-integration-test/src/main/java/org/igniterealtime/smack/inttest/Configuration.java
new file mode 100644
index 000000000..1681eee09
--- /dev/null
+++ b/smack-integration-test/src/main/java/org/igniterealtime/smack/inttest/Configuration.java
@@ -0,0 +1,291 @@
+/**
+ *
+ * Copyright 2015 Florian Schmaus
+ *
+ * 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.igniterealtime.smack.inttest;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.util.HashSet;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map.Entry;
+import java.util.Properties;
+import java.util.Set;
+
+import org.jivesoftware.smack.ConnectionConfiguration.SecurityMode;
+import org.jivesoftware.smack.util.StringUtils;
+import org.jxmpp.jid.DomainBareJid;
+import org.jxmpp.jid.impl.JidCreate;
+import org.jxmpp.stringprep.XmppStringprepException;
+
+public class Configuration {
+
+ public final DomainBareJid service;
+
+ public final String serviceTlsPin;
+
+ public final SecurityMode securityMode;
+
+ public final int replyTimeout;
+
+ public final boolean registerAccounts;
+
+ public final String accountOneUsername;
+
+ public final String accountOnePassword;
+
+ public final String accountTwoUsername;
+
+ public final String accountTwoPassword;
+
+ public final boolean debug;
+
+ public final Set enabledTests;
+
+ public final Set disabledTests;
+
+ public final Set testPackages;
+
+ private Configuration(DomainBareJid service, String serviceTlsPin, SecurityMode securityMode, int replyTimeout,
+ boolean debug, String accountOneUsername, String accountOnePassword, String accountTwoUsername,
+ String accountTwoPassword, Set enabledTests, Set disabledTests,
+ Set testPackages) {
+ this.service = service;
+ this.serviceTlsPin = serviceTlsPin;
+ this.securityMode = securityMode;
+ this.replyTimeout = replyTimeout;
+ this.debug = debug;
+ if (StringUtils.isNullOrEmpty(accountOneUsername) || StringUtils.isNullOrEmpty(accountOnePassword)
+ || StringUtils.isNullOrEmpty(accountTwoUsername)
+ || StringUtils.isNullOrEmpty(accountTwoPassword)) {
+ registerAccounts = true;
+ }
+ else {
+ registerAccounts = false;
+ }
+ this.accountOneUsername = accountOneUsername;
+ this.accountOnePassword = accountOnePassword;
+ this.accountTwoUsername = accountTwoUsername;
+ this.accountTwoPassword = accountTwoPassword;
+ this.enabledTests = enabledTests;
+ this.disabledTests = disabledTests;
+ this.testPackages = testPackages;
+ }
+
+ public static Builder builder() {
+ return new Builder();
+ }
+
+ public static class Builder {
+
+ private DomainBareJid service;
+
+ private String serviceTlsPin;
+
+ private SecurityMode securityMode;
+
+ private int replyTimeout;
+
+ private String accountOneUsername;
+
+ private String accountOnePassword;
+
+ private String accountTwoUsername;
+
+ private String accountTwoPassword;
+
+ private boolean debug;
+
+ private Set enabledTests;
+
+ private Set disabledTests;
+
+ private Set testPackages;
+
+ private Builder() {
+ }
+
+ public Builder setService(String service) throws XmppStringprepException {
+ return setService(JidCreate.domainBareFrom(service));
+ }
+
+ public Builder setService(DomainBareJid service) {
+ this.service = service;
+ return this;
+ }
+
+ public Builder addEnabledTest(Class extends AbstractSmackIntTest> enabledTest) {
+ if (enabledTests == null) {
+ enabledTests = new HashSet<>();
+ }
+ enabledTests.add(enabledTest.getName());
+ // Also add the package of the test as test package
+ return addTestPackage(enabledTest.getPackage().getName());
+ }
+
+ public Builder addTestPackage(String testPackage) {
+ if (testPackages == null) {
+ testPackages = new HashSet<>();
+ }
+ testPackages.add(testPackage);
+ return this;
+ }
+
+ public Builder setUsernamesAndPassword(String accountOneUsername, String accountOnePassword,
+ String accountTwoUsername, String accountTwoPassword) {
+ this.accountOneUsername = accountOneUsername;
+ this.accountOnePassword = accountOnePassword;
+ this.accountTwoUsername = accountTwoUsername;
+ this.accountTwoPassword = accountTwoPassword;
+ return this;
+ }
+
+ public Builder setServiceTlsPin(String tlsPin) {
+ this.serviceTlsPin = tlsPin;
+ return this;
+ }
+
+ public Builder setSecurityMode(String securityModeString) {
+ if (securityModeString != null) {
+ securityMode = SecurityMode.valueOf(securityModeString);
+ }
+ else {
+ securityMode = SecurityMode.required;
+ }
+ return this;
+ }
+
+ public Builder setReplyTimeout(String timeout) {
+ if (timeout != null) {
+ replyTimeout = Integer.valueOf(timeout);
+ }
+ return this;
+ }
+
+ public Builder setDebug(String debugString) {
+ if (debugString != null) {
+ debug = Boolean.valueOf(debugString);
+ }
+ return this;
+ }
+
+ public Builder setEnabledTests(String enabledTestsString) {
+ enabledTests = getTestSetFrom(enabledTestsString);
+ return this;
+ }
+
+ public Builder setDisabledTests(String disabledTestsString) {
+ disabledTests = getTestSetFrom(disabledTestsString);
+ return this;
+ }
+
+ public Builder setTestPackages(String testPackagesString) {
+ if (testPackagesString != null) {
+ String[] testPackagesArray = testPackagesString.split(",");
+ testPackages = new HashSet<>(testPackagesArray.length);
+ for (String s : testPackagesArray) {
+ testPackages.add(s.trim());
+ }
+ }
+ return this;
+ }
+
+ public Configuration build() {
+ return new Configuration(service, serviceTlsPin, securityMode, replyTimeout, debug, accountOneUsername,
+ accountOnePassword, accountTwoUsername, accountTwoPassword, enabledTests, disabledTests,
+ testPackages);
+ }
+ }
+
+ private static final String SINTTEST = "sinttest.";
+
+ public static Configuration newConfiguration() throws IOException {
+ File propertiesFile = findPropertiesFile();
+ Properties properties = new Properties();
+
+ try (FileInputStream in = new FileInputStream(propertiesFile)) {
+ properties.load(in);
+ }
+
+ // Properties set via the system override the file properties
+ Properties systemProperties = System.getProperties();
+ for (Entry