/* * Copyright (C) 2011 Google Inc. * * 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.ros.node.topic; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import org.junit.Test; import org.ros.RosTest; import org.ros.concurrent.CancellableLoop; import org.ros.internal.message.definition.MessageDefinitionReflectionProvider; import org.ros.internal.message.topic.TopicMessageFactory; import org.ros.internal.node.topic.DefaultSubscriber; import org.ros.internal.node.topic.PublisherIdentifier; import org.ros.message.MessageDefinitionProvider; import org.ros.message.MessageListener; import org.ros.namespace.GraphName; import org.ros.node.AbstractNodeMain; import org.ros.node.ConnectedNode; import java.net.InetSocketAddress; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; /** * Make sure publishers can talk with subscribers over a network connection. * * @author damonkohler@google.com (Damon Kohler) */ public class TopicIntegrationTest extends RosTest { private static final int QUEUE_CAPACITY = 128; private final std_msgs.String expectedMessage; public TopicIntegrationTest() { MessageDefinitionProvider messageDefinitionProvider = new MessageDefinitionReflectionProvider(); TopicMessageFactory topicMessageFactory = new TopicMessageFactory(messageDefinitionProvider); expectedMessage = topicMessageFactory.newFromType(std_msgs.String._TYPE); expectedMessage.setData("Would you like to play a game?"); } @Test public void testOnePublisherToOneSubscriber() throws InterruptedException { nodeMainExecutor.execute(new AbstractNodeMain() { @Override public GraphName getDefaultNodeName() { return GraphName.of("publisher"); } @Override public void onStart(ConnectedNode connectedNode) { Publisher<std_msgs.String> publisher = connectedNode.newPublisher("foo", std_msgs.String._TYPE); publisher.setLatchMode(true); publisher.publish(expectedMessage); } }, nodeConfiguration); final CountDownLatch messageReceived = new CountDownLatch(1); nodeMainExecutor.execute(new AbstractNodeMain() { @Override public GraphName getDefaultNodeName() { return GraphName.of("subscriber"); } @Override public void onStart(ConnectedNode connectedNode) { Subscriber<std_msgs.String> subscriber = connectedNode.newSubscriber("foo", std_msgs.String._TYPE); subscriber.addMessageListener(new MessageListener<std_msgs.String>() { @Override public void onNewMessage(std_msgs.String message) { assertEquals(expectedMessage, message); messageReceived.countDown(); } }, QUEUE_CAPACITY); } }, nodeConfiguration); assertTrue(messageReceived.await(10, TimeUnit.SECONDS)); } /** * This is a regression test. * * @see <a * href="http://answers.ros.org/question/3591/rosjava-subscriber-unreliable">bug * report</a> * * @throws InterruptedException */ @Test public void testSubscriberStartsBeforePublisher() throws InterruptedException { final CountDownSubscriberListener<std_msgs.String> subscriberListener = CountDownSubscriberListener.newDefault(); final CountDownLatch messageReceived = new CountDownLatch(1); nodeMainExecutor.execute(new AbstractNodeMain() { @Override public GraphName getDefaultNodeName() { return GraphName.of("subscriber"); } @Override public void onStart(ConnectedNode connectedNode) { Subscriber<std_msgs.String> subscriber = connectedNode.newSubscriber("foo", std_msgs.String._TYPE); subscriber.addSubscriberListener(subscriberListener); subscriber.addMessageListener(new MessageListener<std_msgs.String>() { @Override public void onNewMessage(std_msgs.String message) { assertEquals(expectedMessage, message); messageReceived.countDown(); } }, QUEUE_CAPACITY); } }, nodeConfiguration); subscriberListener.awaitMasterRegistrationSuccess(1, TimeUnit.SECONDS); nodeMainExecutor.execute(new AbstractNodeMain() { @Override public GraphName getDefaultNodeName() { return GraphName.of("publisher"); } @Override public void onStart(ConnectedNode connectedNode) { Publisher<std_msgs.String> publisher = connectedNode.newPublisher("foo", std_msgs.String._TYPE); publisher.setLatchMode(true); publisher.publish(expectedMessage); } }, nodeConfiguration); assertTrue(messageReceived.await(1, TimeUnit.SECONDS)); } @Test public void testAddDisconnectedPublisher() { nodeMainExecutor.execute(new AbstractNodeMain() { @Override public GraphName getDefaultNodeName() { return GraphName.of("subscriber"); } @Override public void onStart(ConnectedNode connectedNode) { DefaultSubscriber<std_msgs.String> subscriber = (DefaultSubscriber<std_msgs.String>) connectedNode.<std_msgs.String>newSubscriber( "foo", std_msgs.String._TYPE); try { subscriber.addPublisher(PublisherIdentifier.newFromStrings("foo", "http://foo", "foo"), new InetSocketAddress(1234)); fail(); } catch (RuntimeException e) { // Connecting to a disconnected publisher should fail. } } }, nodeConfiguration); } private final class Listener implements MessageListener<test_ros.TestHeader> { private final CountDownLatch latch = new CountDownLatch(10); private test_ros.TestHeader lastMessage; @Override public void onNewMessage(test_ros.TestHeader message) { int seq = message.getHeader().getSeq(); long stamp = message.getHeader().getStamp().totalNsecs(); if (lastMessage != null) { int lastSeq = lastMessage.getHeader().getSeq(); long lastStamp = lastMessage.getHeader().getStamp().totalNsecs(); assertTrue(String.format("message seq %d <= previous seq %d", seq, lastSeq), seq > lastSeq); assertTrue(String.format("message stamp %d <= previous stamp %d", stamp, lastStamp), stamp > lastStamp); } lastMessage = message; latch.countDown(); } public boolean await(long timeout, TimeUnit unit) throws InterruptedException { return latch.await(timeout, unit); } } @Test public void testHeader() throws InterruptedException { nodeMainExecutor.execute(new AbstractNodeMain() { @Override public GraphName getDefaultNodeName() { return GraphName.of("publisher"); } @Override public void onStart(final ConnectedNode connectedNode) { final Publisher<test_ros.TestHeader> publisher = connectedNode.newPublisher("foo", test_ros.TestHeader._TYPE); connectedNode.executeCancellableLoop(new CancellableLoop() { @Override public void loop() throws InterruptedException { test_ros.TestHeader message = connectedNode.getTopicMessageFactory().newFromType(test_ros.TestHeader._TYPE); message.getHeader().setStamp(connectedNode.getCurrentTime()); publisher.publish(message); // There needs to be some time between messages in order to // guarantee that the timestamp increases. Thread.sleep(1); } }); } }, nodeConfiguration); final Listener listener = new Listener(); nodeMainExecutor.execute(new AbstractNodeMain() { @Override public GraphName getDefaultNodeName() { return GraphName.of("subscriber"); } @Override public void onStart(ConnectedNode connectedNode) { Subscriber<test_ros.TestHeader> subscriber = connectedNode.newSubscriber("foo", test_ros.TestHeader._TYPE); subscriber.addMessageListener(listener, QUEUE_CAPACITY); } }, nodeConfiguration); assertTrue(listener.await(1, TimeUnit.SECONDS)); } }