1package org.testng.xml;
2
3
4import static org.testng.internal.Utils.isStringNotBlank;
5
6import org.testng.collections.Lists;
7import org.testng.internal.Utils;
8import org.testng.log4testng.Logger;
9import org.testng.remote.RemoteTestNG;
10import org.testng.reporters.XMLStringBuffer;
11
12import java.io.File;
13import java.io.FileOutputStream;
14import java.io.IOException;
15import java.io.OutputStreamWriter;
16import java.nio.charset.Charset;
17import java.util.Collection;
18import java.util.List;
19import java.util.Map;
20import java.util.Properties;
21
22/**
23 * This class is used to encapsulate a launch. Various synthetic XML files are created
24 * depending on whether the user is trying to launch a suite, a class, a method, etc...
25 */
26public abstract class LaunchSuite {
27  /** This class's log4testng Logger. */
28  private static final Logger LOGGER = Logger.getLogger(LaunchSuite.class);
29
30  protected boolean m_temporary;
31
32  /**
33   * Constructs a <code>LaunchSuite</code>
34   *
35   * @param isTemp the temporary status
36   */
37  protected LaunchSuite(boolean isTemp) {
38    m_temporary = isTemp;
39  }
40
41  /**
42   * Returns the temporary state.
43   * @return the temporary state.
44   */
45  public boolean isTemporary() {
46    return m_temporary;
47  }
48
49  /**
50   * Saves the suite file in the specified directory and returns the file
51   * pathname.
52   *
53   * @param directory the directory where the suite file is to be saved.
54   * @return the file pathname of the saved file.
55   */
56  public abstract File save(File directory);
57
58  public abstract XMLStringBuffer getSuiteBuffer();
59
60  /**
61   * <code>ExistingSuite</code> is a non-temporary LaunchSuite based on an existing
62   * file.
63   */
64  public static class ExistingSuite extends LaunchSuite {
65
66    /**
67     * The existing suite path (either relative to the project root or an absolute path)
68     */
69    private File m_suitePath;
70
71    /**
72     * Constructs a <code>ExistingSuite</code> based on an existing file
73     *
74     * @param path the path to the existing Launch suite.
75     */
76    public ExistingSuite(File path) {
77      super(false);
78
79      m_suitePath = path;
80    }
81
82    @Override
83    public XMLStringBuffer getSuiteBuffer() {
84      throw new UnsupportedOperationException("Not implemented yet");
85    }
86
87    /**
88     * Trying to run an existing XML file: copy its content to where the plug-in
89     * expects it.
90     */
91    @Override
92    public File save(File directory) {
93      if (RemoteTestNG.isDebug()) {
94        File result = new File(directory, RemoteTestNG.DEBUG_SUITE_FILE);
95        Utils.copyFile(m_suitePath, result);
96        return result;
97      } else {
98        return m_suitePath;
99      }
100    }
101  }
102
103  /**
104   * <code>CustomizedSuite</code> TODO cquezel JavaDoc.
105   */
106  private abstract static class CustomizedSuite extends LaunchSuite {
107    protected String m_projectName;
108    protected String m_suiteName;
109
110    /** The annotation type. May be null. */
111    protected Map<String, String> m_parameters;
112
113    /** The string buffer used to write the XML file. */
114    private XMLStringBuffer m_suiteBuffer;
115
116    /**
117     * Constructs a <code>CustomizedSuite</code> TODO cquezel JavaDoc.
118     *
119     * @param projectName
120     * @param className
121     * @param parameters
122     * @param annotationType
123     */
124    private CustomizedSuite(final String projectName,
125        final String className,
126        final Map<String, String> parameters,
127        final String annotationType)
128    {
129      super(true);
130
131      m_projectName = projectName;
132      m_suiteName = className;
133      m_parameters = parameters;
134    }
135
136    /**
137     * TODO cquezel JavaDoc
138     *
139     * @return
140     */
141    protected XMLStringBuffer createContentBuffer() {
142      XMLStringBuffer suiteBuffer = new XMLStringBuffer();
143      suiteBuffer.setDocType("suite SYSTEM \"" + Parser.TESTNG_DTD_URL + "\"");
144
145      Properties attrs = new Properties();
146      attrs.setProperty("parallel", XmlSuite.ParallelMode.NONE.toString());
147      attrs.setProperty("name", m_suiteName);
148      suiteBuffer.push("suite", attrs);
149
150      if (m_parameters != null) {
151        for (Map.Entry<String, String> entry : m_parameters.entrySet()) {
152          Properties paramAttrs = new Properties();
153          paramAttrs.setProperty("name", entry.getKey());
154          paramAttrs.setProperty("value", entry.getValue());
155          suiteBuffer.push("parameter", paramAttrs);
156          suiteBuffer.pop("parameter");
157        }
158      }
159
160      initContentBuffer(suiteBuffer);
161
162      suiteBuffer.pop("suite");
163
164      return suiteBuffer;
165    }
166
167    /**
168     * TODO cquezel JavaDoc
169     *
170     * @return
171     */
172    @Override
173    public XMLStringBuffer getSuiteBuffer() {
174      if (null == m_suiteBuffer) {
175        m_suiteBuffer = createContentBuffer();
176      }
177
178      return m_suiteBuffer;
179    }
180
181    /**
182     * Initializes the content of the xml string buffer.
183     *
184     * @param suiteBuffer the string buffer to initialize.
185     */
186    protected abstract void initContentBuffer(XMLStringBuffer suiteBuffer);
187
188    /**
189     * {@inheritDoc} This implementation saves the suite to the "temp-testng-customsuite.xml"
190     * file in the specified directory.
191     */
192    @Override
193    public File save(File directory) {
194      final File suiteFile = new File(directory, "temp-testng-customsuite.xml");
195
196      saveSuiteContent(suiteFile, getSuiteBuffer());
197
198      return suiteFile;
199    }
200
201    /**
202     * Saves the content of the string buffer to the specified file.
203     *
204     * @param file the file to write to.
205     * @param content the content to write to the file.
206     */
207    protected void saveSuiteContent(final File file, final XMLStringBuffer content) {
208
209      try {
210        OutputStreamWriter fw = new OutputStreamWriter(new FileOutputStream(file), Charset.forName("UTF-8"));
211        try {
212          fw.write(content.getStringBuffer().toString());
213        }
214        finally {
215          fw.close();
216        }
217      }
218      catch (IOException ioe) {
219        // TODO CQ is this normal to swallow exception here
220        LOGGER.error("IO Exception", ioe);
221      }
222    }
223  }
224
225  /**
226   * A <code>MethodsSuite</code> is a suite made up of methods.
227   */
228  static class MethodsSuite extends CustomizedSuite {
229    protected Collection<String> m_methodNames;
230    protected String m_className;
231    protected int m_logLevel;
232
233    /**
234     * Constructs a <code>MethodsSuite</code> TODO cquezel JavaDoc.
235     *
236     * @param projectName
237     * @param className
238     * @param methodNames
239     * @param parameters
240     * @param annotationType (may be null)
241     * @param logLevel
242     */
243    MethodsSuite(final String projectName,
244        final String className,
245        final Collection<String> methodNames,
246        final Map<String, String> parameters,
247        final String annotationType,
248        final int logLevel) {
249      super(projectName, className, parameters, annotationType);
250
251      m_className = className;
252      m_methodNames = methodNames;
253      m_logLevel = logLevel;
254    }
255
256    /**
257     * {@inheritDoc}
258     */
259    @Override
260    protected void initContentBuffer(XMLStringBuffer suiteBuffer) {
261      Properties testAttrs = new Properties();
262      testAttrs.setProperty("name", m_className);
263      testAttrs.setProperty("verbose", String.valueOf(m_logLevel));
264
265      suiteBuffer.push("test", testAttrs);
266
267      suiteBuffer.push("classes");
268
269      Properties classAttrs = new Properties();
270      classAttrs.setProperty("name", m_className);
271
272      if ((null != m_methodNames) && (m_methodNames.size() > 0)) {
273        suiteBuffer.push("class", classAttrs);
274
275        suiteBuffer.push("methods");
276
277        for (Object methodName : m_methodNames) {
278          Properties methodAttrs = new Properties();
279          methodAttrs.setProperty("name", (String) methodName);
280          suiteBuffer.addEmptyElement("include", methodAttrs);
281        }
282
283        suiteBuffer.pop("methods");
284        suiteBuffer.pop("class");
285      }
286      else {
287        suiteBuffer.addEmptyElement("class", classAttrs);
288      }
289      suiteBuffer.pop("classes");
290      suiteBuffer.pop("test");
291    }
292  }
293
294  static class ClassesAndMethodsSuite extends CustomizedSuite {
295    protected Map<String, Collection<String>> m_classes;
296    protected int m_logLevel;
297
298    ClassesAndMethodsSuite(final String projectName,
299        final Map<String, Collection<String>> classes,
300        final Map<String, String> parameters,
301        final String annotationType,
302        final int logLevel) {
303      super(projectName, "Custom suite", parameters, annotationType);
304      m_classes = classes;
305      m_logLevel = logLevel;
306    }
307
308    /**
309     * {@inheritDoc}
310     */
311    @Override
312    protected void initContentBuffer(XMLStringBuffer suiteBuffer) {
313      Properties testAttrs = new Properties();
314      testAttrs.setProperty("name", m_projectName);
315      testAttrs.setProperty("verbose", String.valueOf(m_logLevel));
316
317      suiteBuffer.push("test", testAttrs);
318
319      suiteBuffer.push("classes");
320
321      for(Map.Entry<String, Collection<String>> entry : m_classes.entrySet()) {
322        Properties classAttrs = new Properties();
323        classAttrs.setProperty("name", entry.getKey());
324
325        Collection<String> methodNames= sanitize(entry.getValue());
326        if ((null != methodNames) && (methodNames.size() > 0)) {
327          suiteBuffer.push("class", classAttrs);
328
329          suiteBuffer.push("methods");
330
331          for (String methodName : methodNames) {
332            Properties methodAttrs = new Properties();
333            methodAttrs.setProperty("name", methodName);
334            suiteBuffer.addEmptyElement("include", methodAttrs);
335          }
336
337          suiteBuffer.pop("methods");
338          suiteBuffer.pop("class");
339        }
340        else {
341          suiteBuffer.addEmptyElement("class", classAttrs);
342        }
343      }
344      suiteBuffer.pop("classes");
345      suiteBuffer.pop("test");
346    }
347
348    private Collection<String> sanitize(Collection<String> source) {
349      if(null == source) {
350        return null;
351      }
352
353      List<String> result= Lists.newArrayList();
354      for(String name: source) {
355        if(isStringNotBlank(name)) {
356          result.add(name);
357        }
358      }
359
360      return result;
361    }
362  }
363
364  /**
365   * <code>ClassListSuite</code> TODO cquezel JavaDoc.
366   */
367  static class ClassListSuite extends CustomizedSuite {
368    protected Collection<String> m_packageNames;
369    protected Collection<String> m_classNames;
370    protected Collection<String> m_groupNames;
371    protected int m_logLevel;
372
373    ClassListSuite(final String projectName,
374        final Collection<String> packageNames,
375        final Collection<String> classNames,
376        final Collection<String> groupNames,
377        final Map<String, String> parameters,
378        final String annotationType,
379        final int logLevel) {
380      super(projectName, "Custom suite", parameters, annotationType);
381
382      m_packageNames = packageNames;
383      m_classNames = classNames;
384      m_groupNames = groupNames;
385      m_logLevel = logLevel;
386    }
387
388    /**
389     * {@inheritDoc}
390     */
391    @Override
392    protected void initContentBuffer(XMLStringBuffer suiteBuffer) {
393      Properties testAttrs = new Properties();
394      testAttrs.setProperty("name", m_projectName);
395      testAttrs.setProperty("verbose", String.valueOf(m_logLevel));
396
397      suiteBuffer.push("test", testAttrs);
398
399      if (null != m_groupNames) {
400        suiteBuffer.push("groups");
401        suiteBuffer.push("run");
402
403        for (String groupName : m_groupNames) {
404          Properties includeAttrs = new Properties();
405          includeAttrs.setProperty("name", groupName);
406          suiteBuffer.addEmptyElement("include", includeAttrs);
407        }
408
409        suiteBuffer.pop("run");
410        suiteBuffer.pop("groups");
411      }
412
413      // packages belongs to suite according to the latest DTD
414      if ((m_packageNames != null) && (m_packageNames.size() > 0)) {
415        suiteBuffer.push("packages");
416
417        for (String packageName : m_packageNames) {
418          Properties packageAttrs = new Properties();
419          packageAttrs.setProperty("name", packageName);
420          suiteBuffer.addEmptyElement("package", packageAttrs);
421        }
422        suiteBuffer.pop("packages");
423      }
424
425      if ((m_classNames != null) && (m_classNames.size() > 0)) {
426        suiteBuffer.push("classes");
427
428        for (String className : m_classNames) {
429          Properties classAttrs = new Properties();
430          classAttrs.setProperty("name", className);
431          suiteBuffer.addEmptyElement("class", classAttrs);
432        }
433
434        suiteBuffer.pop("classes");
435      }
436      suiteBuffer.pop("test");
437    }
438  }
439}
440