package org.testng.xml; import javax.xml.parsers.ParserConfigurationException; import org.testng.collections.Lists; import org.testng.collections.Maps; import org.xml.sax.SAXException; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; import java.util.Collection; import java.util.List; import java.util.Map; /** * <code>Parser</code> is a parser for a TestNG XML test suite file. */ public class Parser { /** The name of the TestNG DTD. */ public static final String TESTNG_DTD = "testng-1.0.dtd"; /** The URL to the deprecated TestNG DTD. */ public static final String DEPRECATED_TESTNG_DTD_URL = "http://beust.com/testng/" + TESTNG_DTD; /** The URL to the TestNG DTD. */ public static final String TESTNG_DTD_URL = "http://testng.org/" + TESTNG_DTD; /** The default file name for the TestNG test suite if none is specified (testng.xml). */ public static final String DEFAULT_FILENAME = "testng.xml"; private static final IFileParser DEFAULT_FILE_PARSER = new XmlParser(); /** The file name of the xml suite being parsed. This may be null if the Parser * has not been initialized with a file name. TODO CQ This member is never used. */ private String m_fileName; private InputStream m_inputStream; private IFileParser m_fileParser; /** * Constructs a <code>Parser</code> to use the inputStream as the source of * the xml test suite to parse. * @param filename the filename corresponding to the inputStream or null if * unknown. * @param inputStream the xml test suite input stream. */ public Parser(String fileName) { init(fileName, null, null); } /** * Creates a parser that will try to find the DEFAULT_FILENAME from the jar. * @throws FileNotFoundException if the DEFAULT_FILENAME resource is not * found in the classpath. */ public Parser() throws FileNotFoundException { init(null, null, null); } public Parser(InputStream is) { init(null, is, null); } private void init(String fileName, InputStream is, IFileParser fp) { m_fileName = fileName != null ? fileName : DEFAULT_FILENAME; m_inputStream = is; m_fileParser = fp != null ? fp : DEFAULT_FILE_PARSER; } /** * Returns an input stream on the resource named DEFAULT_FILENAME. * * @return an input stream on the resource named DEFAULT_FILENAME. * @throws FileNotFoundException if the DEFAULT_FILENAME resource is not * found in the classpath. */ // private static InputStream getInputStream(String fileName) throws FileNotFoundException { // // Try to look for the DEFAULT_FILENAME from the jar // ClassLoader classLoader = Thread.currentThread().getContextClassLoader(); // InputStream in; // // TODO CQ is this OK? should we fall back to the default classloader if the // // context classloader fails. // if (classLoader != null) { // in = classLoader.getResourceAsStream(fileName); // } // else { // in = Parser.class.getResourceAsStream(fileName); // } // if (in == null) { // throw new FileNotFoundException(fileName); // } // return in; // } /** * Parses the TestNG test suite and returns the corresponding XmlSuite, * and possibly, other XmlSuite that are pointed to by <suite-files> * tags. * * @return the parsed TestNG test suite. * * @throws ParserConfigurationException * @throws SAXException * @throws IOException if an I/O error occurs while parsing the test suite file or * if the default testng.xml file is not found. */ public Collection<XmlSuite> parse() throws ParserConfigurationException, SAXException, IOException { // Each suite found is put in this list, using their canonical // path to make sure we don't add a same file twice // (e.g. "testng.xml" and "./testng.xml") List<String> processedSuites = Lists.newArrayList(); XmlSuite resultSuite = null; File parentFile = null; String mainFilePath = null; if (m_fileName != null) { File mainFile = new File(m_fileName); mainFilePath = mainFile.getCanonicalPath(); parentFile = mainFile.getParentFile(); } List<String> toBeParsed = Lists.newArrayList(); List<String> toBeAdded = Lists.newArrayList(); List<String> toBeRemoved = Lists.newArrayList(); toBeParsed.add(mainFilePath); /* * Keeps a track of parent XmlSuite for each child suite */ Map<String, XmlSuite> childToParentMap = Maps.newHashMap(); while (toBeParsed.size() > 0) { for (String currentFile : toBeParsed) { InputStream inputStream = m_inputStream != null ? m_inputStream : new FileInputStream(currentFile); XmlSuite result = m_fileParser.parse(currentFile, inputStream); XmlSuite currentXmlSuite = result; processedSuites.add(currentFile); toBeRemoved.add(currentFile); if (childToParentMap.containsKey(currentFile)) { XmlSuite parentSuite = childToParentMap.get(currentFile); //Set parent currentXmlSuite.setParentSuite(parentSuite); //append children parentSuite.getChildSuites().add(currentXmlSuite); } if (null == resultSuite) { resultSuite = currentXmlSuite; } List<String> suiteFiles = currentXmlSuite.getSuiteFiles(); if (suiteFiles.size() > 0) { for (String path : suiteFiles) { String canonicalPath; if (parentFile != null && new File(parentFile, path).exists()) { canonicalPath = new File(parentFile, path).getCanonicalPath(); } else { canonicalPath = new File(path).getCanonicalPath(); } if (!processedSuites.contains(canonicalPath)) { toBeAdded.add(canonicalPath); childToParentMap.put(canonicalPath, currentXmlSuite); } } } } // // Add and remove files from toBeParsed before we loop // for (String s : toBeRemoved) { toBeParsed.remove(s); } toBeRemoved = Lists.newArrayList(); for (String s : toBeAdded) { toBeParsed.add(s); } toBeAdded = Lists.newArrayList(); } //returning a list of single suite to keep changes minimum List<XmlSuite> resultList = Lists.newArrayList(); resultList.add(resultSuite); return resultList; } public List<XmlSuite> parseToList() throws ParserConfigurationException, SAXException, IOException { List<XmlSuite> result = Lists.newArrayList(); Collection<XmlSuite> suites = parse(); for (XmlSuite suite : suites) { result.add(suite); } return result; } }