/*
* RED5 Open Source Flash Server - http://code.google.com/p/red5/
*
* Copyright 2006-2012 by respective authors (see below). All rights reserved.
*
* 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.red5.server.service;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import java.util.Set;
import java.util.concurrent.atomic.AtomicInteger;
import junit.framework.Assert;
import net.sourceforge.groboutils.junit.v1.MultiThreadedTestRunner;
import net.sourceforge.groboutils.junit.v1.TestRunnable;
import org.junit.Test;
import org.red5.server.Context;
import org.red5.server.DummyClient;
import org.red5.server.api.IClient;
import org.red5.server.api.IClientRegistry;
import org.red5.server.api.IConnection;
import org.red5.server.api.IContext;
import org.red5.server.api.Red5;
import org.red5.server.api.TestConnection;
import org.red5.server.api.scope.IScope;
import org.red5.server.api.service.IPendingServiceCallback;
import org.red5.server.api.service.IServiceCall;
import org.red5.server.api.service.IServiceCapableConnection;
import org.red5.server.scope.Scope;
import org.red5.server.scope.WebScope;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.AbstractJUnit4SpringContextTests;
/**
* @author The Red5 Project (red5@osflash.org)
* @author Luke Hubbard, Codegent Ltd (luke@codegent.com)
* @author Paul Gregoire (mondain@gmail.com)
*/
@ContextConfiguration(locations = { "testcontext.xml" })
public class ServiceInvokerTest extends AbstractJUnit4SpringContextTests {
// TODO: Add more tests!
// we dont have to test all the echo methods, more test the call object works as expected
// the correct types of status are returned (method not found) etc.
// Also, we need to add tests which show the way the parameter conversion works.
// So have a few methods with the same name, and try with diff params, making sure right one gets called.
protected static Logger log = LoggerFactory.getLogger(ServiceInvokerTest.class);
private final static List<String> workerList = new ArrayList<String>(11);
private static AtomicInteger finishedCount = new AtomicInteger(0);
private final static Random rnd = new Random();
static {
String userDir = System.getProperty("user.dir");
System.out.println("User dir: " + userDir);
System.setProperty("red5.deployment.type", "junit");
System.setProperty("red5.root", "file:" + userDir + "/bin");
System.setProperty("red5.config_root", System.getProperty("red5.root") + "/conf");
}
@Test
public void testAppContextLoaded() {
Assert.assertNotNull(applicationContext);
//Assert.assertNotNull(applicationContext.getBean("serviceInvoker"));
Assert.assertNotNull(applicationContext.getBean("echoService"));
}
@Test
public void testExceptionStatus() {
ServiceInvoker invoker = null;
if (applicationContext.containsBean(ServiceInvoker.SERVICE_NAME)) {
invoker = (ServiceInvoker) applicationContext.getBean(ServiceInvoker.SERVICE_NAME);
} else {
invoker = (ServiceInvoker) applicationContext.getBean("global.serviceInvoker");
}
Object service = applicationContext.getBean("echoService");
Object[] params = new Object[] { "Woot this is cool" };
Call call = new Call("echoService", "doesntExist", params);
invoker.invoke(call, service);
Assert.assertEquals(false, call.isSuccess());
Assert.assertEquals(Call.STATUS_METHOD_NOT_FOUND, call.getStatus());
params = new Object[] { "too", "many", "params" };
call = new Call("echoService", "echoNumber", params);
invoker.invoke(call, service);
Assert.assertEquals(false, call.isSuccess());
Assert.assertEquals(Call.STATUS_METHOD_NOT_FOUND, call.getStatus());
}
@Test
public void testSimpleEchoCall() {
ServiceInvoker invoker = null;
if (applicationContext.containsBean(ServiceInvoker.SERVICE_NAME)) {
invoker = (ServiceInvoker) applicationContext.getBean(ServiceInvoker.SERVICE_NAME);
} else {
invoker = (ServiceInvoker) applicationContext.getBean("global.serviceInvoker");
}
Object[] params = new Object[] { "Woot this is cool" };
Object service = applicationContext.getBean("echoService");
PendingCall call = new PendingCall("echoService", "echoString", params);
invoker.invoke(call, service);
Assert.assertEquals(true, call.isSuccess());
Assert.assertEquals(params[0], call.getResult());
}
/**
* Test for memory leak bug #631
* http://trac.red5.org/ticket/631
*/
@Test
public void testBug631() {
final String message = "This is a test";
//create our sender conn and set local
IClientRegistry dummyReg = (IClientRegistry) applicationContext.getBean("global.clientRegistry");
IScope scp = (WebScope) applicationContext.getBean("web.scope"); //conn.getScope();
IContext ctx = (Context) applicationContext.getBean("web.context"); //scope.getContext();
IConnection recipient = new SvcCapableTestConnection("localhost", "/junit", "1");//host, path, session id
IClient rClient = dummyReg.newClient(new Object[] { "recipient" });
((TestConnection) recipient).setClient(rClient);
((TestConnection) recipient).setScope((Scope) scp);
((DummyClient) rClient).registerConnection(recipient);
IConnection sender = new SvcCapableTestConnection("localhost", "/junit", "2");//host, path, session id
IClient sClient = dummyReg.newClient(new Object[] { "sender" });
((TestConnection) sender).setClient(sClient);
((TestConnection) sender).setScope((Scope) scp);
((DummyClient) sClient).registerConnection(sender);
Red5.setConnectionLocal(sender);
//start standard process
IConnection conn = Red5.getConnectionLocal();
log.debug("Check s/c -\n s1: {} s2: {}\n c1: {} c2: {}", new Object[] { scp, conn.getScope(), ctx, conn.getScope().getContext() });
IClient client = conn.getClient();
IScope scope = (WebScope) conn.getScope();
IContext context = (Context) scope.getContext();
IClientRegistry reg = context.getClientRegistry();
log.debug("Client registry: {}", reg.getClass().getName());
IServiceCapableConnection serviceCapCon = null;
if (reg.hasClient("recipient")) {
IClient recip = reg.lookupClient("recipient");
Set<IConnection> rcons = recip.getConnections(scope);
log.debug("Recipient has {} connections", rcons.size());
Object[] sendobj = new Object[] { client.getId(), message };
for (IConnection rcon : rcons) {
if (rcon instanceof IServiceCapableConnection) {
serviceCapCon = (IServiceCapableConnection) rcon;
serviceCapCon.invoke("privMessage", sendobj);
break;
} else {
log.info("Connection is not service capable");
}
}
}
Assert.assertTrue(((SvcCapableTestConnection) serviceCapCon).getPrivateMessageCount() == 1);
}
/**
* Test for memory leak bug #631 with multiple threads
* http://trac.red5.org/ticket/631
*/
@Test
public void testMultiThreadBug631() throws Throwable {
//leak doesnt appear unless this is around 1000 and running outside eclipse
int threadCount = 2000;
//init and run
TestRunnable[] trs = new TestRunnable[threadCount];
for (int t = 0; t < threadCount; t++) {
ConnectionWorker worker = new ConnectionWorker(createConnection(t), t);
trs[t] = worker;
workerList.add(worker.getName());
}
MultiThreadedTestRunner mttr = new MultiThreadedTestRunner(trs);
long start = System.nanoTime();
mttr.runTestRunnables();
log.info("Runtime: {} ns", (System.nanoTime() - start));
for (TestRunnable r : trs) {
ConnectionWorker wkr = (ConnectionWorker) r;
String name = (wkr.getName());
Assert.assertNotNull(name);
//close them all down
wkr.close();
//
Assert.assertTrue(wkr.getServiceCapableConnection().getPrivateMessageCount() > threadCount);
}
//make sure all threads finished
Assert.assertEquals(threadCount, finishedCount.get());
}
private IConnection createConnection(int id) {
//create our sender conn and set local
IClientRegistry dummyReg = (IClientRegistry) applicationContext.getBean("global.clientRegistry");
IScope scp = (WebScope) applicationContext.getBean("web.scope");
//IContext ctx = (Context) applicationContext.getBean("web.context");
IConnection conn = new SvcCapableTestConnection("localhost", "/junit", id + "");//host, path, session id
IClient rClient = dummyReg.newClient(new Object[] { "client-" + id });
((TestConnection) conn).setClient(rClient);
((TestConnection) conn).setScope((Scope) scp);
((DummyClient) rClient).registerConnection(conn);
return conn;
}
final static class ConnectionWorker extends TestRunnable {
IConnection conn;
String name;
public ConnectionWorker(IConnection conn, int index) {
this.conn = conn;
this.name = "client-" + index;
}
public void runTest() throws Throwable {
Red5.setConnectionLocal(conn);
//start standard process
IConnection conn = Red5.getConnectionLocal();
IClient client = conn.getClient();
IScope scope = (WebScope) conn.getScope();
IContext context = (Context) scope.getContext();
IClientRegistry reg = context.getClientRegistry();
IServiceCapableConnection serviceCapCon = null;
//go through the client list and send at least one message to everyone
for (String worker : workerList) {
//dont send to ourself
if (name.equals(worker)) {
log.debug("Dont send to self");
continue;
}
if (reg.hasClient(worker)) {
IClient recip = reg.lookupClient(worker);
Set<IConnection> rcons = recip.getConnections(scope);
//log.debug("Recipient has {} connections", rcons.size());
Object[] sendobj = new Object[] { client.getId(), "This is a message from " + name };
for (IConnection rcon : rcons) {
if (rcon instanceof IServiceCapableConnection) {
serviceCapCon = (IServiceCapableConnection) rcon;
serviceCapCon.invoke("privMessage", sendobj);
break;
} else {
log.info("Connection is not service capable");
}
}
} else {
log.warn("Client not registered {}", worker);
}
}
//number of connections
int connectionCount = workerList.size();
//now send N messages to random recipients
for (int i = 0; i < 4000; i++) {
String worker = workerList.get(rnd.nextInt(connectionCount));
//dont send to ourself
if (name.equals(worker)) {
log.debug("Dont send to self");
continue;
}
if (reg.hasClient(worker)) {
IClient recip = reg.lookupClient(worker);
Set<IConnection> rcons = recip.getConnections(scope);
//log.debug("Recipient has {} connections", rcons.size());
Object[] sendobj = new Object[] { client.getId(), "This is a message from " + name };
for (IConnection rcon : rcons) {
if (rcon instanceof IServiceCapableConnection) {
serviceCapCon = (IServiceCapableConnection) rcon;
serviceCapCon.invoke("privMessage", sendobj);
break;
} else {
log.info("Connection is not service capable");
}
}
} else {
log.warn("Client not registered {}", worker);
}
}
finishedCount.incrementAndGet();
}
public void close() {
DummyClient client = (DummyClient) conn.getClient();
client.unregisterConnection(conn);
conn.close();
}
public String getName() {
return name;
}
public SvcCapableTestConnection getServiceCapableConnection() {
return (SvcCapableTestConnection) conn;
}
}
final static class SvcCapableTestConnection extends TestConnection implements IServiceCapableConnection {
private int privateMessageCount = 0;
public SvcCapableTestConnection(String host, String path, String sessionId) {
super(host, path, sessionId);
}
public int getPrivateMessageCount() {
return privateMessageCount;
}
public void invoke(String method, Object[] params) {
//log.debug("Invoke on connection: {}", this.client.getId());
if ("privMessage".equals(method)) {
//log.info("Got a private message from: {} message: {}", params);
privateMessageCount++;
} else {
log.warn("Method: {} not implemented", method);
}
}
@Override
public void invoke(IServiceCall call) {
}
@Override
public void invoke(IServiceCall call, int channel) {
}
@Override
public void invoke(String method) {
}
@Override
public void invoke(String method, IPendingServiceCallback callback) {
}
@Override
public void invoke(String method, Object[] params, IPendingServiceCallback callback) {
}
@Override
public void notify(IServiceCall call) {
}
@Override
public void notify(IServiceCall call, int channel) {
}
@Override
public void notify(String method) {
}
@Override
public void notify(String method, Object[] params) {
}
}
}