1 package org.testng.xml;
2 
3 import org.testng.collections.Lists;
4 import org.testng.collections.Maps;
5 import org.xml.sax.SAXException;
6 
7 import javax.xml.parsers.ParserConfigurationException;
8 
9 import java.io.File;
10 import java.io.FileInputStream;
11 import java.io.FileNotFoundException;
12 import java.io.IOException;
13 import java.io.InputStream;
14 import java.util.Collection;
15 import java.util.List;
16 import java.util.Map;
17 import java.util.ServiceLoader;
18 
19 /**
20  * <code>Parser</code> is a parser for a TestNG XML test suite file.
21  */
22 public class Parser {
23 
24   /** The name of the TestNG DTD. */
25   public static final String TESTNG_DTD = "testng-1.0.dtd";
26 
27   /** The URL to the deprecated TestNG DTD. */
28   public static final String DEPRECATED_TESTNG_DTD_URL = "http://beust.com/testng/" + TESTNG_DTD;
29 
30   /** The URL to the TestNG DTD. */
31   public static final String TESTNG_DTD_URL = "http://testng.org/" + TESTNG_DTD;
32 
33   /** The default file name for the TestNG test suite if none is specified (testng.xml). */
34   public static final String DEFAULT_FILENAME = "testng.xml";
35 
36   private static final ISuiteParser DEFAULT_FILE_PARSER = new SuiteXmlParser();
37   private static final List<ISuiteParser> PARSERS = Lists.newArrayList(DEFAULT_FILE_PARSER);
38   static {
39     ServiceLoader<ISuiteParser> suiteParserLoader = ServiceLoader.load(ISuiteParser.class);
40     for (ISuiteParser parser : suiteParserLoader) {
41       PARSERS.add(parser);
42     }
43   }
44 
45   /** The file name of the xml suite being parsed. This may be null if the Parser
46    * has not been initialized with a file name. TODO CQ This member is never used. */
47   private String m_fileName;
48 
49   private InputStream m_inputStream;
50   private IPostProcessor m_postProcessor;
51 
52   private boolean m_loadClasses = true;
53 
54   /**
55    * Constructs a <code>Parser</code> to use the inputStream as the source of
56    * the xml test suite to parse.
57    * @param fileName the filename corresponding to the inputStream or null if
58    * unknown.
59    */
Parser(String fileName)60   public Parser(String fileName) {
61     init(fileName, null, null);
62   }
63 
64   /**
65    * Creates a parser that will try to find the DEFAULT_FILENAME from the jar.
66    * @throws FileNotFoundException if the DEFAULT_FILENAME resource is not
67    * found in the classpath.
68    */
Parser()69   public Parser() throws FileNotFoundException {
70     init(null, null, null);
71   }
72 
Parser(InputStream is)73   public Parser(InputStream is) {
74     init(null, is, null);
75   }
76 
init(String fileName, InputStream is, IFileParser fp)77   private void init(String fileName, InputStream is, IFileParser fp) {
78     m_fileName = fileName != null ? fileName : DEFAULT_FILENAME;
79     m_inputStream = is;
80   }
81 
setPostProcessor(IPostProcessor processor)82   public void setPostProcessor(IPostProcessor processor) {
83     m_postProcessor = processor;
84   }
85 
86   /**
87    * If false, don't try to load the classes during the parsing.
88    */
setLoadClasses(boolean loadClasses)89   public void setLoadClasses(boolean loadClasses) {
90     m_loadClasses = loadClasses;
91   }
92 
93   /**
94    * Returns an input stream on the resource named DEFAULT_FILENAME.
95    *
96    * @return an input stream on the resource named DEFAULT_FILENAME.
97    * @throws FileNotFoundException if the DEFAULT_FILENAME resource is not
98    * found in the classpath.
99    */
100 //  private static InputStream getInputStream(String fileName) throws FileNotFoundException {
101 //    // Try to look for the DEFAULT_FILENAME from the jar
102 //    ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
103 //    InputStream in;
104 //    // TODO CQ is this OK? should we fall back to the default classloader if the
105 //    // context classloader fails.
106 //    if (classLoader != null) {
107 //      in = classLoader.getResourceAsStream(fileName);
108 //    }
109 //    else {
110 //      in = Parser.class.getResourceAsStream(fileName);
111 //    }
112 //    if (in == null) {
113 //      throw new FileNotFoundException(fileName);
114 //    }
115 //    return in;
116 //  }
117 
getParser(String fileName)118   private static IFileParser getParser(String fileName) {
119     for (ISuiteParser parser : PARSERS) {
120       if (parser.accept(fileName)) {
121         return parser;
122       }
123     }
124 
125     return DEFAULT_FILE_PARSER;
126   }
127 
128   /**
129    * Parses the TestNG test suite and returns the corresponding XmlSuite,
130    * and possibly, other XmlSuite that are pointed to by <suite-files>
131    * tags.
132    *
133    * @return the parsed TestNG test suite.
134    *
135    * @throws ParserConfigurationException
136    * @throws SAXException
137    * @throws IOException if an I/O error occurs while parsing the test suite file or
138    * if the default testng.xml file is not found.
139    */
parse()140   public Collection<XmlSuite> parse()
141     throws ParserConfigurationException, SAXException, IOException
142   {
143     // Each suite found is put in this list, using their canonical
144     // path to make sure we don't add a same file twice
145     // (e.g. "testng.xml" and "./testng.xml")
146     List<String> processedSuites = Lists.newArrayList();
147     XmlSuite resultSuite = null;
148 
149     List<String> toBeParsed = Lists.newArrayList();
150     List<String> toBeAdded = Lists.newArrayList();
151     List<String> toBeRemoved = Lists.newArrayList();
152 
153     if (m_fileName != null) {
154       File mainFile = new File(m_fileName);
155       toBeParsed.add(mainFile.getCanonicalPath());
156     }
157 
158     /*
159      * Keeps a track of parent XmlSuite for each child suite
160      */
161     Map<String, XmlSuite> childToParentMap = Maps.newHashMap();
162     while (toBeParsed.size() > 0) {
163 
164       for (String currentFile : toBeParsed) {
165         File currFile = new File(currentFile);
166         File parentFile = currFile.getParentFile();
167         InputStream inputStream = m_inputStream != null
168             ? m_inputStream
169             : new FileInputStream(currentFile);
170 
171         IFileParser<XmlSuite> fileParser = getParser(currentFile);
172         XmlSuite currentXmlSuite = fileParser.parse(currentFile, inputStream, m_loadClasses);
173         processedSuites.add(currentFile);
174         toBeRemoved.add(currentFile);
175 
176         if (childToParentMap.containsKey(currentFile)) {
177            XmlSuite parentSuite = childToParentMap.get(currentFile);
178            //Set parent
179            currentXmlSuite.setParentSuite(parentSuite);
180            //append children
181            parentSuite.getChildSuites().add(currentXmlSuite);
182         }
183 
184         if (null == resultSuite) {
185            resultSuite = currentXmlSuite;
186         }
187 
188         List<String> suiteFiles = currentXmlSuite.getSuiteFiles();
189         if (suiteFiles.size() > 0) {
190           for (String path : suiteFiles) {
191             String canonicalPath;
192             if (parentFile != null && new File(parentFile, path).exists()) {
193               canonicalPath = new File(parentFile, path).getCanonicalPath();
194             } else {
195               canonicalPath = new File(path).getCanonicalPath();
196             }
197             if (!processedSuites.contains(canonicalPath)) {
198               toBeAdded.add(canonicalPath);
199               childToParentMap.put(canonicalPath, currentXmlSuite);
200             }
201           }
202         }
203       }
204 
205       //
206       // Add and remove files from toBeParsed before we loop
207       //
208       for (String s : toBeRemoved) {
209         toBeParsed.remove(s);
210       }
211       toBeRemoved = Lists.newArrayList();
212 
213       for (String s : toBeAdded) {
214         toBeParsed.add(s);
215       }
216       toBeAdded = Lists.newArrayList();
217 
218     }
219 
220     //returning a list of single suite to keep changes minimum
221     List<XmlSuite> resultList = Lists.newArrayList();
222     resultList.add(resultSuite);
223 
224     boolean postProcess = true;
225 
226     if (postProcess && m_postProcessor != null) {
227       return m_postProcessor.process(resultList);
228     } else {
229       return resultList;
230     }
231 
232   }
233 
parseToList()234   public List<XmlSuite> parseToList()
235     throws ParserConfigurationException, SAXException, IOException
236   {
237     List<XmlSuite> result = Lists.newArrayList();
238     Collection<XmlSuite> suites = parse();
239     for (XmlSuite suite : suites) {
240       result.add(suite);
241     }
242 
243     return result;
244   }
245 
246 
247 
248 }
249 
250