blob: e6e84f972f8ab58c25f2f2a330a1da1d01dcefad [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2012 protos software gmbh (http://www.protos.de).
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* CONTRIBUTORS:
* Henrik Rentz-Reichert (initial contribution)
* Juergen Haug - added support for in-memory conversion
*
*******************************************************************************/
package org.eclipse.etrice.etunit.converter;
import java.io.BufferedReader;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.math.BigDecimal;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.UUID;
import org.eclipse.emf.common.util.URI;
import org.eclipse.emf.ecore.EPackage;
import org.eclipse.emf.ecore.resource.Resource;
import org.eclipse.emf.ecore.resource.ResourceSet;
import org.eclipse.emf.ecore.resource.impl.ResourceSetImpl;
import org.eclipse.emf.ecore.util.FeatureMapUtil;
import org.eclipse.etrice.etunit.converter.Etunit.DocumentRoot;
import org.eclipse.etrice.etunit.converter.Etunit.EtunitFactory;
import org.eclipse.etrice.etunit.converter.Etunit.EtunitPackage;
import org.eclipse.etrice.etunit.converter.Etunit.FailureType;
import org.eclipse.etrice.etunit.converter.Etunit.TestcaseType;
import org.eclipse.etrice.etunit.converter.Etunit.TestsuiteType;
import org.eclipse.etrice.etunit.converter.Etunit.TestsuitesType;
import org.eclipse.etrice.etunit.converter.Etunit.util.EtunitResourceFactoryImpl;
/**
* @author Henrik Rentz-Reichert
*
*/
public class EtUnitReportConverter {
public static class BaseOptions {
private boolean combinedResults = false;
private boolean replaceSuiteName = false;
private boolean prefixSuiteName = false;
private boolean onlyCombinedResults = false;
private String combinedFile = null;
private String suiteName = null;
private String suiteNamePrefix = null;
/** check that etUnit header line is present */
private boolean checkEtUnitHeader = true;
public boolean isCombinedResults() {
return combinedResults;
}
public void setCombinedResults(boolean combinedResults) {
this.combinedResults = combinedResults;
}
public boolean isReplaceSuiteName() {
return replaceSuiteName;
}
public void setReplaceSuiteName(boolean replaceSuiteName) {
this.replaceSuiteName = replaceSuiteName;
}
public boolean isPrefixSuiteName() {
return prefixSuiteName;
}
public void setPrefixSuiteName(boolean appendSuiteName) {
this.prefixSuiteName = appendSuiteName;
}
public boolean isOnlyCombinedResults() {
return onlyCombinedResults;
}
public void setOnlyCombinedResults(boolean onlyCombinedResults) {
this.onlyCombinedResults = onlyCombinedResults;
}
public String getCombinedFile() {
return combinedFile;
}
public void setCombinedFile(String combinedFile) {
this.combinedFile = combinedFile;
}
public String getSuiteName() {
return suiteName;
}
public void setSuiteName(String suiteName) {
this.suiteName = suiteName;
}
public String getSuiteNamePrefix() {
return suiteNamePrefix;
}
public void setSuiteNamePrefix(String suiteNamePrefix) {
this.suiteNamePrefix = suiteNamePrefix;
}
public boolean needCombined() {
return combinedResults;
}
public boolean checkEtUnitHeader() {
return checkEtUnitHeader;
}
public void setCheckEtUnitHeader(boolean checkEtUnitHeader) {
this.checkEtUnitHeader = checkEtUnitHeader;
}
}
public static class Options extends BaseOptions {
private ArrayList<String> files = new ArrayList<String>();
public ArrayList<String> getFiles() {
return files;
}
public void setFiles(ArrayList<String> files) {
this.files = files;
}
public boolean parseOptions(String[] args) {
for (int i=0; i<args.length; ++i) {
if (args[i].equals(OPTION_COMBINED)) {
setCombinedResults(true);
if (++i<args.length) {
setCombinedFile(args[i]);
}
else {
System.err.println("Error: "+OPTION_COMBINED+" must be followed by filename");
return false;
}
}
else if (args[i].equals(OPTION_SUITE_NAME)) {
setReplaceSuiteName(true);
if (++i<args.length) {
setSuiteName(args[i]);
}
else {
System.err.println("Error: "+OPTION_SUITE_NAME+" must be followed by a suite name");
return false;
}
}
else if(args[i].equals(OPTION_SUITE_NAME_PREFIX)) {
setPrefixSuiteName(true);
if(++i < args.length) {
setSuiteNamePrefix(args[i]);
}
else {
System.err.println("Error: " + OPTION_SUITE_NAME_PREFIX + "must be followed by a suite name prefix");
return false;
}
}
else if (args[i].equals(OPTION_ONLY_COMBINED)) {
setCombinedResults(true);
setOnlyCombinedResults(true);
if (++i<args.length) {
setCombinedFile(args[i]);
}
else {
System.err.println("Error: "+OPTION_ONLY_COMBINED+" must be followed by filename");
return false;
}
}
else if (args[i].startsWith("-")) {
int nextOption = parseOption(args, i);
if (nextOption<0) {
System.err.println("Error: unknown option "+args[i]);
return false;
}
i = nextOption;
}
else {
if (args[i].endsWith(ETU_EXTENSION))
getFiles().add(args[i]);
else {
System.err.println("Error: invalid file name '"+args[i]+"' (only *"+ETU_EXTENSION+" files allowed)");
return false;
}
}
}
if (getFiles().isEmpty()) {
System.err.println("Error: no reports specified");
return false;
}
return true;
}
/**
* @param args
* @param i
* @return
*/
protected int parseOption(String[] args, int i) {
return -1;
}
}
private static final String TC_END = "tc end";
private static final String TC_FAIL = "tc fail";
private static final String TC_START = "tc start";
private static final String TS_START = "ts start: ";
public static final String ETU_EXTENSION = ".etu";
public static final String OPTION_COMBINED = "-combined";
public static final String OPTION_ONLY_COMBINED = "-only_combined";
public static final String OPTION_SUITE_NAME = "-suite";
public static final String OPTION_SUITE_NAME_PREFIX = "-presuite";
protected void printUsage() {
System.err.println("usage: EtUnitReportConverter [("+OPTION_COMBINED+"|"+OPTION_ONLY_COMBINED+") <combined file>] ["+OPTION_SUITE_NAME+" <name>] <*"+ETU_EXTENSION+" files>\n"
+" "+OPTION_COMBINED+" <combined file>: also save a combined result for all tests to the specified file\n"
+" "+OPTION_ONLY_COMBINED+" <combined file>: don't create reports for every single test, only combined one to the specified file\n"
+" "+OPTION_SUITE_NAME+" <name>: replace the suite name in the result\n"
+" "+OPTION_SUITE_NAME_PREFIX+" <prefix>: prefix the prefix to the suitename\n"
);
}
public static void main(String[] args) {
int result = new EtUnitReportConverter().run(args);
System.exit(result);
}
/**
* Can be used for testing.
*/
public int run(String[] args) {
// check options and create file list
Options options = parseOptions(args);
if (options==null)
return 1;
doEMFRegistration();
ResourceSet rs = new ResourceSetImpl();
rs.getResourceFactoryRegistry().getExtensionToFactoryMap().put("xml", new EtunitResourceFactoryImpl());
boolean success = saveReports(options, rs);
if (options.needCombined()) {
success &= saveCombined(createCombinedReport(rs), options, rs);
}
return (success) ? 0 : 2;
}
/**
* In-memory conversion. Returns a list of all successful converted reports.
* TODO don't report errors on stderr.
*/
public List<String> convert(BaseOptions options, Iterable<InputStream> etUnitStreams) {
ResourceSet rs = new ResourceSetImpl();
rs.getResourceFactoryRegistry().getExtensionToFactoryMap().put("xml", new EtunitResourceFactoryImpl());
boolean success = saveReports(options, etUnitStreams, rs);
final List<Resource> resources = new ArrayList<Resource>();
if (options.needCombined()) {
DocumentRoot root = createCombinedReport(rs);
Resource res = rs.createResource(URI.createURI("dummy:/" + UUID.randomUUID() + ".xml"));
res.getContents().add(root);
resources.add(res);
} else {
resources.addAll(rs.getResources());
}
List<String> result = new ArrayList<String>();
for(Resource res : resources) {
ByteArrayOutputStream output = new ByteArrayOutputStream();
try {
res.save(output, null);
result.add(output.toString(StandardCharsets.UTF_8.name()));
} catch (IOException e) {
System.err.println("Error: could not serialize report");
e.printStackTrace();
success = false;
}
}
return (success) ? result : Collections.emptyList();
}
/**
* @param args
* @return
*/
protected Options parseOptions(String[] args) {
Options options = new Options();
if (!options.parseOptions(args)) {
printUsage();
return null;
}
return options;
}
private DocumentRoot createCombinedReport(ResourceSet rs) {
DocumentRoot root = EtunitFactory.eINSTANCE.createDocumentRoot();
TestsuitesType testsuites = EtunitFactory.eINSTANCE.createTestsuitesType();
root.setTestsuites(testsuites);
for (Resource res : rs.getResources()) {
DocumentRoot r = (DocumentRoot) res.getContents().get(0);
testsuites.getTestsuite().addAll(r.getTestsuites().getTestsuite());
}
computeAndSetInfo(testsuites);
return root;
}
protected boolean saveCombined(DocumentRoot root, Options options, ResourceSet rs) {
if (options.isCombinedResults()) {
File report = new File(options.getCombinedFile());
return saveJUnitReport(root, report, rs, true);
}
return true;
}
protected boolean saveReports(BaseOptions options, Iterable<InputStream> etUnitStreams, ResourceSet rs) {
boolean success = true;
for (InputStream stream : etUnitStreams) {
DocumentRoot root = applyOptions(options, createParseTree(stream, options));
if((success &= root != null)) {
Resource resource = rs.createResource(URI.createURI("dummy:/" + UUID.randomUUID() + ".xml"));
resource.getContents().add(root);
}
}
return success;
}
protected boolean saveReports(Options options, ResourceSet rs) {
boolean success = true;
for (String file : options.getFiles()) {
File report = new File(file);
if (report.exists()) {
DocumentRoot root = applyOptions(options, createParseTree(report, options));
success &= root != null && saveJUnitReport(root, report, rs, !options.isCombinedResults());
}
else {
System.err.println("Error: report "+file+" does not exist");
success = false;
}
}
return success;
}
protected DocumentRoot applyOptions(BaseOptions options, DocumentRoot root) {
if (root!=null && options.isReplaceSuiteName()) {
if (root.getTestsuites()!=null) {
if (root.getTestsuites().getTestsuite().size()==1) {
root.getTestsuites().getTestsuite().get(0).setName(options.getSuiteName());
}
else {
int i=0;
for (TestsuiteType suite : root.getTestsuites().getTestsuite()) {
suite.setName(options.getSuiteName()+i);
++i;
}
}
}
}
if(root != null && options.isPrefixSuiteName()) {
if(root.getTestsuites() != null) {
for(TestsuiteType suite : root.getTestsuites().getTestsuite()) {
suite.setName(options.getSuiteNamePrefix() + suite.getName());
}
}
}
return root;
}
private boolean saveJUnitReport(DocumentRoot root, File report, ResourceSet rs, boolean save) {
URI uri = URI.createFileURI(report.toString());
uri = uri.trimFileExtension();
uri = uri.appendFileExtension("xml");
Resource resource = rs.createResource(uri);
resource.getContents().add(root);
if (save) {
try {
resource.save(Collections.EMPTY_MAP);
System.out.println("saved "+uri);
} catch (IOException e) {
e.printStackTrace();
System.err.println("Error: file "+uri+" could not be saved ("+e.getMessage()+")");
return false;
}
}
return true;
}
private DocumentRoot createParseTree(File report, BaseOptions options) {
FileReader input = null;
try {
input = new FileReader(report.toString());
BufferedReader bufRead = new BufferedReader(input);
return createParseTree(bufRead, options);
} catch (IOException e) {
System.err.println("Error: file "+report+" could not be read ("+e.getMessage()+")");
e.printStackTrace();
}
finally {
try {
if(input != null)
input.close();
} catch (IOException e) {}
}
return null;
}
private DocumentRoot createParseTree(InputStream etUnitStream, BaseOptions options) {
try {
return createParseTree(new BufferedReader(new InputStreamReader(etUnitStream)), options);
} catch (IOException e) {
return null;
}
}
private DocumentRoot createParseTree(BufferedReader bufRead, BaseOptions options) throws IOException {
int count = 0;
try {
if(options.checkEtUnitHeader) {
String line = bufRead.readLine();
++count;
if (line==null) {
System.err.println("Error: etUnit report is empty - no etunit file");
return null;
}
if (!line.equals("etUnit report")) {
System.err.println("Error: in etUnt report line "+line+" is missing header line - no etunit file");
return null;
}
}
HashMap<Integer, TestcaseType> id2case = new HashMap<Integer, TestcaseType>();
TestsuiteType currentSuite = null;
DocumentRoot root = EtunitFactory.eINSTANCE.createDocumentRoot();
TestsuitesType testsuites = EtunitFactory.eINSTANCE.createTestsuitesType();
root.setTestsuites(testsuites);
String line = bufRead.readLine();
++count;
while (line!=null) {
if (line.startsWith(TS_START)) {
currentSuite = EtunitFactory.eINSTANCE.createTestsuiteType();
currentSuite.setName(line.substring(TS_START.length(), line.length()));
testsuites.getTestsuite().add(currentSuite);
}
else if (line.startsWith(TC_START)) {
int pos = line.indexOf(':');
int id = Integer.parseInt(line.substring(9, pos));
TestcaseType tc = EtunitFactory.eINSTANCE.createTestcaseType();
tc.setName(line.substring(pos+2));
id2case.put(id, tc);
currentSuite.getTestcase().add(tc);
}
else if (line.startsWith(TC_FAIL)) {
int pos = line.indexOf(':');
int id = Integer.parseInt(line.substring(8, pos));
TestcaseType tc = id2case.get(id);
if (tc==null) {
System.err.println("Error: in etUnit report line "+count+" - unknown test case id");
return null;
}
FailureType fail = EtunitFactory.eINSTANCE.createFailureType();
pos = line.indexOf('#')+1;
int end = line.indexOf('#', pos);
if (end>pos)
fail.setExpected(line.substring(pos, end));
pos = end+1;
end = line.indexOf('#', pos);
if (end>pos)
fail.setActual(line.substring(pos, end));
pos = end+1;
end = line.indexOf('#', pos);
String loc = (end>pos)? line.substring(pos, end) : null;
pos = line.lastIndexOf('#');
String trace = line.substring(pos+1);
if (loc!=null)
trace += "\n at "+loc;
FeatureMapUtil.addText(fail.getMixed(), trace);
tc.setFailure(fail);
}
else if (line.startsWith(TC_END)) {
int pos = line.indexOf(':');
int id = Integer.parseInt(line.substring(7, pos));
int time = Integer.parseInt(line.substring(pos+2));
TestcaseType tc = id2case.get(id);
if (tc==null) {
System.err.println("Error: in etUnit report line "+count+" - unknown test case id");
return null;
}
// time was measured in ms. Convert to s
tc.setTime(BigDecimal.valueOf(time/1000.0));
}
line = bufRead.readLine();
++count;
}
computeAndSetInfo(testsuites);
return root;
} catch (NumberFormatException e) {
System.err.println("Error: in etUnit report line "+count+" - could not read number");
return null;
}
}
private void computeAndSetInfo(TestsuitesType testsuites) {
for (TestsuiteType ts : testsuites.getTestsuite()) {
int failures = 0;
BigDecimal time = new BigDecimal(0);
for (TestcaseType tc : ts.getTestcase()) {
if (tc.getTime()!=null)
time = time.add(tc.getTime());
if (tc.getFailure()!=null)
++failures;
}
ts.setTests(ts.getTestcase().size());
ts.setFailures(failures);
ts.setTime(time);
}
}
private void doEMFRegistration() {
if (!EPackage.Registry.INSTANCE.containsKey("platform:/resource/org.eclipse.etrice.etunit.converter/model/etunit.xsd")) {
EPackage.Registry.INSTANCE.put("platform:/resource/org.eclipse.etrice.etunit.converter/model/etunit.xsd", EtunitPackage.eINSTANCE);
}
}
}