/* * Copyright 2013 Guidewire Software, Inc. */ package gw.test; import gw.lang.reflect.java.JavaTypes; import junit.framework.Test; import junit.framework.TestCase; import junit.framework.TestResult; import junit.framework.TestSuite; import junit.framework.AssertionFailedError; import gw.lang.reflect.IType; import gw.lang.reflect.IMethodInfo; import gw.lang.reflect.IAnnotationInfo; import gw.lang.reflect.ITypeInfo; import gw.lang.reflect.IConstructorInfo; import gw.lang.reflect.java.IJavaType; import gw.lang.reflect.java.JavaTypes; import gw.lang.reflect.gs.IGosuClass; import gw.testharness.IncludeInTestResults; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Callable; import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; import java.util.Set; import java.util.HashSet; import java.lang.annotation.Annotation; public class TestClassWrapper extends TestSuite { private IType _type; private TestExecutionManager _executionManager; public TestClassWrapper(TestExecutionManager executionManager, IType type, String... methods) { _executionManager = executionManager; _type = type; for (String method : methods) { addTest(makeTest(_type, method)); } } @Override public void runTest(Test test, TestResult result) { if (_executionManager.hasTimedOut()) { result.addFailure(test, new AssertionFailedError(String.format("tests timed out"))); } else if (_executionManager.hasTimeOut()) { long timeout = _executionManager.getTimeoutForCurrentTest(); runWithTimeout(test, result, timeout); } else { super.runTest(test, result); } } private void runWithTimeout(final Test test, final TestResult result, long timeout) { ExecutorService service = Executors.newSingleThreadExecutor(); Callable<Object> callable = new Callable<Object>() { public Object call() throws Exception { test.run(result); return null; } }; Future<Object> future = service.submit(callable); service.shutdown(); try { boolean terminated = service.awaitTermination(timeout, TimeUnit.MILLISECONDS); if (!terminated){ service.shutdownNow(); } future.get(0, TimeUnit.MILLISECONDS); // throws the exception if one occurred during the invocation } catch (TimeoutException e) { result.addFailure(test, new AssertionFailedError(String.format("test timed out after %d milliseconds", timeout))); result.endTest(test); _executionManager.markTimedOut(); } catch (Throwable e) { if (e instanceof AssertionFailedError) { result.addFailure(test, (AssertionFailedError) e); } else { result.addFailure(test, new AssertionFailedError(e.getMessage())); } } } @Override public String getName() { return _type.getName(); } private TestCase makeTest(final IType type, String method) { try { TestClass test; if (type.isValid()) { ITypeInfo typeInfo = type.getTypeInfo(); IConstructorInfo noArgCons = typeInfo.getConstructor(); if (noArgCons != null) { test = (TestClass) noArgCons.getConstructor().newInstance(); } else { IConstructorInfo oneArgCons = typeInfo.getConstructor(JavaTypes.STRING()); if (oneArgCons != null) { test = (TestClass) oneArgCons.getConstructor().newInstance(method); } else { throw new IllegalStateException("Test type " + type + " does not have either a no-arg constructor or a one-arg constructor taking a String"); } } } else { test = new InvalidTestClass(_type); } test.setExecutionManager( _executionManager ); test.setName(method); test.setGosuTest(_type instanceof IGosuClass); test.initMetadata( method ); return test; } catch (final Exception e) { e.printStackTrace(); return new ExceptionTestClass(type, e.getMessage()); } } static private Set<TestMetadata> getTestMethodMetadata(TestClass test, String method) { Set<TestMetadata> set = new HashSet<TestMetadata>(); IMethodInfo testMethod = test.getType().getTypeInfo().getMethod(method); if(testMethod == null) { throw new IllegalStateException( "Method not found: " + test.getName() + "." + method); } for (IAnnotationInfo ai : testMethod.getAnnotations()) { if (isMetaAnnotationInfo(ai)) { set.add(new TestMetadata((Annotation) ai.getInstance())); } } return set; } static private Set<TestMetadata> getTestClassMetadata(TestClass test) { Set<TestMetadata> set = new HashSet<TestMetadata>(); for (IAnnotationInfo ai : test.getType().getTypeInfo().getAnnotations()) { if (isMetaAnnotationInfo(ai)) { set.add(new TestMetadata((Annotation) ai.getInstance())); } } return set; } private static boolean isMetaAnnotationInfo(IAnnotationInfo ai) { boolean isMetadata = false; for (IAnnotationInfo a : ai.getType().getTypeInfo().getAnnotations()) { if (a.getName().equals(IncludeInTestResults.class.getName())) { isMetadata = true; break; } } return isMetadata; } public IType getBackingType() { return _type; } private static class InvalidTestClass extends TestClass { private IType _type; private InvalidTestClass(IType type) { super(false); _type = type; initInternalData(); } @Override public void run(TestResult result) { result.addError(this, getCompileError(_type)); } @Override public IType getType() { return _type; } private Throwable getCompileError(IType type) { if (type instanceof IGosuClass) { type.isValid(); // just in case there has been a typesystem refresh, to ensure there is a PRE return ((IGosuClass)type).getParseResultsException(); } else { return new IllegalStateException("Test type " + type + " is not valid."); } } @Override protected String getFullClassNameInternal() { return _type.getName(); } }; private static class ExceptionTestClass extends TestClass { private IType _type; private String _message; private ExceptionTestClass(IType type, String message) { super(false); _type = type; _message = message; initInternalData(); } @Override public void run(TestResult result) { result.addError(this, new RuntimeException("Could not construct test " + _type.getName() + ". Reason : " + _message) ); } @Override public IType getType() { return _type; } @Override protected String getFullClassNameInternal() { return _type.getName(); } }; }