diff --git a/pom.xml b/pom.xml
index 14ef3704d..c3ffdbe1c 100644
--- a/pom.xml
+++ b/pom.xml
@@ -119,6 +119,12 @@
4.8.1
test
+
+ org.hsqldb
+ hsqldb
+ 2.0.0
+ test
+
diff --git a/src/test/java/com/threerings/presents/peer/server/PeerTest.java b/src/test/java/com/threerings/presents/peer/server/PeerTest.java
new file mode 100644
index 000000000..5a597897b
--- /dev/null
+++ b/src/test/java/com/threerings/presents/peer/server/PeerTest.java
@@ -0,0 +1,71 @@
+//
+// $Id$
+//
+// Narya library - tools for developing networked games
+// Copyright (C) 2002-2011 Three Rings Design, Inc., All Rights Reserved
+// http://code.google.com/p/narya/
+//
+// This library is free software; you can redistribute it and/or modify it
+// under the terms of the GNU Lesser General Public License as published
+// by the Free Software Foundation; either version 2.1 of the License, or
+// (at your option) any later version.
+//
+// This library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+// Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public
+// License along with this library; if not, write to the Free Software
+// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
+package com.threerings.presents.peer.server;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+import org.junit.Test;
+import static org.junit.Assert.*;
+
+/**
+ * Tests basic peer network setup and peer communication.
+ */
+public class PeerTest
+{
+ @Test
+ public void testPeerConnect ()
+ throws Exception
+ {
+ PeerTestGroup group = new PeerTestGroup(2, true);
+
+ // wire up callbacks for when we're connected to a peer
+ final CountDownLatch done = new CountDownLatch(group.servers.size());
+ TestPeerManager p1 = group.injectors.get(0).getInstance(TestPeerManager.class);
+ p1.setOnConnected(new TestPeerManager.Callback() {
+ public void apply (String nodeName) {
+ done.countDown();
+ }
+ });
+ TestPeerManager p2 = group.injectors.get(1).getInstance(TestPeerManager.class);
+ p2.setOnConnected(new TestPeerManager.Callback() {
+ public void apply (String nodeName) {
+ done.countDown();
+ }
+ });
+
+ // start up all of our servers
+ group.start();
+
+ // trigger the immediate establishment of the peer network (normally this is delayed for 5
+ // seconds to allow slack for differing peer startup times)
+ p1.refreshPeers();
+
+ try {
+ done.await(1, TimeUnit.SECONDS);
+ } catch (InterruptedException e) {
+ fail("Interrupte?");
+ }
+
+ group.shutdown();
+ }
+}
diff --git a/src/test/java/com/threerings/presents/peer/server/PeerTestGroup.java b/src/test/java/com/threerings/presents/peer/server/PeerTestGroup.java
new file mode 100644
index 000000000..1219e1792
--- /dev/null
+++ b/src/test/java/com/threerings/presents/peer/server/PeerTestGroup.java
@@ -0,0 +1,132 @@
+//
+// $Id$
+//
+// Narya library - tools for developing networked games
+// Copyright (C) 2002-2011 Three Rings Design, Inc., All Rights Reserved
+// http://code.google.com/p/narya/
+//
+// This library is free software; you can redistribute it and/or modify it
+// under the terms of the GNU Lesser General Public License as published
+// by the Free Software Foundation; either version 2.1 of the License, or
+// (at your option) any later version.
+//
+// This library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+// Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public
+// License along with this library; if not, write to the Free Software
+// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
+package com.threerings.presents.peer.server;
+
+import java.util.List;
+import java.util.Properties;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import com.google.common.base.Preconditions;
+import com.google.common.collect.Lists;
+import com.google.inject.Guice;
+import com.google.inject.Inject;
+import com.google.inject.Injector;
+
+import com.samskivert.depot.PersistenceContext;
+import com.samskivert.jdbc.StaticConnectionProvider;
+
+import com.threerings.NaryaLog;
+import com.threerings.presents.server.PresentsServer;
+
+/**
+ * Manages a collection of peer servers.
+ */
+public class PeerTestGroup
+{
+ public final List servers = Lists.newArrayList();
+
+ public final List injectors = Lists.newArrayList();
+
+ public PeerTestGroup (int count, boolean suppressInfoLogging)
+ throws Exception
+ {
+ if (suppressInfoLogging) {
+ Logger.getLogger("").setLevel(Level.WARNING);
+ }
+
+ for (int ii = 0; ii < count; ii++) {
+ final String nodename = "node" + ii;
+ final int port = BASE_PORT + ii;
+
+ Injector inj = Guice.createInjector(new PresentsServer.PresentsModule() {
+ @Override protected void configure () {
+ super.configure();
+ bind(PresentsServer.class).toInstance(new PeerTestServer(port));
+ bind(PeerManager.class).to(TestPeerManager.class);
+ bind(PersistenceContext.class).toInstance(new PersistenceContext());
+ }
+ });
+ this.injectors.add(inj);
+
+ PersistenceContext pctx = inj .getInstance(PersistenceContext.class);
+ Properties props = new Properties();
+ props.put("default.driver", "org.hsqldb.jdbcDriver");
+ props.put("default.url", "jdbc:hsqldb:mem:testdb");
+ props.put("default.username", "sa");
+ props.put("default.password", "");
+ pctx.init("testdb", new StaticConnectionProvider(props), null);
+ pctx.initializeRepositories(true);
+
+ PresentsServer server = inj.getInstance(PresentsServer.class);
+ server.init(inj);
+ this.servers.add(server);
+
+ PeerManager peermgr = inj.getInstance(PeerManager.class);
+ peermgr.init(nodename, "I has a s3cr3t!", "localhost", "localhost", port);
+ }
+ }
+
+ public void start ()
+ {
+ Preconditions.checkState(_threads.isEmpty(), "Group already started.");
+ for (final PresentsServer server : this.servers) {
+ Thread thread = new Thread() {
+ public void run () {
+ server.run();
+ }
+ };
+ _threads.add(thread);
+ thread.start();
+ }
+ }
+
+ public void shutdown ()
+ {
+ for (PresentsServer server : this.servers) {
+ server.queueShutdown();
+ }
+ try {
+ // shutdown our servers
+ for (Thread t : _threads) {
+ t.join(1000);
+ }
+ } catch (InterruptedException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ protected static class PeerTestServer extends PresentsServer {
+ public PeerTestServer (int port) {
+ _ports = new int[] { port };
+ }
+ @Override protected int[] getListenPorts () {
+ return _ports;
+ }
+ protected int[] _ports;
+ @Inject protected PeerManager _pmgr; // trigger pmgr resolution
+ }
+
+ protected final List _threads = Lists.newArrayList();
+
+ protected static final int BASE_PORT = 1234;
+}
diff --git a/src/test/java/com/threerings/presents/peer/server/TestPeerManager.java b/src/test/java/com/threerings/presents/peer/server/TestPeerManager.java
new file mode 100644
index 000000000..1b7894713
--- /dev/null
+++ b/src/test/java/com/threerings/presents/peer/server/TestPeerManager.java
@@ -0,0 +1,56 @@
+//
+// $Id$
+//
+// Narya library - tools for developing networked games
+// Copyright (C) 2002-2011 Three Rings Design, Inc., All Rights Reserved
+// http://code.google.com/p/narya/
+//
+// This library is free software; you can redistribute it and/or modify it
+// under the terms of the GNU Lesser General Public License as published
+// by the Free Software Foundation; either version 2.1 of the License, or
+// (at your option) any later version.
+//
+// This library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+// Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public
+// License along with this library; if not, write to the Free Software
+// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
+package com.threerings.presents.peer.server;
+
+import com.google.inject.Inject;
+import com.google.inject.Singleton;
+
+import com.samskivert.util.Lifecycle;
+
+/**
+ * A peer manager with hooks for testing.
+ */
+@Singleton
+public class TestPeerManager extends PeerManager
+{
+ public interface Callback {
+ void apply (T value);
+ }
+
+ @Inject
+ public TestPeerManager (Lifecycle cycle) {
+ super(cycle);
+ }
+
+ public void setOnConnected (Callback onConnected) {
+ _onConnected = onConnected;
+ }
+
+ protected void connectedToPeer (PeerNode peer) {
+ super.connectedToPeer(peer);
+ if (_onConnected != null) {
+ _onConnected.apply(peer.nodeobj.nodeName);
+ }
+ }
+
+ protected Callback _onConnected;
+}