package com.redhat.qe.tools.remotelog;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.logging.Logger;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.testng.ISuite;
import org.testng.ISuiteListener;
import org.testng.ITestContext;
import org.testng.ITestListener;
import org.testng.ITestResult;
/**
* TestNG listener that intercepts {@link RemoteLogAccess} watcher on test method calls.
* Watching and checking log file can be enabled either globally by setting <b>com.redhat.qe.tools.remote.log.check</b> property,
* that will result to global log checker defined by {@link RemoteLog} defaults.<br>
* And/or you can enable it on test class or method by adding {@link CheckRemoteLog} annotation.
* @see CheckRemoteLog
* @author lzoubek@redhat.com
*
*/
public class RemoteLogCheckTestNGListener implements ITestListener,ISuiteListener {
protected static Logger log = Logger.getLogger(RemoteLogCheckTestNGListener.class.getName());
private static final Pattern envVarPattern = Pattern.compile("\\$\\{env\\:([^\\}]+)\\}");
private static final Pattern systemPropPattern = Pattern.compile("\\$\\{([^\\}]+)\\}");
private RemoteLogHandle classWatcher = null;
private RemoteLogHandle globalWatcher = null;
@Override
public void onStart(ISuite arg0) {
if (System.getProperty("com.redhat.qe.tools.remote.log.check")!=null) {
globalWatcher = new RemoteLogHandle("global",true,true);
try {
String user = RemoteLog.class.getMethod("user", new Class<?>[0]).getDefaultValue().toString();
String host = RemoteLog.class.getMethod("host", new Class<?>[0]).getDefaultValue().toString();
String pass = RemoteLog.class.getMethod("pass", new Class<?>[0]).getDefaultValue().toString();
String logFile = RemoteLog.class.getMethod("logFile", new Class<?>[0]).getDefaultValue().toString();
String filter = RemoteLog.class.getMethod("failExpression", new Class<?>[0]).getDefaultValue().toString();
RemoteLogAccess rla = new RemoteLogAccess(substValues(user), substValues(host), substValues(pass), substValues(logFile));
rla.setFilter(filter);
globalWatcher.getLogs().add(rla);
log.fine("Created global checker "+globalWatcher.toString());
}
catch (Exception ex) {
ex.printStackTrace();
}
}
}
@Override
public void onFinish(ISuite arg0) {
disconnectWatcher(globalWatcher);
}
@Override
public void onStart(ITestContext context) {
}
@Override
public void onTestFailure(ITestResult result) {
checkLogs(result);
}
@Override
public void onFinish(ITestContext context) {
}
@Override
public void onTestFailedButWithinSuccessPercentage(ITestResult arg0) {
disconnectWatcher(classWatcher);
}
@Override
public void onTestSkipped(ITestResult arg0) {
disconnectWatcher(classWatcher);
}
@Override
public void onTestStart(ITestResult result) {
Class<?> klass = result.getTestClass().getRealClass();
// find our annotation on method level
CheckRemoteLog check = result.getMethod().getMethod().getAnnotation(CheckRemoteLog.class);
if (check==null) {
// not found .. lets look at class level
check = getClassAnnotation(klass);
}
// class/method watcher has always higher priority
RemoteLogHandle watcher = create(check);
if (watcher==null) {
watcher = globalWatcher;
}
else {
classWatcher = watcher;
}
if (watcher!=null && watcher.isEnabled()) {
log.fine("Enabling checker "+watcher.toString()+ " for class "+klass.getCanonicalName());
watcher.watch();
}
}
@Override
public void onTestSuccess(ITestResult result) {
checkLogs(result);
}
private void checkLogs(ITestResult result) {
try {
// class/method watcher has always higher priority
RemoteLogHandle watcher = classWatcher;
if (watcher==null) {
watcher = globalWatcher;
}
if (watcher!=null && watcher.isEnabled()) {
// we check only successful tests and also failed when enabled
if (result.isSuccess() || (result.getStatus() == ITestResult.FAILURE && watcher.isAssertFailed())) {
StringBuilder message = new StringBuilder();
for (RemoteLogAccess rla : watcher.getLogs()) {
log.fine("Examining "+rla.toString()+"...");
List<String> errorLines = rla.filteredLines();
if (!errorLines.isEmpty()) {
log.warning("Founds lines matching ["+rla.getFilter()+"] in "+rla.toString()+" , seting test result as FAILED");
message.append(rla.toString()+":\n");
message.append(linesToStr(errorLines)+"\n");
}
}
if (message.length()>0) {
result.setStatus(ITestResult.FAILURE);
result.setThrowable(new RuntimeException("Following error lines were found in\n"+message.toString(),result.getThrowable()));
}
}
}
}
catch (Exception ex) {
ex.printStackTrace();
}
disconnectWatcher(classWatcher);
}
private String substValues(String value) {
Matcher m = envVarPattern.matcher(value);
while (m.find()) {
String repl = System.getenv(m.group(1));
if (repl==null) {
repl = "${env:"+m.group(1)+"}";
}
value = value.replaceAll(envVar(m.group(1)).toString(),Matcher.quoteReplacement(repl));
}
m = systemPropPattern.matcher(value);
while (m.find()) {
value = value.replaceAll(
sysProp(m.group(1)).toString(),Matcher.quoteReplacement(System.getProperty(m.group(1), "${" + m.group(1) + "}")));
}
return value;
}
private Pattern envVar(String value) {
value = value.replaceAll("\\.", "\\\\.");
return Pattern.compile("\\$\\{env\\:"+value+"\\}");
}
private Pattern sysProp(String value) {
value = value.replaceAll("\\.", "\\\\.");
return Pattern.compile("\\$\\{"+value+"\\}");
}
/**
* finds {@link CheckRemoteLog} annotation in given class or recursive in super classes
* @param klass
* @return
*/
private CheckRemoteLog getClassAnnotation(Class<?> klass) {
if (klass==null || Object.class.equals(klass)) {
return null;
}
CheckRemoteLog check = klass.getAnnotation(CheckRemoteLog.class);
if (check!=null) {
return check;
}
return getClassAnnotation(klass.getSuperclass());
}
private RemoteLogAccess create(RemoteLog rl) {
RemoteLogAccess inst = null;
try {
inst = new RemoteLogAccess(substValues(rl.user()), substValues(rl.host()), substValues(rl.pass()), substValues(rl.logFile()));
inst.setFilter(rl.failExpression());
} catch (IOException e) {
e.printStackTrace();
}
return inst;
}
/**
* creates new instance of agent log based on {@link CheckRemoteLog annotation}
*/
private RemoteLogHandle create(CheckRemoteLog check) {
if (check==null) {
return null;
}
if (!check.enabled()) {
// user requires to turn off checker
return new RemoteLogHandle(null,false,false);
}
RemoteLogHandle inst = new RemoteLogHandle("class",true,check.assertFailed());
for (RemoteLog rl : check.logs()) {
RemoteLogAccess rla = create(rl);
if (rla!=null) {
inst.getLogs().add(rla);
}
}
return inst;
}
private void disconnectWatcher(RemoteLogHandle watcher) {
if (watcher!=null && watcher.getLogs()!=null) {
watcher.disconnect();
watcher.setEnabled(false);
}
watcher = null;
}
private String linesToStr(List<String> lines) {
StringBuilder sb = new StringBuilder();
for (String line : lines) {
sb.append(line+"\n");
}
return sb.toString();
}
/**
* this handles watcher together with a flag whether it's enabled or not
* @author lzoubek
*
*/
private static class RemoteLogHandle {
private final List<RemoteLogAccess> logs = new ArrayList<RemoteLogAccess>();
private boolean enabled = true;
private boolean assertFailed = true;
private final String level;
private RemoteLogHandle(String level,boolean enabled, boolean assertFailed) {
this.enabled = enabled;
this.level = level;
this.assertFailed = assertFailed;
}
public boolean isAssertFailed() {
return assertFailed;
}
public void setEnabled(boolean enabled) {
this.enabled = enabled;
}
public boolean isEnabled() {
return enabled;
}
public List<RemoteLogAccess> getLogs() {
return logs;
}
public void disconnect() {
for (RemoteLogAccess rla : getLogs()) {
rla.disconnect();
}
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder("[level="+level+",enabled="+isEnabled());
for (RemoteLogAccess rla : getLogs()) {
sb.append(","+rla.toString());
}
sb.append("]");
return sb.toString();
}
public void watch() {
for (RemoteLogAccess rla : getLogs()) {
rla.watch();
}
}
}
}