/*
* Copyright 2008 Glencoe Software, Inc. All rights reserved.
* Use is subject to license terms supplied in LICENSE.txt
*/
package ome.services.blitz.test.mock;
import java.lang.reflect.Array;
import java.lang.reflect.GenericArrayType;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import omero.api.IScriptPrx;
import omero.api.ServiceFactoryPrx;
import org.jmock.Mock;
import org.jmock.MockObjectTestCase;
import org.jmock.builder.ArgumentsMatchBuilder;
import org.testng.annotations.AfterMethod;
import org.testng.annotations.Test;
import Ice.ObjectPrxHelperBase;
/**
* Reflectively calls all interface methods defined in omero/API.ice and checks
* for exceptions. Generally called "fuzz-testing".
*/
public class ReflectiveApiTest extends MockObjectTestCase {
MockFixture fixture;
@AfterMethod(groups = "integration")
public void shutdownFixture() {
fixture.tearDown();
}
@Test(groups = "integration")
public void testByReflection() throws Exception {
fixture = new MockFixture(this);
ServiceFactoryPrx sf = fixture.createServiceFactory();
List<Method> factoryMethods = factoryMethods();
for (Method factoryMethod : factoryMethods) {
Object service = factoryMethod.invoke(sf);
// Filtering the blitz-only services for now
if (service instanceof IScriptPrx) {
continue;
}
List<Method> serviceMethods = serviceMethods(service.getClass());
for (Method method : serviceMethods) {
callWithFuzz(service, method);
}
}
System.out.println("boo");
}
// Helpers
// =========================================================================
/**
* Returns all methods on the {@link ServiceFactoryPrx} which will return a
* concrete service implementation.
*/
List<Method> factoryMethods() {
List<Method> rv = new ArrayList<Method>();
Method[] methods = ServiceFactoryPrx.class.getMethods();
for (Method method : methods) {
String name = method.getName();
// These requirement a string argument
if (method.getParameterTypes().length > 0) {
continue;
}
if (name.startsWith("get") || name.startsWith("create")) {
rv.add(method);
}
}
return rv;
}
/**
* Returns all service methods which are intended for client use. Filters
* async methods as well as private and Ice-only methods.
*/
List<Method> serviceMethods(Class serviceClass) {
Map<String, Method> rv = new HashMap<String, Method>();
Method[] methods = serviceClass.getMethods();
for (Method current : methods) {
String name = current.getName();
Class[] types = current.getParameterTypes();
if (name.startsWith("_") || name.startsWith("ice_")
|| name.endsWith("_async") || name.endsWith("checkedCast")) {
continue;
}
try {
// If method exists, then we don't want it.
Object.class.getMethod(current.getName(), types);
continue;
} catch (Exception e) {
// Good
}
try {
// Same as for Object.class
ObjectPrxHelperBase.class.getMethod(current.getName(), types);
continue;
} catch (Exception e) {
// Good.
}
Method already = rv.get(name);
if (already == null) {
rv.put(name, current);
} else {
// Filter out methods with Ice context parameters
int currentLength = current.getParameterTypes().length;
int alreadyLength = already.getParameterTypes().length;
if (alreadyLength < currentLength) {
rv.put(name, current);
}
}
}
return new ArrayList(rv.values());
}
/**
* Determines the necessary arguments for the given method and calls with
* random values to test the Ice mapping code.
*/
void callWithFuzz(Object service, Method method) throws Exception {
Type[] parameterTypes = method.getGenericParameterTypes();
Object[] parameters = new Object[parameterTypes.length];
for (int i = 0; i < parameters.length; i++) {
Type t = parameterTypes[i];
parameters[i] = makeFuzz(method, t);
}
Mock mock = fixture.blitzMock(service.getClass());
ArgumentsMatchBuilder builder = mock.expects(once()).method(
method.getName());
Class<?> returnClass = method.getReturnType();
if (void.class.isAssignableFrom(returnClass)) {
// nothing
} else if (long.class.isAssignableFrom(returnClass)) {
builder.will(returnValue(1L));
} else if (int.class.isAssignableFrom(returnClass)) {
builder.will(returnValue(1));
} else if (double.class.isAssignableFrom(returnClass)) {
builder.will(returnValue(0.0));
} else if (float.class.isAssignableFrom(returnClass)) {
builder.will(returnValue(0.0f));
} else if (boolean.class.isAssignableFrom(returnClass)) {
builder.will(returnValue(false));
} else {
builder.will(returnValue(null));
}
String msg = "Error running " + method + " with parameters "
+ Arrays.deepToString(parameters);
try {
method.invoke(service, parameters);
} catch (InvocationTargetException ite) {
Exception t = (Exception) ite.getCause();
if (t instanceof omero.ApiUsageException) {
// Ok. This means our fuzz was bad, but we can try to improve it
omero.ApiUsageException aue = (omero.ApiUsageException) t;
if (aue.message.contains("does not specify a valid class")) {
for (int i = 0; i < parameters.length; i++) {
if (parameters[i] instanceof String) {
parameters[i] = "Image";
}
}
// TODO
}
} else {
throw new RuntimeException(msg, t);
}
} catch (IllegalArgumentException iae) {
throw new RuntimeException(msg, iae);
}
}
private Object makeFuzz(Method method, Type type)
throws ClassNotFoundException, InstantiationException,
IllegalAccessException {
Class<?> t = null;
ParameterizedType pt = null;
GenericArrayType at = null;
if (type instanceof ParameterizedType) {
pt = (ParameterizedType) type;
Type raw = pt.getRawType();
if (raw instanceof GenericArrayType) {
throw new RuntimeException(method.toString());
} else {
t = (Class<?>) raw;
}
} else if (type instanceof GenericArrayType) {
at = (GenericArrayType) type;
} else {
t = (Class<?>) type;
}
try {
Object v;
if (at != null) {
// Handle arrays
Type componentType = at.getGenericComponentType();
Class<?> componentClass = (Class<?>) componentType;
v = Array.newInstance(componentClass, 0);
} else if (long.class.isAssignableFrom(t)) {
v = new Long(0);
} else if (int.class.isAssignableFrom(t)) {
v = new Integer(0);
} else if (double.class.isAssignableFrom(t)) {
v = new Double(0.0);
} else if (float.class.isAssignableFrom(t)) {
v = new Float(0.0);
} else if (boolean.class.isAssignableFrom(t)) {
v = Boolean.FALSE;
} else if (Integer.class.isAssignableFrom(t)) {
v = new Integer(0);
} else if (Long.class.isAssignableFrom(t)) {
v = new Long(0);
} else if (List.class.isAssignableFrom(t)) {
List l = new ArrayList<Object>();
if (pt != null) {
java.lang.reflect.Type[] types = pt
.getActualTypeArguments();
java.lang.reflect.Type listType = types[0];
Class<?> typeClass = (Class<?>) listType;
try {
l.add(makeFuzz(method, listType));
} catch (Exception e) {
throw new RuntimeException("Error instantiating "
+ typeClass, e);
}
}
v = l;
} else if (Map.class.isAssignableFrom(t)) {
v = new HashMap();
} else if (omero.model.IObject.class.equals(t)) {
// Picking a random IObjectI since IObject is abstract.
v = new omero.model.FormatI();
} else if (omero.model.Annotation.class.equals(t)) {
v = new omero.model.BooleanAnnotationI();
} else if (omero.model.Job.class.equals(t)) {
v = new omero.model.ImportJobI();
} else if (t.getName().startsWith("omero.model.")) {
Class t2 = Class.forName(t.getName() + "I");
v = t2.newInstance();
} else {
v = t.newInstance();
}
return v;
} catch (InstantiationException ie) {
throw new RuntimeException("Failed to instantiate a " + t.getName()
+ " for method " + method, ie);
}
}
}