1
0
Fork 0
mirror of https://codeberg.org/Mercury-IM/Smack synced 2025-12-09 06:31:08 +01:00

Introduce test fixtures

This also removes the powermock dependency. Although powermock is a
fine library, it currently prevents dropping Junit4. And since we only
use the Whitebox API of powermock, this simply replaced powermock's
Whitebox with our own.
This commit is contained in:
Florian Schmaus 2020-04-11 21:59:21 +02:00
parent 4a99f7252c
commit b5f9d4d7a3
51 changed files with 123 additions and 80 deletions

View file

@ -1,240 +0,0 @@
/**
*
* Copyright 2010 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.smack;
import java.io.IOException;
import java.util.Date;
import java.util.Random;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit;
import org.jivesoftware.smack.packet.ExtensionElement;
import org.jivesoftware.smack.packet.Nonza;
import org.jivesoftware.smack.packet.Stanza;
import org.jivesoftware.smack.packet.TopLevelStreamElement;
import org.jxmpp.jid.EntityFullJid;
import org.jxmpp.jid.JidTestUtil;
import org.jxmpp.jid.impl.JidCreate;
import org.jxmpp.jid.parts.Resourcepart;
import org.jxmpp.stringprep.XmppStringprepException;
/**
* A dummy implementation of {@link XMPPConnection}, intended to be used during
* unit tests.
*
* Instances store any packets that are delivered to be send using the
* {@link #sendStanza(Stanza)} method in a blocking queue. The content of this queue
* can be inspected using {@link #getSentPacket()}. Typically these queues are
* used to retrieve a message that was generated by the client.
*
* Packets that should be processed by the client to simulate a received stanza
* can be delivered using the {@linkplain #processStanza(Stanza)} method.
* It invokes the registered stanza interceptors and listeners.
*
* @see XMPPConnection
* @author Guenther Niess
*/
public class DummyConnection extends AbstractXMPPConnection {
private final BlockingQueue<TopLevelStreamElement> queue = new LinkedBlockingQueue<TopLevelStreamElement>();
public static DummyConnectionConfiguration.Builder getDummyConfigurationBuilder() {
return DummyConnectionConfiguration.builder().setXmppDomain(JidTestUtil.EXAMPLE_ORG).setUsernameAndPassword("dummy",
"dummypass");
}
public DummyConnection() {
this(getDummyConfigurationBuilder().build());
}
public DummyConnection(CharSequence username, String password, String serviceName) throws XmppStringprepException {
this(getDummyConfigurationBuilder().setUsernameAndPassword(username, password).setXmppDomain(
JidCreate.domainBareFrom(serviceName)).build());
}
private EntityFullJid getUserJid() {
try {
return JidCreate.entityFullFrom(config.getUsername()
+ "@"
+ config.getXMPPServiceDomain()
+ "/"
+ (config.getResource() != null ? config.getResource() : "Test"));
}
catch (XmppStringprepException e) {
throw new IllegalStateException(e);
}
}
public DummyConnection(DummyConnectionConfiguration configuration) {
super(configuration);
for (ConnectionCreationListener listener : XMPPConnectionRegistry.getConnectionCreationListeners()) {
listener.connectionCreated(this);
}
user = getUserJid();
}
@Override
protected void connectInternal() {
connected = true;
streamId = "dummy-" + new Random(new Date().getTime()).nextInt();
}
@Override
protected void shutdown() {
user = null;
authenticated = false;
callConnectionClosedListener();
}
@Override
public void instantShutdown() {
shutdown();
}
@Override
public boolean isSecureConnection() {
return false;
}
@Override
public boolean isUsingCompression() {
return false;
}
@Override
protected void loginInternal(String username, String password, Resourcepart resource)
throws XMPPException {
user = getUserJid();
authenticated = true;
}
@Override
public void sendNonza(Nonza element) {
queue.add(element);
}
@Override
protected void sendStanzaInternal(Stanza packet) {
queue.add(packet);
}
/**
* Returns the number of packets that's sent through {@link #sendStanza(Stanza)} and
* that has not been returned by {@link #getSentPacket()}.
*
* @return the number of packets which are in the queue.
*/
public int getNumberOfSentPackets() {
return queue.size();
}
/**
* Returns the first stanza that's sent through {@link #sendStanza(Stanza)}
* and that has not been returned by earlier calls to this method.
*
* @return a sent packet.
*/
public <P extends TopLevelStreamElement> P getSentPacket() {
return getSentPacket(5 * 60);
}
/**
* Returns the first stanza that's sent through {@link #sendStanza(Stanza)}
* and that has not been returned by earlier calls to this method. This
* method will block for up to the specified number of seconds if no packets
* have been sent yet.
*
* @return a sent packet.
*/
@SuppressWarnings("unchecked")
public <P extends TopLevelStreamElement> P getSentPacket(int wait) {
try {
return (P) queue.poll(wait, TimeUnit.SECONDS);
}
catch (InterruptedException e) {
throw new IllegalStateException(e);
}
}
/**
* Processes a stanza through the installed stanza collectors and listeners
* and letting them examine the stanza to see if they are a match with the
* filter.
*
* @param packet the stanza to process.
*/
@Override
public void processStanza(Stanza packet) {
invokeStanzaCollectorsAndNotifyRecvListeners(packet);
}
/**
* Enable stream feature.
*
* @param streamFeature the stream feature.
* @since 4.2
*/
public void enableStreamFeature(ExtensionElement streamFeature) {
addStreamFeature(streamFeature);
}
public static DummyConnection newConnectedDummyConnection() {
DummyConnection dummyConnection = new DummyConnection();
try {
dummyConnection.connect();
dummyConnection.login();
}
catch (InterruptedException | SmackException | IOException | XMPPException e) {
throw new IllegalStateException(e);
}
return dummyConnection;
}
public static class DummyConnectionConfiguration extends ConnectionConfiguration {
protected DummyConnectionConfiguration(Builder builder) {
super(builder);
}
public static Builder builder() {
return new Builder();
}
public static final class Builder
extends
ConnectionConfiguration.Builder<Builder, DummyConnectionConfiguration> {
private Builder() {
}
@Override
public DummyConnectionConfiguration build() {
return new DummyConnectionConfiguration(this);
}
@Override
protected Builder getThis() {
return this;
}
}
}
}

View file

@ -1,115 +0,0 @@
/**
*
* Copyright the original author or authors
*
* 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.smack;
import java.io.IOException;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.jivesoftware.smack.packet.IQ;
import org.jivesoftware.smack.packet.IQ.Type;
import org.jivesoftware.smack.packet.Message;
import org.jivesoftware.smack.packet.Stanza;
/**
* A threaded dummy connection.
* @author Robin Collier
*
*/
public class ThreadedDummyConnection extends DummyConnection {
private static final Logger LOGGER = Logger.getLogger(ThreadedDummyConnection.class.getName());
private final BlockingQueue<IQ> replyQ = new ArrayBlockingQueue<>(1);
private final BlockingQueue<Stanza> messageQ = new LinkedBlockingQueue<>(5);
private volatile boolean timeout = false;
@Override
protected void sendStanzaInternal(Stanza packet) {
super.sendStanzaInternal(packet);
if (packet instanceof IQ && !timeout) {
timeout = false;
// Set reply packet to match one being sent. We haven't started the
// other thread yet so this is still safe.
IQ replyPacket = replyQ.peek();
// If no reply has been set via addIQReply, then we create a simple reply
if (replyPacket == null) {
replyPacket = IQ.createResultIQ((IQ) packet);
replyQ.add(replyPacket);
}
replyPacket.setStanzaId(packet.getStanzaId());
replyPacket.setTo(packet.getFrom());
if (replyPacket.getType() == null) {
replyPacket.setType(Type.result);
}
new ProcessQueue(replyQ).start();
}
}
/**
* Calling this method will cause the next sendStanza call with an IQ stanza to timeout.
* This is accomplished by simply stopping the auto creating of the reply stanza
* or processing one that was entered via {@link #processStanza(Stanza)}.
*/
public void setTimeout() {
timeout = true;
}
public void addMessage(Message msgToProcess) {
messageQ.add(msgToProcess);
}
public void addIQReply(IQ reply) {
replyQ.add(reply);
}
public void processMessages() {
if (!messageQ.isEmpty())
new ProcessQueue(messageQ).start();
else
LOGGER.warning("No messages to process");
}
class ProcessQueue extends Thread {
private BlockingQueue<? extends Stanza> processQ;
ProcessQueue(BlockingQueue<? extends Stanza> queue) {
processQ = queue;
}
@Override
public void run() {
try {
processStanza(processQ.take());
} catch (InterruptedException e) {
LOGGER.log(Level.WARNING, "exception", e);
}
}
}
public static ThreadedDummyConnection newInstance() throws SmackException, IOException, XMPPException, InterruptedException {
ThreadedDummyConnection threadedDummyConnection = new ThreadedDummyConnection();
threadedDummyConnection.connect();
return threadedDummyConnection;
}
}

View file

@ -1,30 +0,0 @@
/**
*
* Copyright © 2014-2016 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.jivesoftware.smack.sasl;
import org.jivesoftware.smack.DummyConnection;
public class AbstractSaslTest {
protected final DummyConnection xmppConnection = new DummyConnection();
protected final SASLMechanism saslMechanism;
protected AbstractSaslTest(SASLMechanism saslMechanism) {
this.saslMechanism = saslMechanism.instanceForAuthentication(xmppConnection, xmppConnection.getConfiguration());
}
}

View file

@ -1,72 +0,0 @@
/**
*
* Copyright © 2014-2019 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.jivesoftware.smack.sasl;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Map;
import org.jivesoftware.smack.SmackException;
import org.jivesoftware.smack.util.StringUtils;
import org.jxmpp.jid.EntityBareJid;
import org.jxmpp.jid.impl.JidCreate;
import org.jxmpp.stringprep.XmppStringprepException;
public class DigestMd5SaslTest extends AbstractSaslTest {
protected static final String challenge = "realm=\"xmpp.org\",nonce=\"jgGgnz+cQcmyVaAs2n88kQ==\",qop=\"auth\",charset=utf-8,algorithm=md5-sess";
protected static final byte[] challengeBytes = StringUtils.toUtf8Bytes(challenge);
public DigestMd5SaslTest(SASLMechanism saslMechanism) {
super(saslMechanism);
}
protected void runTest(boolean useAuthzid) throws SmackException, InterruptedException, XmppStringprepException {
EntityBareJid authzid = null;
if (useAuthzid) {
authzid = JidCreate.entityBareFrom("shazbat@xmpp.org");
}
saslMechanism.authenticate("florian", "irrelevant", JidCreate.domainBareFrom("xmpp.org"), "secret", authzid, null);
byte[] response = saslMechanism.evaluateChallenge(challengeBytes);
String responseString = new String(response, StandardCharsets.UTF_8);
String[] responseParts = responseString.split(",");
Map<String, String> responsePairs = new HashMap<String, String>();
for (String part : responseParts) {
String[] keyValue = part.split("=", 2);
String key = keyValue[0];
String value = keyValue[1].replace("\"", "");
responsePairs.put(key, value);
}
if (useAuthzid) {
assertMapValue("authzid", "shazbat@xmpp.org", responsePairs);
} else {
assertTrue (!responsePairs.containsKey("authzid"));
}
assertMapValue("username", "florian", responsePairs);
assertMapValue("realm", "xmpp.org", responsePairs);
assertMapValue("digest-uri", "xmpp/xmpp.org", responsePairs);
assertMapValue("qop", "auth", responsePairs);
}
private static void assertMapValue(String key, String value, Map<String, String> map) {
assertEquals(value, map.get(key));
}
}

View file

@ -1,47 +0,0 @@
/**
*
* Copyright © 2014 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.jivesoftware.smack.test.util;
import org.hamcrest.Description;
import org.hamcrest.Factory;
import org.hamcrest.Matcher;
import org.hamcrest.TypeSafeMatcher;
public class CharSequenceEquals extends TypeSafeMatcher<CharSequence> {
private final String charSequenceString;
public CharSequenceEquals(CharSequence charSequence) {
charSequenceString = charSequence.toString();
}
@Override
public void describeTo(Description description) {
description.appendText("Does not match CharSequence ").appendValue(charSequenceString);
}
@Override
protected boolean matchesSafely(CharSequence item) {
String itemString = item.toString();
return charSequenceString.equals(itemString);
}
@Factory
public static Matcher<CharSequence> equalsCharSequence(CharSequence charSequence) {
return new CharSequenceEquals(charSequence);
}
}

View file

@ -1,61 +0,0 @@
/**
*
* Copyright © 2014-2020 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.jivesoftware.smack.test.util;
import java.security.Security;
import java.util.Base64;
import org.jivesoftware.smack.SmackConfiguration;
import org.jivesoftware.smack.util.stringencoder.Base64.Encoder;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
/**
* The SmackTestSuite takes care of initializing Smack for the unit tests. For example the Base64
* encoder is configured.
*/
public class SmackTestSuite {
static {
SmackConfiguration.getVersion();
org.jivesoftware.smack.util.stringencoder.Base64.setEncoder(new Encoder() {
@Override
public byte[] decode(String string) {
return Base64.getDecoder().decode(string);
}
@Override
public String encodeToString(byte[] input) {
return Base64.getEncoder().encodeToString(input);
}
@Override
public String encodeToStringWithoutPadding(byte[] input) {
return Base64.getEncoder().withoutPadding().encodeToString(input);
}
@Override
public byte[] encode(byte[] input) {
return Base64.getEncoder().encode(input);
}
});
Security.addProvider(new BouncyCastleProvider());
}
}

View file

@ -1,174 +0,0 @@
/**
*
* Copyright 2019 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.jivesoftware.smack.test.util;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;
import java.io.StringReader;
import java.lang.reflect.InvocationTargetException;
import java.nio.charset.StandardCharsets;
import java.util.function.Predicate;
import javax.xml.namespace.QName;
import org.jivesoftware.smack.packet.Element;
import org.jivesoftware.smack.parsing.SmackParsingException;
import org.jivesoftware.smack.provider.Provider;
import org.jivesoftware.smack.xml.XmlPullParser;
import org.jivesoftware.smack.xml.XmlPullParserException;
import org.jivesoftware.smack.xml.XmlPullParserFactory;
import org.jivesoftware.smack.xml.stax.StaxXmlPullParserFactory;
import org.jivesoftware.smack.xml.xpp3.Xpp3XmlPullParserFactory;
public class SmackTestUtil {
@SuppressWarnings("ImmutableEnumChecker")
public enum XmlPullParserKind {
StAX(StaxXmlPullParserFactory.class),
XPP3(Xpp3XmlPullParserFactory.class),
;
public final XmlPullParserFactory factory;
XmlPullParserKind(Class<? extends XmlPullParserFactory> factoryClass) {
try {
factory = factoryClass.getDeclaredConstructor().newInstance();
}
catch (InstantiationException | IllegalAccessException | IllegalArgumentException
| InvocationTargetException | NoSuchMethodException | SecurityException e) {
throw new AssertionError(e);
}
}
}
public static <E extends Element, P extends Provider<E>> E parse(CharSequence xml, Class<P> providerClass, XmlPullParserKind parserKind)
throws XmlPullParserException, IOException, SmackParsingException {
P provider = providerClassToProvider(providerClass);
return parse(xml, provider, parserKind);
}
public static <E extends Element, P extends Provider<E>> E parse(InputStream inputStream, Class<P> providerClass, XmlPullParserKind parserKind)
throws XmlPullParserException, IOException, SmackParsingException {
P provider = providerClassToProvider(providerClass);
return parse(inputStream, provider, parserKind);
}
public static <E extends Element, P extends Provider<E>> E parse(Reader reader, Class<P> providerClass, XmlPullParserKind parserKind)
throws XmlPullParserException, IOException, SmackParsingException {
P provider = providerClassToProvider(providerClass);
return parse(reader, provider, parserKind);
}
public static <E extends Element> E parse(CharSequence xml, Provider<E> provider, XmlPullParserKind parserKind)
throws XmlPullParserException, IOException, SmackParsingException {
String xmlString = xml.toString();
Reader reader = new StringReader(xmlString);
return parse(reader, provider, parserKind);
}
public static <E extends Element> E parse(InputStream inputStream, Provider<E> provider, XmlPullParserKind parserKind)
throws XmlPullParserException, IOException, SmackParsingException {
InputStreamReader inputStreamReader = new InputStreamReader(inputStream, StandardCharsets.UTF_8);
return parse(inputStreamReader, provider, parserKind);
}
public static <E extends Element> E parse(Reader reader, Provider<E> provider, XmlPullParserKind parserKind)
throws XmlPullParserException, IOException, SmackParsingException {
XmlPullParser parser = getParserFor(reader, parserKind);
E element = provider.parse(parser);
return element;
}
public static XmlPullParser getParserFor(String xml, XmlPullParserKind parserKind) throws XmlPullParserException, IOException {
Reader reader = new StringReader(xml);
return getParserFor(reader, parserKind);
}
public static XmlPullParser getParserFor(InputStream inputStream, XmlPullParserKind parserKind) throws XmlPullParserException, IOException {
InputStreamReader inputStreamReader = new InputStreamReader(inputStream, StandardCharsets.UTF_8);
return getParserFor(inputStreamReader, parserKind);
}
public static XmlPullParser getParserFor(Reader reader, XmlPullParserKind parserKind) throws XmlPullParserException, IOException {
XmlPullParser parser = parserKind.factory.newXmlPullParser(reader);
forwardParserToStartElement(parser);
return parser;
}
public static XmlPullParser getParserFor(String xml, QName startTagQName, XmlPullParserKind parserKind)
throws XmlPullParserException, IOException {
XmlPullParser parser = getParserFor(xml, parserKind);
forwardParserToStartElement(parser, p -> p.getQName().equals(startTagQName));
return parser;
}
public static XmlPullParser getParserFor(String xml, String startTagLocalpart, XmlPullParserKind parserKind)
throws XmlPullParserException, IOException {
XmlPullParser parser = getParserFor(xml, parserKind);
forwardParserToStartElement(parser, p -> p.getName().equals(startTagLocalpart));
return parser;
}
@SuppressWarnings("unchecked")
private static <E extends Element, P extends Provider<E>> P providerClassToProvider(Class<P> providerClass) {
P provider;
try {
provider = (P) providerClass.getDeclaredField("INSTANCE").get(null);
return provider;
} catch (NoSuchFieldException e) {
// Continue with the next approach.
} catch (IllegalArgumentException | IllegalAccessException | SecurityException e) {
throw new AssertionError(e);
}
try {
provider = providerClass.getDeclaredConstructor().newInstance();
}
catch (InstantiationException | IllegalAccessException | IllegalArgumentException | InvocationTargetException
| NoSuchMethodException | SecurityException e) {
throw new AssertionError(e);
}
return provider;
}
private static void forwardParserToStartElement(XmlPullParser parser) throws XmlPullParserException, IOException {
forwardParserToStartElement(parser, p -> true);
}
private static void forwardParserToStartElement(XmlPullParser parser,
Predicate<XmlPullParser> doneForwarding) throws XmlPullParserException, IOException {
outerloop: while (true) {
XmlPullParser.Event event = parser.getEventType();
switch (event) {
case START_ELEMENT:
if (doneForwarding.test(parser)) {
break outerloop;
}
break;
case END_DOCUMENT:
throw new IllegalArgumentException("Not matching START_ELEMENT found");
default:
// Catch all for incomplete switch (MissingCasesInEnumSwitch) statement.
break;
}
parser.next();
}
}
}

View file

@ -1,79 +0,0 @@
/**
*
* Copyright 2013 Robin Collier
*
* 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.smack.test.util;
import java.io.IOException;
import java.io.Reader;
import java.io.StringReader;
import org.jivesoftware.smack.xml.SmackXmlParser;
import org.jivesoftware.smack.xml.XmlPullParser;
import org.jivesoftware.smack.xml.XmlPullParserException;
// TODO: Remove this class and replace it with SmackTestUtil.
public final class TestUtils {
private TestUtils() {
}
public static XmlPullParser getIQParser(String stanza) {
return getParser(stanza, "iq");
}
public static XmlPullParser getMessageParser(String stanza) {
return getParser(stanza, "message");
}
public static XmlPullParser getPresenceParser(String stanza) {
return getParser(stanza, "presence");
}
public static XmlPullParser getParser(String string) {
return getParser(string, null);
}
public static XmlPullParser getParser(String string, String startTag) {
return getParser(new StringReader(string), startTag);
}
private static XmlPullParser getParser(Reader reader, String startTag) {
XmlPullParser parser;
try {
parser = SmackXmlParser.newXmlParser(reader);
if (startTag == null) {
while (parser.getEventType() != XmlPullParser.Event.START_ELEMENT) {
parser.next();
}
return parser;
}
boolean found = false;
while (!found) {
if ((parser.next() == XmlPullParser.Event.START_ELEMENT) && parser.getName().equals(startTag))
found = true;
}
if (!found)
throw new IllegalArgumentException("Can not find start tag '" + startTag + "'");
} catch (XmlPullParserException e) {
throw new RuntimeException(e);
} catch (IOException e) {
throw new RuntimeException(e);
}
return parser;
}
}

View file

@ -1,59 +0,0 @@
/**
*
* 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.jivesoftware.smack.test.util;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import org.jivesoftware.smack.SmackException.NotConnectedException;
import org.jivesoftware.smack.StanzaListener;
import org.jivesoftware.smack.packet.Stanza;
public class WaitForPacketListener implements StanzaListener {
private CountDownLatch latch = new CountDownLatch(1);
@Override
public void processStanza(Stanza packet) throws NotConnectedException {
reportInvoked();
}
protected void reportInvoked() {
latch.countDown();
}
public void waitAndReset() {
waitUntilInvocationOrTimeout();
reset();
}
public void waitUntilInvocationOrTimeout() {
try {
boolean res = latch.await(300, TimeUnit.SECONDS);
if (!res) {
throw new IllegalStateException("Latch timed out before it reached zero");
}
}
catch (InterruptedException e) {
throw new IllegalStateException(e);
}
}
public void reset() {
latch = new CountDownLatch(1);
}
}

View file

@ -1,51 +0,0 @@
/**
*
* Copyright 2014-2019 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.jivesoftware.smack.test.util;
import java.io.StringReader;
import javax.xml.transform.stream.StreamSource;
import org.xmlunit.assertj.CompareAssert;
import org.xmlunit.assertj.XmlAssert;
import org.xmlunit.diff.DefaultNodeMatcher;
import org.xmlunit.diff.ElementSelectors;
import org.xmlunit.input.NormalizedSource;
// TODO: Rename this class to XmlAssertUtil
public class XmlUnitUtils {
public static void assertXmlNotSimilar(CharSequence xmlOne, CharSequence xmlTwo) {
normalizedCompare(xmlOne, xmlTwo).areNotSimilar();
}
public static void assertXmlSimilar(CharSequence expected, CharSequence actual) {
normalizedCompare(expected, actual).areSimilar();
}
private static CompareAssert normalizedCompare(CharSequence expectedCharSequence, CharSequence actualCharSequence) {
String expectedString = expectedCharSequence.toString();
String actualString = actualCharSequence.toString();
NormalizedSource expected = new NormalizedSource(new StreamSource(new StringReader(expectedString)));
NormalizedSource actual = new NormalizedSource(new StreamSource(new StringReader(actualString)));
return XmlAssert.assertThat(actual).and(expected)
.ignoreChildNodesOrder()
.withNodeMatcher(new DefaultNodeMatcher(ElementSelectors.byNameAndAllAttributes, ElementSelectors.byNameAndText))
.normalizeWhitespace();
}
}

View file

@ -1,161 +0,0 @@
/**
*
* Copyright 2019 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.jivesoftware.smack.util;
import static org.junit.Assert.assertNull;
import static org.junit.jupiter.api.Assertions.fail;
import java.lang.ref.PhantomReference;
import java.lang.ref.Reference;
import java.lang.ref.ReferenceQueue;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.function.Function;
import java.util.logging.Logger;
import org.jivesoftware.smack.DummyConnection;
import org.jivesoftware.smack.Manager;
import org.jxmpp.stringprep.XmppStringprepException;
/**
* Utility class to test for memory leaks caused by Smack.
* <p>
* Note that this test is based on the assumption that it is possible to trigger a full garbage collection run, which is
* not the case. See also this
* <a href="https://stackoverflow.com/questions/1481178/how-to-force-garbage-collection-in-java">stackoverflow
* question</a>. Hence the {@link #triggerGarbageCollection()} method defined in this class is not portable and depends
* on implementation depended Java Virtual Machine behavior.
* </p>
*
* @see <a href="https://issues.igniterealtime.org/browse/SMACK-383">SMACK-383 Jira Issue</a>
*/
public class MemoryLeakTestUtil {
private static final Logger LOGGER = Logger.getLogger(MemoryLeakTestUtil.class.getName());
@SuppressWarnings("UnusedVariable")
public static <M extends Manager> void noResourceLeakTest(Function<DummyConnection, M> managerSupplier)
throws XmppStringprepException, IllegalArgumentException, InterruptedException {
final int numConnections = 10;
ReferenceQueue<DummyConnection> connectionsReferenceQueue = new ReferenceQueue<>();
ReferenceQueue<Manager> managerReferenceQueue = new ReferenceQueue<>();
// Those two sets ensure that we hold a strong reference to the created PhantomReferences until the end of the
// test.
@SuppressWarnings("ModifiedButNotUsed")
Set<PhantomReference<DummyConnection>> connectionsPhantomReferences = new HashSet<>();
@SuppressWarnings("ModifiedButNotUsed")
Set<PhantomReference<Manager>> managersPhantomReferences = new HashSet<>();
List<DummyConnection> connections = new ArrayList<>(numConnections);
for (int i = 0; i < numConnections; i++) {
DummyConnection connection = new DummyConnection("foo" + i, "bar", "baz");
PhantomReference<DummyConnection> connectionPhantomReference = new PhantomReference<>(connection, connectionsReferenceQueue);
connectionsPhantomReferences.add(connectionPhantomReference);
Manager manager = managerSupplier.apply(connection);
PhantomReference<Manager> managerPhantomReference = new PhantomReference<Manager>(manager, managerReferenceQueue);
managersPhantomReferences.add(managerPhantomReference);
connections.add(connection);
}
// Clear the only references to the created connections.
connections = null;
triggerGarbageCollection();
// Now the connections should have been gc'ed, but not managers not yet.
assertReferencesQueueSize(connectionsReferenceQueue, numConnections);
assertReferencesQueueIsEmpty(managerReferenceQueue);
// We new create another connection and explicitly a new Manager. This will trigger the cleanup mechanism in the
// WeakHashMaps used by the Manager's iNSTANCE field. This should clean up all references to the Managers.
DummyConnection connection = new DummyConnection("last", "bar", "baz");
@SuppressWarnings("unused")
Manager manager = managerSupplier.apply(connection);
// The previous Managers should now be reclaimable by the garbage collector. First trigger a GC run.
triggerGarbageCollection();
// Now the Managers should have been freed and this means we should see their phantom references in the
// reference queue.
assertReferencesQueueSize(managerReferenceQueue, numConnections);
}
private static void assertReferencesQueueSize(ReferenceQueue<?> referenceQueue, int expectedSize) throws IllegalArgumentException, InterruptedException {
final int timeout = 120000;
final int maxAttempts = 3;
for (int itemsRemoved = 0; itemsRemoved < expectedSize; ++itemsRemoved) {
int attempt = 0;
Reference<?> reference = null;
do {
reference = referenceQueue.remove(timeout);
if (reference != null) {
break;
}
attempt++;
String message = "No reference to a gc'ed object found after " + timeout + "ms in the " + attempt
+ ". attempt.";
if (attempt >= maxAttempts) {
fail(message);
}
LOGGER.warning(message);
triggerGarbageCollection();
} while (true);
reference.clear();
}
Reference<?> reference = referenceQueue.poll();
assertNull("Reference queue is not empty when it should be", reference);
}
private static void assertReferencesQueueIsEmpty(ReferenceQueue<?> referenceQueue) {
Reference<?> reference = referenceQueue.poll();
assertNull(reference);
}
@SuppressWarnings("UnusedVariable")
private static void triggerGarbageCollection() {
Object object = new Object();
WeakReference<Object> weakReference = new WeakReference<>(object);
object = null;
int gcCalls = 0;
do {
if (gcCalls > 1000) {
throw new AssertionError("No observed gargabe collection after " + gcCalls + " calls of System.gc()");
}
System.gc();
// TODO: Would a Thread.yield() here improve the chances of a full GC? It appears that on some systems we
// observe a partial GC here.
gcCalls++;
} while (weakReference.get() != null);
// Note that this is no guarantee that a *full* garbage collection run has been made, which is what we actually
// need here in order to prevent false negatives.
LOGGER.finer("Observed garbage collection after " + gcCalls + " calls of System.gc()");
}
}

View file

@ -1,53 +0,0 @@
/**
*
* Copyright 2016-2019 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.jivesoftware.smack.util;
import java.io.IOException;
import java.net.BindException;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.util.logging.Level;
import java.util.logging.Logger;
public class NetworkUtil {
private static final Logger LOGGER = Logger.getLogger(NetworkUtil.class.getName());
public static ServerSocket getSocketOnLoopback() throws IOException {
final InetAddress loopbackAddress = InetAddress.getLoopbackAddress();
final int portMin = 1024;
final int portMax = (1 << 16) - 1;
final int backlog = 1;
ServerSocket serverSocket = null;
for (int port = portMin; port <= portMax; port++) {
try {
serverSocket = new ServerSocket(port, backlog, loopbackAddress);
break;
} catch (BindException e) {
LOGGER.log(Level.FINEST, "Could not bind port " + port + ", trying next", e);
}
}
if (serverSocket == null) {
throw new IOException("Could not bind any port between " + portMin + " and " + portMax
+ " on loopback address" + loopbackAddress);
}
return serverSocket;
}
}