/*
* RHQ Management Platform
* Copyright (C) 2005-2012 Red Hat, Inc.
* All rights reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation version 2 of the License.
*
* This program 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
package org.rhq.bindings;
import static org.testng.Assert.fail;
import java.io.IOException;
import java.lang.reflect.Method;
import java.security.PermissionCollection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import javax.script.ScriptEngine;
import javax.script.ScriptException;
import org.testng.IHookCallBack;
import org.testng.IHookable;
import org.testng.ITestContext;
import org.testng.ITestNGMethod;
import org.testng.ITestResult;
import org.testng.annotations.AfterClass;
import org.rhq.bindings.util.PackageFinder;
/**
* This is a base class for tests that need to test the some functionality using
* the scripting languages.
* <p>
* It enforces the naming convention for the tests:
* <code>
* @Test
* public void myTestMethod_language() ...
* </code>
* The language is to be replaced with the name of the scripting language that the test method
* uses (e.g. javacript, python). This class then makes sure that each test exists for all
* the scripting languages that are present on the classpath.
* <p>
* This class also provides the {@link #getScriptEngine(PackageFinder, StandardBindings)} and
* {@link #getSecuredScriptEngine(PackageFinder, StandardBindings, PermissionCollection)} methods
* that, if invoked from within a test method call-chain, will return the script engine for the
* correct language under test (determined by the test method name suffix).
*
* @author Lukas Krejci
*/
public abstract class ScriptedTestBase implements IHookable {
private String currentLanguage;
@AfterClass
public void checkTestImplsForEachLanguage(ITestContext ctx) {
Set<Method> methods = getAllTestMethods(getClass(), ctx);
Set<String> supportedLanguages = ScriptEngineFactory.getSupportedLanguages();
Map<String, Set<String>> methodBaseNamesByLanguage = new HashMap<String, Set<String>>();
for (String lang : supportedLanguages) {
methodBaseNamesByLanguage.put(lang, new HashSet<String>());
}
Set<Method> invalidTestMethods = new HashSet<Method>();
for (Method m : methods) {
//check that the method name ends with one of the supported language names
boolean valid = false;
for (String lang : supportedLanguages) {
String suffix = "_" + lang;
if (!m.getName().endsWith(suffix)) {
continue;
}
//now put the method "basename" into our mapping array
String baseName = m.getName().substring(0, m.getName().lastIndexOf("_"));
methodBaseNamesByLanguage.get(lang).add(baseName);
valid = true;
break;
}
if (!valid) {
invalidTestMethods.add(m);
}
}
Set<String> missingTests = new HashSet<String>();
//now check that all languages have all test methods
for (Map.Entry<String, Set<String>> a : methodBaseNamesByLanguage.entrySet()) {
for (Map.Entry<String, Set<String>> b : methodBaseNamesByLanguage.entrySet()) {
String alang = a.getKey();
String blang = b.getKey();
Set<String> amethods = a.getValue();
Set<String> bmethods = b.getValue();
if (alang.equals(blang)) {
continue;
}
addMissing(amethods, bmethods, alang, missingTests);
addMissing(bmethods, amethods, blang, missingTests);
}
}
if (!invalidTestMethods.isEmpty() || !missingTests.isEmpty()) {
StringBuilder msg = new StringBuilder("Scripted test " + getClass() + " is invalid:\n");
if (!invalidTestMethods.isEmpty()) {
msg.append("Invalid method names:\n");
for (Method m : invalidTestMethods) {
msg.append(m.getName()).append("\n");
}
}
if (!missingTests.isEmpty()) {
msg.append("\nMissing tests for languages:\n");
for (String m : missingTests) {
msg.append(m).append("\n");
}
}
fail(msg.toString());
}
}
@Override
public final void run(IHookCallBack callBack, ITestResult testResult) {
String methodName = testResult.getMethod().getMethodName();
int underScoreIdx = methodName.lastIndexOf('_');
if (underScoreIdx >= 0 && underScoreIdx < methodName.length() - 1) {
currentLanguage = methodName.substring(underScoreIdx + 1);
} else {
currentLanguage = null;
}
callBack.runTestMethod(testResult);
currentLanguage = null;
}
/**
* Returns a new script engine implementation for the current test method.
* <p>
* The script engine implementation is determined based on the test method's name
* suffix.
* <p>
* E.g. if the test method ends with "_javascript", this method will return the javascript
* script engine.
* <p>
* This method is calling {@link ScriptEngineFactory#getScriptEngine(String, PackageFinder, StandardBindings)}
* but determines the "language" parameter for you.
*
* @param packageFinder the package finder to use for the script engine initialization
* @param bindings the bindings to use in the script engine
* @return the script engine
* @throws ScriptException
* @throws IOException
*/
protected ScriptEngine getScriptEngine(PackageFinder packageFinder, StandardBindings bindings)
throws ScriptException, IOException {
return ScriptEngineFactory.getScriptEngine(currentLanguage, packageFinder, bindings);
}
/**
* Similar to {@link #getScriptEngine(PackageFinder, StandardBindings)} but returns the
* secured version of the script engine.
*
* @param packageFinder the package finder to use for the script engine initialization
* @param bindings the bindings to use in the script engine
* @param perms the permissions to run the scripts with
* @return the script engine
* @throws ScriptException
* @throws IOException
*/
protected ScriptEngine getSecuredScriptEngine(PackageFinder packageFinder, StandardBindings bindings,
PermissionCollection perms) throws ScriptException, IOException {
return ScriptEngineFactory.getSecuredScriptEngine(currentLanguage, packageFinder, bindings, perms);
}
private static Set<Method> getAllTestMethods(Class<?> cls, ITestContext ctx) {
HashSet<Method> ret = new HashSet<Method>();
for (ITestNGMethod m : ctx.getAllTestMethods()) {
if (m.getTestClass().getRealClass().equals(cls)) {
ret.add(m.getConstructorOrMethod().getMethod());
}
}
return ret;
}
private static void addMissing(Set<String> methods, Set<String> referenceMethods, String lang, Set<String> missing) {
for (String m : referenceMethods) {
if (!methods.contains(m)) {
String fullName = m + "_" + lang;
missing.add(fullName);
}
}
}
}