package hudson.plugins.testlink.result;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import org.jenkinsci.remoting.Role;
import org.jenkinsci.remoting.RoleChecker;
import org.jenkinsci.remoting.RoleSensitive;
import org.tap4j.consumer.TapConsumer;
import org.tap4j.consumer.TapConsumerFactory;
import org.tap4j.model.Directive;
import org.tap4j.model.Plan;
import org.tap4j.model.TestResult;
import org.tap4j.model.TestSet;
import org.tap4j.producer.TapProducer;
import org.tap4j.producer.TapProducerFactory;
import org.tap4j.util.DirectiveValues;
import br.eti.kinoshita.testlinkjavaapi.constants.ExecutionStatus;
import br.eti.kinoshita.testlinkjavaapi.model.Attachment;
import br.eti.kinoshita.testlinkjavaapi.util.TestLinkAPIException;
import hudson.FilePath;
import hudson.FilePath.FileCallable;
import hudson.Launcher;
import hudson.model.AbstractBuild;
import hudson.model.BuildListener;
import hudson.model.Result;
import hudson.plugins.testlink.TestLinkSite;
import hudson.remoting.VirtualChannel;
/**
* @author Bruno P. Kinoshita - http://www.kinoshita.eti.br
* @author Javier Delgado - http://github.com/witokondoria
* @since 3.9
*/
public abstract class AbstractTAPFileNameResultSeeker extends ResultSeeker {
private static final long serialVersionUID = 3068999690225092293L;
protected static final String TEXT_PLAIN_CONTENT_TYPE = "text/plain";
private boolean attachTAPStream = false;
private boolean attachYAMLishAttachments = false;
private Boolean compareFullPath = false;
/**
* @param includePattern
* @param keyCustomField
* @param attachTAPStream
* @param attachYAMLishAttachments
*/
public AbstractTAPFileNameResultSeeker(String includePattern, String keyCustomField, boolean attachTAPStream,
boolean attachYAMLishAttachments, boolean includeNotes, Boolean compareFullPath) {
super(includePattern, keyCustomField, includeNotes);
this.attachTAPStream = attachTAPStream;
this.attachYAMLishAttachments = attachYAMLishAttachments;
this.compareFullPath = compareFullPath;
}
public void setAttachTAPStream(boolean attachTAPStream) {
this.attachTAPStream = attachTAPStream;
}
public boolean isAttachTAPStream() {
return attachTAPStream;
}
public void setAttachYAMLishAttachments(boolean attachYAMLishAttachments) {
this.attachYAMLishAttachments = attachYAMLishAttachments;
}
public boolean isAttachYAMLishAttachments() {
return attachYAMLishAttachments;
}
public Boolean isCompareFullPath() {
if (compareFullPath == null)
compareFullPath = false;
return compareFullPath;
}
public Boolean getCompareFullPath() {
return this.isCompareFullPath();
}
public void setCompareFullPath(Boolean compareFullPath) {
this.compareFullPath = compareFullPath;
}
/*
* (non-Javadoc)
*
* @see
* hudson.plugins.testlink.result.ResultSeeker#seekAndUpdate(hudson.plugins.testlink.result.TestCaseWrapper<?>[],
* hudson.model.AbstractBuild, hudson.Launcher, hudson.model.BuildListener, hudson.plugins.testlink.TestLinkSite,
* hudson.plugins.testlink.result.Report)
*/
@Override
public void seek(final TestCaseWrapper[] automatedTestCases, AbstractBuild<?, ?> build, Launcher launcher,
final BuildListener listener, TestLinkSite testlink) throws ResultSeekerException {
try {
final Map<String, TestSet> testSets = build.getWorkspace().act(
new FilePath.FileCallable<Map<String, TestSet>>() {
private static final long serialVersionUID = 1L;
private Map<String, TestSet> testSets;
public Map<String, TestSet> invoke(File workspace, VirtualChannel channel) throws IOException,
InterruptedException {
final String[] tapFiles = AbstractTAPFileNameResultSeeker.this.scan(workspace,
includePattern, listener);
testSets = new HashMap<String, TestSet>(tapFiles.length);
for (String tapFile : tapFiles) {
final File input = new File(workspace, tapFile);
final TapConsumer tapConsumer = TapConsumerFactory.makeTap13YamlConsumer();
final TestSet testSet = tapConsumer.load(input);
testSets.put(tapFile, testSet);
}
return testSets;
}
public void checkRoles(RoleChecker roleChecker) throws SecurityException {
roleChecker.check((RoleSensitive) this, Role.UNKNOWN);
}
});
for (String key : testSets.keySet()) {
for (TestCaseWrapper automatedTestCase : automatedTestCases) {
final String[] commaSeparatedValues = automatedTestCase
.getKeyCustomFieldValues(this.keyCustomField);
for (String value : commaSeparatedValues) {
String tapFileNameWithoutExtension = key;
int leftIndex = 0;
if (!this.isCompareFullPath()) {
int lastIndex = tapFileNameWithoutExtension.lastIndexOf(File.separator);
if (lastIndex > 0)
leftIndex = lastIndex + 1;
}
int extensionIndex = tapFileNameWithoutExtension.lastIndexOf('.');
if (extensionIndex != -1) {
tapFileNameWithoutExtension = tapFileNameWithoutExtension.substring(leftIndex,
tapFileNameWithoutExtension.lastIndexOf('.'));
}
if (tapFileNameWithoutExtension.equals(value)) {
this.updateTestCase(testSets, key, automatedTestCase, value, build, listener, testlink);
}
}
}
}
} catch (IOException e) {
throw new ResultSeekerException(e);
} catch (InterruptedException e) {
throw new ResultSeekerException(e);
}
}
protected void updateTestCase(Map<String, TestSet> testSets, String key, TestCaseWrapper automatedTestCase,
String value, AbstractBuild<?, ?> build, BuildListener listener, TestLinkSite testlink) {
final ExecutionStatus status = this.getExecutionStatus(testSets.get(key));
automatedTestCase.addCustomFieldAndStatus(value, status);
if (this.isIncludeNotes()) {
final String notes = this.getTapNotes(testSets.get(key));
automatedTestCase.appendNotes(notes);
}
this.handleResult(automatedTestCase, build, listener, testlink, status, testSets, key);
}
protected void handleResult(TestCaseWrapper automatedTestCase, final AbstractBuild<?, ?> build,
BuildListener listener, TestLinkSite testlink, ExecutionStatus status, final Map<String, TestSet> testSets,
final String key) {
if (automatedTestCase.getExecutionStatus(this.keyCustomField) != ExecutionStatus.NOT_RUN) {
String platform = this.retrievePlatform(testSets.get(key));
automatedTestCase.setPlatform(platform);
try {
final int executionId = testlink.updateTestCase(automatedTestCase);
if (executionId > 0 && this.isAttachTAPStream()) {
final String remoteWs = build.getWorkspace().getRemote();
List<Attachment> attachments = build.getWorkspace().act(new FileCallable<List<Attachment>>() {
private static final long serialVersionUID = -5411683541842375558L;
List<Attachment> attachments = new ArrayList<Attachment>();
public List<Attachment> invoke(File f, VirtualChannel channel) throws IOException,
InterruptedException {
File reportFile = new File(remoteWs, key);
final Attachment attachment = new Attachment();
attachment.setContent(AbstractTAPFileNameResultSeeker.this.getBase64FileContent(reportFile));
attachment.setDescription(reportFile.getName());
attachment.setFileName(reportFile.getName());
attachment.setFileSize(reportFile.length());
attachment.setFileType(TEXT_PLAIN_CONTENT_TYPE);
attachment.setTitle(reportFile.getName());
attachments.add(attachment);
if (AbstractTAPFileNameResultSeeker.this.isAttachYAMLishAttachments()) {
attachments.addAll(AbstractTAPFileNameResultSeeker.this
.retrieveListOfTapAttachments(testSets.get(key)));
}
return attachments;
}
@Override
public void checkRoles(RoleChecker roleChecker) throws SecurityException {
roleChecker.check((RoleSensitive) this, Role.UNKNOWN);
}
});
for (Attachment attachment : attachments) {
testlink.uploadAttachment(executionId, attachment);
}
}
} catch (TestLinkAPIException te) {
build.setResult(Result.UNSTABLE);
te.printStackTrace(listener.getLogger());
} catch (IOException e) {
build.setResult(Result.UNSTABLE);
e.printStackTrace(listener.getLogger());
} catch (InterruptedException e) {
build.setResult(Result.UNSTABLE);
e.printStackTrace(listener.getLogger());
}
}
}
/**
* @param testSet
* @return
*/
private ExecutionStatus getExecutionStatus(TestSet testSet) {
ExecutionStatus status = ExecutionStatus.PASSED;
if (isSkipped(testSet)) {
status = ExecutionStatus.BLOCKED;
} else if (isFailed(testSet)) {
status = ExecutionStatus.FAILED;
}
return status;
}
/**
* Checks if a test set contains a plan with skip directive or any test case with the same.
*/
private boolean isSkipped(TestSet testSet) {
boolean r = false;
if (testSet.getPlan().isSkip()) {
r = true;
} else {
for (TestResult testResult : testSet.getTestResults()) {
final Directive directive = testResult.getDirective();
if (directive != null && directive.getDirectiveValue() == DirectiveValues.SKIP) {
r = true;
break;
}
}
}
return r;
}
/**
* Checks if a test set contains not ok's, bail out!'s or a TO-DO directive.
*/
private boolean isFailed(TestSet testSet) {
boolean r = false;
if (testSet.containsNotOk() || testSet.containsBailOut()) {
r = true;
} else {
for (TestResult testResult : testSet.getTestResults()) {
final Directive directive = testResult.getDirective();
if (directive != null && directive.getDirectiveValue() == DirectiveValues.TODO) {
r = true;
break;
}
}
}
return r;
}
/**
* Retrieves notes for a TAP test set.
*
* @param testSet TAP test set.
* @return notes for a TAP test set.
*/
protected String getTapNotes(TestSet testSet) {
TapProducer producer = TapProducerFactory.makeTap13YamlProducer();
return producer.dump(testSet);
}
/**
* Retrieves the TestLink platform.
*
* @param tapTestSet TAP test set.
* @return TestLink platform.
*/
protected String retrievePlatform(TestSet tapTestSet) {
String platform = null;
Plan plan = tapTestSet.getPlan();
Map<String, Object> planDiagnostic = plan.getDiagnostic();
platform = this.extractPlatform(planDiagnostic);
if (platform == null) {
for (TestResult testResult : tapTestSet.getTestResults()) {
Map<String, Object> diagnostic = testResult.getDiagnostic();
platform = this.extractPlatform(diagnostic);
if (platform != null) {
break;
}
}
}
return platform;
}
/**
* @param diagnostic
* @return TestLink Platform if present, {@code null} otherwise
*/
@SuppressWarnings("unchecked")
private String extractPlatform(Map<String, Object> diagnostic) {
String platform = null;
Object extensions = diagnostic.get("extensions");
if (extensions != null && extensions instanceof Map<?, ?>) {
Map<String, Object> extensionsInfo = (Map<String, Object>) extensions;
Object testlink = extensionsInfo.get("TestLink");
if (testlink != null && testlink instanceof Map<?, ?>) {
Map<String, Object> testLinkInfo = (Map<String, Object>) testlink;
Object o = testLinkInfo.get("Platform");
if (o == null) {
o = testLinkInfo.get("platform");
}
if (o != null && o instanceof String) {
platform = (String) o;
}
}
}
return platform;
}
/**
* Retrieves list of attachments from a TAP Test Set by using its YAMLish data.
*
* @param testSet TAP Test Set.
* @return List of attachments.
* @throws IOException
*/
List<Attachment> retrieveListOfTapAttachments(TestSet testSet) throws IOException {
List<Attachment> attachments = new LinkedList<Attachment>();
Plan plan = testSet.getPlan();
Map<String, Object> diagnostic = plan.getDiagnostic();
this.extractAttachments(attachments, diagnostic);
for (org.tap4j.model.TestResult testResult : testSet.getTestResults()) {
this.extractAttachments(attachments, testResult.getDiagnostic());
}
return attachments;
}
/**
* Extracts attachments from a TAP diagnostic and adds into a list of attachments.
*
* @param attachments List of attachments
* @param diagnostic TAP diagnostic
* @throws IOException
*/
@SuppressWarnings("unchecked")
protected void extractAttachments(List<Attachment> attachments, Map<String, Object> diagnostic) throws IOException {
final Object extensions = diagnostic.get("extensions");
if (extensions != null && extensions instanceof Map<?, ?>) {
Map<String, Object> extensionsMap = (Map<String, Object>) extensions;
Object files = extensionsMap.get("Files");
if (files != null && files instanceof Map<?, ?>) {
Map<String, Object> filesMap = (Map<String, Object>) files;
Set<Entry<String, Object>> filesMapEntrySet = filesMap.entrySet();
Iterator<Entry<String, Object>> iterator = filesMapEntrySet.iterator();
while (iterator != null && iterator.hasNext()) {
Entry<String, Object> filesMapEntry = iterator.next();
Object entryObject = filesMapEntry.getValue();
if (entryObject != null && entryObject instanceof Map<?, ?>) {
Map<String, Object> entryObjectMap = (Map<String, Object>) entryObject;
Object oFileContent = entryObjectMap.get("File-Content");
if (oFileContent != null) {
String fileContent = "" + oFileContent;
Attachment attachment = new Attachment();
attachment.setContent(fileContent);
try {
attachment.setFileSize(Long.parseLong("" + entryObjectMap.get("File-Size")));
} catch (NumberFormatException nfe) {
}
attachment.setFileName("" + entryObjectMap.get("File-Name"));
attachment.setTitle("" + entryObjectMap.get("File-Title"));
attachment.setDescription("" + entryObjectMap.get("File-Description"));
attachment.setFileType("" + entryObjectMap.get("File-Type"));
attachments.add(attachment);
} else {
Object fileLocation = entryObjectMap.get("File-Location");
String fileLocationText = "" + fileLocation;
File file = new File(fileLocationText);
if (file.exists()) {
Attachment attachment = new Attachment();
Object oContent = entryObjectMap.get("File-Content");
if (oContent != null) {
attachment.setContent("" + oContent);
try {
attachment.setFileSize(Long.parseLong("" + entryObjectMap.get("File-Size")));
} catch (NumberFormatException nfe) {
attachment.setFileSize(file.length());
}
} else {
String fileContent = this.getBase64FileContent(file);
attachment.setContent(fileContent);
attachment.setFileSize(file.length());
}
attachment.setFileName("" + entryObjectMap.get("File-Name"));
attachment.setTitle("" + entryObjectMap.get("File-Title"));
attachment.setDescription("" + entryObjectMap.get("File-Description"));
attachment.setFileType("" + entryObjectMap.get("File-Location"));
attachments.add(attachment);
}
}
}
}
}
}
}
}